"""Módulo de scoring crediticio unificado.""" import logging from datetime import datetime from typing import List, Optional from app.reports.schemas import ( ScoreCrediticio, ScoreHistorial, PersonReport, CompanyReport, GroupReport ) logger = logging.getLogger(__name__) def compute_score(report: PersonReport, history_scores: List[ScoreHistorial] = None) -> ScoreCrediticio: """ Calcula el score crediticio (1-100) para una persona basado en su reporte. """ score = 60 # Base score factores = [] # 1. BCRA Situación Actual sit = report.financiero.bcra_situacion_actual if sit is None or sit <= 1: score += 40 factores.append("BCRA Situación Normal (Sit. 1 o sin deuda) +40pts") elif sit == 2: score += 28 factores.append("BCRA Situación 2 (Seguimiento especial) +28pts") elif sit == 3: score += 14 factores.append("BCRA Situación 3 (Con problemas) +14pts") elif sit == 4: score += 4 factores.append("BCRA Situación 4 (Alto riesgo de insolvencia) +4pts") else: # Sit 5 o 6 factores.append(f"BCRA Situación {sit} (Irrecuperable / Incoobrable) +0pts") # 2. Cheques Rechazados cant_cheques = len(report.financiero.cheques_rechazados) if cant_cheques > 0: deduccion = cant_cheques * 5 score -= deduccion factores.append(f"{cant_cheques} Cheques rechazados -{deduccion}pts") # 3. Causas Judiciales # Consideramos causas judiciales activas (que no tengan estado Terminado/Archivado) causas_activas = 0 for c in report.judicial.causas: estado = (c.estado or "").lower() if not any(x in estado for x in ["archiv", "termin", "finaliz", "cerrad", "resuelt"]): causas_activas += 1 if causas_activas > 0: deduccion = causas_activas * 4 score -= deduccion factores.append(f"{causas_activas} Causas judiciales activas -{deduccion}pts") # 4. Inhibiciones / Embargos # Usar solo patrimonial.inhibiciones que ya está consolidado (evitar duplicados con judicial) inhibiciones_totales = len(report.patrimonial.inhibiciones) if inhibiciones_totales > 0: deduccion = inhibiciones_totales * 10 score -= deduccion factores.append(f"{inhibiciones_totales} Inhibiciones/Embargos registrados -{deduccion}pts") # 5. AFIP Activo (Señal positiva) estado_afip = (report.fiscal.estado_afip or "").lower() if "activ" in estado_afip: score += 5 factores.append("AFIP Activo +5pts") # 6. Antigüedad laboral ANSES if report.previsional.tiene_aportes: alta_os = report.previsional.fecha_alta_obra_social es_antiguo = False if alta_os: try: # Intentamos parsear la fecha de alta. Formatos típicos: YYYY-MM-DD o DD/MM/YYYY fecha_alta = None for fmt in ("%Y-%m-%d", "%d/%m/%Y", "%Y/%m/%d"): try: fecha_alta = datetime.strptime(alta_os, fmt) break except ValueError: continue if fecha_alta: anios = (datetime.now() - fecha_alta).days / 365.25 if anios >= 3.0: es_antiguo = True except Exception as e: logger.debug(f"Error parseando fecha alta obra social: {e}") # Fallback si no hay fecha pero tiene aportes al día if es_antiguo or (report.previsional.aportes_al_dia and not alta_os): score += 3 factores.append("Antigüedad laboral/Aportes > 3 años +3pts") # 7. Inmuebles registrados cant_inmuebles = len(report.patrimonial.inmuebles) if cant_inmuebles > 0: puntos_inm = min(6, cant_inmuebles * 2) score += puntos_inm factores.append(f"{cant_inmuebles} Inmuebles registrados +{puntos_inm}pts (máx +6)") # 8. Marcas INPI (solo para empresas; en personas siempre 0) cant_marcas = len(getattr(report, "marcas_inpi", [])) if cant_marcas > 0: puntos_marcas = min(4, cant_marcas * 2) score += puntos_marcas factores.append(f"{cant_marcas} Marcas INPI registradas +{puntos_marcas}pts (máx +4)") # Acotar el score final entre 1 y 100 score_final = max(1, min(100, score)) # Determinar nivel if score_final >= 80: nivel = "Excelente" elif score_final >= 60: nivel = "Bueno" elif score_final >= 40: nivel = "Regular" elif score_final >= 20: nivel = "Malo" else: nivel = "Crítico" return ScoreCrediticio( valor=score_final, nivel=nivel, historial=history_scores or [], factores=factores ) def compute_company_score(report: CompanyReport, history_scores: List[ScoreHistorial] = None) -> ScoreCrediticio: """ Calcula el score crediticio (0-1000) para una empresa/ persona jurídica. Replica la lógica del frontend: base 950, penalizaciones por BCRA, cheques y causas. """ score = 950 factores = [] # 1. BCRA Situación Actual sit = report.financiero.bcra_situacion_actual if sit is None or sit <= 1: factores.append("BCRA Situación Normal +0pts") elif sit == 2: score -= 150 factores.append("BCRA Situación 2 -150pts") elif sit == 3: score -= 300 factores.append("BCRA Situación 3 -300pts") elif sit == 4: score -= 500 factores.append("BCRA Situación 4 -500pts") elif sit >= 5: score -= 750 factores.append(f"BCRA Situación {sit} -750pts") # 2. Cheques Rechazados (máx -250) cant_cheques = len(report.financiero.cheques_rechazados) if cant_cheques > 0: deduccion = min(250, cant_cheques * 50) score -= deduccion factores.append(f"{cant_cheques} Cheques rechazados -{deduccion}pts (máx -250)") # 3. Causas Judiciales (máx -200) cant_causas = len(report.judicial.causas) if cant_causas > 0: deduccion = min(200, cant_causas * 40) score -= deduccion factores.append(f"{cant_causas} Causas judiciales -{deduccion}pts (máx -200)") # 4. AFIP Activo (bonus) estado_afip = (report.fiscal.estado_afip or "").lower() if "activ" in estado_afip: score += 20 factores.append("AFIP Activo +20pts") # 5. IGJ inscripto (bonus) if report.igj.numero_inscripcion: score += 10 factores.append("IGJ Inscripto +10pts") # Acotar entre 0 y 1000 score_final = max(0, min(1000, score)) # Determinar nivel if score_final >= 700: nivel = "Excelente" elif score_final >= 450: nivel = "Medio" else: nivel = "Crítico" return ScoreCrediticio( valor=score_final, nivel=nivel, historial=history_scores or [], factores=factores ) def compute_group_score(report: GroupReport, history_scores: List[ScoreHistorial] = None) -> ScoreCrediticio: """ Calcula el score crediticio (0-1000) para un grupo económico (titular + empresas + vehículos). Replica la lógica del frontend: base 950, penalizaciones por BCRA/riesgos, bonus por vehículos. """ score = 950 factores = [] # 1. BCRA del titular sit = report.target.financiero.bcra_situacion_actual if sit is None or sit <= 1: factores.append("BCRA Titular Normal +0pts") elif sit == 2: score -= 150 factores.append("BCRA Titular Sit. 2 -150pts") elif sit == 3: score -= 300 factores.append("BCRA Titular Sit. 3 -300pts") elif sit == 4: score -= 500 factores.append("BCRA Titular Sit. 4 -500pts") elif sit >= 5: score -= 750 factores.append(f"BCRA Titular Sit. {sit} -750pts") # 2. Riesgos consolidados (máx -300) total_riesgos = len(report.riesgo_consolidado) if total_riesgos > 0: deduccion = min(300, total_riesgos * 60) score -= deduccion factores.append(f"{total_riesgos} Riesgos consolidados -{deduccion}pts (máx -300)") # 3. Vehículos vinculados (bonus, máx +100) cant_vehiculos = len(report.vehiculos_vinculados) if cant_vehiculos > 0: bonus = min(100, cant_vehiculos * 25) score += bonus factores.append(f"{cant_vehiculos} Vehículos vinculados +{bonus}pts (máx +100)") # 4. Empresas vinculadas (bonus si están activas) empresas_activas = 0 for e in report.empresas_vinculadas: if "activ" in (e.fiscal.estado_afip or "").lower(): empresas_activas += 1 if empresas_activas > 0: bonus = min(50, empresas_activas * 25) score += bonus factores.append(f"{empresas_activas} Empresas activas +{bonus}pts (máx +50)") # Acotar entre 0 y 1000 score_final = max(0, min(1000, score)) # Determinar nivel if score_final >= 700: nivel = "Excelente" elif score_final >= 450: nivel = "Medio" else: nivel = "Crítico" return ScoreCrediticio( valor=score_final, nivel=nivel, historial=history_scores or [], factores=factores )