CCCPaste

get deuda activo por puesto

import mysql.connector
from collections import defaultdict

def conectar_db():
    """
    Conecta a la base de datos acu_v2.
    Ajusta host, user, password y database según tu entorno real.
    """
    try:
        conexion = mysql.connector.connect(
            host="192.168.1.2",
            user="root",
            password="Perla17*+",
            database="acu_v2"
        )
        return conexion
    except mysql.connector.Error as err:
        print(f"Error al conectar a la base de datos: {err}")
        return None

def obtener_datos_por_dni(dni):
    """
    Retorna todos los puestos (activos) asociados al DNI dado,
    junto con el nombre completo del socio y otros datos.
    Si no hay resultados, retorna lista vacía.
    """
    if dni == "0":
        return "atras"

    conexion = conectar_db()
    if not conexion:
        return []

    cursor = conexion.cursor(dictionary=True)
    query = """
        SELECT  
            g_persona.ndoc,
            g_persona.nomcompleto,
            p_puesto.codigo,
            p_puesto.codigo_ant,
            p_giro.des_giro
        FROM g_persona
        INNER JOIN p_puesto_socio
            ON p_puesto_socio.idsocio = g_persona.idpersona
        INNER JOIN p_puesto
            ON p_puesto.idpuesto = p_puesto_socio.idpuesto
        INNER JOIN p_giro
            ON p_puesto.idgiro = p_giro.idgiro
        WHERE 
            g_persona.ndoc = %s
            AND p_puesto.activo = 1
            AND p_puesto_socio.activo = 1
    """
    cursor.execute(query, (dni,))
    resultados = cursor.fetchall()
    cursor.close()
    conexion.close()
    return resultados

def obtener_deuda_detallada(codigo_puesto):
    """
    Retorna el detalle de las deudas activas para el puesto cuyo 'codigo_puesto' se indica,
    usando LEFT JOIN con d_periodo para permitir idperiodo NULL y filtrando por saldo > 0,
    estado <> 'BA' y condicion = 1.
    """
    conexion = conectar_db()
    if not conexion:
        return []

    cursor = conexion.cursor(dictionary=True)
    query = """
        SELECT 
            c.descripcion       AS CONCEPTO,
            p.anio              AS ANIO,
            p.nperiodo          AS NPERIODO,
            p.fini              AS FINI,       -- Puede ser NULL si no hay periodo
            p.ffin              AS FFIN,       -- Puede ser NULL si no hay periodo
            d.cargo             AS CARGO,
            d.saldo             AS SALDO,
            d.moneda            AS MONEDA,
            pp.codigo           AS CODIGO,
            pp.codigo_ant       AS CODIGO_ANT,
            gp.nomcompleto      AS NOMBRE
        FROM d_deuda d
        INNER JOIN d_deuda_puesto dp 
            ON d.ideuda = dp.ideuda
        INNER JOIN p_puesto pp 
            ON dp.idpuesto = pp.idpuesto
        INNER JOIN p_puesto_socio pps 
            ON pp.idpuesto = pps.idpuesto
            AND d.idpersona = pps.idsocio
        INNER JOIN g_persona gp 
            ON d.idpersona = gp.idpersona
        INNER JOIN d_concepto c 
            ON d.idconcepto = c.idconcepto
        -- IMPORTANTE: LEFT JOIN para permitir idperiodo NULL
        LEFT JOIN d_periodo p 
            ON d.idperiodo = p.idperiodo
        WHERE 
            pp.codigo = %s
            AND pp.activo = 1
            AND pps.activo = 1
            AND d.estado <> 'BA'
            AND d.condicion = 1
            AND d.saldo > 0
        ORDER BY 
            p.anio DESC,
            p.nperiodo DESC
    """
    cursor.execute(query, (codigo_puesto,))
    filas = cursor.fetchall()
    cursor.close()
    conexion.close()
    return filas

