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()