Actualizar backend/modules/cobranza/processor.py

This commit is contained in:
2026-10-05 13:36:41 -05:00
parent 1ff3dde86d
commit 86800569e9

View File

@@ -388,7 +388,7 @@ class CobranzaProcessor:
def obtener_datos_procesados(self, ano, mes, sectorista_filtro="TODOS", agrupacion="SEDE", def obtener_datos_procesados(self, ano, mes, sectorista_filtro="TODOS", agrupacion="SEDE",
sede_filtro="TODOS", prog_filtro="TODOS", venc_hasta=None, sede_filtro="TODOS", prog_filtro="TODOS", venc_hasta=None,
mats_retirar=None): mats_retirar=None, frec_filtro="TODOS"):
fecha_filtro, fecha_filtro_siguiente = self.obtener_fechas_filtro(ano, mes) fecha_filtro, fecha_filtro_siguiente = self.obtener_fechas_filtro(ano, mes)
hoy = datetime.now() hoy = datetime.now()
# Filtro adicional por F. Vencimiento: solo cuotas con vencimiento <= venc_hasta. # Filtro adicional por F. Vencimiento: solo cuotas con vencimiento <= venc_hasta.
@@ -414,6 +414,8 @@ class CobranzaProcessor:
indices_cronograma = set() indices_cronograma = set()
mapa_tc = {} mapa_tc = {}
mapa_programa = {} mapa_programa = {}
mapa_clasif = {} # mat -> (sede_meta, filtro_prog) para filtrar por alumno
mapa_frec = {} # mat -> frecuencia (cod_frecuencia) para filtrar por alumno
mapa_sectorista = {} # <-- NUEVO MAPA PARA CORREGIR ASESORES mapa_sectorista = {} # <-- NUEVO MAPA PARA CORREGIR ASESORES
# Sectorista puede venir con varios separados por pipe (multi). Vacío/TODOS = todos. # Sectorista puede venir con varios separados por pipe (multi). Vacío/TODOS = todos.
@@ -425,6 +427,9 @@ class CobranzaProcessor:
_sede_all = (not _sede_set) or ("TODOS" in _sede_set) _sede_all = (not _sede_set) or ("TODOS" in _sede_set)
_prog_set = {p.strip().upper() for p in str(prog_filtro or "TODOS").split(",") if p.strip()} _prog_set = {p.strip().upper() for p in str(prog_filtro or "TODOS").split(",") if p.strip()}
_prog_all = (not _prog_set) or ("TODOS" in _prog_set) _prog_all = (not _prog_set) or ("TODOS" in _prog_set)
# Frecuencia multi (coma). Vacío/TODOS = todas.
_frec_set = {f.strip().upper() for f in str(frec_filtro or "TODOS").split(",") if f.strip()}
_frec_all = (not _frec_set) or ("TODOS" in _frec_set)
for fila in cronograma_raw: for fila in cronograma_raw:
sectorista_str = str(fila.get('dsc_sectorista', '')) sectorista_str = str(fila.get('dsc_sectorista', ''))
@@ -451,21 +456,36 @@ class CobranzaProcessor:
prog = str(fila.get('dsc_det_programa', '')).strip() prog = str(fila.get('dsc_det_programa', '')).strip()
mapa_programa[mat] = prog if (prog and prog != "None") else "SIN PROGRAMA" mapa_programa[mat] = prog if (prog and prog != "None") else "SIN PROGRAMA"
# Clasificación SEDE/PROGRAMA por MATRICULA (1ª cuota), para filtrar por
# alumno completo igual que el detalle (Ver), no cuota por cuota.
# Usa dsc_det_programa (el MISMO campo que el detalle/Ver).
if mat not in mapa_clasif:
_pm = str(fila.get('dsc_det_programa', '')).strip()
if not _pm or _pm == "None": _pm = "SIN PROGRAMA"
mapa_clasif[mat] = (
str(self.clasificar_sede_meta(_pm)).upper(),
str(self.clasificar_filtro_programa(_pm)).upper(),
)
# Frecuencia por MATRICULA (1ª cuota), para filtrar por alumno.
if mat not in mapa_frec:
_fm = str(fila.get('cod_frecuencia', '')).strip().upper()
mapa_frec[mat] = _fm if _fm and _fm != "NONE" else ""
for fila in cronograma_raw: for fila in cronograma_raw:
idx = str(fila.get('num_indice', '')).strip() idx = str(fila.get('num_indice', '')).strip()
if idx not in indices_cronograma: continue if idx not in indices_cronograma: continue
mat = str(fila.get('num_matricula', '')).strip() mat = str(fila.get('num_matricula', '')).strip()
# Filtro por SEDE y PROGRAMA (REPLACE_META) — se aplica en cualquier # Filtro por SEDE y PROGRAMA (REPLACE_META) — por MATRICULA (igual que "Ver").
# agrupación cuando se pide (usado sobre todo en ASESOR). _cs, _cp = mapa_clasif.get(mat, ("", ""))
_prog = fila.get('dsc_programa', '') if not _sede_all and _cs not in _sede_set:
if not _sede_all: continue
if str(self.clasificar_sede_meta(_prog)).upper() not in _sede_set: if not _prog_all and _cp not in _prog_set:
continue continue
if not _prog_all: if not _frec_all and mapa_frec.get(mat, "") not in _frec_set:
if str(self.clasificar_filtro_programa(_prog)).upper() not in _prog_set: continue
continue
# Filtro por F. Vencimiento (<=). Sin fecha => se incluye siempre. # Filtro por F. Vencimiento (<=). Sin fecha => se incluye siempre.
if venc_corte is not None: if venc_corte is not None:
@@ -519,14 +539,14 @@ class CobranzaProcessor:
mat_factura = str(fila.get('num_matricula', '')).strip() mat_factura = str(fila.get('num_matricula', '')).strip()
tc_correcto = mapa_tc.get(mat_factura, 1.0) tc_correcto = mapa_tc.get(mat_factura, 1.0)
# Mismo filtro SEDE/PROGRAMA (REPLACE_META) que en el cronograma. # Mismo filtro SEDE/PROGRAMA por MATRICULA (igual que el cronograma y "Ver").
_progF = fila.get('dsc_programa', '') _csF, _cpF = mapa_clasif.get(mat_factura, ("", ""))
if not _sede_all: if not _sede_all and _csF not in _sede_set:
if str(self.clasificar_sede_meta(_progF)).upper() not in _sede_set: continue
continue if not _prog_all and _cpF not in _prog_set:
if not _prog_all: continue
if str(self.clasificar_filtro_programa(_progF)).upper() not in _prog_set: if not _frec_all and mapa_frec.get(mat_factura, "") not in _frec_set:
continue continue
# Filtro por F. Vencimiento (<=). En facturas la fecha es fch_vencimiento_cuota. # Filtro por F. Vencimiento (<=). En facturas la fecha es fch_vencimiento_cuota.
# Sin fecha => se incluye siempre. # Sin fecha => se incluye siempre.
@@ -666,11 +686,14 @@ class CobranzaProcessor:
# ========================================================================= # =========================================================================
# DESGLOSE POR ALUMNO/MATRÍCULA # DESGLOSE POR ALUMNO/MATRÍCULA
# ========================================================================= # =========================================================================
def obtener_detalle_programa(self, ano, mes, sectorista_filtro, programa_nombre, venc_hasta=None): def obtener_detalle_programa(self, ano, mes, sectorista_filtro, programa_nombre, venc_hasta=None, frec_filtro="TODOS"):
fecha_filtro, fecha_filtro_siguiente = self.obtener_fechas_filtro(ano, mes) fecha_filtro, fecha_filtro_siguiente = self.obtener_fechas_filtro(ano, mes)
hoy = datetime.now() hoy = datetime.now()
# Filtro por F. Vencimiento (<=). Sin fecha => se incluye siempre. # Filtro por F. Vencimiento (<=). Sin fecha => se incluye siempre.
venc_corte = self.parse_corte_venc(venc_hasta) venc_corte = self.parse_corte_venc(venc_hasta)
# Frecuencia multi (coma). Vacío/TODOS = todas.
_frec_set = {f.strip().upper() for f in str(frec_filtro or "TODOS").split(",") if f.strip()}
_frec_all = (not _frec_set) or ("TODOS" in _frec_set)
try: try:
cronograma_raw, facturas_raw = self._obtener_datos_cached(ano) cronograma_raw, facturas_raw = self._obtener_datos_cached(ano)
@@ -697,7 +720,11 @@ class CobranzaProcessor:
if not prog or prog == "None": prog = "SIN PROGRAMA" if not prog or prog == "None": prog = "SIN PROGRAMA"
if programa_nombre != "TODOS": if programa_nombre != "TODOS":
if self._aplicar_reemplazos_nombre(prog) != programa_nombre: continue if self._aplicar_reemplazos_nombre(prog) != programa_nombre: continue
if not _frec_all:
_fm = str(fila.get('cod_frecuencia', '')).strip().upper()
if (_fm if _fm and _fm != "NONE" else "") not in _frec_set: continue
idx = str(fila.get('num_indice', '')).strip() idx = str(fila.get('num_indice', '')).strip()
if idx: indices_cronograma.add(idx) if idx: indices_cronograma.add(idx)