# Plan Técnico — Módulo de Scoring IA para Concesión de Créditos

**Fecha:** 2026-06-20  
**Autor:** Claude (basado en documentos Recuperación Créditos v1.0 + arquitectura Novasis)  
**Complementa:** `plan-solicitud_credito.md` (flow básico ya implementado) + `plan-modulo-mora.md`  
**Stack:** NestJS + Prisma + PostgreSQL + React/MUI + Claude API (ya integrado en Novasis)  
**Decisiones clave:**
- Motor de scoring ejecutado por Claude AI (API ya integrada en el ERP)
- IA recomienda → analista siempre decide (semáforo Verde/Amarillo/Rojo)
- Ambos tipos: persona física Y jurídica (flujos diferenciados)
- Garantes personales + garantías reales registradas
- Centrales de riesgo: arquitectura de adapters (ninguna contratada aún → Mocks listos para activar)
- Extiende `solicitud_credito` existente — NO lo reemplaza

---

## 1. VISIÓN GENERAL DEL FLUJO

```
Vendedor / Asesor de Crédito
        │
        ▼
[Formulario de Solicitud Enriquecido]
  • Datos personales / empresariales
  • Situación laboral
  • Declaración de ingresos y gastos
  • Referencias
  • Garantes + garantías reales
        │
        ▼ (estado: "en_evaluacion")
[Motor de Consulta a Centrales] ← paralelo, async
  • Informconf      (base positiva + negativa)
  • Equifax         (score + historial)
  • BCP CRB         (Central de Riesgos BCP)
  • INCOOP          (riesgo cooperativo)
  • Casas de crédito (sector informal)
        │
        ▼
[Historial Interno Novasis]
  • Tiempo como cliente
  • Puntualidad histórica de pagos
  • Promesas cumplidas/rotas
  • Mora activa (vía módulo mora)
        │
        ▼
[Motor IA — Claude API]
  • Consolida todas las variables
  • Detecta inconsistencias
  • Calcula score 0–1000 con componentes
  • Genera semáforo + informe narrativo
  • Recomienda monto máx, plazo, condiciones
        │
        ▼
[Dashboard Analista] ← estado: "pendiente_revision"
  • Vista unificada: score, centrales, historial, declaración
  • Simulador "¿qué pasa si...?"
  • Aprueba / Rechaza / Solicita datos adicionales
        │
        ▼
[Resultado] → aprobada / rechazada / condicional
  • Se integra con flujo existente de solicitud_credito
  • Notificación al cliente (Twilio SMS/WhatsApp)
```

---

## 2. INTEGRACIÓN CON FLUJO EXISTENTE

El módulo de scoring se inserta entre los estados actuales de `solicitud_credito`:

```
FLUJO ACTUAL:
borrador → pendiente_aprobacion → aprobada | rechazada

FLUJO NUEVO (estados adicionales):
borrador 
  → en_evaluacion          ← NUEVO: consultando centrales + calculando score
  → pendiente_revision     ← NUEVO: score listo, esperando analista
  → aprobada_condicionada  ← NUEVO: aprobada con condiciones especiales
  → aprobada
  → rechazada
  → cancelada
  → facturada
```

**Modificación en `solicitud_credito` existente:**
```sql
ALTER TABLE solicitud_credito
  ADD COLUMN IF NOT EXISTS scoring_id         UUID,  -- referencia a evaluacion_credito
  ADD COLUMN IF NOT EXISTS requiere_scoring   BOOLEAN DEFAULT true,
  ADD COLUMN IF NOT EXISTS monto_solicitado   DECIMAL(19,4),
  ADD COLUMN IF NOT EXISTS plazo_meses        SMALLINT,
  ADD COLUMN IF NOT EXISTS proposito_credito  VARCHAR(100);
  -- consumo | electrodomesticos | vehiculo | vivienda | negocio | refinanciacion | otro
```

---

## 3. BASE DE DATOS — TABLAS NUEVAS

### 3.1 `evaluaciones_credito` (cabecera del proceso de scoring)

```sql
CREATE TABLE evaluaciones_credito (
  id                      UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  empresa_id              UUID NOT NULL REFERENCES empresas(id),
  solicitud_credito_id    UUID NOT NULL REFERENCES solicitud_credito(id),
  cliente_id              UUID NOT NULL REFERENCES clientes(id),
  
  -- Tipo de evaluación
  tipo_solicitante        VARCHAR(20) NOT NULL,  -- fisica | juridica
  
  -- Score final (calculado por IA)
  score_total             SMALLINT,              -- 0 a 1000
  semaforo                VARCHAR(10),           -- verde | amarillo | rojo
  -- Verde: 700-1000, Amarillo: 450-699, Rojo: 0-449
  
  monto_recomendado       DECIMAL(19,4),         -- monto máx que recomienda la IA
  plazo_recomendado_meses SMALLINT,
  
  -- Componentes de score (para desglose visual)
  score_capacidad_pago    SMALLINT,              -- 0-300 (30%)
  score_historial_interno SMALLINT,              -- 0-250 (25%)
  score_centrales         SMALLINT,              -- 0-250 (25%)
  score_garantias         SMALLINT,              -- 0-100 (10%)
  score_perfil_riesgo     SMALLINT,              -- 0-100 (10%)
  
  -- Resultado del analista (decisión final)
  decision_analista       VARCHAR(20),           -- aprobado | rechazado | condicional | pendiente
  analista_id             UUID REFERENCES usuarios(id),
  decision_fecha          TIMESTAMPTZ,
  comentario_analista     TEXT,
  condiciones_especiales  TEXT,                  -- si decision = condicional
  
  -- Informe IA (texto generado por Claude)
  informe_ia              TEXT,                  -- análisis narrativo completo
  factores_positivos      JSONB,                 -- array de strings
  factores_riesgo         JSONB,                 -- array de strings
  alertas                 JSONB,                 -- alertas críticas detectadas
  
  -- Estado del proceso
  estado                  VARCHAR(30) DEFAULT 'iniciada',
  -- iniciada | consultando_centrales | calculando_score | listo | error
  error_mensaje           TEXT,
  
  -- Versión del modelo para auditoría
  version_scoring         VARCHAR(20) DEFAULT '1.0',
  
  creado_en               TIMESTAMPTZ DEFAULT NOW(),
  actualizado_en          TIMESTAMPTZ DEFAULT NOW(),
  creado_por              UUID REFERENCES usuarios(id)
);

CREATE INDEX idx_eval_credito_solicitud  ON evaluaciones_credito(solicitud_credito_id);
CREATE INDEX idx_eval_credito_cliente    ON evaluaciones_credito(cliente_id, empresa_id);
CREATE INDEX idx_eval_credito_semaforo   ON evaluaciones_credito(semaforo);
```

---

### 3.2 `declaracion_financiera_solicitante`

Una por evaluación. Contiene ingresos, gastos, activos y pasivos declarados.

