Files
2026-06-27 11:58:05 -05:00

193 lines
8.5 KiB
Python

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