Banco Express S.A. es una institución financiera de alcance regional con sede principal en la Ciudad de Panamá, operando además en Guatemala, El Salvador, Honduras y Costa Rica. Fundado en 2003, el banco ha crecido a través de una estrategia centrada en banca de personas naturales y pequeñas y medianas empresas (PyMEs), acumulando a la fecha un portafolio de crédito vigente de aproximadamente USD 1,200 millones, distribuidos principalmente en crédito de consumo, nómina y financiamiento PyME.
El banco cuenta con 87 sucursales físicas y una plataforma digital que procesa aproximadamente 4,200 solicitudes de crédito por mes. A pesar de este volumen, el proceso de originación actual opera mayoritariamente de forma manual, con tiempos de respuesta que oscilan entre 48 y 72 horas para productos de consumo, y hasta 15 días hábiles para créditos PyME. Esta brecha operativa representa una pérdida significativa de competitividad frente a fintechs y bancos digitales que ofrecen resoluciones en minutos.
Contexto de mercado: El sistema bancario centroamericano procesó en 2024 aproximadamente 6.2 millones de solicitudes de crédito anuales, con una tasa de aprobación promedio del 42%. La adopción de motores de decisión automatizados en la región aún se encuentra en etapa incipiente, representando una ventana de oportunidad estratégica para Banco Express que decide invertir en esta capacidad antes que sus competidores directos.
La transformación que impulsa este proyecto responde a tres presiones convergentes: (i) la necesidad operativa de reducir el tiempo de ciclo de originación de crédito de días a segundos; (ii) la oportunidad de capturar rentabilidad adicional mediante pricing diferenciado por perfil de riesgo —actualmente Banco Express ofrece una tasa uniforme dentro de cada producto, independientemente del riesgo individual del solicitante—; y (iii) la creciente exigencia regulatoria de demostrar rigor técnico en la gestión del riesgo de crédito, alineado con los principios de Basilea III y la normativa local de la Superintendencia de Bancos.
El business case del Motor de Decisión Crediticia de Banco Express descansa sobre cuatro palancas de valor cuantificadas, cada una con proyecciones financieras sustentadas en benchmarks de la industria regional y análisis interno del banco:
Business Case Crediticio: Documento que formaliza la justificación económica y estratégica de una inversión, cuantificando los beneficios esperados —en términos de ingresos adicionales, costos evitados y mejoras de riesgo— contra los costos de implementación y operación del proyecto, con una proyección temporal que permita calcular el retorno sobre la inversión (ROI) y el período de recuperación (payback period).
| # | Palanca de Valor | Mecanismo | Estimación Anual | Supuestos Clave |
|---|---|---|---|---|
| 1 | Incremento de volumen originado | Reducir el tiempo de respuesta de 48–72h a <3 segundos captura solicitudes que hoy se pierden por abandono del cliente | +USD 85M en desembolsos adicionales (+7.1% sobre cartera actual) |
Tasa de abandono actual del 34%; conversión post-implementación del 18% de esos abandonos |
| 2 | Mejora de calidad de cartera | Score predictivo elimina aprobaciones con alta PD que el criterio subjetivo actual no detecta | -USD 12M en pérdidas esperadas evitadas | Reducción del 18% en mora temprana (30-90 días) basada en benchmarks de implementaciones similares en LATAM |
| 3 | Revenue por pricing diferenciado | Cargar tasas más altas a perfiles de mayor riesgo (actualmente con tasa plana subvaluados) sin perder competitividad en perfiles prime | +USD 9.6M en margen de interés adicional | Dispersión de 450 bps entre banda A+ y banda C; aplicación a 60% de la cartera nueva |
| 4 | Eficiencia operativa | Automatización reduce costo de análisis manual (salarios, tiempo, papel); reasignación de analistas a casos complejos | +USD 3.2M en ahorro de costos | Automatización del 75% de decisiones; reorientación de 38 analistas a otras funciones de valor |
| Beneficio Total Anual Proyectado (Año 2 en adelante) | USD 109.8M | |||
Fuente: Análisis propio basado en datos internos de Banco Express S.A. y benchmarks de implementaciones en Grupo Financiero Banorte (México, 2022), Banco de Bogotá (Colombia, 2023) y BAC Credomatic (Centroamérica, 2024).
El retorno financiero del proyecto se formaliza a través de las siguientes métricas estándar de evaluación de inversiones:
Proyección financiera — Banco Express:
Inversión inicial total estimada: USD 4.2M (tecnología, datos, talento especializado, consultoría y cumplimiento regulatorio).
Costos operativos anuales recurrentes: USD 1.1M (licencias, equipo de modelo risk, infraestructura cloud, monitoreo).
Aplicando Ec. (0.1) con una tasa de descuento del 12% anual y horizonte de 5 años, el VPN proyectado es de USD 287M, con un período de recuperación (payback) de 8.3 meses desde la entrada en producción del MVP.
El ROI acumulado a 3 años (Ec. 0.2) se estima en +2,340%, reflejando el carácter altamente apalancado de este tipo de inversión en plataformas de decisión de crédito.
Los objetivos estratégicos del proyecto se articulan en cuatro dimensiones que abarcan el espectro completo desde la eficiencia operativa hasta la sostenibilidad del riesgo. Cada objetivo está formulado de manera SMART (Específico, Medible, Alcanzable, Relevante, con Tiempo definido) para facilitar su seguimiento:
| Dimensión | Objetivo | Métrica | Línea Base Actual | Meta a 12 Meses | Meta a 24 Meses |
|---|---|---|---|---|---|
| Velocidad | Automatizar decisiones de crédito en tiempo real | Tiempo promedio de decisión (seg) | 48–72 horas | < 60 segundos (piloto) | < 3 segundos |
| Escala | Incrementar volumen de originación sin incrementar riesgo proporcionalmente | Desembolsos mensuales (USD M) | USD 56M | USD 63M (+12.5%) | USD 75M (+34%) |
| Calidad | Mejorar predictibilidad del default | Coeficiente Gini del modelo | ~0.38 (estimado) | ≥ 0.52 | ≥ 0.58 |
| Mora | Reducir mora temprana en cartera nueva | NPL 30+ días, cosecha 6M | 4.8% cartera nueva | ≤ 4.2% | ≤ 3.9% |
| Pricing | Implementar diferenciación de tasa por perfil de riesgo | Dispersión máx–mín de tasa (bps) | 0 bps (tasa plana) | ±150 bps | ±450 bps |
| Rentabilidad | Alcanzar RORAC objetivo por segmento | RORAC promedio cartera nueva | 8.2% (no ajustado) | ≥ 14% | ≥ 18% |
| Regulatorio | Obtener aprobación interna y regulatoria del modelo | Validación independiente completada | No aplica | Modelo en validación | Modelo aprobado en producción |
| Experiencia | Mejorar NPS del proceso de solicitud de crédito | Net Promoter Score | NPS = 22 | NPS ≥ 38 | NPS ≥ 52 |
Los Indicadores Clave de Desempeño (KPIs) del Motor de Decisión Crediticia se organizan en cuatro cuadrantes que reflejan los distintos lentes bajo los cuales se evalúa el sistema: riesgo, negocio, tecnología y experiencia del cliente.
KPI (Key Performance Indicator): Métrica cuantificable seleccionada para evaluar el grado de avance hacia un objetivo estratégico específico. En sistemas de crédito, los KPIs deben ser monitoreables en tiempo real o con frecuencia mensual, tener benchmarks de industria establecidos, y estar vinculados directamente a decisiones de gestión operativa y de riesgo.
El Coeficiente Gini y el AUC-ROC son las métricas primarias de evaluación de poder discriminatorio en modelos de scoring. La relación entre ambas es directa:
$$ \text{Gini} = 2 \times \text{AUC} - 1 $$
Para Banco Express, un Gini inicial de 0.38 en el sistema actual (estimado implícitamente por el nivel de mora) debe elevarse a un mínimo de 0.52 en el primer modelo productivo y 0.58 en la versión optimizada. La práctica internacional de banca de consumo masivo establece como referencia un Gini ≥ 0.50 para modelos aceptables y ≥ 0.65 para modelos de clase mundial.
| Categoría | KPI | Fórmula / Definición | Frecuencia | Meta | Alerta |
|---|---|---|---|---|---|
| RIESGO DEL MODELO | Gini del Score | $2 \times AUC - 1$ sobre muestra de validación | Mensual | ≥ 0.58 | < 0.50 |
| KS Statistic | $\max |F_{buenos}(x) - F_{malos}(x)|$ | Mensual | ≥ 42% | < 35% | |
| PSI (Population Stability Index) | $\sum (A_i - E_i) \ln(A_i/E_i)$ | Mensual | < 0.10 | > 0.25 | |
| Brier Score | $\frac{1}{n}\sum(p_i - y_i)^2$ | Trimestral | ≤ 0.12 | > 0.18 | |
| CALIDAD DE CARTERA | NPL 30+ días (cosecha 6M) | Saldo 30+d / Saldo originado | Mensual | ≤ 3.9% | > 5.5% |
| Pérdida Esperada / Cartera | $PD \times LGD \times EAD$ / Saldo | Mensual | ≤ 1.8% | > 2.5% | |
| Tasa de Default Real vs. PD Estimada | $|DR_{real} - PD_{modelo}| / PD_{modelo}$ | Trimestral | ≤ 15% | > 30% | |
| NEGOCIO | RORAC por Banda | Utilidad ajustada / Capital económico | Mensual | ≥ 18% | < 12% |
| Tasa de Aprobación | Solicitudes aprobadas / Total solicitudes | Semanal | 48–60% | < 35% o > 68% | |
| Dispersión de Tasa (bps) | Tasa banda A+ vs. Tasa banda C | Mensual | ≥ 400 bps | < 200 bps | |
| TECNOLOGÍA | Latencia P99 (seg) | Tiempo de respuesta percentil 99 | Continua | < 2.5 seg | > 5.0 seg |
| Disponibilidad del Sistema | Uptime / Tiempo total | Mensual | ≥ 99.95% | < 99.50% | |
| Tasa de STP (Straight-Through) | Decisiones automáticas / Total | Semanal | ≥ 78% | < 65% |
La gobernanza de un proyecto de esta envergadura exige una estructura formal de toma de decisiones que equilibre la velocidad de ejecución con los controles necesarios de riesgo y cumplimiento. Se propone un modelo de gobernanza de tres niveles:
| Stakeholder | Rol en el Proyecto | Interés Principal | RACI Principal | Poder / Influencia |
|---|---|---|---|---|
| Director General (CEO) | Sponsor Ejecutivo | Competitividad y crecimiento del banco | Accountable | Alto / Alto |
| CRO (Chief Risk Officer) | Patrocinador del Proyecto | Riesgo controlado, cumplimiento regulatorio | Responsible | Alto / Alto |
| CFO | Aprobador de presupuesto | ROI, control de costos, RORAC | Consulted | Alto / Medio |
| CTO | Líder de Arquitectura Tecnológica | Escalabilidad, seguridad, integración | Responsible | Alto / Alto |
| Equipo de Ciencia de Datos | Desarrollo de Modelos | Calidad técnica, datos, MLOps | Responsible | Medio / Alto |
| Gerencia Comercial | Cliente interno del sistema | Volumen de aprobaciones, rapidez | Consulted | Medio / Medio |
| Auditoría Interna | Validación independiente de modelos | Control, documentación, trazabilidad | Consulted | Alto / Medio |
| Superintendencia de Bancos | Ente regulador | Cumplimiento, capital, protección al consumidor | Informed | Muy Alto / Medio |
| Clientes finales | Beneficiarios del sistema | Rapidez, trato justo, explicabilidad | Informed | Bajo / Alto |
Risk Appetite Framework (RAF): Marco formal que define la cantidad y tipo de riesgo que una institución financiera está dispuesta a asumir en la búsqueda de sus objetivos estratégicos y de negocio, estableciendo límites cuantitativos y cualitativos que actúan como guardarraíles del Motor de Decisión. El RAF trasciende la política de crédito estática al incorporar mecanismos de ajuste dinámico según condiciones de mercado, ciclo económico y cumplimiento del capital regulatorio.
Para Banco Express, el RAF del Motor de Decisión Crediticia se estructura en tres capas jerárquicas que se conectan desde la visión estratégica institucional hasta los parámetros operativos del motor en tiempo real:
El enunciado formal del apetito de riesgo de Banco Express, aprobado por el Consejo de Administración para efectos de este proyecto, es el siguiente:
Enunciado Oficial del RAF — Banco Express S.A. (2026):
"Banco Express está dispuesto a asumir un nivel de riesgo crediticio moderado-alto en su cartera de consumo y crédito PyME, siempre que: (i) la pérdida esperada anualizada de la cartera no supere el 2.0% del saldo vigente; (ii) el capital regulatorio Tier 1 se mantenga en todo momento por encima del 13%; (iii) el RORAC de cada segmento sea superior al 15%; y (iv) no exista concentración superior al 15% del portafolio en ningún sector económico individual. Fuera de estos límites, el Motor de Decisión debe aplicar restricciones automáticas sin requerir autorización manual."
Los límites de tolerancia traducen el apetito de riesgo en restricciones concretas y operables para el motor. Se organizan en cuatro dimensiones de riesgo crediticio:
| Dimensión | Métrica | Límite Verde (OK) | Límite Ámbar (Alerta) | Límite Rojo (Acción) | Acción Automática del Motor |
|---|---|---|---|---|---|
| Pérdida Esperada | EL / Cartera vigente | ≤ 1.6% | 1.6% – 2.0% | > 2.0% | Reducir cutoff de aprobación en banda C y D |
| Mora | NPL 90+ días | ≤ 3.5% | 3.5% – 4.5% | > 4.5% | Suspender aprobaciones en bandas C–D |
| Concentración Sectorial | Máx. exposición / sector | ≤ 10% | 10% – 15% | > 15% | Bloquear nuevas aprobaciones en sector afectado |
| Concentración Geográfica | Máx. exposición / país | ≤ 35% | 35% – 45% | > 45% | Incrementar tasa 100 bps en país sobreexpuesto |
| Concentración por Empleador | Máx. exposición / empleador (nómina) | ≤ 5% | 5% – 8% | > 8% | Reducir monto máximo en 30% para ese empleador |
| Capital Regulatorio | CET1 Ratio | ≥ 14% | 13% – 14% | < 13% | Suspender toda nueva originación hasta revisión |
| Thin File | % solicitantes sin historial buró | ≤ 20% del total | 20% – 28% | > 28% | Reducir monto máximo para thin files en -25% |
| Relación Cuota/Ingreso | DTI máximo por cliente | ≤ 35% | 35% – 40% | > 40% | Rechazo automático por capacidad de pago insuficiente |
Principio del Apetito de Riesgo Dinámico: Los límites de tolerancia no son parámetros estáticos. En un motor de decisión moderno, estos límites deben poder ajustarse en tiempo real —bajo un proceso de gobierno controlado— respondiendo a señales de deterioro de la cartera, cambios macroeconómicos (incremento del desempleo, inflación, variaciones cambiarias) o cambios en la disponibilidad de capital. Esta capacidad de ajuste dinámico es lo que diferencia a un sistema de clase mundial de uno convencional basado en políticas de crédito estáticas revisadas anualmente.
RORAC (Return on Risk-Adjusted Capital): Métrica de rentabilidad que relaciona la utilidad generada por una operación o segmento con el capital económico que la institución debe reservar para cubrir las pérdidas inesperadas asociadas a dicha operación. A diferencia del ROE o el ROA, el RORAC captura el verdadero costo del riesgo, permitiendo comparar la rentabilidad entre segmentos con distintos perfiles de riesgo sobre una base homogénea.
Ejemplo numérico — Cálculo de RORAC para un crédito de consumo Banco Express:
Supongamos un crédito de consumo con las siguientes características: EAD = USD 25,000 · PD = 4.5% · LGD = 55% · Tasa activa = 22% TNA · Costo de fondeo = 6.5% · Costo operativo = 3.0% · Tasa de impuesto = 25%.
Pérdida Esperada (anual): $EL = 25{,}000 \times 0.045 \times 0.55 = \text{USD } 618.75$
Capital Económico: $UL = 25{,}000 \times 0.55 \times \sqrt{0.045 \times 0.955} \times 3.09 = \text{USD } 2{,}780$
Ingreso neto de intereses: $(0.22 - 0.065 - 0.03) \times 25{,}000 = \text{USD } 3{,}125$ anuales
Utilidad ajustada al riesgo (post-EL, pre-impuestos): $3{,}125 - 618.75 = \text{USD } 2{,}506$
Utilidad post-impuestos: $2{,}506 \times (1-0.25) = \text{USD } 1{,}879.50$
$\mathbf{RORAC} = 1{,}879.50 / 2{,}780 = \mathbf{67.6\%}$ — Este crédito supera holgadamente el umbral mínimo de 15%.
| Banda de Riesgo | PD Estimada | RORAC Mínimo Aceptable | RORAC Objetivo | RORAC Excelente | Acción si < Mínimo |
|---|---|---|---|---|---|
| Banda A+ (Prime) | 0.5% – 1.5% | 15% | 20% | > 28% | Revisión de tasa (muy improbable) |
| Banda A (Bajo) | 1.5% – 3.0% | 15% | 22% | > 30% | Incrementar tasa mínima 50 bps |
| Banda B (Moderado) | 3.0% – 7.0% | 18% | 25% | > 35% | Incrementar tasa mínima 100 bps |
| Banda C (Alto) | 7.0% – 15% | 20% | 30% | > 40% | Reducir monto máximo 30%, incrementar tasa 200 bps |
| Banda D (Excluido) | > 15% | N/A — Rechazo automático | — | — | Rechazo directo por política |
El Motor de Decisión no opera en un vacío regulatorio. Las restricciones de Basilea III/IV imponen condicionantes directos sobre los parámetros del sistema, particularmente en lo que respecta a los requerimientos de capital ponderado por riesgo (Risk-Weighted Assets, RWA) y las métricas de apalancamiento:
Requerimiento de Capital Regulatorio Basilea III: La normativa establece que el capital de Nivel 1 (CET1) debe ser suficiente para cubrir el $8\%$ de los activos ponderados por riesgo, más el colchón de conservación del $2.5\%$, resultando en un requerimiento mínimo efectivo del $10.5\%$. Banco Express, con sus reguladores locales, opera con un requerimiento nacional del $12\%$, y el RAF interno establece $13\%$ como mínimo operativo:
$$ \text{CET1 Ratio} = \frac{\text{Capital de Nivel 1 (CET1)}}{\text{Activos Ponderados por Riesgo (RWA)}} \geq 13\% $$
Los activos ponderados por riesgo crediticio se calculan bajo el Método Estándar como: $RWA_{cr} = \sum_i EAD_i \times w_i$, donde $w_i$ es el ponderador de riesgo según la clasificación regulatoria del activo.
La Ecuación (0.6) es de una importancia crítica para el diseño del Motor de Decisión: implica que cada decisión de crédito individual tiene un impacto directo y medible sobre el capital regulatorio del banco. Un motor bien diseñado debe incorporar este vínculo de forma que las decisiones optimicen rentabilidad ajustada por capital (RORAC), no solo la tasa de aprobación o el margen de interés bruto. La ausencia de esta integración es uno de los errores más frecuentes en implementaciones de scoring de primera generación.
El Motor de Decisión se diseña para soportar cinco líneas de producto en fases progresivas. La priorización responde a criterios de volumen de solicitudes, homogeneidad del proceso de decisión y disponibilidad de datos históricos suficientes para entrenar modelos:
| Producto | Monto Típico (USD) | Plazo Típico | Tasa Actual | Vol. Mensual | Complejidad Decisión | Prioridad |
|---|---|---|---|---|---|---|
| Consumo Personal | USD 1,000 – 30,000 | 12–60 meses | 18–24% TNA | ~1,800 sol. | Media | 🟢 Alta — MVP |
| Nómina | USD 500 – 20,000 | 6–36 meses | 14–18% TNA | ~1,200 sol. | Baja | 🟢 Alta — MVP |
| Tarjeta de Crédito | Línea USD 500–15,000 | Revolving | 24–36% TNA | ~650 sol. | Media-Alta | 🟡 Media — Fase 2 |
| PyME Pequeño | USD 10,000 – 50,000 | 12–48 meses | 16–22% TNA | ~380 sol. | Alta | 🟡 Media — Fase 2 |
| PyME Mediano | USD 50,000 – 500,000 | 24–84 meses | 12–18% TNA | ~170 sol. | Muy Alta | 🔴 Baja — Fase 3 |
| Total de solicitudes en scope del MVP | ~3,000 sol./mes | 71% del volumen total | ||||
La segmentación de clientes para efectos del Motor de Decisión combina criterios de perfil crediticio (información buró), perfil socioeconómico (ingreso, empleo, nivel de bancarización) y perfil de relación bancaria (cliente actual vs. nuevo). Esta segmentación define qué modelo de scoring aplica a cada solicitante:
| Segmento | Descripción | % del Volumen | Ingreso Promedio (USD) | PD Esperada | LGD Típica | Modelo Principal |
|---|---|---|---|---|---|---|
| A — Relacional | Cliente banco ≥12M con historial de transacciones | 28% | USD 1,800 – 5,500/mes | 1.5% – 4.0% | 38% – 45% | Behavioral Score (gradient boosting + variables de cuenta) |
| B — Nuevo-Banco | Cliente banco <12M o sin historial interno suficiente | 18% | USD 1,200 – 3,500/mes | 2.5% – 6.0% | 42% – 52% | Application Score (buró + variables socioeco.) |
| C — Externo con Buró | No es cliente banco; tiene score buró ≥ 450 | 35% | USD 1,000 – 4,000/mes | 3.5% – 8.0% | 45% – 58% | Application Score puro (variables buró + socioeco.) |
| D — Thin File Alt. | Buró pobre/nulo; datos alternativos disponibles | 14% | USD 600 – 1,800/mes | 6.0% – 14.0% | 55% – 68% | Alternative Data Score (telco, mobile, open banking) |
| E — Sin Perfil | Sin historial ni datos alternativos disponibles | 5% | Variable / Desconocido | > 15% | > 65% | Rechazo / Producto inicial de construcción de historial |
El Motor de Decisión debe ser agnóstico al canal de originación, entregando la misma decisión calibrada independientemente de si la solicitud proviene de la app móvil, la plataforma web, una sucursal física o una API de un partner. Sin embargo, cada canal aporta distintos datos de contexto que pueden enriquecer la decisión:
| Canal | % Solicitudes | Latencia Requerida | Datos Únicos del Canal | Consideraciones Especiales |
|---|---|---|---|---|
| 📱 App Móvil | 38% | < 3 segundos | Geolocalización, device fingerprint, comportamiento en app, horario de solicitud, versión OS | Alta expectativa de UX; respuesta instantánea crítica |
| 💻 Portal Web | 24% | < 5 segundos | IP, navegador, tiempo en formulario, fuente de tráfico (SEO/SEM/directo) | Más tolerancia a formularios largos; mayor conversión en desktop |
| 🏦 Sucursal Física | 28% | < 60 segundos | Interacción con oficial, documentos físicos, contexto verbal del asesor | El asesor puede agregar contexto cualitativo; override manual permitido bajo política |
| 🔗 API Partners | 10% | < 2 segundos | Datos de la plataforma partner (e-commerce, empleador, plataforma fintech) | Requiere acuerdos de intercambio de datos y SLAs contractuales de latencia |
| País | Regulador Principal | Tasa Máxima Legal | Cobertura de Buró | Regulación de Datos | Requisito Capital Mínimo |
|---|---|---|---|---|---|
| 🇵🇦 Panamá | Superintendencia de Bancos (SBP) | Sin tope (mercado libre) | 85% población adulta | Ley 81/2019 (PDPL) | CET1 ≥ 8% + colchón local |
| 🇬🇹 Guatemala | Superintendencia de Bancos (SIB) | Sin tope formal; monitoreo SIB | 62% población adulta | Sin ley específica; en proceso | Patrimonio ≥ 10% activos ponderados |
| 🇸🇻 El Salvador | Superintendencia del Sistema Financiero (SSF) | Tasa usura: máx 2x tasa promedio del sistema | 58% población adulta | Ley de Protección de Datos en proceso | Fondo patrimonial ≥ 12% |
| 🇭🇳 Honduras | Comisión Nacional de Bancos y Seguros (CNBS) | Regulado por CNBS cada trimestre | 44% población adulta | Decreto 243-2021 | Capital ≥ 10% activos ponderados |
| 🇨🇷 Costa Rica | Superintendencia General de Entidades Financieras (SUGEF) | Sin tope (banca privada) | 78% población adulta | Ley 8968 (Protección Persona frente Tratamiento Datos) | Basilea: CET1 ≥ 10.5% |
La heterogeneidad regulatoria en los cinco mercados de operación de Banco Express implica que el Motor de Decisión debe soportar políticas de pricing diferenciadas por país, incluyendo techos de tasa variables, distintos niveles de cobertura de buró (que impactan la disponibilidad de datos para el modelo) y distintos marcos de privacidad que determinan qué variables pueden usarse en el scoring. Esta capa de complejidad se debe reflejar explícitamente en la arquitectura tecnológica desde el inicio del diseño.
Los siguientes bloques de código Python son completamente ejecutables en Google Colab. Implementan los cálculos cuantitativos presentados en esta Fase 0, desde la proyección del Business Case hasta la simulación del apetito de riesgo y cálculos de capital regulatorio.
# ============================================================
# FASE 0 — BANCO EXPRESS S.A.
# Script 0.1: Proyección del Business Case
# Autor: Victor Miranda | Especialista en Riesgos Financieros
# ============================================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.gridspec import GridSpec
# ----------------------------------------------------------
# 1. PARÁMETROS DEL PROYECTO
# ----------------------------------------------------------
INVERSION_INICIAL = 4_200_000 # USD — Inversión total año 0
COSTO_OPERATIVO = 1_100_000 # USD — Costo anual recurrente
TASA_DESCUENTO = 0.12 # 12% anual (tasa ajustada al riesgo)
HORIZONTE = 5 # años de proyección
IMPUESTO = 0.25 # Tasa de impuesto corporativo (25%)
# Beneficios anuales por palanca (en USD)
beneficios_anuales = {
"Incremento de Volumen": 85_000_000,
"Mejora Calidad Cartera": 12_000_000,
"Revenue Pricing Diferenc.": 9_600_000,
"Eficiencia Operativa": 3_200_000,
}
# Perfil de maduración: los beneficios no llegan al 100% en el año 1
maduración = {
1: 0.20, # 20% en año 1 (implementación + piloto)
2: 0.70, # 70% en año 2 (escala parcial)
3: 1.00, # 100% en año 3+
4: 1.05, # 5% de mejora continua
5: 1.10, # 10% de mejora continua acumulada
}
# ----------------------------------------------------------
# 2. PROYECCIÓN DE FLUJOS DE CAJA
# ----------------------------------------------------------
beneficio_total = sum(beneficios_anuales.values()) # Total anual base
resultados = []
for año in range(1, HORIZONTE + 1):
beneficio_bruto = beneficio_total * maduración[año]
beneficio_neto_impuestos = beneficio_bruto * (1 - IMPUESTO)
flujo_caja_neto = beneficio_neto_impuestos - COSTO_OPERATIVO
if año == 1:
flujo_caja_neto -= INVERSION_INICIAL # Inversión en año 1
valor_presente = flujo_caja_neto / (1 + TASA_DESCUENTO) ** año
resultados.append({
"Año": año,
"Beneficio Bruto (USD M)": round(beneficio_bruto / 1e6, 2),
"Beneficio Neto Imp. (USD M)": round(beneficio_neto_impuestos / 1e6, 2),
"Flujo de Caja Neto (USD M)": round(flujo_caja_neto / 1e6, 2),
"Valor Presente (USD M)": round(valor_presente / 1e6, 2),
})
df_fc = pd.DataFrame(resultados)
# ----------------------------------------------------------
# 3. MÉTRICAS FINANCIERAS PRINCIPALES
# ----------------------------------------------------------
vpn = sum([r["Valor Presente (USD M)"] for r in resultados]) - (INVERSION_INICIAL / 1e6)
beneficios_cumulativos = df_fc["Flujo de Caja Neto (USD M)"].cumsum()
roi_5y = (beneficios_cumulativos.iloc[-1] - (INVERSION_INICIAL/1e6)) / (INVERSION_INICIAL/1e6) * 100
# Payback period (mes aproximado)
flujos = df_fc["Flujo de Caja Neto (USD M)"].values
acumulado = 0
for i, f in enumerate(flujos):
acumulado += f
if acumulado > 0:
fraccion = (acumulado - f) / abs(f)
payback_años = (i) + fraccion
break
payback_meses = payback_años * 12
# ----------------------------------------------------------
# 4. TABLA DE RESULTADOS
# ----------------------------------------------------------
print("=" * 75)
print(" BANCO EXPRESS S.A. — PROYECCIÓN FINANCIERA DEL PROYECTO")
print(" Motor de Decisión Crediticia en Tiempo Real")
print("=" * 75)
print(f"\n Inversión Inicial: USD {INVERSION_INICIAL/1e6:.1f}M")
print(f" Tasa de Descuento: {TASA_DESCUENTO*100:.0f}%")
print(f" Horizonte: {HORIZONTE} años\n")
print(df_fc.to_string(index=False))
print("\n" + "=" * 75)
print(f" VPN (5 años, 12%): USD {vpn:.1f}M")
print(f" ROI Acumulado (5 años): {roi_5y:,.0f}%")
print(f" Período de Recuperación: {payback_meses:.1f} meses desde inicio del proyecto")
print("=" * 75)
# ----------------------------------------------------------
# 5. VISUALIZACIÓN
# ----------------------------------------------------------
fig = plt.figure(figsize=(15, 10))
fig.patch.set_facecolor('#faf8f3')
gs = GridSpec(2, 2, figure=fig, hspace=0.4, wspace=0.35)
años_label = [f"Año {i}" for i in range(1, HORIZONTE+1)]
colores_fc = ['#c0392b' if v < 0 else '#27ae60'
for v in df_fc["Flujo de Caja Neto (USD M)"]]
# Subplot 1: Flujos de Caja
ax1 = fig.add_subplot(gs[0, 0])
bars = ax1.bar(años_label, df_fc["Flujo de Caja Neto (USD M)"], color=colores_fc,
edgecolor='white', linewidth=1.5)
for bar, val in zip(bars, df_fc["Flujo de Caja Neto (USD M)"]):
ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
f"${val:.1f}M", ha='center', va='bottom', fontsize=9, fontweight='bold')
ax1.axhline(0, color='#2c3e50', linewidth=1)
ax1.set_title('Flujo de Caja Neto por Año', fontweight='bold', fontsize=11, pad=10)
ax1.set_ylabel('USD Millones', fontsize=9)
ax1.set_facecolor('#f8f5ef')
ax1.grid(axis='y', alpha=0.4)
# Subplot 2: Flujo Acumulado (Payback)
ax2 = fig.add_subplot(gs[0, 1])
acum_vals = df_fc["Flujo de Caja Neto (USD M)"].cumsum()
ax2.plot(años_label, acum_vals, marker='o', color='#2980b9', linewidth=2.5, markersize=8)
ax2.axhline(0, color='#c0392b', linewidth=2, linestyle='--', label='Punto de equilibrio')
ax2.fill_between(range(len(años_label)), acum_vals,
where=[v > 0 for v in acum_vals], alpha=0.2, color='#27ae60')
ax2.fill_between(range(len(años_label)), acum_vals,
where=[v < 0 for v in acum_vals], alpha=0.2, color='#c0392b')
for i, (año, val) in enumerate(zip(años_label, acum_vals)):
ax2.annotate(f"${val:.1f}M", (i, val), textcoords="offset points",
xytext=(0, 12), ha='center', fontsize=9, fontweight='bold')
ax2.set_title('Flujo de Caja Acumulado (Payback)', fontweight='bold', fontsize=11, pad=10)
ax2.set_ylabel('USD Millones', fontsize=9)
ax2.set_facecolor('#f8f5ef')
ax2.set_xticks(range(len(años_label)))
ax2.set_xticklabels(años_label)
ax2.legend(fontsize=9)
ax2.grid(alpha=0.4)
# Subplot 3: Desglose de Beneficios por Palanca
ax3 = fig.add_subplot(gs[1, 0])
palancas = list(beneficios_anuales.keys())
valores = [v / 1e6 for v in beneficios_anuales.values()]
colores_p = ['#1a3a5c', '#2980b9', '#27ae60', '#e67e22']
wedges, texts, autotexts = ax3.pie(valores, labels=None, autopct='%1.1f%%',
colors=colores_p, startangle=90,
pctdistance=0.7, wedgeprops={'edgecolor': 'white', 'linewidth': 2})
for at in autotexts:
at.set_fontsize(10); at.set_fontweight('bold'); at.set_color('white')
ax3.legend(palancas, loc='lower center', bbox_to_anchor=(0.5, -0.22),
ncol=2, fontsize=8, framealpha=0.8)
ax3.set_title(f'Distribución de Beneficios\nTotal: USD {sum(valores):.1f}M/año base',
fontweight='bold', fontsize=11, pad=10)
# Subplot 4: KPIs Financieros
ax4 = fig.add_subplot(gs[1, 1])
ax4.axis('off')
ax4.set_facecolor('#1a3a5c')
kpis = [
("VPN (5 años, 12%)", f"USD {vpn:.0f}M", "#27ae60"),
("ROI Acumulado 5 años", f"{roi_5y:,.0f}%", "#f39c12"),
("Payback Period", f"{payback_meses:.1f} meses", "#3498db"),
("Beneficio Anual Base", f"USD {beneficio_total/1e6:.1f}M", "#9b59b6"),
("Inversión Total", f"USD {INVERSION_INICIAL/1e6:.1f}M", "#e74c3c"),
("Costo Operativo Anual", f"USD {COSTO_OPERATIVO/1e6:.1f}M", "#95a5a6"),
]
ax4.set_title('Resumen KPIs Financieros del Proyecto', fontweight='bold', fontsize=11, pad=10)
for i, (label, value, color) in enumerate(kpis):
y = 0.88 - i * 0.15
ax4.add_patch(mpatches.FancyBboxPatch((0.02, y - 0.06), 0.96, 0.12,
boxstyle="round,pad=0.01", facecolor=color + '22', edgecolor=color, linewidth=2,
transform=ax4.transAxes))
ax4.text(0.08, y, label, transform=ax4.transAxes, fontsize=9.5, va='center', color='#2c3e50')
ax4.text(0.92, y, value, transform=ax4.transAxes, fontsize=11, va='center',
ha='right', fontweight='bold', color=color)
fig.suptitle('BANCO EXPRESS S.A. — Business Case: Motor de Decisión Crediticia',
fontsize=14, fontweight='bold', color='#1a3a5c', y=1.01)
plt.savefig('business_case_banco_express.png', dpi=150, bbox_inches='tight',
facecolor='#faf8f3')
plt.show()
print("\n✅ Gráfico guardado como 'business_case_banco_express.png'")
# ============================================================
# FASE 0 — BANCO EXPRESS S.A.
# Script 0.2: RORAC y Capital Económico/Regulatorio por Banda
# Implementa las Ecuaciones (0.3), (0.4) y (0.6) del documento
# Autor: Victor Miranda | Especialista en Riesgos Financieros
# ============================================================
import numpy as np
import pandas as pd
from scipy import stats
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import warnings
warnings.filterwarnings('ignore')
# ----------------------------------------------------------
# 1. DEFINICIÓN DE BANDAS DE RIESGO — BANCO EXPRESS
# ----------------------------------------------------------
bandas = {
"A+": {"pd": 0.010, "lgd": 0.40, "ead": 15_000, "tasa": 0.155, "plazo": 24},
"A": {"pd": 0.025, "lgd": 0.45, "ead": 18_000, "tasa": 0.180, "plazo": 24},
"B": {"pd": 0.055, "lgd": 0.50, "ead": 20_000, "tasa": 0.210, "plazo": 36},
"C": {"pd": 0.110, "lgd": 0.55, "ead": 12_000, "tasa": 0.265, "plazo": 24},
"D": {"pd": 0.200, "lgd": 0.62, "ead": 8_000, "tasa": None, "plazo": 12},
}
COSTO_FONDEO = 0.065 # 6.5% TNA
COSTO_OPERAT = 0.030 # 3.0% TNA sobre EAD
IMPUESTO = 0.25 # 25% tasa impositiva
CONFIANZA_ECAP = 0.999 # 99.9% para capital económico
Z_ECap = stats.norm.ppf(CONFIANZA_ECAP) # ≈ 3.0902
# ----------------------------------------------------------
# 2. FUNCIÓN: CAPITAL REGULATORIO BASILEA III (IRB)
# ----------------------------------------------------------
def calcular_K_basilea(pd, lgd, m=2.0):
"""
Calcula K (requerimiento de capital por unidad) según Basilea III IRB.
Ec. (0.6) del documento.
"""
if pd >= 1.0:
return lgd # caso límite: default seguro
# Correlación de activos para crédito de consumo (fórmula Basilea)
rho = 0.03 * (1 - np.exp(-35 * pd)) / (1 - np.exp(-35)) + \
0.16 * (1 - (1 - np.exp(-35 * pd)) / (1 - np.exp(-35)))
# Ajuste por madurez
b = (0.11852 - 0.05478 * np.log(pd)) ** 2
# Función de capital IRB
arg = (stats.norm.ppf(pd) + np.sqrt(rho) * stats.norm.ppf(0.999)) / np.sqrt(1 - rho)
K = lgd * (stats.norm.cdf(arg) - pd) * (1 + (m - 2.5) * b) / (1 - 1.5 * b)
return max(0, K)
# ----------------------------------------------------------
# 3. CÁLCULO DE MÉTRICAS POR BANDA
# ----------------------------------------------------------
resultados_bandas = []
for banda, params in bandas.items():
pd = params["pd"]
lgd = params["lgd"]
ead = params["ead"]
tasa = params["tasa"]
plazo = params["plazo"]
if tasa is None: # Banda D: rechazo
resultados_bandas.append({
"Banda": f"D (Rechazo)",
"PD": f"{pd*100:.1f}%",
"LGD": f"{lgd*100:.0f}%",
"EAD (USD)": f"{ead:,}",
"EL (USD)": "N/A",
"Capital Econ. (USD)": "N/A",
"K Basilea": "N/A",
"RWA (USD)": "N/A",
"RORAC": "RECHAZADO",
"Cumple RAF (≥15%)": "❌ N/A",
})
continue
# Pérdida Esperada
EL = pd * lgd * ead
# Capital Económico (Ec. 0.4)
UL = ead * lgd * np.sqrt(pd * (1 - pd)) * Z_ECap
# Capital Regulatorio Basilea III (Ec. 0.6)
K = calcular_K_basilea(pd, lgd, m=plazo/12)
RWA = K * 12.5 * ead
Capital_Reg = K * ead # Capital regulatorio requerido
# Ingresos netos de interés anualizados
ingreso_neto = (tasa - COSTO_FONDEO - COSTO_OPERAT) * ead
# Utilidad ajustada al riesgo (post-EL, post-impuesto)
utilidad_ajustada = (ingreso_neto - EL) * (1 - IMPUESTO)
# RORAC sobre capital económico (Ec. 0.3)
rorac = utilidad_ajustada / UL if UL > 0 else 0
rorac_pct = rorac * 100
cumple = "✅ SÍ" if rorac_pct >= 15 else "⚠️ NO"
resultados_bandas.append({
"Banda": banda,
"PD": f"{pd*100:.1f}%",
"LGD": f"{lgd*100:.0f}%",
"EAD (USD)": f"{ead:,}",
"EL (USD)": f"${EL:,.0f}",
"Capital Econ. (USD)": f"${UL:,.0f}",
"K Basilea": f"{K:.4f}",
"RWA (USD)": f"${RWA:,.0f}",
"RORAC": f"{rorac_pct:.1f}%",
"Cumple RAF (≥15%)": cumple,
# Para gráficos:
"_rorac_num": rorac_pct,
"_el_num": EL,
"_ul_num": UL,
"_k_num": K,
"_rwa_num": RWA,
})
df_bandas = pd.DataFrame(resultados_bandas)
# ----------------------------------------------------------
# 4. IMPRESIÓN DE RESULTADOS
# ----------------------------------------------------------
cols_display = ["Banda","PD","LGD","EAD (USD)","EL (USD)",
"Capital Econ. (USD)","K Basilea","RWA (USD)","RORAC","Cumple RAF (≥15%)"]
print("=" * 110)
print(" BANCO EXPRESS S.A. — ANÁLISIS RORAC & CAPITAL REGULATORIO POR BANDA")
print(" Basado en Ecuaciones (0.3), (0.4) y (0.6) — Marco Basilea III IRB")
print("=" * 110)
print(df_bandas[cols_display].to_string(index=False))
print("=" * 110)
# TABLA RESUMEN APETITO DE RIESGO
print("\n📊 VERIFICACIÓN DEL RISK APPETITE FRAMEWORK — BANCO EXPRESS")
print("-" * 60)
bandas_validas = [r for r in resultados_bandas if r["Banda"] != "D (Rechazo)"]
for r in bandas_validas:
rorac = r["_rorac_num"]
estado = "🟢 VERDE" if rorac >= 20 else ("🟡 ÁMBAR" if rorac >= 15 else "🔴 ROJO")
print(f" Banda {r['Banda']:2s} | RORAC: {rorac:5.1f}% | RAF Min: 15% | Estado: {estado}")
print("-" * 60)
# ----------------------------------------------------------
# 5. VISUALIZACIÓN
# ----------------------------------------------------------
bandas_plot = [r for r in resultados_bandas if r["Banda"] != "D (Rechazo)"]
nombres = [r["Banda"] for r in bandas_plot]
roracs = [r["_rorac_num"] for r in bandas_plot]
els = [r["_el_num"] for r in bandas_plot]
uls = [r["_ul_num"] for r in bandas_plot]
fig, axes = plt.subplots(1, 3, figsize=(16, 7))
fig.patch.set_facecolor('#faf8f3')
# RORAC por Banda
colores_rorac = ['#27ae60' if r >= 20 else '#f39c12' if r >= 15 else '#c0392b' for r in roracs]
bars1 = axes[0].bar(nombres, roracs, color=colores_rorac, edgecolor='white', linewidth=2, width=0.55)
axes[0].axhline(15, color='#e74c3c', linestyle='--', linewidth=2, label='RAF Mínimo (15%)')
axes[0].axhline(20, color='#f39c12', linestyle='--', linewidth=1.5, label='RAF Objetivo (20%)')
for bar, val in zip(bars1, roracs):
axes[0].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
f'{val:.1f}%', ha='center', va='bottom', fontweight='bold', fontsize=11)
axes[0].set_title('RORAC por Banda de Riesgo', fontweight='bold', fontsize=12)
axes[0].set_ylabel('RORAC (%)', fontsize=10)
axes[0].set_ylim(0, max(roracs) * 1.2)
axes[0].legend(fontsize=9)
axes[0].set_facecolor('#f8f5ef')
axes[0].grid(axis='y', alpha=0.4)
# EL vs Capital Económico
x = np.arange(len(nombres))
w = 0.35
bars_el = axes[1].bar(x - w/2, els, w, label='Pérdida Esperada (EL)',
color='#e74c3c', alpha=0.85, edgecolor='white')
bars_ul = axes[1].bar(x + w/2, uls, w, label='Capital Económico (UL)',
color='#2980b9', alpha=0.85, edgecolor='white')
axes[1].set_xticks(x); axes[1].set_xticklabels(nombres)
axes[1].set_title('EL vs Capital Económico por Banda', fontweight='bold', fontsize=12)
axes[1].set_ylabel('USD por Operación Típica', fontsize=10)
axes[1].legend(fontsize=9)
axes[1].set_facecolor('#f8f5ef')
axes[1].grid(axis='y', alpha=0.4)
for bar in [*bars_el, *bars_ul]:
h = bar.get_height()
axes[1].text(bar.get_x() + bar.get_width()/2, h + 20,
f'${h:,.0f}', ha='center', va='bottom', fontsize=8, fontweight='bold')
# K Basilea y RWA
rwas_num = [r["_rwa_num"] for r in bandas_plot]
axes[2].bar(nombres, [v/1000 for v in rwas_num], color='#8e44ad', alpha=0.85,
edgecolor='white', linewidth=2)
axes[2].set_title('Activos Ponderados por Riesgo (RWA)', fontweight='bold', fontsize=12)
axes[2].set_ylabel('RWA (USD Miles)', fontsize=10)
axes[2].set_facecolor('#f8f5ef')
axes[2].grid(axis='y', alpha=0.4)
for i, (nombre, rwa) in enumerate(zip(nombres, rwas_num)):
k_val = bandas_plot[i]["_k_num"]
axes[2].text(i, rwa/1000 + 0.5, f'K={k_val:.3f}', ha='center', va='bottom',
fontsize=9, fontweight='bold')
plt.suptitle('Banco Express — Análisis de Riesgo-Rentabilidad por Banda | Basilea III',
fontsize=13, fontweight='bold', color='#1a3a5c', y=1.02)
plt.tight_layout()
plt.savefig('rorac_capital_bandas.png', dpi=150, bbox_inches='tight', facecolor='#faf8f3')
plt.show()
print("\n✅ Gráfico guardado como 'rorac_capital_bandas.png'")
# ============================================================
# FASE 0 — BANCO EXPRESS S.A.
# Script 0.3: Simulación del RAF Dinámico
# Cómo el Motor ajusta parámetros según semáforo de riesgo
# Autor: Victor Miranda | Especialista en Riesgos Financieros
# ============================================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyArrowPatch
# ----------------------------------------------------------
# 1. SIMULACIÓN DE INDICADORES DE CARTERA (12 meses)
# ----------------------------------------------------------
np.random.seed(42)
meses = [f"M{i:02d}" for i in range(1, 13)]
# Simulación de mora 90d con shock en meses 7-8
mora_base = np.array([3.1, 3.2, 3.4, 3.5, 3.6, 3.8, 4.6, 4.9, 4.7, 4.3, 4.0, 3.8])
npl_90d = mora_base + np.random.normal(0, 0.08, 12)
# Simulación pérdida esperada / cartera
el_ratio = np.array([1.4, 1.45, 1.5, 1.55, 1.6, 1.72, 2.1, 2.25, 2.15, 1.95, 1.82, 1.71])
el_ratio += np.random.normal(0, 0.04, 12)
# CET1 simulado
cet1 = np.array([15.2, 15.1, 15.0, 14.9, 14.8, 14.6, 14.1, 13.8, 13.9, 14.1, 14.3, 14.5])
# ----------------------------------------------------------
# 2. FUNCIÓN DE SEMÁFORO RAF
# ----------------------------------------------------------
def get_semaforo(npl, el, cet1):
"""Determina el estado del RAF en función de los KPIs."""
semaforo = "VERDE"
alertas = []
if npl > 4.5 or el > 2.0 or cet1 < 13.0:
semaforo = "ROJO"
elif npl > 3.5 or el > 1.6 or cet1 < 14.0:
semaforo = "ÁMBAR"
if npl > 4.5: alertas.append(f"NPL 90d = {npl:.2f}% > límite 4.5%")
if el > 2.0: alertas.append(f"EL/Cartera = {el:.2f}% > límite 2.0%")
if cet1 < 13: alertas.append(f"CET1 = {cet1:.1f}% < mínimo 13.0%")
if npl > 3.5 and semaforo != "ROJO": alertas.append(f"NPL 90d = {npl:.2f}% en zona ámbar (3.5-4.5%)")
if el > 1.6 and semaforo != "ROJO": alertas.append(f"EL/Cartera = {el:.2f}% en zona ámbar (1.6-2.0%)")
return semaforo, alertas
# Acciones del motor según semáforo
ACCIONES_MOTOR = {
"VERDE": "Motor opera con parámetros estándar. Sin restricciones adicionales.",
"ÁMBAR": "Motor reduce cutoff Banda C en -5 puntos. Monto máximo Banda C reducido en 20%.",
"ROJO": "Motor suspende aprobaciones Banda C y D. Incrementa tasa Banda B en 150 bps.",
}
# ----------------------------------------------------------
# 3. TABLA DE ESTADOS POR MES
# ----------------------------------------------------------
estado_mensual = []
for i, mes in enumerate(meses):
semaforo, alertas = get_semaforo(npl_90d[i], el_ratio[i], cet1[i])
estado_mensual.append({
"Mes": mes,
"NPL 90d (%)": f"{npl_90d[i]:.2f}%",
"EL/Cartera (%)": f"{el_ratio[i]:.2f}%",
"CET1 (%)": f"{cet1[i]:.1f}%",
"Estado RAF": semaforo,
"Acción Motor": ACCIONES_MOTOR[semaforo][:55] + "...",
})
df_estado = pd.DataFrame(estado_mensual)
print("=" * 105)
print(" BANCO EXPRESS S.A. — MONITOREO MENSUAL DEL RISK APPETITE FRAMEWORK")
print("=" * 105)
print(df_estado.to_string(index=False))
print("=" * 105)
# Resumen de estados
conteo = df_estado["Estado RAF"].value_counts()
print(f"\n Resumen: 🟢 VERDE: {conteo.get('VERDE',0)} meses | 🟡 ÁMBAR: {conteo.get('ÁMBAR',0)} meses | 🔴 ROJO: {conteo.get('ROJO',0)} meses")
# ----------------------------------------------------------
# 4. VISUALIZACIÓN DEL SEMÁFORO RAF
# ----------------------------------------------------------
fig, axes = plt.subplots(3, 1, figsize=(14, 12), sharex=True)
fig.patch.set_facecolor('#faf8f3')
colores_sem = {"VERDE": "#27ae60", "ÁMBAR": "#f39c12", "ROJO": "#c0392b"}
colores_mes = [colores_sem[get_semaforo(npl_90d[i], el_ratio[i], cet1[i])[0]] for i in range(12)]
# NPL
axes[0].plot(meses, npl_90d, color='#2c3e50', linewidth=2.5, marker='o', markersize=8)
axes[0].scatter(meses, npl_90d, c=colores_mes, s=100, zorder=5, edgecolors='white', linewidth=1.5)
axes[0].axhline(4.5, color='#c0392b', linestyle='--', linewidth=2, label='Límite Rojo (4.5%)')
axes[0].axhline(3.5, color='#f39c12', linestyle='--', linewidth=1.5, label='Límite Ámbar (3.5%)')
axes[0].fill_between(range(12), 4.5, [max(4.5, v) for v in npl_90d], alpha=0.15, color='#c0392b')
axes[0].fill_between(range(12), 3.5, 4.5, alpha=0.10, color='#f39c12')
axes[0].set_ylabel('NPL 90+ días (%)', fontsize=10)
axes[0].set_title('Monitoreo del RAF — NPL 90 días vs. Límites de Tolerancia', fontsize=11, fontweight='bold')
axes[0].legend(loc='upper left', fontsize=9)
axes[0].set_facecolor('#f8f5ef')
axes[0].grid(alpha=0.4)
# EL / Cartera
axes[1].plot(meses, el_ratio, color='#8e44ad', linewidth=2.5, marker='s', markersize=8)
axes[1].scatter(meses, el_ratio, c=colores_mes, s=100, zorder=5, edgecolors='white', linewidth=1.5)
axes[1].axhline(2.0, color='#c0392b', linestyle='--', linewidth=2, label='Límite Rojo (2.0%)')
axes[1].axhline(1.6, color='#f39c12', linestyle='--', linewidth=1.5, label='Límite Ámbar (1.6%)')
axes[1].set_ylabel('EL / Cartera Vigente (%)', fontsize=10)
axes[1].set_title('Pérdida Esperada / Cartera vs. Apetito de Riesgo', fontsize=11, fontweight='bold')
axes[1].legend(loc='upper left', fontsize=9)
axes[1].set_facecolor('#f8f5ef')
axes[1].grid(alpha=0.4)
# CET1
axes[2].plot(meses, cet1, color='#1a3a5c', linewidth=2.5, marker='^', markersize=8)
axes[2].scatter(meses, cet1, c=colores_mes, s=100, zorder=5, edgecolors='white', linewidth=1.5)
axes[2].axhline(13.0, color='#c0392b', linestyle='--', linewidth=2, label='Límite Mínimo RAF (13%)')
axes[2].axhline(14.0, color='#f39c12', linestyle='--', linewidth=1.5, label='Alerta Ámbar (14%)')
axes[2].fill_between(range(12), 13.0, [min(13.0, v) for v in cet1], alpha=0.15, color='#c0392b')
axes[2].set_ylabel('CET1 Ratio (%)', fontsize=10)
axes[2].set_title('Capital Tier 1 (CET1) vs. Límites Regulatorios y de RAF', fontsize=11, fontweight='bold')
axes[2].legend(loc='lower left', fontsize=9)
axes[2].set_facecolor('#f8f5ef')
axes[2].grid(alpha=0.4)
axes[2].set_xticks(range(12))
axes[2].set_xticklabels(meses, rotation=45)
# Zonas de color de fondo por estado
for ax in axes:
for i, col in enumerate(colores_mes):
ax.axvspan(i - 0.5, i + 0.5, alpha=0.06, color=col, zorder=0)
leyenda_sem = [mpatches.Patch(color=c, label=s, alpha=0.7)
for s, c in colores_sem.items()]
fig.legend(handles=leyenda_sem, loc='lower center', ncol=3,
title='Estado del RAF', fontsize=10, title_fontsize=10, framealpha=0.9,
bbox_to_anchor=(0.5, -0.02))
fig.suptitle('BANCO EXPRESS S.A. — Dashboard de Monitoreo del Risk Appetite Framework',
fontsize=13, fontweight='bold', color='#1a3a5c', y=1.01)
plt.tight_layout()
plt.savefig('raf_dashboard_banco_express.png', dpi=150, bbox_inches='tight', facecolor='#faf8f3')
plt.show()
print("\n✅ Gráfico guardado como 'raf_dashboard_banco_express.png'")
La Fase 0 establece los cimientos irrevocables sobre los cuales se construirán las fases técnicas del proyecto. Las decisiones adoptadas en esta fase son de carácter estratégico e institucional, y cualquier cambio posterior requeriría revisión del Comité Ejecutivo. Las decisiones clave son:
| # | Entregable | Descripción | Responsable | Estado |
|---|---|---|---|---|
| E0.1 | Documento de Business Case | Proyecciones financieras, ROI, payback y palancas de valor | CFO + CRO | ✅ Completado |
| E0.2 | Risk Appetite Statement | Enunciado formal del RAF, límites por dimensión, aprobado por Consejo | CRO | ✅ Completado |
| E0.3 | Framework de RORAC | Metodología, objetivos por banda, validación con Ec. (0.3)–(0.6) | Equipo de Riesgo | ✅ Completado |
| E0.4 | Mapa de Productos y Segmentos | Definición de scope MVP, segmentación de clientes, canales | Gerencia Comercial + Riesgo | ✅ Completado |
| E0.5 | Estructura de Gobierno | Comités, RACI, frecuencias de revisión, proceso de escalamiento | Gerente de Proyecto | ✅ Completado |
| E0.6 | Marco Regulatorio Multi-país | Restricciones por país: tasa máx, capital mínimo, privacidad de datos | Cumplimiento + Legal | ✅ Completado |
| E0.7 | KPI Dashboard Framework | Métricas, umbrales, frecuencias y responsables de monitoreo | CRO + CTO | ✅ Completado |
| E0.8 | Scripts Python Fase 0 | Business Case, RORAC/Capital, RAF Dinámico — ejecutables en Colab | Equipo de Datos | ✅ Completado |
Principio de Continuidad entre Fases: Cada entregable de la Fase 0 es una entrada formal de la Fase 1. Específicamente: el RAF (E0.2) define qué variables son críticas para el modelo de scoring; el mapa de segmentos (E0.4) determina el diseño del inventario de datos; y el framework de RORAC (E0.3) establece las métricas de performance que deben ser calculables con los datos que se auditarán en la Fase 1. Esta trazabilidad vertical entre fases es lo que garantiza la coherencia arquitectónica del proyecto a lo largo de sus ocho fases completas.
Referencias técnicas: Basel Committee on Banking Supervision (2017). Basel III: Finalising post-crisis reforms. BIS Publications. · Siddiqi, N. (2017). Intelligent Credit Scoring. Wiley Finance. · Thomas, L.C., Edelman, D.B., & Crook, J.N. (2002). Credit Scoring and Its Applications. SIAM. · Bluhm, C., Overbeck, L. & Wagner, C. (2010). Introduction to Credit Risk Modeling. CRC Press. · Mays, E. (Ed.) (2004). Credit Scoring for Risk Managers. South-Western. · GARP (2023). Financial Risk Manager Handbook. FRM Exam Guide.
Nota de derechos de autor: © 2026 Victor Miranda. Este documento es de uso exclusivo de Banco Express S.A. como cliente de consultoría. Se prohíbe su reproducción, distribución o uso fuera del contexto del proyecto contratado sin autorización escrita del autor. Documento preparado bajo los más altos estándares técnicos de la profesión de riesgos financieros.