```sql
CREATE TABLE declaracion_financiera_solicitante (
  id                        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  evaluacion_id             UUID NOT NULL UNIQUE REFERENCES evaluaciones_credito(id),
  
  -- === SITUACIÓN LABORAL ===
  tipo_actividad            VARCHAR(30) NOT NULL,
  -- dependiente | independiente | empresario | jubilado_pensionado | sin_actividad_formal
  
  -- Si dependiente:
  empleador_nombre          VARCHAR(200),
  empleador_ruc             VARCHAR(20),
  empleador_telefono        VARCHAR(30),
  cargo                     VARCHAR(100),
  tipo_contrato             VARCHAR(30),         -- indefinido | temporal | por_obra
  antiguedad_meses          SMALLINT,
  
  -- Si independiente/empresario:
  tipo_negocio              VARCHAR(100),
  antiguedad_negocio_meses  SMALLINT,
  
  -- === INGRESOS MENSUALES (en PYG equivalente) ===
  ingreso_salario_neto      DECIMAL(19,4) DEFAULT 0,  -- salario después de descuentos
  ingreso_negocio_propio    DECIMAL(19,4) DEFAULT 0,  -- utilidad neta negocio
  ingreso_alquileres        DECIMAL(19,4) DEFAULT 0,
  ingreso_honorarios        DECIMAL(19,4) DEFAULT 0,
  ingreso_jubilacion        DECIMAL(19,4) DEFAULT 0,
  ingreso_otros             DECIMAL(19,4) DEFAULT 0,
  ingreso_otros_detalle     VARCHAR(200),
  
  -- Total calculado en backend
  ingreso_total_declarado   DECIMAL(19,4) GENERATED ALWAYS AS (
    ingreso_salario_neto + ingreso_negocio_propio + ingreso_alquileres +
    ingreso_honorarios + ingreso_jubilacion + ingreso_otros
  ) STORED,
  
  -- === GASTOS FIJOS MENSUALES ===
  gasto_alquiler_vivienda   DECIMAL(19,4) DEFAULT 0,
  gasto_servicios_basicos   DECIMAL(19,4) DEFAULT 0,  -- agua, luz, gas, internet
  gasto_alimentacion        DECIMAL(19,4) DEFAULT 0,
  gasto_educacion           DECIMAL(19,4) DEFAULT 0,
  gasto_salud               DECIMAL(19,4) DEFAULT 0,
  gasto_transporte          DECIMAL(19,4) DEFAULT 0,
  gasto_otros_creditos      DECIMAL(19,4) DEFAULT 0,  -- suma cuotas otras deudas
  gasto_tarjetas_credito    DECIMAL(19,4) DEFAULT 0,  -- cuota mínima tarjetas
  gasto_otros               DECIMAL(19,4) DEFAULT 0,
  gasto_otros_detalle       VARCHAR(200),
  
  gasto_total_declarado     DECIMAL(19,4) GENERATED ALWAYS AS (
    gasto_alquiler_vivienda + gasto_servicios_basicos + gasto_alimentacion +
    gasto_educacion + gasto_salud + gasto_transporte +
    gasto_otros_creditos + gasto_tarjetas_credito + gasto_otros
  ) STORED,
  
  -- Ingreso disponible (calculado)
  ingreso_disponible        DECIMAL(19,4) GENERATED ALWAYS AS (
    (ingreso_salario_neto + ingreso_negocio_propio + ingreso_alquileres +
     ingreso_honorarios + ingreso_jubilacion + ingreso_otros) -
    (gasto_alquiler_vivienda + gasto_servicios_basicos + gasto_alimentacion +
     gasto_educacion + gasto_salud + gasto_transporte +
     gasto_otros_creditos + gasto_tarjetas_credito + gasto_otros)
  ) STORED,
  
  -- === ACTIVOS DECLARADOS ===
  tiene_vivienda_propia     BOOLEAN DEFAULT false,
  valor_vivienda_estimado   DECIMAL(19,4),
  tiene_vehiculo            BOOLEAN DEFAULT false,
  cantidad_vehiculos        SMALLINT DEFAULT 0,
  valor_vehiculos_estimado  DECIMAL(19,4),
  tiene_terreno             BOOLEAN DEFAULT false,
  valor_terrenos_estimado   DECIMAL(19,4),
  otros_activos_detalle     TEXT,
  otros_activos_valor       DECIMAL(19,4),
  
  -- === PASIVOS DECLARADOS (deudas actuales fuera de la empresa) ===
  -- (se registran en detalle en tabla separada: deudas_activas_declaradas)
  deuda_total_externa_declarada DECIMAL(19,4) DEFAULT 0,
  
  -- === DATOS PERSONALES (complementan personas/clientes) ===
  estado_civil              VARCHAR(20),
  -- soltero | casado | union_de_hecho | divorciado | viudo
  cantidad_dependientes     SMALLINT DEFAULT 0,
  nivel_educativo           VARCHAR(30),
  -- primaria | secundaria | tecnico | universitario | posgrado
  
  -- === VIVIENDA ===
  tipo_vivienda             VARCHAR(20),
  -- propia | alquilada | familiar | hipotecada
  
  creado_en   TIMESTAMPTZ DEFAULT NOW(),
  creado_por  UUID REFERENCES usuarios(id)
);
```

---

### 3.3 `deudas_activas_declaradas` (detalle de otras deudas del solicitante)

```sql
CREATE TABLE deudas_activas_declaradas (
  id                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  evaluacion_id         UUID NOT NULL REFERENCES evaluaciones_credito(id),
  
  institucion           VARCHAR(150) NOT NULL,   -- "Banco X", "Financiera Y", etc.
  tipo_deuda            VARCHAR(30),             -- credito_personal | hipoteca | tarjeta | vehiculo | otro
  monto_original        DECIMAL(19,4),
  saldo_pendiente       DECIMAL(19,4),
  cuota_mensual         DECIMAL(19,4),
  cuotas_restantes      SMALLINT,
  al_dia                BOOLEAN DEFAULT true,
  
  creado_en   TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_deudas_evaluacion ON deudas_activas_declaradas(evaluacion_id);
```

---

### 3.4 `garantes_solicitud`

```sql
CREATE TABLE garantes_solicitud (
  id                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  evaluacion_id         UUID NOT NULL REFERENCES evaluaciones_credito(id),
  orden                 SMALLINT DEFAULT 1,      -- 1° garante, 2° garante
  
  -- Datos identificación
  nombre_completo       VARCHAR(200) NOT NULL,
  nro_documento         VARCHAR(30) NOT NULL,    -- CI o RUC
  tipo_documento        VARCHAR(10) DEFAULT 'CI', -- CI | RUC | pasaporte
  fecha_nacimiento      DATE,
  
  -- Contacto
  celular               VARCHAR(30),
  telefono              VARCHAR(30),
  email                 VARCHAR(150),
  direccion             VARCHAR(300),
  
  -- Relación con el solicitante
  relacion              VARCHAR(30),
  -- familiar | conyugue | amigo | socio_comercial | otro
  relacion_detalle      VARCHAR(100),
  
  -- Situación laboral simplificada
  tipo_actividad        VARCHAR(30),
  empleador_nombre      VARCHAR(200),
  ingreso_mensual_estimado DECIMAL(19,4),
  
  -- Resultado consulta en centrales (se llena automáticamente)
  resultado_central_riesgo VARCHAR(20),         -- limpio | observaciones | negativo | no_consultado
  detalles_central      JSONB,                  -- raw condensado de la respuesta
  
  -- ¿Ya es cliente de la empresa?
  cliente_id            UUID REFERENCES clientes(id),
  historial_interno     JSONB,                  -- snapshot si es cliente
  
  creado_en   TIMESTAMPTZ DEFAULT NOW(),
  creado_por  UUID REFERENCES usuarios(id)
);

CREATE INDEX idx_garantes_evaluacion ON garantes_solicitud(evaluacion_id);
CREATE INDEX idx_garantes_documento ON garantes_solicitud(nro_documento);
```

---

### 3.5 `garantias_reales_solicitud`

```sql
CREATE TABLE garantias_reales_solicitud (
  id                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  evaluacion_id         UUID NOT NULL REFERENCES evaluaciones_credito(id),
  
  tipo                  VARCHAR(30) NOT NULL,
  -- hipoteca | prenda_vehiculo | prenda_maquinaria | deposito_bancario | 
  -- terreno | bien_inmueble | otro
  
  descripcion           TEXT NOT NULL,           -- descripción detallada del bien
  
  -- Identificación legal del bien
  numero_registro       VARCHAR(100),            -- matrícula, padrón catastral, nº chapa
  entidad_registro      VARCHAR(150),            -- DNIT, Municipalidad, SET, etc.
  
  -- Valuación
  valor_estimado        DECIMAL(19,4),           -- valor declarado por el solicitante
  valor_tasado          DECIMAL(19,4),           -- valor según tasación profesional (si existe)
  fecha_tasacion        DATE,
  tasador_nombre        VARCHAR(200),
  
  -- Estado legal
  tiene_cargas          BOOLEAN DEFAULT false,   -- hipotecas previas, embargos
  descripcion_cargas    TEXT,
  
  -- Documentación
  documentos_adjuntos   JSONB,                   -- array de { nombre, url, tipo }
  
  -- Relación cobertura
  porcentaje_cobertura  DECIMAL(5,2),
  -- (valor_tasado / monto_solicitado) * 100 — calculado en backend
  
  creado_en   TIMESTAMPTZ DEFAULT NOW(),
  creado_por  UUID REFERENCES usuarios(id)
);
```

