Deilify es una solución integral SaaS (Software as a Service) multi-tenant diseñada para optimizar la gestión de cuentas por cobrar, seguimiento de facturación, registro de recaudos y prevención de mora empresarial. Combina una arquitectura backend desacoplada en Python / Flask, una interfaz de usuario reactiva en React 19 + TypeScript + Tailwind CSS, y un motor de Machine Learning supervisado capaz de predecir la probabilidad de incumplimiento de clientes con base en su comportamiento transaccional histórico.
Propuesta de Valor & Capacidades Operativas
-
Aislamiento Multi-tenant Estricto: Cada registro de facturas, clientes, pagos y usuarios está vinculado a una
empresa_id. El backend extrae esta identidad de forma segura desde los claims del token JWT, garantizando confidencialidad absoluta entre organizaciones. -
Cálculo Dinámico de Saldo y Mora: Al registrar un recaudo, el backend actualiza de inmediato el
saldo_pendientede la factura. Si se salda por completo, transmuta su estado aPAGADA. Mediante CLI programado (flask cartera check-overdue), las facturas vencidas impagas se clasifican automáticamente. -
KPIs Financieros Automatizados: Generación instantánea de DSO (Days Sales Outstanding) y distribución de edades de vencimiento en buckets estándares de la industria: 0–30 días, 31–60 días, 61–90 días y 90+ días.
-
Scoring Predictivo de Riesgo Crediticio: El módulo de IA extrae 13 variables de comportamiento histórico (frecuencia de mora, promedio de días de retraso, ratio deuda/límite, notas de cobranza) y evalúa al cliente mediante modelos entrenados (RandomForest / XGBoost) retornando un score de riesgo (0 a 100) y un dictamen descriptivo en lenguaje natural.
Estructura del Ecosistema Deilify
El código auditado está dividido en tres proyectos clave en el espacio de trabajo:
back-deilify
API REST construida con Flask, Flask-RESTX y SQLAlchemy. Expone endpoints de autenticación, administración de usuarios, clientes, cartera financiera y el pipeline completo de entrenamiento e inferencia de Machine Learning.
deilify-frontend
Single Page Application (SPA) en React 19 + Vite 6 + Tailwind CSS. Tableros analíticos con Recharts, formularios con Zod y React Hook Form, gestión de sesión por JWT en AuthContext y visualización interactiva de predicciones de riesgo.
documentacion-cartera
Portal unificado de documentación técnica interactiva y repositorio de arquitectura que describe la realidad fáctica del sistema, inventarios, modelos, contratos de API y guías para el equipo técnico.
Tabla Maestra de Estado de Módulos
| Módulo | Estado General | Backend | Frontend | Observaciones Técnicas |
|---|---|---|---|---|
| Autenticación | COMPLETADO | Sí (rama develop) | Sí (Login.tsx) | Registro de empresa + usuario, login con Argon2id, JWT con claims de rol y tenant, /auth/me. |
| Usuarios | COMPLETADO | Sí (rama develop) | Sí (Usuarios.tsx) | CRUD de usuarios por empresa con control de permisos ADMIN/USER y prevención de auto-eliminación. |
| Clientes | COMPLETADO | Sí (rama develop) | Sí (Clientes.tsx) | CRUD completo de clientes, validación de identificación única por empresa, ficha detallada en ClienteDetalle.tsx. |
| Cartera (Cálculos & Dashboard) | COMPLETADO | Sí (/cartera/dashboard) | Sí (Dashboard.tsx) | DSO proyectado, distribución de cartera vencida por edades (0-30, 31-60, 61-90, 90+) con Pandas. |
| Sincronización de Facturas | COMPLETADO | Sí (POST /cartera/facturas) | Sí (Modal en Facturas.tsx) | Carga por lote o unitaria con validación de pertenencia de cliente y creación de saldo pendiente. |
| Listado General de Facturas | PARCIAL | Incompleto | Parcial | El backend carece de GET /cartera/facturas general; frontend intenta extraerlas del dashboard agregado. |
| Registro de Recaudos (Pagos) | COMPLETADO | Sí (POST /cartera/recaudos) | Sí (Modal en Pagos.tsx) | Descuenta saldo en factura, cambia estado a PAGADA si saldo ≤ 0. Requiere mapear campo referencia a transaccion_id. |
| Listado Histórico de Pagos | PARCIAL | Incompleto | Parcial | No existe endpoint para listar todos los pagos de la empresa; actualmente solo se consultan dentro de cada cliente. |
| Inteligencia Artificial (Entrenamiento) | COMPLETADO | Sí (/api/v1/ai/train) | Sí (IA.tsx) | Entrenamiento de RandomForest y XGBoost, métricas de validación, selección automática por ROC AUC y guardado en disco. |
| Inteligencia Artificial (Inferencia) | COMPLETADO | Sí (/api/v1/ai/predict/<id>) | Sí (ClienteDetalle.tsx) | Predicción en tiempo real con 13 variables, cálculo de score 0-100, escala de 5 niveles y recomendación de cobro. |
| Reporte Inteligente IA | COMPLETADO | Sí (/predict/<id>/report) | Sí (ClienteDetalle.tsx) | Consolida KPIs de cartera del cliente, score predictivo y un dictamen descriptivo narrativo sintetizado. |
| Gestión de Cobros / Refinanciación | NO IMPLEMENTADO | 0 bytes (acuerdo_pago.py) | Alias visual | app/models/acuerdo_pago.py está vacío. GestionCobros.tsx en frontend es un alias a Pagos. |
| Módulo de Reportes PDF/Excel | NO IMPLEMENTADO | 0 bytes (api/reportes) | Alias visual | Endpoints de exportación no programados. Reportes.tsx en frontend redirige a Dashboard. |
| Control de Planes Comerciales | NO IMPLEMENTADO | 0 bytes (middleware/plan.py) | No conectado | Existía en la documentación antigua pero nunca fue codificado en el backend. No hay limitación de planes. |
| Documentación Antigua ("GesCartera") | OBSOLETO | N/A | N/A | La landing previa describía Vue.js 3, Pinia, triggers SQL y tablas no respaldadas por el código. |
Diagrama de Arquitectura de Capas
React Router 7 · Axios Client · Recharts · AuthContext
/auth · /usuarios · /clientes · /cartera · /api/v1/ai
Reglas financieras · Cálculo DSO · Extracción de 13 Features
SQLite / PostgreSQL · Alembic
Joblib Storage · Inferencias en vivo
Flujo de una Operación Real: Registro de un Recaudo (Pago)
- Interacción en Frontend: El analista abre el modal de registro de pago en
Pagos.tsx, ingresa ID de factura, monto, fecha, método y referencia. - Envío HTTP: Axios intercepta la petición, añade el token en la cabecera
Authorization: Bearer <JWT>y envía unPOST /cartera/recaudos. - Autenticación & Aislamiento: Flask-JWT-Extended valida la firma del token. El middleware
tenant.pyextrae laempresa_iddel usuario autenticado. - Validación de Esquema: Marshmallow (
PagoSchema) valida que los campos requeridos y tipos numéricos sean válidos. - Lógica Financiera (CarteraService): Se consulta la factura en base de datos verificando que pertenezca a la
empresa_id. Se resta el monto pagado desaldo_pendiente. Si el saldo es ≤ 0, el estado de la factura cambia automáticamente aEstadoFactura.PAGADA. - Persistencia: SQLAlchemy persiste el nuevo registro en la tabla
pagosy confirma la transacción en una sola operación atómica. - Respuesta: El backend retorna status
201 Createdcon el ID del pago generado; la interfaz en React notifica con un Toast de éxito y refresca el tablero.
Estructura de Directorios del Backend
Patrones Arquitectónicos Aplicados
Application Factory: La función create_app() en app/__init__.py inicializa dinámicamente extensiones, modelos, namespaces de RESTX y comandos CLI, permitiendo inyectar configuraciones independientes para pruebas unitarias.
Clean Architecture en IA: El módulo de IA implementa separación estricta entre capa de presentación (Controllers RESTX), Casos de Uso (Services), Acceso a Datos (Repositories) y Algoritmos de Machine Learning (ML Features).
Scoped Session de SQLAlchemy: Manejo de sesiones de base de datos thread-safe con db_session = scoped_session(sessionmaker()).
Root / Monitoreo
Módulo: app.api.root | Autenticación: Pública (No requiere token)
Respuesta (200 OK):
{"status": "ok", "message": "Deilify backend está en marcha"}
Autenticación (Namespace /auth)
Módulo: app.api.auth | Autenticación: Pública
Descripción: Crea un usuario. Si no se pasa empresa_id, crea una nueva Empresa y vincula al usuario.
{
"nombre": "string (min 2)",
"email": "correo@ejemplo.com",
"password": "min 6 caracteres",
"rol": "ADMIN | USER",
"empresa_nombre": "Mi Empresa",
"empresa_nit": "900123456-1"
}
{
"id": 1,
"nombre": "Juan Perez",
"email": "juan@deilify.com",
"rol": "ADMIN",
"empresa_id": 1,
"created_at": "2026-09-14T...",
"empresa": {"id": 1, "nombre": "...", "nit": "..."}
}
Errores: 400 Bad Request si el email o NIT ya existen, o validación de campos falla.
Módulo: app.api.auth | Autenticación: Pública
Descripción: Valida credenciales contra el hash Argon2id y genera un JWT con claims role y empresa_id.
{
"email": "juan@deilify.com",
"password": "mypassword123"
}
{
"access_token": "eyJhbGciOiJIUzI1Ni...",
"usuario": {
"id": 1,
"nombre": "Juan Perez",
"email": "juan@deilify.com",
"rol": "ADMIN",
"empresa_id": 1,
"empresa": {"id": 1, "nombre": "Deilify", "nit": "..."}
}
}
Errores: 401 Unauthorized si las credenciales son incorrectas.
Módulo: app.api.auth | Autenticación: Bearer JWT
Respuesta (200 OK): Información detallada del usuario y empresa asociada.
Errores: 401 Unauthorized, 404 Not Found.
Usuarios (Namespace /usuarios)
Módulo: app.api.usuarios | Autenticación: Bearer JWT
Respuesta (200 OK): Array con todos los usuarios registrados bajo la empresa_id del token.
Módulo: app.api.usuarios | Permisos: Rol ADMIN
Body: {"nombre": str, "email": str, "password": str, "rol": "ADMIN | USER"}
Errores: 403 Forbidden si no es administrador, 400 Bad Request si el correo ya existe.
Módulo: app.api.usuarios | Permisos: Mismo tenant
Errores: 403 Forbidden (otra empresa), 404 Not Found.
Módulo: app.api.usuarios | Permisos: ADMIN o el propio usuario
Regla: Solo un administrador puede modificar el campo rol.
Módulo: app.api.usuarios | Permisos: ADMIN (no puede auto-eliminarse)
Respuesta (200 OK): {"message": "Usuario eliminado correctamente"}
Clientes (Namespace /clientes)
Módulo: app.api.clientes | Autenticación: Bearer JWT
Respuesta (200 OK): Lista de clientes con id, nombre, identificacion, dias_plazo, limite_credito.
Módulo: app.api.clientes | Autenticación: Bearer JWT
Body: {"nombre": str, "identificacion": str, "dias_plazo": int, "limite_credito": float}
Validación: La identificacion debe ser única dentro de la misma empresa.
Módulo: app.api.clientes | Errores: 403 Forbidden, 404 Not Found
Módulo: app.api.clientes | Body parcial: Permite actualizar nombre, identificación, plazo o límite.
Módulo: app.api.clientes | Restricción: Bloqueado con error 400 si el cliente posee facturas.
Cartera & Finanzas (Namespace /cartera)
Módulo: app.api.cartera | Autenticación: Bearer JWT
Respuesta (200 OK):
{
"cartera_vencida_total": 12500000.0,
"dso_proyectado": 38.4,
"distribucion_edades": {
"0-30": 6000000.0,
"31-60": 4000000.0,
"61-90": 2000000.0,
"90+": 500000.0
}
}
Módulo: app.api.cartera | Autenticación: Bearer JWT
Body JSON: Array de facturas a importar:
[
{
"cliente_id": 1,
"numero": "FAC-2026-001",
"fecha_emision": "2026-09-01T00:00:00",
"fecha_vencimiento": "2026-09-30T00:00:00",
"monto_total": 450000.0
}
]
Efecto: Crea facturas con saldo_pendiente = monto_total y estado PENDIENTE.
Módulo: app.api.cartera | Autenticación: Bearer JWT
{
"factura_id": 10,
"monto": 250000.0,
"metodo_pago": "transferencia",
"transaccion_id": "TRX-893120"
}
{
"message": "Pago registrado",
"pago_id": 4
}
Regla financiera: Resta el monto del saldo pendiente. Si saldo ≤ 0, cambia el estado a EstadoFactura.PAGADA.
Módulo: app.api.cartera | Autenticación: Bearer JWT
Respuesta (200 OK): Array con todas las facturas del cliente, fecha de vencimiento, montos, saldos y estados.
Inteligencia Artificial (Namespace /api/v1/ai)
Módulo: app.ia | Permisos: Requiere claim role: "ADMIN"
Descripción: Extrae 13 variables cuantitativas de clientes históricos, entrena RandomForest y XGBoost, evalúa métricas, almacena el archivo .joblib, guarda registros en ai_models y activa automáticamente el de mayor ROC AUC.
{
"version": "v_20260914_210000",
"best_model_id": 2,
"best_algorithm": "XGBClassifier",
"models": [
{"id": 1, "algorithm": "RandomForestClassifier", "accuracy": 0.93, "precision": 0.91, "recall": 0.89, "f1_score": 0.90, "roc_auc": 0.94, "is_active": false},
{"id": 2, "algorithm": "XGBClassifier", "accuracy": 0.96, "precision": 0.94, "recall": 0.92, "f1_score": 0.93, "roc_auc": 0.97, "is_active": true}
]
}
Módulo: app.ia | Autenticación: Bearer JWT
Respuesta (200 OK): Lista con ID, algoritmo, accuracy, precision, recall, f1, roc_auc y booleano is_active.
Módulo: app.ia | Permisos: Rol ADMIN
Efecto: Marca is_active = True en el modelo elegido y False en todos los demás.
Módulo: app.ia | Autenticación: Bearer JWT
Respuesta (200 OK):
{
"client_id": 5,
"risk_score": 74,
"probability": 0.742,
"risk_level": "ALTO",
"recommendation": "Seguimiento preventivo"
}
Módulo: app.ia | Query Params: page (default 1), per_page (default 10)
Respuesta (200 OK): Total de predicciones, página y logs con score, probabilidad y fecha.
Módulo: app.ia | Autenticación: Bearer JWT
Respuesta (200 OK):
{
"cliente_id": 5,
"nombre": "Comercializadora XYZ",
"analisis_cartera": {
"total_facturas": 8,
"facturas_vencidas": 1,
"saldo_pendiente_total": 1200000.0,
"ratio_uso_credito": 0.40,
"promedio_dias_retraso": 4.2
},
"evaluacion_ia": {
"score_riesgo": 32,
"probabilidad_mora": 0.32,
"nivel_riesgo": "BAJO",
"recomendacion": "Monitoreo estándar"
},
"dictamen_detallado": "El cliente Comercializadora XYZ tiene un límite de crédito de 3,000,000.00 con plazo de 30 días..."
}
Tokens JWT y Claims Personalizados
La autenticación en Deilify está basada en Flask-JWT-Extended. Al iniciar sesión exitosamente en /auth/login, se genera un token con la siguiente estructura de payload:
{
"sub": "1", // ID del usuario (identity)
"role": "ADMIN", // Rol del usuario: "ADMIN" o "USER"
"empresa_id": 1, // Tenant ID al que pertenece el usuario
"exp": 1726358400 // Tiempo de expiración (1 día por configuración)
}
Mecanismo de Extracción Multi-tenant (tenant.py)
En app/middleware/tenant.py, la función get_empresa_id_from_jwt() asegura que ninguna consulta acceda a información de otra empresa:
def get_empresa_id_from_jwt():
claims = get_jwt()
emp_id = claims.get("empresa_id")
if emp_id is not None:
return int(emp_id)
# Soporte para identidad directa o fallback controlado en tests
...
Control de Acceso por Roles (RBAC)
| Operación | Rol Requerido | Comportamiento si es Denegado |
|---|---|---|
Entrenamiento IA (POST /api/v1/ai/train) |
ADMIN | 403 Forbidden: "Se requiere rol de ADMINISTRADOR" |
Activar Modelo IA (PUT /ai/models/<id>/activate) |
ADMIN | 403 Forbidden: "Se requiere rol de ADMINISTRADOR" |
Crear Usuarios (POST /usuarios/) |
ADMIN | 403 Forbidden: "Solo administradores pueden crear usuarios" |
Eliminar Usuarios (DELETE /usuarios/<id>) |
ADMIN | 403 Forbidden (además no permite auto-eliminación) |
| Operar Cartera, Facturas y Pagos | USER / ADMIN | Requiere token válido del tenant |
Almacenamiento de Contraseñas
Deilify utiliza Argon2id mediante la biblioteca argon2-cffi. Cada contraseña se hashea con sales criptográficos antes de persistirse en la base de datos (Usuario.set_password), y se valida con Usuario.check_password.
Stack Tecnológico Real
| Librería / Herramienta | Versión | Propósito en Deilify |
|---|---|---|
| React | 19.2.0 | Biblioteca declarativa principal de interfaz de usuario. |
| Vite | 6.3.5 | Herramienta de compilación ultra rápida y servidor de desarrollo HMR. |
| TypeScript | 5.x | Tipado estático para páginas y componentes. |
| React Router DOM | 7.1.3 | Enrutamiento del lado del cliente, rutas protegidas y layouts. |
| Tailwind CSS | 4.1.12 | Framework de estilos utilitarios moderno. |
| Radix UI | Primitivas | Componentes accesibles: Dialog, Dropdown, Tabs, Accordion, Select. |
| Lucide React | 0.487.0 | Iconografía SVG estilizada para toda la aplicación. |
| Recharts | 2.15.2 | Visualización de gráficas de barras y pastel para DSO y edades de mora. |
| Axios | 1.7.9 | Cliente HTTP con interceptores para inyección y expiración de JWT. |
| React Hook Form + Zod | 7.55 / 4.1 | Gestión reactiva de formularios y validación estricta de esquemas. |
| React Hot Toast | 2.6.0 | Feedback visual e interactivo de operaciones y errores. |
Estructura de Directorios del Frontend
Catálogo de Vistas
| Ruta | Componente | Estado de Conexión | Propósito |
|---|---|---|---|
/login |
Login.tsx |
Conectado | Formulario dual con modo "Iniciar Sesión" y "Registrar Empresa". Llama a authService.login o register. |
/ o /dashboard |
Dashboard.tsx |
Conectado | KPIs agregados de cartera, barras de antigüedad (Recharts), distribución de estado y listado de últimas predicciones IA. |
/clientes |
Clientes.tsx |
Conectado | Tabla de clientes con búsqueda interactiva, botón para nuevo cliente y acciones de edición y eliminación. |
/clientes/:id |
ClienteDetalle.tsx |
Conectado | Ficha 360° del cliente: estado de cuenta con facturas y pagos, botón "Predecir riesgo" y tarjeta de reporte predictivo. |
/facturas |
Facturas.tsx |
Parcial | Modal de creación/carga de facturas conectada a POST /cartera/facturas. La lectura de listado depende del dashboard. |
/pagos |
Pagos.tsx |
Conectado | Modal de registro de pagos conectada a POST /cartera/recaudos. |
/ia/modelos |
IA.tsx |
Conectado | Pestañas interactivas: Modelos (tabla de métricas + botón Activar), Entrenamiento (botón Entrenar), Predicciones e Historial. |
/usuarios |
Usuarios.tsx |
Conectado | Listado y modal CRUD de usuarios con asignación de roles ADMIN/USER. Protegido en frontend para rol ADMIN. |
/configuracion |
Configuracion.tsx |
Conectado | Datos de la empresa y usuario activo, botón para refrescar sesión y cierre de sesión. |
/perfil |
Perfil.tsx |
Conectado | Tarjeta con datos del usuario retornados por /auth/me. |
Manejo de Sesión Global (AuthContext.jsx)
El frontend utiliza la Context API de React con persistencia local:
deilify_token: Almacena el JWT enlocalStorage(osessionStoragesi no se selecciona "Recordar").deilify_user: Datos del usuario en sesión (nombre, email, rol).deilify_company: Información básica de la empresa (nombre, NIT).hasRole('ADMIN'): Utilidad para verificar privilegios en vistas protegidas.
Interceptor de Axios (src/api/axios.js)
api.interceptors.request.use((config) => {
const token = localStorage.getItem("deilify_token") || sessionStorage.getItem("deilify_token");
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem("deilify_token");
window.dispatchEvent(new Event("auth-logout"));
}
return Promise.reject(error);
}
);
| Columna | Tipo | Modificadores / Relación |
|---|---|---|
| PK id | Integer | Autoincremental |
| nombre | String(100) | NOT NULL — Razón social |
| nit | String(20) | NOT NULL, UNIQUE |
| created_at | DateTime | Default: utcnow |
| Columna | Tipo | Modificadores / Relación |
|---|---|---|
| PK id | Integer | Autoincremental |
| FK empresa_id | Integer | NOT NULL → empresas.id |
| nombre | String(100) | NOT NULL |
| String(120) | NOT NULL, UNIQUE | |
| password_hash | String(255) | NOT NULL — Hash Argon2id |
| rol | String(20) | NOT NULL, Default "USER" ("ADMIN" | "USER") |
| created_at | DateTime | Default: utcnow |
| Columna | Tipo | Modificadores / Relación |
|---|---|---|
| PK id | Integer | Autoincremental |
| FK empresa_id | Integer | NOT NULL → empresas.id |
| nombre | String(150) | NOT NULL |
| identificacion | String(20) | NOT NULL (Validada única por tenant en API) |
| dias_plazo | Integer | Default: 30 días |
| limite_credito | Float | Default: 0.0 |
| Columna | Tipo | Modificadores / Relación |
|---|---|---|
| PK id | Integer | Autoincremental |
| FK empresa_id | Integer | NOT NULL → empresas.id |
| FK cliente_id | Integer | NOT NULL → clientes.id |
| numero | String(50) | NOT NULL — Código o consecutivo |
| fecha_emision | DateTime | NOT NULL |
| fecha_vencimiento | DateTime | NOT NULL |
| monto_total | Float | NOT NULL |
| saldo_pendiente | Float | NOT NULL (Disminuye con recaudos) |
| estado | Enum(EstadoFactura) | PAGADA | PENDIENTE | VENCIDA (Default: PENDIENTE) |
| Columna | Tipo | Modificadores / Relación |
|---|---|---|
| PK id | Integer | Autoincremental |
| FK empresa_id | Integer | NOT NULL → empresas.id |
| FK factura_id | Integer | NOT NULL → facturas.id |
| monto | Float | NOT NULL — Valor del recaudo |
| fecha_pago | DateTime | Default: utcnow |
| metodo_pago | String(50) | NOT NULL (transferencia, tarjeta, efectivo, etc.) |
| transaccion_id | String(100) | UNIQUE, Nullable — Referencia bancaria |
| Columna | Tipo | Modificadores / Relación |
|---|---|---|
| PK id | Integer | Autoincremental |
| name | String(100) | NOT NULL |
| version | String(50) | NOT NULL |
| algorithm | String(100) | NOT NULL (RandomForestClassifier / XGBClassifier) |
| accuracy | Float | NOT NULL |
| precision | Float | NOT NULL |
| recall | Float | NOT NULL |
| f1_score | Float | NOT NULL |
| roc_auc | Float | NOT NULL (Criterio de selección del mejor) |
| model_path | String(255) | NOT NULL (Ruta al archivo .joblib) |
| is_active | Boolean | NOT NULL, Default False |
| created_at | DateTime | NOT NULL, Default utcnow |
| Columna | Tipo | Modificadores / Relación |
|---|---|---|
| PK id | Integer | Autoincremental |
| FK client_id | Integer | NOT NULL → clientes.id |
| FK model_id | Integer | NOT NULL → ai_models.id |
| prediction | Float | NOT NULL (Clase predicha: 0 o 1) |
| risk_score | Integer | NOT NULL (Puntaje de 0 a 100) |
| probability | Float | NOT NULL (Probabilidad 0.0 a 1.0) |
| created_at | DateTime | NOT NULL, Default utcnow |
| Columna | Tipo | Modificadores / Relación |
|---|---|---|
| PK id | Integer | Autoincremental |
| FK empresa_id | Integer | NOT NULL → empresas.id |
| FK cliente_id | Integer | NOT NULL → clientes.id |
| fecha | DateTime | Default: utcnow |
| nota | Text | NOT NULL — Detalle de la gestión |
| usuario_id | Integer | Nullable — ID del gestor |
Estrategia de Inicialización
En desarrollo, el backend ejecuta Base.metadata.create_all(bind=engine) en app/__init__.py, asegurando que las tablas se creen automáticamente si no existen.
Para entornos de producción y control formal de cambios, se utiliza Alembic configurado en alembic.ini con scripts en migrations/versions/.
Comandos de Migración
# Crear una nueva revisión automática alembic revision --autogenerate -m "nombre_de_migracion" # Aplicar migraciones pendientes alembic upgrade head # Revertir última migración alembic downgrade -1
Flujo de Entrenamiento (ModelTrainingService)
build_training_dataset() consulta Clientes, Facturas y Pagosapp/ia/modelos/*.joblibFlujo de Inferencia en Vivo (PredictionService)
- Se invoca
POST /api/v1/ai/predict/<client_id>. - Se valida que el cliente exista y pertenezca a la empresa del token JWT.
- Se busca el modelo marcado como
is_active = Trueen la tablaai_models. - Se carga el clasificador serializado desde el archivo
.joblibmediantejoblib.load(). - Se extraen las 13 características del cliente en tiempo real a partir de sus facturas vigentes e históricas.
- El modelo computa
clf.predict_proba()para obtener la probabilidad de mora. - Se escala la probabilidad a un score entero entre 0 y 100.
- Se categoriza el nivel de riesgo y se formula una recomendación accionable.
- Se persiste el log en la tabla
prediction_logspara trazabilidad y auditoría.
Variables del Modelo (FEATURE_COLUMNS)
| Variable | Tipo | Cálculo & Definición en el Código |
|---|---|---|
limite_credito |
Float | Límite de crédito asignado contractualmente al cliente. |
dias_plazo |
Integer | Plazo de pago estipulado en días (ej. 30 días). |
total_facturas |
Integer | Cantidad histórica total de facturas emitidas al cliente. |
facturas_pagadas |
Integer | Facturas con saldo_pendiente ≤ 0 o estado PAGADA. |
facturas_vencidas |
Integer | Facturas con saldo_pendiente > 0 y fecha_vencimiento < hoy. |
total_monto_facturado |
Float | Sumatoria total del monto facturado históricamente. |
saldo_pendiente_total |
Float | Deuda total impaga consolidada en el momento del análisis. |
monto_promedio_factura |
Float | total_monto_facturado / total_facturas. |
ratio_saldo_limite |
Float | saldo_pendiente_total / limite_credito (Nivel de consumo de crédito). |
promedio_dias_pago |
Float | Días de retraso promedio entre pago y vencimiento en facturas pagadas y vigentes. |
max_dias_mora |
Float | Retraso máximo en días registrado en toda la historia del cliente. |
tasa_mora |
Float | Proporción de facturas con mora respecto al total emitido (0.0 a 1.0). |
cantidad_notas_cobranza |
Integer | Conteo de gestiones de cobro registradas en historial_cobranza. |
Definición del Target de Entrenamiento
La función determine_target(features) clasifica al cliente con etiqueta de riesgo target = 1 si cumple cualquiera de las siguientes condiciones empíricas:
if features["facturas_vencidas"] > 0 or features["max_dias_mora"] > 30 or features["promedio_dias_pago"] > 15:
return 1 # Cliente en mora / riesgo
return 0 # Cliente al día / bajo riesgo
Escala de Riesgo Financiero (0 a 100)
| Rango de Score | Nivel de Riesgo | Acción y Recomendación Sugerida |
|---|---|---|
| 0 – 20 | MUY BAJO | Sin observaciones / Aprobación estándar |
| 21 – 40 | BAJO | Monitoreo estándar |
| 41 – 60 | MEDIO | Monitoreo periódico |
| 61 – 80 | ALTO | Seguimiento preventivo |
| 81 – 100 | MUY ALTO | Acción de cobro inmediata / Suspensión de crédito |
Síntesis Narrativa (SmartReportService)
Ejemplo de Dictamen Generado Automáticamente por el Backend:
"El cliente Distribuidora del Norte S.A.S. tiene un límite de crédito de $10,000,000.00 con plazo de 30 días. Actualmente posee 2 factura(s) vencida(s) con un retraso máximo de 45 días, lo cual representa un riesgo directo de mora. Históricamente presenta un promedio de demora en sus pagos de 18.5 días. Su saldo pendiente actual ($8,500,000.00) consume un porcentaje crítico de su límite de crédito (85.0%)."
Matriz Comparativa de Integración
| Funcionalidad | Backend Disponible | Frontend Implementado | Integración | Estado Real |
|---|---|---|---|---|
| Registro de Empresa / Usuario | Sí (POST /auth/register) | Sí (Login.tsx) | Sí | Completo |
| Login JWT | Sí (POST /auth/login) | Sí (Login.tsx) | Sí | Completo |
| Perfil de Sesión | Sí (GET /auth/me) | Sí (Perfil.tsx, Configuracion.tsx) | Sí | Completo |
| CRUD de Usuarios | Sí (/usuarios/) | Sí (Usuarios.tsx) | Sí | Completo |
| CRUD de Clientes | Sí (/clientes/) | Sí (Clientes.tsx) | Sí | Completo |
| Dashboard (DSO, Antigüedad) | Sí (/cartera/dashboard) | Sí (Dashboard.tsx) | Sí | Completo |
| Carga de Facturas | Sí (POST /cartera/facturas) | Sí (Modal Facturas.tsx) | Sí | Completo |
| Listado General de Facturas | No (Solo por cliente) | Parcial (Extrae de dashboard) | Inconsistente | En desarrollo |
| Registro de Recaudo | Sí (POST /cartera/recaudos) | Sí (Modal Pagos.tsx) | Sí (Alinear referencia) | Completo |
| Listado Histórico de Pagos | No (Solo en estado de cuenta) | Parcial (Extrae de dashboard) | Inconsistente | En desarrollo |
| Estado de Cuenta Cliente | Sí (/cartera/clientes/<id>/estado-cuenta) | Sí (ClienteDetalle.tsx) | Sí | Completo |
| Entrenamiento de Modelos IA | Sí (POST /api/v1/ai/train) | Sí (IA.tsx) | Sí | Completo |
| Listar & Activar Modelos IA | Sí (/api/v1/ai/models) | Sí (IA.tsx) | Sí | Completo |
| Inferencia de Riesgo Individual | Sí (/api/v1/ai/predict/<id>) | Sí (ClienteDetalle.tsx) | Sí | Completo |
| Historial de Predicciones | Sí (/api/v1/ai/predictions) | Sí (IA.tsx y Dashboard.tsx) | Sí | Completo |
| Reporte Inteligente IA 360° | Sí (/predict/<id>/report) | Sí (ClienteDetalle.tsx) | Sí | Completo |
Inconsistencias y Hallazgos de Integración
-
Desfase en Listado de Facturas:
Facturas.tsxllama acarteraService.getDashboard()esperando encontrar un arreglodata.facturaspara alimentar la tabla principal. Sin embargo,/cartera/dashboardretorna únicamente agregados estadísticos (cartera vencida, DSO y distribución por edades). Se requiere implementarGET /cartera/facturasen el backend. -
Nombre de campo en Recaudos: En
Pagos.tsxel formulario envíareferencia, mientras que el esquema del backend (PagoSchema) esperatransaccion_id. Se recomienda mapeartransaccion_id: parsed.data.referencia. -
Rutas huérfanas en Frontend: Los archivos
CarteraModule.tsx,GestionCobros.tsxyReportes.tsxson placeholders que reexportan otros componentes. No están enlazados en el menú lateral principal.
| Tópico | Lo que decía la documentación antigua ("GesCartera") | La realidad en el código fuente actual (Deilify) |
|---|---|---|
| Framework Frontend | Vue.js 3 (Composition API, Pinia, Vuetify, VeeValidate). | React 19.2.0, TypeScript, Vite 6, Tailwind CSS 4, Radix UI y Recharts. |
| Nombre de la Plataforma | "GesCartera" | Deilify (Deilify SaaS) |
| Base de Datos | PostgreSQL 15 con triggers PL/pgSQL obligatorios y pg_cron. | SQLAlchemy 2.0 ORM. Por defecto corre sobre SQLite (sqlite:///deilify.db) y soporta PostgreSQL vía SQLALCHEMY_DATABASE_URI. |
| Lógica de Aplicación de Pago | Disparador SQL nativo de base de datos (trg_aplicar_pago). |
Lógica en capa de servicios Python (CarteraService.registrar_pago descuenta saldo y actualiza estado). |
| Endpoints de Cartera | Rutas como /api/v1/cartera/resumen, /vencida, /morosos, /acuerdos. |
Rutas unificadas bajo /cartera: /cartera/dashboard, /cartera/facturas, /cartera/recaudos, /cartera/clientes/<id>/estado-cuenta. |
| Módulo de Inteligencia Artificial | Documentado como genérico con segmentación y rutas inexistentes. | Pipeline real completo: RandomForest + XGBoost, 13 features cuantitativas, selección automática por ROC AUC y reportes narrativos. |
| Planes Comerciales | Decorador @requiere_plan('enterprise') para bloquear IA. |
No implementado. El archivo middleware/plan.py está vacío. La IA está accesible para usuarios autenticados. |
Requisitos Previos
- Python: 3.10, 3.11 o 3.12 (Se recomienda crear entorno virtual con
venv). - Node.js: v18.x o v20.x y gestor de paquetes
npmopnpm. - Git: Para control de versiones y manejo de ramas.
1. Puesta en Marcha del Backend (back-deilify)
# 1. Ingresar a la carpeta del backend cd back-deilify # 2. Asegurarse de estar en la rama de desarrollo git checkout develop # 3. Crear y activar entorno virtual # En Windows (PowerShell): python -m venv venv .\venv\Scripts\Activate.ps1 # En Linux / macOS: python3 -m venv venv source venv/bin/activate # 4. Instalar dependencias de Python pip install -r requirements.txt # 5. Configurar variables de entorno mínimas (ejemplo) # Windows PowerShell: $env:FLASK_DEBUG = "1" $env:SECRET_KEY = "clave-secreta-desarrollo" $env:JWT_SECRET_KEY = "clave-jwt-desarrollo" # Linux / macOS: export FLASK_DEBUG="1" export SECRET_KEY="clave-secreta-desarrollo" export JWT_SECRET_KEY="clave-jwt-desarrollo" # 6. Iniciar el servidor de desarrollo python wsgi.py # Servidor escuchando en: http://localhost:5000
2. Puesta en Marcha del Frontend (deilify-frontend)
# 1. Ingresar a la carpeta del frontend cd deilify-frontend # 2. Instalar dependencias con npm npm install # 3. Configurar variable de entorno para apuntar a la API # Crear archivo .env en la raíz de deilify-frontend con: VITE_API_URL=http://localhost:5000 # 4. Iniciar el servidor Vite npm run dev # Interfaz disponible en: http://localhost:5173
Variables de Entorno
Variables del Backend (back-deilify):
FLASK_DEBUG=1 SECRET_KEY=clave-secreta-para-sesiones-flask JWT_SECRET_KEY=clave-secreta-para-firmar-tokens-jwt SQLALCHEMY_DATABASE_URI=sqlite:///deilify.db # O postgresql://user:pass@host:5432/dbname
Variables del Frontend (deilify-frontend):
VITE_API_URL=http://localhost:5000 VITE_API_TIMEOUT=15000
Comandos CLI Útiles
# Marcar facturas vencidas impagas en la base de datos flask cartera check-overdue # Ejecutar pruebas unitarias e integración de IA y Auth python -m unittest discover -s tests
Prioridad Alta (Inmediata)
-
Fusionar cambios de
developamainen back-deilify: Actualmente la ramamaincarece de los módulos deauth,usuariosyclientesque ya están terminados endevelop. Se debe realizar el Merge Request para que producción coincida con el desarrollo. -
Crear endpoint
GET /cartera/facturas: Permitir al frontend listar todas las facturas de la empresa paginadas con filtros por estado (PAGADA, PENDIENTE, VENCIDA) y cliente, resolviendo el desfase enFacturas.tsx. -
Crear endpoint
GET /cartera/pagos: Exponer el historial general de recaudos de la empresa para poblar directamente la vistaPagos.tsx. -
Alinear campo
transaccion_idenPagos.tsx: Ajustar el modal de registro de pagos para que envíetransaccion_iden lugar dereferencia.
Prioridad Media (Consolidación)
-
Implementar Módulo de Acuerdos de Pago (Refinanciación):
Completar el archivo
app/models/acuerdo_pago.pycon tabla de acuerdos, cuotas e integración con facturas refinanciadas. -
Exportación de Reportes Financieros:
Desarrollar generador de reportes PDF y Excel en
app/api/reportespara estados de cuenta de cartera y recaudos mensuales. -
Configuración de Producción con Gunicorn & Docker:
Llenar el archivo
Dockerfile(actualmente en 0 bytes) y definirdocker-compose.ymlorquestando API, PostgreSQL y Frontend Nginx.
Prioridad Baja (Optimizaciones Futuras)
- Reentrenamiento Periódico Automatizado de IA: Configurar un Celery Beat o cron job para reentrenar los modelos de mora trimestralmente a medida que ingresan nuevos recaudos.
- Auditoría Completa de Modificaciones: Implementar middleware de logging estructurado que grabe snapshots de cambios críticos en cartera.
Recomendaciones Técnicas del Arquitecto
Float a Numeric(18, 2) para evitar imprecisiones de coma flotante en balances contables.