def mostrar_deuda_detallada(filas):
    """
    Muestra la deuda agrupada por 'CONCEPTO' (columna 'descripcion' de d_concepto),
    con subtotales y un total general. Maneja p.anio, p.nperiodo, p.fini, p.ffin si son NULL.
    """
    if not filas:
        print("No se encontraron deudas activas para este puesto.")
        return

    # Agrupamos las filas por 'CONCEPTO'
    agrupado = defaultdict(list)
    for f in filas:
        # Si no existe 'CONCEPTO', usamos un valor por defecto
        concepto = f.get('CONCEPTO', 'SIN_CONCEPTO')
        agrupado[concepto].append(f)

    total_general = 0.0

    # Recorremos cada grupo de concepto
    for concepto, items in agrupado.items():
        # Encabezado para cada concepto
        print(f"\n{concepto.upper()}")

        # Cabecera de columnas
        print("ANIO  | NPERIODO | FINI       | FFIN       | CARGO   | SALDO   | MONEDA | CODIGO  | CODIGO_ANT  | NOMBRE")
        
        subtotal = 0.0
        for it in items:
            anio       = it.get('ANIO') or ''       # Manejo de NULL
            nperiodo   = it.get('NPERIODO') or ''
            fini       = str(it.get('FINI') or '')
            ffin       = str(it.get('FFIN') or '')
            cargo      = it.get('CARGO', 0.0)
            saldo      = it.get('SALDO', 0.0)
            moneda     = it.get('MONEDA', '')
            codigo     = it.get('CODIGO', '')
            codigo_ant = it.get('CODIGO_ANT', '')
            nombre     = it.get('NOMBRE', '')

            print(f"{anio:<5} | {nperiodo:<9} | {fini:<10} | {ffin:<10} | "
                  f"{cargo:7.2f} | {saldo:7.2f} | {moneda:<6} | {codigo} | {codigo_ant} | {nombre}")

            subtotal += saldo

        print(f"SUB TOTAL: {subtotal:10.2f}")
        total_general += subtotal

    print(f"\nTOTAL GENERAL: {total_general:10.2f}")

def menu():
    """
    Menú principal de búsqueda:
    1) Solicita DNI, lista los puestos activos asociados,
       y permite seleccionar uno para ver su detalle de deudas (agrupadas por concepto).
    2) Salir
    """
    while True:
        print("\n=== MENÚ DE CONSULTA DE DEUDAS ===")
        print("1. Buscar puestos por DNI")
        print("2. Salir")
        opcion = input("Seleccione una opción: ").strip()

        if opcion == "1":
            while True:
                print("\n(Ingrese 0 para volver al menú principal)")
                dni = input("Ingrese el DNI: ").strip()
                if dni == "0":
                    break

                puestos = obtener_datos_por_dni(dni)
                if puestos == "atras" or not puestos:
                    print("NO ES SOCIO o no hay puestos activos asociados a este DNI.")
                    continue

                # Listar todos los puestos asociados a ese DNI
                print("\nSe encontraron los siguientes puestos activos para este DNI:")
                for i, reg in enumerate(puestos, start=1):
                    print(f"{i}. Código: {reg['codigo']} - Nombre: {reg['nomcompleto']}")

                eleccion = input("Seleccione el número del puesto para ver sus deudas (0 para cancelar): ").strip()
                if eleccion == "0":
                    break
                if not eleccion.isdigit():
                    print("Opción no válida. Intente nuevamente.")
                    continue

                idx = int(eleccion)
                if idx < 1 or idx > len(puestos):
                    print("Número fuera de rango. Intente nuevamente.")
                    continue

                # Obtener el código del puesto seleccionado
                puesto_sel = puestos[idx - 1]
                codigo_puesto = puesto_sel['codigo']

                print(f"\nConsultando deudas para el puesto: {codigo_puesto} ...")
                filas_deuda = obtener_deuda_detallada(codigo_puesto)
                mostrar_deuda_detallada(filas_deuda)
                break

        elif opcion == "2":
            print("Saliendo del programa.")
            break
        else:
            print("Opción no válida. Intente nuevamente.")

if __name__ == "__main__":
    menu()