· 9 months ago · Mar 06, 2025, 03:25 PM
1import mysql.connector
2from collections import defaultdict
3
4def conectar_db():
5 """
6 Conecta a la base de datos acu_v2.
7 Ajusta host, user, password y database según tu entorno real.
8 """
9 try:
10 conexion = mysql.connector.connect(
11 host="192.168.1.2",
12 user="root",
13 password="Perla17*+",
14 database="acu_v2"
15 )
16 return conexion
17 except mysql.connector.Error as err:
18 print(f"Error al conectar a la base de datos: {err}")
19 return None
20
21def obtener_datos_por_dni(dni):
22 """
23 Retorna todos los puestos (activos) asociados al DNI dado,
24 junto con el nombre completo del socio y otros datos.
25 Si no hay resultados, retorna lista vacía.
26 """
27 if dni == "0":
28 return "atras"
29
30 conexion = conectar_db()
31 if not conexion:
32 return []
33
34 cursor = conexion.cursor(dictionary=True)
35 query = """
36 SELECT
37 g_persona.ndoc,
38 g_persona.nomcompleto,
39 p_puesto.codigo,
40 p_puesto.codigo_ant,
41 p_giro.des_giro
42 FROM g_persona
43 INNER JOIN p_puesto_socio
44 ON p_puesto_socio.idsocio = g_persona.idpersona
45 INNER JOIN p_puesto
46 ON p_puesto.idpuesto = p_puesto_socio.idpuesto
47 INNER JOIN p_giro
48 ON p_puesto.idgiro = p_giro.idgiro
49 WHERE
50 g_persona.ndoc = %s
51 AND p_puesto.activo = 1
52 AND p_puesto_socio.activo = 1
53 """
54 cursor.execute(query, (dni,))
55 resultados = cursor.fetchall()
56 cursor.close()
57 conexion.close()
58 return resultados
59
60def obtener_deuda_detallada(codigo_puesto):
61 """
62 Retorna el detalle de las deudas activas para el puesto cuyo 'codigo_puesto' se indica,
63 usando LEFT JOIN con d_periodo para permitir idperiodo NULL y filtrando por saldo > 0,
64 estado <> 'BA' y condicion = 1.
65 """
66 conexion = conectar_db()
67 if not conexion:
68 return []
69
70 cursor = conexion.cursor(dictionary=True)
71 query = """
72 SELECT
73 c.descripcion AS CONCEPTO,
74 p.anio AS ANIO,
75 p.nperiodo AS NPERIODO,
76 p.fini AS FINI, -- Puede ser NULL si no hay periodo
77 p.ffin AS FFIN, -- Puede ser NULL si no hay periodo
78 d.cargo AS CARGO,
79 d.saldo AS SALDO,
80 d.moneda AS MONEDA,
81 pp.codigo AS CODIGO,
82 pp.codigo_ant AS CODIGO_ANT,
83 gp.nomcompleto AS NOMBRE
84 FROM d_deuda d
85 INNER JOIN d_deuda_puesto dp
86 ON d.ideuda = dp.ideuda
87 INNER JOIN p_puesto pp
88 ON dp.idpuesto = pp.idpuesto
89 INNER JOIN p_puesto_socio pps
90 ON pp.idpuesto = pps.idpuesto
91 AND d.idpersona = pps.idsocio
92 INNER JOIN g_persona gp
93 ON d.idpersona = gp.idpersona
94 INNER JOIN d_concepto c
95 ON d.idconcepto = c.idconcepto
96 -- IMPORTANTE: LEFT JOIN para permitir idperiodo NULL
97 LEFT JOIN d_periodo p
98 ON d.idperiodo = p.idperiodo
99 WHERE
100 pp.codigo = %s
101 AND pp.activo = 1
102 AND pps.activo = 1
103 AND d.estado <> 'BA'
104 AND d.condicion = 1
105 AND d.saldo > 0
106 ORDER BY
107 p.anio DESC,
108 p.nperiodo DESC
109 """
110 cursor.execute(query, (codigo_puesto,))
111 filas = cursor.fetchall()
112 cursor.close()
113 conexion.close()
114 return filas
115
116def mostrar_deuda_detallada(filas):
117 """
118 Muestra la deuda agrupada por 'CONCEPTO' (columna 'descripcion' de d_concepto),
119 con subtotales y un total general. Maneja p.anio, p.nperiodo, p.fini, p.ffin si son NULL.
120 """
121 if not filas:
122 print("No se encontraron deudas activas para este puesto.")
123 return
124
125 # Agrupamos las filas por 'CONCEPTO'
126 agrupado = defaultdict(list)
127 for f in filas:
128 # Si no existe 'CONCEPTO', usamos un valor por defecto
129 concepto = f.get('CONCEPTO', 'SIN_CONCEPTO')
130 agrupado[concepto].append(f)
131
132 total_general = 0.0
133
134 # Recorremos cada grupo de concepto
135 for concepto, items in agrupado.items():
136 # Encabezado para cada concepto
137 print(f"\n{concepto.upper()}")
138
139 # Cabecera de columnas
140 print("ANIO | NPERIODO | FINI | FFIN | CARGO | SALDO | MONEDA | CODIGO | CODIGO_ANT | NOMBRE")
141
142 subtotal = 0.0
143 for it in items:
144 anio = it.get('ANIO') or '' # Manejo de NULL
145 nperiodo = it.get('NPERIODO') or ''
146 fini = str(it.get('FINI') or '')
147 ffin = str(it.get('FFIN') or '')
148 cargo = it.get('CARGO', 0.0)
149 saldo = it.get('SALDO', 0.0)
150 moneda = it.get('MONEDA', '')
151 codigo = it.get('CODIGO', '')
152 codigo_ant = it.get('CODIGO_ANT', '')
153 nombre = it.get('NOMBRE', '')
154
155 print(f"{anio:<5} | {nperiodo:<9} | {fini:<10} | {ffin:<10} | "
156 f"{cargo:7.2f} | {saldo:7.2f} | {moneda:<6} | {codigo} | {codigo_ant} | {nombre}")
157
158 subtotal += saldo
159
160 print(f"SUB TOTAL: {subtotal:10.2f}")
161 total_general += subtotal
162
163 print(f"\nTOTAL GENERAL: {total_general:10.2f}")
164
165def menu():
166 """
167 Menú principal de búsqueda:
168 1) Solicita DNI, lista los puestos activos asociados,
169 y permite seleccionar uno para ver su detalle de deudas (agrupadas por concepto).
170 2) Salir
171 """
172 while True:
173 print("\n=== MENÚ DE CONSULTA DE DEUDAS ===")
174 print("1. Buscar puestos por DNI")
175 print("2. Salir")
176 opcion = input("Seleccione una opción: ").strip()
177
178 if opcion == "1":
179 while True:
180 print("\n(Ingrese 0 para volver al menú principal)")
181 dni = input("Ingrese el DNI: ").strip()
182 if dni == "0":
183 break
184
185 puestos = obtener_datos_por_dni(dni)
186 if puestos == "atras" or not puestos:
187 print("NO ES SOCIO o no hay puestos activos asociados a este DNI.")
188 continue
189
190 # Listar todos los puestos asociados a ese DNI
191 print("\nSe encontraron los siguientes puestos activos para este DNI:")
192 for i, reg in enumerate(puestos, start=1):
193 print(f"{i}. Código: {reg['codigo']} - Nombre: {reg['nomcompleto']}")
194
195 eleccion = input("Seleccione el número del puesto para ver sus deudas (0 para cancelar): ").strip()
196 if eleccion == "0":
197 break
198 if not eleccion.isdigit():
199 print("Opción no válida. Intente nuevamente.")
200 continue
201
202 idx = int(eleccion)
203 if idx < 1 or idx > len(puestos):
204 print("Número fuera de rango. Intente nuevamente.")
205 continue
206
207 # Obtener el código del puesto seleccionado
208 puesto_sel = puestos[idx - 1]
209 codigo_puesto = puesto_sel['codigo']
210
211 print(f"\nConsultando deudas para el puesto: {codigo_puesto} ...")
212 filas_deuda = obtener_deuda_detallada(codigo_puesto)
213 mostrar_deuda_detallada(filas_deuda)
214 break
215
216 elif opcion == "2":
217 print("Saliendo del programa.")
218 break
219 else:
220 print("Opción no válida. Intente nuevamente.")
221
222if __name__ == "__main__":
223 menu()
224