---

### 3.6 `consultas_centrales_riesgo`

Una fila por cada consulta realizada (solicitante + cada garante).

```sql
CREATE TABLE consultas_centrales_riesgo (
  id                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  evaluacion_id         UUID NOT NULL REFERENCES evaluaciones_credito(id),
  
  -- A quién se consultó
  tipo_consultado       VARCHAR(20) NOT NULL,    -- solicitante | garante
  garante_id            UUID REFERENCES garantes_solicitud(id),
  nro_documento         VARCHAR(30) NOT NULL,
  nombre_consultado     VARCHAR(200),
  
  -- Central consultada
  central              VARCHAR(30) NOT NULL,
  -- informconf_positiva | informconf_negativa | equifax | bcp_crb | incoop | casa_credito
  
  -- Estado de la consulta
  estado                VARCHAR(20) DEFAULT 'pendiente',
  -- pendiente | en_proceso | completada | error | timeout | no_disponible
  
  -- Resultado resumido (lo que muestra el analista)
  resultado_resumido    VARCHAR(20),
  -- limpio | observaciones | negativo | sin_historial | no_consultado
  
  -- Datos clave extraídos (schema flexible por central)
  datos_extractados     JSONB,
  -- Para informconf: { protestos: 0, deudas_impagas: 0, score: null }
  -- Para BCP CRB: { saldo_deudas: X, categoría_riesgo: "Normal", instituciones: [] }
  -- Para Equifax: { score: 750, morosidad_90d: false, ... }
  
  -- Respuesta raw (para auditoría, no se muestra en UI directamente)
  respuesta_raw         JSONB,
  
  -- Timing
  solicitada_en         TIMESTAMPTZ DEFAULT NOW(),
  completada_en         TIMESTAMPTZ,
  duracion_ms           INT,
  
  -- Error si falló
  error_codigo          VARCHAR(50),
  error_mensaje         TEXT
);

CREATE INDEX idx_consultas_evaluacion ON consultas_centrales_riesgo(evaluacion_id);
CREATE INDEX idx_consultas_documento  ON consultas_centrales_riesgo(nro_documento, central);
```

---

### 3.7 `documentos_solicitud_credito`

```sql
CREATE TABLE documentos_solicitud_credito (
  id                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  evaluacion_id         UUID NOT NULL REFERENCES evaluaciones_credito(id),
  
  tipo_documento        VARCHAR(40) NOT NULL,
  -- ci_solicitante | recibo_sueldo | extracto_bancario | declaracion_renta
  -- escritura_propiedad | patente_vehiculo | ci_garante | balance_empresa | otro
  
  nombre_archivo        VARCHAR(255) NOT NULL,
  url_storage           VARCHAR(500) NOT NULL,  -- URL en S3/MinIO
  mime_type             VARCHAR(100),
  tamano_bytes          INT,
  
  -- A quién pertenece el documento
  pertenece_a           VARCHAR(20) DEFAULT 'solicitante',
  -- solicitante | garante_1 | garante_2 | garantia
  referencia_id         UUID,                   -- id del garante o garantia si aplica
  
  verificado            BOOLEAN DEFAULT false,
  verificado_por        UUID REFERENCES usuarios(id),
  verificado_en         TIMESTAMPTZ,
  observacion_verificacion TEXT,
  
  creado_en   TIMESTAMPTZ DEFAULT NOW(),
  creado_por  UUID REFERENCES usuarios(id)
);

CREATE INDEX idx_docs_evaluacion ON documentos_solicitud_credito(evaluacion_id);
```

---

### 3.8 `referencias_personales_solicitud`

```sql
CREATE TABLE referencias_personales_solicitud (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  evaluacion_id   UUID NOT NULL REFERENCES evaluaciones_credito(id),
  tipo            VARCHAR(20) DEFAULT 'personal',  -- personal | comercial | laboral
  nombre          VARCHAR(200) NOT NULL,
  relacion        VARCHAR(100),
  telefono        VARCHAR(30),
  email           VARCHAR(150),
  verificada      BOOLEAN DEFAULT false,
  notas_verificacion TEXT,
  creado_en       TIMESTAMPTZ DEFAULT NOW()
);
```

---

## 4. MOTOR DE SCORING — ARQUITECTURA

### 4.1 Adapters para Centrales de Riesgo (patrón Strategy)

```typescript
// src/scoring-credito/adapters/central-riesgo.interface.ts

/**
 * Interfaz unificada para cualquier central de riesgo.
 * Cada implementación se activa cuando se tiene credenciales.
 */
export interface CentralRiesgoAdapter {
  readonly nombre: string;
  readonly version: string;
  
  /** Indica si la central está operativa (credenciales configuradas) */
  estaDisponible(): boolean;
  
  /** Consulta el documento en la central y retorna resultado normalizado */
  consultar(
    documento: string,
    tipoDocumento: 'CI' | 'RUC',
    tipoConsulta: 'fisica' | 'juridica'
  ): Promise<ResultadoCentral>;
}

export interface ResultadoCentral {
  estado: 'limpio' | 'observaciones' | 'negativo' | 'sin_historial' | 'error' | 'no_disponible';
  datos: DatosExtraidosCentral;
  rawResponse?: unknown;     // respuesta original para auditoría
  duracion_ms: number;
  error?: string;
}

export interface DatosExtraidosCentral {
  // Campos comunes a todas las centrales
  protestos?: number;
  deudas_impagas?: number;
  score_externo?: number;          // si la central provee score propio
  nivel_endeudamiento?: 'bajo' | 'medio' | 'alto' | 'muy_alto';
  categoria_riesgo?: string;       // "Normal", "Con problemas potenciales", etc. (BCP)
  saldo_deudas_totales?: number;
  instituciones_deudoras?: string[];
  fecha_ultimo_incumplimiento?: string;
  morosidad_activa?: boolean;
  // Campos específicos por central van en datos raw
  [key: string]: unknown;
}
```

**Implementaciones — una por central:**

```typescript
// src/scoring-credito/adapters/informconf.adapter.ts
export class InformconfAdapter implements CentralRiesgoAdapter {
  readonly nombre = 'Informconf';
  readonly version = '1.0';
  
  estaDisponible(): boolean {
    return !!(process.env.INFORMCONF_API_URL && process.env.INFORMCONF_API_KEY);
  }
  
  async consultar(documento, tipoDocumento, tipoConsulta): Promise<ResultadoCentral> {
    if (!this.estaDisponible()) {
      return { estado: 'no_disponible', datos: {}, duracion_ms: 0 };
    }
    // TODO: implementar cuando se contrate
    // POST ${INFORMCONF_API_URL}/consulta
    // Headers: Authorization: Bearer ${INFORMCONF_API_KEY}
    // Body: { documento, tipo_documento, tipo_persona }
  }
}

// Los demás adapters siguen el mismo patrón:
// EquifaxAdapter, BCPCentralAdapter, INCOOPAdapter, CasaCreditoAdapter
// Mientras no estén contratados, estaDisponible() retorna false
// y el sistema los marca como "no_disponible" en la BD
```

**Registry centralizado:**

