# modules/saldo_pendiente/processor.py from datetime import datetime class SaldoProcessor: """ Procesador de lógica de negocio para Saldos Pendientes. Filtra estrictamente por estado ALU/PRE, saldos positivos, vendedores permitidos y FECHA DE VENCIMIENTO (No mostrar futuro). """ def __init__(self, data_manager): self.data_manager = data_manager def obtener_datos_procesados(self, ano, mes, tipo_cuota): """ Recupera datos crudos y aplica los filtros de negocio. """ # 1. Traer la data cruda desde DataManager raw_data = self.data_manager.ejecutar_consulta_saldos_pendientes(ano, mes) datos_limpios = [] # 2. Obtener fecha actual para saber qué es "futuro" ahora = datetime.now() ano_actual = ahora.year mes_actual = ahora.month # 3. Cargar la "Lista Blanca" de vendedores desde el JSON config = self.data_manager.config_data lista_raw = config.get("lista_pendientes", []) # RESPALDO DE EMERGENCIA if not lista_raw: lista_raw = [ "AGUILAR U. JUAN CARLOS", "CHAVEZ P. DIANA", "HUAMAN C. ALONSO AGUSTIN", "LA ROSA C. VERONICA ASTRID", "LAZARO Q. DIEGO ARTURO", "MONTOYA D. CARMEN ISABEL", "PERALTA C. ALMENDRA LUCIA" ] # Normalizamos la lista lista_permitidos = set([str(v).strip().upper() for v in lista_raw]) # 4. Mapeo de columnas según la selección mapa_columnas = { "1° Cuota": ("imp_saldo_cuota1", "fch_venc_cuota1"), "2° Cuota": ("imp_saldo_cuota2", "fch_venc_cuota2"), "3° Cuota": ("imp_saldo_cuota3", "fch_venc_cuota3"), } col_saldo_target, col_venc_target = mapa_columnas.get(tipo_cuota, (None, None)) if not col_saldo_target: return [] for row in raw_data: # ================================================================= # 🛑 FILTRO 1: VENDEDOR PERMITIDO # ================================================================= vendedor_actual = str(row.get('dsc_vendedor', '')).strip().upper() if lista_permitidos: if vendedor_actual not in lista_permitidos: # Búsqueda parcial por si hay errores de espacios encontrado = False for permitido in lista_permitidos: v_norm = vendedor_actual.replace(" ", "") p_norm = permitido.replace(" ", "") if p_norm in v_norm: encontrado = True break if not encontrado: continue # ================================================================= # 🛑 FILTRO 2: ESTADO (ALU/PRE) # ================================================================= estado = str(row.get('estado_matricula', '')).strip().upper() if estado not in ('ALU', 'PRE'): continue # ================================================================= # 🛑 FILTRO 3: FECHA DE VENCIMIENTO (NO MOSTRAR FUTURO) # ================================================================= # Este filtro aplica PRINCIPALMENTE para 2° Cuota en adelante. # (Aunque la lógica es válida para todas, la 1° suele ser inmediata). if tipo_cuota != "1° Cuota": fch_venc_raw = row.get(col_venc_target) es_futuro = False if fch_venc_raw: try: # Convertir a objeto fecha si es texto if isinstance(fch_venc_raw, str): # Asumimos formato SQL YYYY-MM-DD f_obj = datetime.strptime(fch_venc_raw[:10], '%Y-%m-%d') else: f_obj = fch_venc_raw # Ya es datetime venc_ano = f_obj.year venc_mes = f_obj.month # LÓGICA DE TIEMPO: # Si el año de vencimiento es mayor al actual -> ES FUTURO if venc_ano > ano_actual: es_futuro = True # Si es el mismo año, pero el mes es mayor al actual -> ES FUTURO elif venc_ano == ano_actual and venc_mes > mes_actual: es_futuro = True except Exception as e: # Si falla la fecha, asumimos que no es futuro para no ocultar por error pass # Si la cuota vence en el futuro (ej: Marzo cuando estamos en Febrero), LA OCULTAMOS. if es_futuro: continue # ================================================================= # 🛑 FILTRO 4: SALDO > 0 # ================================================================= try: val = row.get(col_saldo_target, 0) saldo_a_mostrar = float(val) if val is not None else 0.0 except: saldo_a_mostrar = 0.0 if saldo_a_mostrar <= 0.1: continue # --- FORMATEO PARA VISUALIZACIÓN --- def format_date(val): if not val: return "" if isinstance(val, str): return val[:10] if isinstance(val, datetime): return val.strftime('%d-%m-%Y') return str(val) f_inicio = format_date(row.get('fch_inicio')) f_matr = format_date(row.get('fch_matricula')) f_vencimiento = format_date(row.get(col_venc_target)) # --- CONSTRUCCIÓN DE LA FILA FINAL --- fila = { 'MATRICULA': row.get('num_matricula'), 'VENDEDOR': row.get('dsc_vendedor'), 'ALUMNO': row.get('dsc_alumno'), 'PROGRAMA': row.get('dsc_promocion'), 'FECHA INICIO': f_inicio, 'FECHA MATR.': f_matr, 'SALDO MAT.': float(row.get('imp_saldo_matricula', 0)), f'SALDO {tipo_cuota.upper()}': saldo_a_mostrar, 'VENCIMIENTO': f_vencimiento, # Aquí se verá la fecha (ej: 14-02-2026) 'INV. NETA': float(row.get('INV_NETA', 0)), } datos_limpios.append(fila) return datos_limpios