# modules/saldo_pendiente/logic.py from .processor import SaldoProcessor from datetime import datetime class SaldoLogic: """ Controlador lógico optimizado. 1. Filtro Global Manual (lista_saldo_pendiente). 2. Filtro REFRIPERU (Precio Neto < 1900/1000 o Etiquetas). 3. Filtros de Negocio (Vendedor, Fecha, Estado). """ def __init__(self, data_manager): self.data_manager = data_manager self.processor = SaldoProcessor(self.data_manager) def obtener_saldos_consolidados(self, tipo_cuota): # 1. Recargar Configuración (Para leer tu nueva lista en vivo) try: self.data_manager.cargar_toda_configuracion() except: pass all_debtors = [] years_to_scan = [2025, 2026] print(f"⚡ [Logic] Iniciando carga para {tipo_cuota}...") for year in years_to_scan: try: # Usamos la consulta anual optimizada raw_year_data = self.data_manager.ejecutar_consulta_saldos_anual(str(year)) if raw_year_data: datos_procesados = self._procesar_lote_local(raw_year_data, tipo_cuota) all_debtors.extend(datos_procesados) except AttributeError: print("⚠️ Error: DataManager no actualizado.") continue # Ordenar alfabéticamente all_debtors.sort(key=lambda x: (x.get('VENDEDOR', '') or "ZZZ", x.get('ALUMNO', '') or "")) print(f"✅ Carga Finalizada. Total en tabla: {len(all_debtors)}") return all_debtors def _procesar_lote_local(self, raw_data, tipo_cuota): datos_limpios = [] # --- Configuración --- ahora = datetime.now() ano_actual = ahora.year mes_actual = ahora.month config = self.data_manager.config_data # 1. CARGAMOS TUS LISTAS DEL JSON correcciones_refri = config.get("correcciones_descuento", {}) exclusion_manual_global = config.get("lista_saldo_pendiente", {}) # <--- TU NUEVA LISTA # 2. Lista Vendedores Permitidos lista_raw = config.get("lista_pendientes", []) 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"] lista_permitidos = set([str(v).strip().upper() for v in lista_raw]) # 3. Mapeo de columnas mapa_cols = { "1° Cuota": ("imp_saldo_cuota1", "fch_venc_cuota1"), "2° Cuota": ("imp_saldo_cuota2", "fch_venc_cuota2"), "3° Cuota": ("imp_saldo_cuota3", "fch_venc_cuota3"), "4° Cuota": ("imp_saldo_cuota4", "fch_venc_cuota4"), "5° Cuota": ("imp_saldo_cuota5", "fch_venc_cuota5"), } col_saldo, col_venc = mapa_cols.get(tipo_cuota, (None, None)) if not col_saldo: return [] for row in raw_data: # Identificadores num_mat = str(row.get('num_matricula', '')).split('.')[0].strip() # ================================================================= # 🛑 FILTRO 0: EXCLUSIÓN GLOBAL MANUAL (TU NUEVO PEDIDO) # ================================================================= # Si la matrícula está en "lista_saldo_pendiente" con valor "NO", ADIÓS. if num_mat in exclusion_manual_global: val_excl = str(exclusion_manual_global[num_mat]).strip().upper() if val_excl == "NO": continue # Se salta inmediatamente, no importa nada más. # ================================================================= # 🛑 FILTRO 1: DETECCIÓN DE REFRIPERU (PRECIO NETO / ETIQUETAS) # ================================================================= es_refriperu = False # A) Manual (correcciones_descuento) if num_mat in correcciones_refri: val = str(correcciones_refri[num_mat]).strip().upper() if val == "SI": es_refriperu = True elif val == "NO": es_refriperu = False # Forzamos a cobrar else: # B) Automático (Etiquetas) dsc_beca = str(row.get('dsc_beca', '')).strip().upper() dsc_prog = str(row.get('dsc_programa', '')).strip().upper() dsc_prom = str(row.get('dsc_promocion', '')).strip().upper() if 'REFRIPERU' in dsc_beca or '100%' in dsc_beca or 'BECA' in dsc_beca: es_refriperu = True elif 'REFRIPERU' in dsc_prog or 'REFRIPERU' in dsc_prom: es_refriperu = True # C) Automático por PRECIO FINAL (INV_NETA) if not es_refriperu: try: # Usamos INV_NETA (Precio Real) inv_neta = float(row.get('INV_NETA', 0)) except: inv_neta = 0.0 if inv_neta > 0: # Rango Técnicos/Especialistas if ("TECNICO" in dsc_prog or "ESPECIALISTA" in dsc_prog or "TEAC" in dsc_prog): if inv_neta < 1900: es_refriperu = True # ej: 1799 # Rango Gestión/Ventas elif "GESTION" in dsc_prog or "VENTA" in dsc_prog: if inv_neta < 1000: es_refriperu = True if es_refriperu: continue # ================================================================= # 🛑 FILTROS ESTÁNDAR (VENDEDOR, ESTADO, FECHA) # ================================================================= # Vendedor vendedor = str(row.get('dsc_vendedor', '')).strip().upper() if lista_permitidos: encontrado = False if vendedor in lista_permitidos: encontrado = True else: for p in lista_permitidos: if p.replace(" ","") in vendedor.replace(" ",""): encontrado = True; break if not encontrado: continue # Estado estado = str(row.get('estado_matricula', '')).strip().upper() if estado not in ('ALU', 'PRE'): continue # Fecha Futura (Solo 2da cuota en adelante) if tipo_cuota != "1° Cuota": fch_raw = row.get(col_venc) if fch_raw: try: if isinstance(fch_raw, str): f_obj = datetime.strptime(fch_raw[:10], '%Y-%m-%d') else: f_obj = fch_raw if f_obj.year > ano_actual: continue elif f_obj.year == ano_actual and f_obj.month > mes_actual: continue except: pass # Saldo Positivo try: val = float(row.get(col_saldo, 0)) except: val = 0.0 if val <= 0.1: continue # --- 4. Construcción de Fila --- def fmt(v): if not v: return "" if isinstance(v, str): return v[:10] if isinstance(v, datetime): return v.strftime('%d-%m-%Y') return str(v) fila = { 'MATRICULA': row.get('num_matricula'), 'VENDEDOR': row.get('dsc_vendedor'), 'ALUMNO': row.get('dsc_alumno'), 'PROGRAMA': row.get('dsc_promocion'), 'FECHA INICIO': fmt(row.get('fch_inicio')), 'FECHA MATR.': fmt(row.get('fch_matricula')), 'SALDO MAT.': float(row.get('imp_saldo_matricula', 0)), f'SALDO {tipo_cuota.upper()}': val, 'VENCIMIENTO': fmt(row.get(col_venc)), 'INV. NETA': float(row.get('INV_NETA', 0)), 'DNI': row.get('dsc_documento', ''), 'CELULAR': row.get('dsc_telefono_1', '') } datos_limpios.append(fila) return datos_limpios # Métodos legacy def obtener_saldos(self, ano, mes, tipo_cuota): return self.processor.obtener_datos_procesados(ano, mes, tipo_cuota) def get_current_year(self): return self.data_manager.get_current_year() def get_current_month(self): return self.data_manager.get_current_month()