```typescript
// src/scoring-credito/adapters/centrales.registry.ts

@Injectable()
export class CentralesRiesgoRegistry {
  private adapters: Map<string, CentralRiesgoAdapter>;
  
  constructor(
    private informconf: InformconfAdapter,
    private equifax: EquifaxAdapter,
    private bcpCrb: BCPCentralAdapter,
    private incoop: INCOOPAdapter,
    private casaCredito: CasaCreditoAdapter,
  ) {
    this.adapters = new Map([
      ['informconf_positiva', this.informconf],
      ['informconf_negativa', this.informconf],
      ['equifax', this.equifax],
      ['bcp_crb', this.bcpCrb],
      ['incoop', this.incoop],
      ['casa_credito', this.casaCredito],
    ]);
  }
  
  /**
   * Consulta todas las centrales disponibles para un documento.
   * Las no disponibles quedan marcadas como 'no_disponible' en BD.
   * Se ejecutan en paralelo con timeout de 30 segundos cada una.
   */
  async consultarTodas(evaluacionId: string, documento: string): Promise<void> {
    const consultas = Array.from(this.adapters.entries()).map(
      ([central, adapter]) => this.consultarConTimeout(evaluacionId, central, adapter, documento)
    );
    await Promise.allSettled(consultas);  // allSettled = no falla si una central falla
  }
  
  private async consultarConTimeout(
    evaluacionId, central, adapter, documento
  ): Promise<void> {
    const timeoutMs = 30_000;
    // Guardar registro en BD → estado: 'en_proceso'
    // Ejecutar consulta con timeout
    // Actualizar BD con resultado
  }
}
```

---

### 4.2 Motor de Scoring con Claude API

```typescript
// src/scoring-credito/scoring.service.ts

@Injectable()
export class ScoringCreditoService {
  
  /**
   * Punto de entrada principal: ejecuta todo el proceso de evaluación.
   * Se llama cuando la solicitud pasa a estado 'en_evaluacion'.
   */
  async ejecutarEvaluacion(evaluacionId: string): Promise<void> {
    // 1. Obtener todos los datos de la evaluación
    const datos = await this.consolidarDatos(evaluacionId);
    
    // 2. Consultar centrales en paralelo (async, con allSettled)
    await this.centralesRegistry.consultarTodas(evaluacionId, datos.documento);
    
    // 3. Recuperar historial interno de Novasis
    const historialInterno = await this.obtenerHistorialInterno(datos.clienteId, datos.empresaId);
    
    // 4. Ejecutar scoring con Claude
    const resultado = await this.calcularScoreConIA(datos, historialInterno);
    
    // 5. Persistir resultado
    await this.persistirResultado(evaluacionId, resultado);
    
    // 6. Cambiar estado solicitud → pendiente_revision
    await this.solicititudService.cambiarEstado(datos.solicitudId, 'pendiente_revision');
    
    // 7. Notificar al analista asignado (WebSocket + email si configurado)
    await this.notificarAnalistaDisponible(evaluacionId);
  }
  
  private async calcularScoreConIA(
    datos: DatosConsolidados,
    historialInterno: HistorialInternoCliente
  ): Promise<ResultadoScoring> {
    
    const prompt = this.construirPromptScoring(datos, historialInterno);
    
    const respuestaIA = await this.claudeService.mensaje({
      system: SYSTEM_PROMPT_ANALISTA_CREDITO,
      messages: [{ role: 'user', content: prompt }],
      temperature: 0,      // determinístico para scoring
      max_tokens: 4096,
    });
    
    // Parsear respuesta estructurada de Claude (JSON en markdown)
    return this.parsearRespuestaIA(respuestaIA.content);
  }
  
  private construirPromptScoring(datos: DatosConsolidados, historial: HistorialInternoCliente): string {
    return `
Analiza esta solicitud de crédito y genera un scoring completo en formato JSON.

## DATOS DEL SOLICITANTE
- Nombre: ${datos.nombreCompleto}
- Tipo: ${datos.tipoSolicitante} (${datos.tipoActividad})
- Documento: ${datos.nroDocumento}
- Estado civil: ${datos.estadoCivil} | Dependientes: ${datos.cantidadDependientes}
- Nivel educativo: ${datos.nivelEducativo} | Tipo vivienda: ${datos.tipoVivienda}

## SITUACIÓN LABORAL
- Tipo de actividad: ${datos.tipoActividad}
- Empleador: ${datos.empleadorNombre || 'N/A'} | Antigüedad: ${datos.antiguedadMeses} meses
- Tipo contrato: ${datos.tipoContrato || 'N/A'}

## DECLARACIÓN FINANCIERA MENSUAL (PYG)
Ingresos:
- Salario neto: ${formatPYG(datos.ingresoSalarioNeto)}
- Negocio propio: ${formatPYG(datos.ingresoNegocioPropio)}
- Alquileres: ${formatPYG(datos.ingresoAlquileres)}
- Otros: ${formatPYG(datos.ingresoOtros)}
TOTAL INGRESOS: ${formatPYG(datos.ingresoTotalDeclarado)}

Gastos fijos:
- Alquiler/expensas: ${formatPYG(datos.gastoAlquilerVivienda)}
- Servicios básicos: ${formatPYG(datos.gastoServiciosBasicos)}
- Alimentación estimada: ${formatPYG(datos.gastoAlimentacion)}
- Cuotas otras deudas: ${formatPYG(datos.gastoOtrosCreditos)}
- Tarjetas de crédito: ${formatPYG(datos.gastoTarjetasCredito)}
- Otros gastos: ${formatPYG(datos.gastoOtros)}
TOTAL GASTOS: ${formatPYG(datos.gastoTotalDeclarado)}
INGRESO DISPONIBLE: ${formatPYG(datos.ingresoDisponible)}

## CRÉDITO SOLICITADO
- Monto: ${formatPYG(datos.montoSolicitado)}
- Plazo: ${datos.plazoMeses} meses
- Cuota estimada: ${formatPYG(datos.cuotaEstimada)}
- Propósito: ${datos.propositoCredito}
- Ratio cuota/ingreso disponible: ${((datos.cuotaEstimada / datos.ingresoDisponible) * 100).toFixed(1)}%

## DEUDAS ACTIVAS DECLARADAS
${datos.deudasActivas.map(d => `- ${d.institucion}: cuota ${formatPYG(d.cuotaMensual)}, saldo ${formatPYG(d.saldoPendiente)}, al día: ${d.alDia}`).join('\n')}
Total deuda externa declarada: ${formatPYG(datos.deudaTotalExternaDeclarada)}

## HISTORIAL INTERNO (Novasis ERP)
- Es cliente desde: ${historial.clienteDesde}
- Total créditos históricos: ${historial.totalCreditosHistoricos}
- Créditos pagados al día: ${historial.pagosAlDia}
- Cuotas vencidas pagadas: ${historial.cuotasVencidasPagadas}
- Promesas de pago cumplidas: ${historial.promesasCumplidas} / ${historial.promesasTotales}
- Mora activa actual: ${historial.tieneMoreActiva ? `SÍ — ${historial.diasMoraActual} días, Gs. ${historial.montoMoraActual}` : 'NO'}
- Peor atraso histórico: ${historial.peorAtrasoHistoricosDias} días

## RESULTADO CONSULTAS CENTRALES DE RIESGO
${datos.resultadosCentrales.map(c => `
### ${c.central.toUpperCase()}
- Estado: ${c.estado}
- Resultado: ${c.resultadoResumido}
- Protestos: ${c.datos?.protestos ?? 'N/D'}
- Deudas impagas: ${c.datos?.deudasImpagas ?? 'N/D'}
- Score externo: ${c.datos?.scoreExterno ?? 'N/D'}
- Categoría riesgo: ${c.datos?.categoriaRiesgo ?? 'N/D'}
- Endeudamiento total: ${c.datos?.saldoDeudasTotales ? formatPYG(c.datos.saldoDeudasTotales) : 'N/D'}
`).join('')}

## GARANTES
${datos.garantes.map((g, i) => `
Garante ${i + 1}: ${g.nombreCompleto}
- Relación: ${g.relacion}
- Ingresos estimados: ${formatPYG(g.ingresoMensualEstimado)}
- Resultado centrales: ${g.resultadoCentralRiesgo}
`).join('')}

