Spaces:
Paused
Paused
File size: 9,280 Bytes
4223796 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | """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
)
|