## GARANTÍAS REALES
${datos.garantias.map(g => `
- Tipo: ${g.tipo} | Descripción: ${g.descripcion}
- Valor estimado: ${formatPYG(g.valorEstimado)} | Tasado: ${g.valorTasado ? formatPYG(g.valorTasado) : 'sin tasación'}
- Cargas: ${g.tieneCargas ? g.descripcionCargas : 'Ninguna'}
- Cobertura: ${g.porcentajeCobertura?.toFixed(1)}%
`).join('')}

---

Genera el análisis en el siguiente formato JSON exacto:

\`\`\`json
{
  "score_total": <número 0-1000>,
  "semaforo": "<verde|amarillo|rojo>",
  "componentes": {
    "capacidad_pago": <0-300>,
    "historial_interno": <0-250>,
    "centrales_riesgo": <0-250>,
    "garantias": <0-100>,
    "perfil_riesgo": <0-100>
  },
  "monto_recomendado": <número en PYG o null si rechazar>,
  "plazo_recomendado_meses": <número o null>,
  "decision_sugerida": "<aprobar|rechazar|condicional>",
  "condiciones_especiales": "<texto o null>",
  "factores_positivos": ["factor1", "factor2", "factor3"],
  "factores_riesgo": ["factor1", "factor2", "factor3"],
  "alertas": ["alerta crítica si existe"],
  "informe_narrativo": "<análisis completo en 200-400 palabras en español>"
}
\`\`\`
`;
  }
}

// Prompt del sistema para el modelo Claude
const SYSTEM_PROMPT_ANALISTA_CREDITO = `
Sos un analista de crédito senior con 15 años de experiencia en instituciones financieras de Paraguay.
Tu función es evaluar solicitudes de crédito de manera objetiva y conservadora.

Principios que seguís:
- La cuota no debe superar el 30-35% del ingreso disponible del solicitante
- Un ratio cuota/ingreso >40% es señal de rojo automática en capacidad de pago
- La presencia en base negativa (protestos, deudas impagas) es factor crítico
- La mora activa en la empresa es una señal de alerta importante
- Las garantías reales mejoran el score pero no compensan mala capacidad de pago
- Un buen garante puede elevar el score hasta 15-20 puntos
- Detectás inconsistencias entre ingresos declarados y endeudamiento externo visible en centrales
- Analizás el ratio préstamo/valor (LTV) para garantías reales (máximo recomendado: 70%)
- Tu recomendación de monto nunca excede 4x el ingreso mensual neto para créditos sin garantía real

TABLA DE SCORE (referencia):
- 800-1000: Excelente. Verde. Aprobación recomendada.
- 700-799:  Bueno. Verde. Aprobación recomendada.
- 600-699:  Aceptable. Amarillo. Aprobación con análisis adicional.
- 450-599:  Riesgoso. Amarillo. Condicional o reducir monto.
- 300-449:  Alto riesgo. Rojo. Rechazar o exigir garantías sólidas.
- 0-299:    Muy alto riesgo. Rojo. Rechazar.

Respondés SIEMPRE en el formato JSON exacto solicitado, sin texto adicional fuera del JSON.
`;
```

---

### 4.3 Cálculo por Componentes (backup si Claude falla)

Si la API de Claude no responde, el sistema cae a un scoring por reglas determinístico:

```typescript
/**
 * Scoring de respaldo basado en reglas puras (sin IA).
 * Se activa si Claude API falla o timeout > 30 segundos.
 */
calcularScorePorReglas(datos: DatosConsolidados): ResultadoScoring {
  let score = 0;
  
  // === BLOQUE 1: CAPACIDAD DE PAGO (máx 300 pts) ===
  const ratioCuotaIngreso = datos.cuotaEstimada / datos.ingresoDisponible;
  const scoreCap =
    ratioCuotaIngreso <= 0.20 ? 300 :
    ratioCuotaIngreso <= 0.25 ? 270 :
    ratioCuotaIngreso <= 0.30 ? 230 :
    ratioCuotaIngreso <= 0.35 ? 180 :
    ratioCuotaIngreso <= 0.40 ? 120 :
    ratioCuotaIngreso <= 0.50 ? 60  : 0;
  
  // Bonus estabilidad laboral
  const bonusLaboral =
    datos.tipoActividad === 'dependiente' && datos.antiguedadMeses >= 24 ? 20 :
    datos.tipoActividad === 'dependiente' && datos.antiguedadMeses >= 12 ? 10 : 0;
  
  score += Math.min(scoreCap + bonusLaboral, 300);
  
  // === BLOQUE 2: HISTORIAL INTERNO (máx 250 pts) ===
  const h = historialInterno;
  let scoreHist = h.clienteDesdeAnios >= 3 ? 80 : h.clienteDesdeAnios >= 1 ? 40 : 10;
  scoreHist += h.tieneMoreActiva ? -80 : 80;
  scoreHist += h.peorAtrasoHistoricosDias <= 7 ? 50 : h.peorAtrasoHistoricosDias <= 30 ? 20 : 0;
  const tasaPromesas = h.promesasTotales > 0 ? h.promesasCumplidas / h.promesasTotales : 1;
  scoreHist += tasaPromesas >= 0.9 ? 40 : tasaPromesas >= 0.7 ? 20 : 0;
  score += Math.max(0, Math.min(scoreHist, 250));
  
  // === BLOQUE 3: CENTRALES DE RIESGO (máx 250 pts) ===
  const tieneNegativo = datos.resultadosCentrales.some(c => c.resultadoResumido === 'negativo');
  const tieneObservaciones = datos.resultadosCentrales.some(c => c.resultadoResumido === 'observaciones');
  const scoreCent = tieneNegativo ? 0 : tieneObservaciones ? 100 : 200;
  // Penalización por endeudamiento externo vs ingreso
  score += scoreCent;
  
  // === BLOQUE 4: GARANTÍAS (máx 100 pts) ===
  const tieneGaranteBueno = datos.garantes.some(g => g.resultadoCentralRiesgo === 'limpio' && g.ingresoMensualEstimado > datos.cuotaEstimada * 2);
  const tieneGarantiaReal = datos.garantias.some(g => g.porcentajeCobertura >= 100);
  score += tieneGarantiaReal ? 80 : tieneGaranteBueno ? 60 : datos.garantes.length > 0 ? 30 : 0;
  
  // === BLOQUE 5: PERFIL DE RIESGO (máx 100 pts) ===
  const edad = calcularEdad(datos.fechaNacimiento);
  const scoreEdad = (edad >= 25 && edad <= 55) ? 40 : (edad >= 20 && edad < 25) ? 20 : 30;
  score += scoreEdad;
  score += datos.nivelEducativo === 'universitario' || datos.nivelEducativo === 'posgrado' ? 30 : 
           datos.nivelEducativo === 'tecnico' ? 20 : 10;
  
  return {
    score_total: Math.min(1000, Math.max(0, score)),
    semaforo: score >= 700 ? 'verde' : score >= 450 ? 'amarillo' : 'rojo',
    generado_por: 'reglas',  // ← marca que NO fue IA
    // ...
  };
}
```

---

## 5. ENDPOINTS API

```
Módulo: /api/v1/credito-scoring

POST   /credito-scoring/iniciar/:solicitudId   → inicia evaluación (async)
GET    /credito-scoring/:evaluacionId           → estado + resultado de evaluación
GET    /credito-scoring/:evaluacionId/informe   → informe completo para analista

POST   /credito-scoring/:evaluacionId/declaracion     → guardar declaración financiera
GET    /credito-scoring/:evaluacionId/declaracion     → obtener declaración

POST   /credito-scoring/:evaluacionId/garantes        → agregar garante
DELETE /credito-scoring/:evaluacionId/garantes/:id    → eliminar garante
GET    /credito-scoring/:evaluacionId/garantes        → listar garantes

POST   /credito-scoring/:evaluacionId/garantias       → agregar garantía real
DELETE /credito-scoring/:evaluacionId/garantias/:id   → eliminar garantía
GET    /credito-scoring/:evaluacionId/garantias       → listar garantías

GET    /credito-scoring/:evaluacionId/centrales        → estado de todas las consultas
POST   /credito-scoring/:evaluacionId/centrales/reintentar/:central → reintentar consulta fallida

POST   /credito-scoring/:evaluacionId/documentos       → subir documento
DELETE /credito-scoring/:evaluacionId/documentos/:id   → eliminar documento
GET    /credito-scoring/:evaluacionId/documentos       → listar documentos

POST   /credito-scoring/:evaluacionId/decision         → registrar decisión del analista
GET    /credito-scoring/historial/:clienteId           → historial de evaluaciones de un cliente
GET    /credito-scoring/pendientes                     → evaluaciones pendientes de revisión (analista)

Simulador:
POST   /credito-scoring/:evaluacionId/simular          → simula score con parámetros alternativos
```

---

## 6. FRONTEND — PANTALLAS

### 6.1 Formulario de Solicitud Enriquecido

Nuevo formulario multicapas que reemplaza al formulario simple de solicitud de crédito.  
Se accede desde `/solicitudes-credito/nueva-evaluacion`.

**Stepper de 6 pasos:**

```
Paso 1: Datos del solicitante
  • Tipo: persona física | jurídica
  • Datos personales (muchos ya existen en clientes/personas, se pre-cargan)
  • Estado civil, dependientes, nivel educativo, tipo vivienda
  • Para jurídica: razón social, RUC, representante legal, sector

Paso 2: Situación laboral y financiera
  • Tipo de actividad + empleador (si aplica)
  • Declaración de ingresos (tabla de fuentes)
  • Declaración de gastos (tabla de rubros)
  • Visualización: barra de capacidad de pago (ingreso disponible vs cuota propuesta)
  • Deudas activas (+ o editar filas)

Paso 3: Garantes
  • Hasta 3 garantes con sus datos
  • Cada garante tiene sub-formulario de ingresos
  • Indicador de si ya es cliente (auto-carga historial)

Paso 4: Garantías reales
  • Tipo de garantía
  • Datos del bien + valuación
  • Carga de documentos
  • Indicador de cobertura calculada (valor / monto solicitado)

Paso 5: Documentación requerida
  • Lista de documentos según tipo de crédito y garantías
  • Upload de archivos (PDF, JPG, PNG)
  • Estado de cada documento (pendiente / cargado / verificado)

Paso 6: Resumen y envío
  • Resumen de todos los datos ingresados
  • Botón "Iniciar Evaluación" → cambia estado a 'en_evaluacion' y dispara el proceso
```

### 6.2 Dashboard del Analista (`/credito-scoring/revision/:evaluacionId`)

La pantalla más importante del módulo:

```
┌──────────────────────────────────────────────────────────────────────┐
│  EVALUACIÓN CREDITICIA — María López                [#EVL-0001]      │
│  Solicitud: #SOL-0042 | Monto: Gs. 5.000.000 | Plazo: 12 meses     │
│  Evaluación completada: 20/06/2026 14:32                             │
├─────────────────┬────────────────────────────────────────────────────┤
│                 │                                                     │
│   SCORE TOTAL   │  COMPONENTES                                        │
│                 │                                                     │
│     ╔═══════╗   │  Capacidad de pago    ▓▓▓▓▓▓▓▓░░  245/300 (82%)   │
│     ║  742  ║   │  Historial interno    ▓▓▓▓▓▓░░░░  190/250 (76%)   │
│     ║ VERDE ║   │  Centrales de riesgo  ▓▓▓▓▓▓▓░░░  200/250 (80%)   │
│     ╚═══════╝   │  Garantías            ▓▓▓▓▓░░░░░   60/100 (60%)   │
│                 │  Perfil de riesgo     ▓▓▓▓▓▓▓░░░   70/100 (70%)   │
│  IA recomienda: │                                                     │
│  APROBAR        │  Ratio cuota/ingreso: 28%  ← ✅ dentro del rango   │
│  hasta Gs.5.0M  │  Monto recomendado: Gs. 5.000.000                  │
│  12-18 meses    │  Plazo recomendado: 12-18 meses                    │
│                 │                                                     │
├─────────────────┴────────────────────────────────────────────────────┤
│  CENTRALES DE RIESGO                                                  │
│  Informconf (+): ✅ LIMPIO    Informconf (-): ✅ LIMPIO              │
│  Equifax:        ⚪ NO DISPONIBLE   BCP CRB: ⚪ NO DISPONIBLE       │
│  INCOOP:         ⚪ NO DISPONIBLE   Casa Crédito: ⚪ NO DISPONIBLE   │
├──────────────────────────────────────────────────────────────────────┤
│  ANÁLISIS DE LA IA                                                    │
│  ┌──────────────────────────────────────────────────────────────────┐│
│  │ ✅ FACTORES POSITIVOS           ⚠️ FACTORES DE RIESGO           ││
│  │ • Cliente con 3 años de historial• Ingresos como independiente   ││
│  │ • Sin mora activa ni historial   • Solo 1 garante registrado     ││
│  │ • Ratio cuota/ingreso del 28%    • Sin garantía real             ││
│  └──────────────────────────────────────────────────────────────────┘│
│  [Ver informe completo IA ↓]                                         │
├──────────────────────────────────────────────────────────────────────┤
│  SIMULADOR "¿QUÉ PASA SI...?"                                        │
│  Monto: [__5.000.000__]  Plazo: [12] meses  [Recalcular]            │
│  → Cuota estimada: Gs. 583.333 | Ratio: 28% | Capacidad: OK ✅      │
├──────────────────────────────────────────────────────────────────────┤
│  DECISIÓN DEL ANALISTA                                                │
│  Comentario: [_________________________________]                      │
│  Condiciones especiales: [_____________________]                      │
│                                                                       │
│  [✅ APROBAR]  [🟡 APROBAR CONDICIONAL]  [❌ RECHAZAR]               │
└──────────────────────────────────────────────────────────────────────┘
```

### 6.3 Vista de Seguimiento (`/solicitudes-credito`)

Extender la tabla existente con:
- Columna "Score" con badge de color cuando disponible
- Columna "Estado Evaluación" (en evaluación / listo / pendiente revisión)
- Filtro por semáforo (verde/amarillo/rojo)
- Indicador de tiempo en cola (cuando lleva >24hs sin decisión)

### 6.4 Historial de Evaluaciones del Cliente

Al ver la ficha del cliente, nueva tab "Historial Crediticio":
- Timeline de todas sus evaluaciones pasadas
- Score en cada evaluación + evolución
- Decisiones tomadas y motivos de rechazo

---

## 7. DATOS REQUERIDOS POR TIPO DE CRÉDITO

Best practices de concesión crediticia en Paraguay:

| Campo | Consumo | Electrodomésticos | Vehículo | Inmobiliario | Empresarial |
|---|---|---|---|---|---|
| CI solicitante | ✅ obligatorio | ✅ | ✅ | ✅ | ✅ |
| Recibo de sueldo (últ. 3 meses) | ✅ | ✅ | ✅ | ✅ | N/A |
| Extracto bancario (últ. 3 meses) | Recomendado | Recomendado | ✅ | ✅ | ✅ |
| Declaración renta (SET) | No | No | Recomendado | ✅ | ✅ |
| Balance empresarial | No | No | No | Recomendado | ✅ |
| Garante | Recomendado | Recomendado | Opcional | Opcional | Opcional |
| Garantía real | No | No | Prenda vehículo | Hipoteca | Recomendado |
| Consulta centrales | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ratio cuota/ingreso máx | 30% | 30% | 35% | 30% | 40% |
| Monto máx sin garantía real | 4x ingreso | 6x ingreso | LTV 70% | LTV 70% | Según flujo |

---

## 8. VARIABLES DEL SCORING — DETALLE CON PESOS

### Bloque 1 — Capacidad de Pago (300 puntos, 30%)

| Variable | Puntos | Criterio |
|---|---|---|
| Ratio cuota/ingreso ≤ 20% | 300 | Excelente capacidad |
| Ratio cuota/ingreso 20-25% | 270 | Muy buena capacidad |
| Ratio cuota/ingreso 25-30% | 230 | Buena capacidad |
| Ratio cuota/ingreso 30-35% | 180 | Capacidad aceptable |
| Ratio cuota/ingreso 35-40% | 120 | Capacidad ajustada |
| Ratio cuota/ingreso 40-50% | 60 | Capacidad límite |
| Ratio cuota/ingreso > 50% | 0 | RECHAZO por capacidad |
| Bonus: relación dependencia + 24 meses | +20 | Estabilidad laboral |
| Bonus: tipo contrato indefinido | +10 | Seguridad laboral |
| Penalización: ingresos inconsistentes con centrales | -30 | Inconsistencia declarativa |

### Bloque 2 — Historial Interno Novasis (250 puntos, 25%)

| Variable | Puntos | Criterio |
|---|---|---|
| Cliente ≥ 3 años, siempre al día | 250 | Historial impecable |
| Cliente ≥ 1 año, sin mora activa | 150 | Buen historial |
| Cliente nuevo (0-12 meses) | 80 | Sin historial suficiente |
| Mora activa en la empresa | -80 | Señal roja fuerte |
| Peor atraso histórico ≤ 7 días | +50 | Muy puntual |
| Peor atraso histórico 8-30 días | +20 | Puntual aceptable |
| Peor atraso > 30 días | 0 | Sin bonus |
| Tasa cumplimiento promesas ≥ 90% | +40 | Muy confiable |
| Tasa cumplimiento promesas ≥ 70% | +20 | Confiable |
| Promesa vencida activa | -40 | Señal de riesgo |

### Bloque 3 — Centrales de Riesgo (250 puntos, 25%)

| Variable | Puntos | Criterio |
|---|---|---|
| Todas las centrales: LIMPIO | 250 | Perfil impecable |
| Observaciones menores (1-2 centrales) | 150 | Riesgo moderado |
| Negativos en 1 central | 80 | Riesgo alto |
| Negativos en 2+ centrales | 20 | Riesgo muy alto |
| Protestos activos | 0 (señal roja) | Rechazo recomendado |
| Centrales no disponibles (mock) | 125 | Score neutro (mitad) |
| Deuda externa total < 2x ingreso mensual | +30 | Endeudamiento bajo |
| Deuda externa total 2-4x ingreso | 0 | Normal |
| Deuda externa total > 4x ingreso | -30 | Sobreendeudado |

### Bloque 4 — Garantías y Garantes (100 puntos, 10%)

| Variable | Puntos | Criterio |
|---|---|---|
| Garantía real con LTV < 70% | 100 | Cobertura excelente |
| Garantía real con LTV 70-90% | 70 | Cobertura buena |
| Garante limpio con ingresos > 2x cuota | 60 | Garante fuerte |
| Garante limpio con ingresos > 1x cuota | 40 | Garante aceptable |
| Solo garante con observaciones | 20 | Garante débil |
| Sin garantías ni garantes | 0 | Sin respaldo |

### Bloque 5 — Perfil de Riesgo (100 puntos, 10%)

| Variable | Puntos | Criterio |
|---|---|---|
| Edad 30-50 años | 40 | Perfil más estable |
| Edad 25-29 o 51-60 años | 30 | Perfil aceptable |
| Edad < 25 o > 60 años | 20 | Perfil de mayor riesgo |
| Nivel educativo universitario/posgrado | 30 | Menor riesgo estadístico |
| Nivel técnico | 20 | Riesgo medio-bajo |
| Educación básica/secundaria | 10 | Neutro |
| Vivienda propia | +15 | Estabilidad patrimonial |
| Vivienda alquilada < 1 año en dir. actual | -5 | Inestabilidad domicilio |

---

## 9. PERMISOS RBAC

```
CREDITO_SCORING
  CREDITO.INICIAR_EVALUACION        ← vendedor/asesor: inicia proceso
  CREDITO.CARGAR_DOCUMENTOS         ← vendedor/asesor: sube docs del cliente
  CREDITO.VER_EVALUACION_PROPIA     ← vendedor: ver evaluaciones que inició
  CREDITO.VER_EVALUACION_EQUIPO     ← supervisor: ver todas las evaluaciones
  CREDITO.REVISAR_Y_DECIDIR         ← analista/supervisor: aprobar o rechazar
  CREDITO.CONFIGURAR_SCORING        ← admin: modificar pesos del scoring
  CREDITO.VER_INFORME_IA            ← analista/gerente: ver informe completo Claude
  CREDITO.SIMULAR_ESCENARIOS        ← analista: usar simulador de escenarios
  CREDITO.VER_AUDIT_EVALUACIONES    ← compliance: ver historial completo
  CREDITO.REINTENTAR_CENTRALES      ← analista/admin: reintentar consulta fallida
```

---

## 10. VARIABLES DE ENTORNO NUEVAS

```env
# Claude API (ya existe en Novasis — verificar está configurado)
CLAUDE_API_KEY=sk-ant-...
CLAUDE_SCORING_MODEL=claude-sonnet-4-6   # modelo a usar para scoring
CLAUDE_SCORING_TIMEOUT_MS=45000          # timeout para respuesta de IA

# Centrales de Riesgo (se completan cuando se contratan)
INFORMCONF_API_URL=          # pendiente de contratación
INFORMCONF_API_KEY=          # pendiente de contratación
EQUIFAX_API_URL=             # pendiente de contratación
EQUIFAX_API_KEY=             # pendiente de contratación
BCP_CRB_API_URL=             # pendiente de contratación
BCP_CRB_USUARIO=             # pendiente de contratación
BCP_CRB_PASSWORD=            # pendiente de contratación
INCOOP_API_URL=              # pendiente de contratación
INCOOP_API_KEY=              # pendiente de contratación

# Storage para documentos
DOCS_STORAGE_BUCKET=credito-documentos
DOCS_STORAGE_URL=https://...
DOCS_STORAGE_KEY=
DOCS_STORAGE_SECRET=
DOCS_MAX_SIZE_MB=10

# Scoring
SCORING_TIMEOUT_CENTRALES_MS=30000  # timeout por central
SCORING_SEMAFORO_VERDE=700          # umbral score verde
SCORING_SEMAFORO_AMARILLO=450       # umbral score amarillo (debajo = rojo)
SCORING_RATIO_CUOTA_MAXIMO=0.40    # ratio máximo cuota/ingreso (40%)
```

---

## 11. ESTRUCTURA DE ARCHIVOS

```
src/scoring-credito/
├── scoring-credito.module.ts
│
├── adapters/
│   ├── central-riesgo.interface.ts
│   ├── centrales.registry.ts
│   ├── informconf.adapter.ts        ← mock hasta contratar
│   ├── equifax.adapter.ts           ← mock hasta contratar
│   ├── bcp-crb.adapter.ts           ← mock hasta contratar
│   ├── incoop.adapter.ts            ← mock hasta contratar
│   └── casa-credito.adapter.ts      ← mock hasta contratar
│
├── scoring/
│   ├── scoring.service.ts           ← orquesta todo el proceso
│   ├── scoring-reglas.service.ts    ← fallback sin IA
│   └── scoring-prompts.ts           ← prompts para Claude
│
├── evaluaciones/
│   ├── evaluaciones.service.ts
│   └── evaluaciones.controller.ts
│
├── declaracion/
│   ├── declaracion.service.ts
│   └── declaracion.controller.ts
│
├── garantes/
│   ├── garantes.service.ts
│   └── garantes.controller.ts
│
├── garantias/
│   ├── garantias.service.ts
│   └── garantias.controller.ts
│
├── documentos/
│   ├── documentos.service.ts        ← upload a S3/MinIO
│   └── documentos.controller.ts
│
└── dto/
    ├── iniciar-evaluacion.dto.ts
    ├── declaracion-financiera.dto.ts
    ├── garante.dto.ts
    ├── garantia-real.dto.ts
    └── decision-analista.dto.ts

Frontend:
src/pages/credito-scoring/
├── NuevaEvaluacionCredito.jsx       ← stepper 6 pasos
├── RevisionEvaluacion.jsx           ← dashboard analista
└── HistorialEvaluaciones.jsx        ← listado con filtros

src/components/credito-scoring/
├── StepperEvaluacion.jsx
├── DeclaracionFinancieraForm.jsx
├── GarantesForm.jsx
├── GarantiasRealesForm.jsx
├── DocumentosUploadPanel.jsx
├── ScoreGauge.jsx                   ← medidor visual 0-1000
├── SemaforoScore.jsx                ← indicador verde/amarillo/rojo
├── ComponentesScoreBar.jsx          ← barras por componente
├── CentralesRiesgoGrid.jsx          ← grid de estado de centrales
├── SimuladorEscenarios.jsx          ← herramienta "¿qué pasa si?"
└── InformeIADialog.jsx              ← modal con informe narrativo completo
```

---

## 12. ROADMAP POR SPRINTS

### Sprint A — Formulario enriquecido + BD (2 semanas)

| Tarea | Tipo | Prioridad |
|---|---|---|
| Migraciones todas las tablas nuevas | Backend | 🔴 Crítico |
| Modificar `solicitud_credito` (campos nuevos + estados) | Backend | 🔴 Crítico |
| DTOs y módulo NestJS base | Backend | 🔴 Crítico |
| Stepper formulario (pasos 1-2: datos + declaración) | Frontend | 🔴 Crítico |
| Stepper pasos 3-4: garantes + garantías | Frontend | 🟠 Alto |
| Upload de documentos a S3/MinIO | Backend + Frontend | 🟠 Alto |
| Paso 5-6 de stepper (docs + resumen) | Frontend | 🟠 Alto |

**Entregable:** Se puede cargar una solicitud completa con todos los datos. Nada de IA todavía.

---

### Sprint B — Motor de consulta a centrales + arquitectura adapters (2 semanas)

| Tarea | Tipo | Prioridad |
|---|---|---|
| `CentralRiesgoAdapter` interface + registry | Backend | 🔴 Crítico |
| 5 adapters con mock (no_disponible por defecto) | Backend | 🔴 Crítico |
| Job async de consulta paralela a centrales | Backend | 🔴 Crítico |
| Persistencia de resultados en `consultas_centrales_riesgo` | Backend | 🔴 Crítico |
| `CentralesRiesgoGrid.jsx` en frontend | Frontend | 🟠 Alto |
| Endpoint reintentar central fallida | Backend | 🟡 Medio |

**Entregable:** La arquitectura de centrales está lista. Los mocks retornan "no_disponible". Cuando se contrate Informconf, solo hay que completar el adapter.

---

### Sprint C — Motor IA Claude + scoring (2 semanas)

| Tarea | Tipo | Prioridad |
|---|---|---|
| `scoring.service.ts` con llamada a Claude API | Backend | 🔴 Crítico |
| Construcción del prompt completo | Backend | 🔴 Crítico |
| Parser de respuesta JSON de Claude | Backend | 🔴 Crítico |
| Fallback `scoring-reglas.service.ts` | Backend | 🔴 Crítico |
| `obtenerHistorialInterno()` (integra con módulo mora) | Backend | 🟠 Alto |
| Persistencia score en `evaluaciones_credito` | Backend | 🔴 Crítico |
| WebSocket notificación a analista cuando score listo | Backend | 🟠 Alto |

**Entregable:** El proceso completo funciona de punta a punta. Se puede iniciar una evaluación y Claude genera un score con informe.

---

### Sprint D — Dashboard Analista + simulador (2 semanas)

| Tarea | Tipo | Prioridad |
|---|---|---|
| `RevisionEvaluacion.jsx` completo | Frontend | 🔴 Crítico |
| `ScoreGauge.jsx` + `SemaforoScore.jsx` | Frontend | 🔴 Crítico |
| `ComponentesScoreBar.jsx` | Frontend | 🟠 Alto |
| `InformeIADialog.jsx` | Frontend | 🟠 Alto |
| `SimuladorEscenarios.jsx` | Frontend | 🟠 Alto |
| Decisión analista (aprobar/rechazar/condicional) | Frontend + Backend | 🔴 Crítico |
| Notificación Twilio al cliente con resultado | Backend | 🟠 Alto |

**Entregable:** HU analista completa. Se puede revisar y decidir desde el dashboard.

---

### Sprint E — Integración con Informconf real + refinamiento (2 semanas)

*Requiere que Informconf esté contratado.*

| Tarea | Tipo | Prioridad |
|---|---|---|
| Completar `InformconfAdapter` con API real | Backend | 🔴 Crítico |
| Parseo de base positiva y negativa | Backend | 🔴 Crítico |
| Actualizar prompt IA con datos reales de Informconf | Backend | 🟠 Alto |
| Ajuste de pesos del scoring con primeros datos reales | Backend | 🟠 Alto |
| `HistorialEvaluaciones.jsx` + análisis de cartera | Frontend | 🟠 Alto |
| PDF de informe de evaluación (para el expediente) | Backend | 🟡 Medio |

---

## 13. RELACIÓN CON PLAN MORA

El módulo de scoring es la contracara del módulo de mora (`plan-modulo-mora.md`):

```
CONCESIÓN (este módulo)                    RECUPERACIÓN (plan-modulo-mora)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Evalúa al solicitante ANTES del crédito    Gestiona al cliente DESPUÉS de mora
                        │                                   ▲
                        │  El historial interno que         │
                        │  usa el scoring proviene de ──────┘
                        │  cuentas_mora, gestiones y
                        └─ promesas_pago del módulo mora

Funciones compartidas:
• obtenerHistorialInterno() en scoring.service.ts llama a datos de mora
• El score de scoring alimenta (futuro) el scoring de mora
• Twilio: ambos módulos usan el mismo servicio de notificaciones
• RBAC: los permisos de analista son distintos a los de cobrador/supervisor mora
```

---

## 14. NOTAS FINALES Y RECOMENDACIONES

**① Sobre las centrales de riesgo en Paraguay:**
- **Informconf** (más usada en Paraguay): tiene API REST. Ofrece base positiva (historial de pagos) y base negativa (protestos, cheques rechazados). Costo por consulta.
- **Equifax** tiene presencia en Paraguay vía partnership. Consultar localmente.
- **BCP Central de Riesgos**: No tiene API pública fácil. Requiere certificado digital y acceso especial. Muchas financieras lo consultan manualmente vía web.
- **INCOOP**: Solo aplica si la empresa es cooperativa registrada.
- **Casas de crédito**: No existe central formal. Se refiere a consultar directamente a otras financieras informales (no automatizable).

**Recomendación práctica:** Contratar Informconf primero (la más accesible y usada). El resto puede ser consulta manual del analista, y el sistema tiene campos para que el analista cargue el resultado manual si no hay API.

**② Sobre el prompt de Claude:**
El prompt incluye instrucciones explícitas de ratios y criterios de rechazo para que Claude sea conservador y alineado con las mejores prácticas locales. La temperatura 0 asegura reproducibilidad.

**③ Sobre la seguridad de datos:**
Los datos de declaración financiera y documentos son datos sensibles. Asegurar:
- Los documentos en S3/MinIO deben tener URLs firmadas con TTL (no URLs públicas permanentes)
- Los datos de centrales (raw response) no deben exponerse en APIs públicas
- Audit log de quién consultó qué central y cuándo
- GDPR/Ley de Datos Personales PY: registrar consentimiento del solicitante para consulta en centrales

**④ Calibración del scoring:**
Los pesos iniciales propuestos son basados en best practices estándar. Después de 3-6 meses con datos reales, el equipo debe analizar el tasa de default por segmento de score y ajustar los pesos. Campos `version_scoring` y los pesos configurables en `reglas_scoring_mora` (patrón similar) permiten esta evolución.
