Repository navigation
09 Backend y API
Ruta(s):
apps/backend(API Express + TypeScript + Mongoose, desplegada en Railway) y los route handlers deapps/web/src/app/api· Archivos principales:src/index.ts,src/routes/,src/controllers/,src/services/,src/models/,src/middleware/,src/config/· Prioridad: P0 · Esfuerzo total: XL (≈ 36 semanas-dev de backend entre la Fase 0 y la Fase 4, de las cuales ≈ 19 corresponden a las Fases 0 y 1; detalle en Tareas)
Esta página es el plan técnico del backend. Para cada módulo dice qué hace hoy, en qué estado está y qué se decide: mantener, refactorizar, mover a un servicio aparte o eliminar. También describe los componentes nuevos que necesita el Sistema de demos: modelos, rutas, mensajería unificada, jobs, almacenamiento y configuración.
- La especificación funcional de solicitudes, accesos, estados y permisos está en Sistema de demos. Esta página no la repite: explica cómo se construye y cómo se opera.
- Las pantallas que consumen esta API están en Panel de administración y Portal del cliente.
- La wiki es pública. La seguridad se trata solo como tareas genéricas (sección 16 del plan y Seguridad y calidad).
- En una mirada
- Objetivo
- Estado actual
- Problemas detectados
-
Plan detallado
- Arquitectura objetivo · 2. Decisiones de arquitectura · 3. Plan por módulo · 4. Componentes nuevos del sistema de demos · 5. Mensajería unificada · 6. Jobs y worker · 7. Persistencia y almacenamiento · 8. Redis · 9. Variables de entorno con zod · 10. Estructura de carpetas · 11. Convenciones de la API · 12. Swagger / OpenAPI · 13. Observabilidad, despliegue y entornos · 14. Inventario de la API objetivo · 15. Limpieza de código muerto · 16. Seguridad
- Integración con el sistema de demos
- SEO · i18n · accesibilidad · rendimiento
- Tareas
- Métricas de éxito
| Tema | Hoy | Objetivo |
|---|---|---|
| Procesos | Un solo proceso Express en Railway. Además de atender la API, procesa PDFs con IA durante hasta 15 minutos por petición | Servicio api y, desde la Fase 2, un servicio worker con colas para el trabajo pesado |
| Tamaño | 22 archivos de rutas con 179 endpoints montados. ≈ 34.300 líneas alcanzables desde index.ts, y ≈ 65 % de ellas son del vertical de salud |
Plataforma (cuentas, leads, demos, portal) organizada en capas. Cada producto (RAG, documentos, salud) en un módulo aislado |
| Estado | Archivos subidos, estado del chatbot, decisiones de la IA y logs en el disco efímero de Railway: se pierden en cada despliegue | MongoDB con réplica y almacenamiento de objetos privado. En el disco solo quedan temporales |
| Sesiones | Token de refresco en Redis, con una sola sesión por usuario |
AuthSession en MongoDB y cookies httpOnly. Redis pasa a ser opcional |
| Permisos | Roles user, admin, manager, developer y un filtro por rol para todo /api/admin
|
Matriz de permisos con admin, sales, manager, developer, prospect y client, aplicada siempre en el servidor |
| IA | 6 modelos de OpenAI y Anthropic escritos en el código, sin medir el costo | Pasarela de IA: modelos por variable de entorno, cupos, tope mensual y registro de uso (AiUsage) |
| Mensajes | Email y WhatsApp disparados desde los controladores, sin reintentos ni registro | Outbox OutboundMessage con reintentos, plantillas y reglas de envío |
| Configuración | Variables leídas en cualquier archivo y valores por defecto en el código |
config/env.ts validado con zod al arrancar; el servidor no arranca si falta algo |
| Documentación | Swagger con anotaciones en 9 de los 22 archivos de rutas | OpenAPI generado desde los esquemas zod en CI y cliente tipado para la web |
| Pruebas | 14 archivos de prueba, todos dentro de código que no se monta. No corren | Jest + supertest + MongoDB en memoria con réplica, todo en CI |
| Código muerto | ≈ 5.300 líneas sin uso: 18 módulos en memoria, copias .bak, SQL de PostgreSQL, un chat de eco… |
Eliminado en la Fase 0 |
| Módulo | Montaje actual | Endpoints | Líneas aprox. | Lo usa hoy | Decisión | Fase |
|---|---|---|---|---|---|---|
| Autenticación | /api/auth |
9 | 870 |
/login, /register, Google, recuperar contraseña |
Refactorizar | 0–1 |
| Usuarios y administración | /api/admin |
13 | 580 | Panel /admin
|
Refactorizar | 0–1 |
| Contacto → Leads | /api/contact |
3 (2 de prueba) | 160 | /contact |
Refactorizar (pasa a ser el canal de Leads) | 1 |
| Sistema de demos y Leads | — | ≈ 45 nuevos | — | — | Nuevo | 1 |
| Mensajería (email, WhatsApp, avisos internos) | servicios | — | 890 | Contacto, pedidos, restablecer contraseña | Refactorizar hacia el outbox unificado | 1 |
| Chatbot RAG: constructor y playground |
/api/chatbot/bots/*, /models
|
12 | ≈ 950 | /demo/chatbot |
Refactorizar (MongoDB, S3 y núcleo RAG) | 0–2 |
| Chatbot heredado "por sesión" |
/api/chatbot/session, /upload, /message… |
7 | ≈ 700 | Ninguno | Eliminado (14 de octubre de 2026; limpieza única de sus datos al arrancar) | 2 |
| Documentos | /api/documents |
11 | 1.220 | /demo/gestor-documentos |
Refactorizar (modo muestra, grant y núcleo RAG) | 1–2 |
| Portal: proyectos, pedidos, facturas, entregables, mensajes, notificaciones | 6 routers | 37 | 3.850 |
/dashboard y /admin
|
Mantener y refactorizar | 1–3 |
Propuestas (Quote) |
/api/quotes |
1 | 70 | Ninguno | Eliminar el POST público; se reconstruye en la Fase 3 | 0 y 3 |
| Chat heredado | /api/chat |
3 | 200 | Ninguno (responde con un eco) | Eliminar | 0 |
| Salud: cuentas médicas, auditoría, Ley 100, CUPS, liquidación, glosas, reglas | 6 routers (uno montado en la raíz /api) |
70 | ≈ 20.000 |
/demo/cuentas-medicas, páginas internas /liquidacion y /test
|
Mover a un servicio aparte, por etapas: módulo aislado (Fase 2) desplegable como instancia dedicada por cliente | 1–3 |
| Sistema experto | /api/expert |
6 | 2.060 | /demo/sistema-experto |
Mover al módulo de salud como "Motor de reglas" | 1–2 |
| Gestor de contenido con IA | /api/content |
5 | 630 | /demo/gestor-contenido |
Mantener detrás de la pasarela de IA; módulo content-ai en la Fase 2 |
0–2 |
Módulos en memoria (src/modules/) |
no montados | 126 | 3.440 | Ninguno | Eliminar | 0 |
| Rutas de prueba |
routes/test.routes.ts y las rutas de prueba de contact.routes.ts
|
4 | 160 | Ninguno | Eliminar de producción | 0 |
El backend tiene que sostener el negocio que describen Visión de producto y Flujo del cliente:
- Sostener el funnel de la Fase 1 de punta a punta: solicitud de demo → aprobación → acceso verificado en el servidor → uso medido → propuesta. Ningún paso puede depender de un archivo en disco ni de un paso manual fuera del panel.
- Controlar el costo de la IA. Las demos públicas (asistente RAG, "Prueba con tu documento", contenido) llaman a modelos de pago. Toda llamada pasa por cupos y por un tope mensual.
-
Poder vender por partes.
- El chatbot RAG es el primer producto que pasa a SaaS real (Fase 4).
- El vertical de salud se ofrece como instancia dedicada por cliente (Demo cuentas médicas).
- El resto de soluciones a medida se demuestra con maquetas.
-
Ser operable por un equipo pequeño:
- desplegar sin perder datos;
- salud visible de jobs y mensajes;
- alertas;
- documentación generada;
- pruebas en CI.
- Cumplir la Ley 1581: prueba del consentimiento, retención, supresión, transmisión a proveedores declarada y registros técnicos sin datos personales.
flowchart LR
U["Navegador"]
subgraph VC["Vercel: web Next.js 14"]
WP["Páginas, demos, portal y panel"]
WR["Route handlers: proxy del chatbot heredado, LinkedIn Ads con OpenAI, TRM"]
end
subgraph RW["Railway: backend Express en un solo proceso"]
IDX["index.ts: helmet, CORS, rate-limit en memoria, Swagger"]
RT["22 archivos de rutas, 179 endpoints"]
SV["Controladores y servicios, aprox. 65 por ciento salud"]
FS[("Disco efímero: uploads, estado del chatbot en JSON, decisiones de IA, logs")]
MOD["src/modules: 18 módulos en memoria sin montar"]
end
DB[("MongoDB")]
RD[("Redis: tokens de refresco y de restablecimiento")]
S3[("S3 opcional, solo documentos")]
AI["OpenAI y Anthropic, modelos fijos en el código"]
MSG["SMTP y WhatsApp: Twilio, Business API o UltraMsg"]
U --> WP
U -->|"llamadas directas con CORS"| IDX
WP --> WR
WR --> IDX
WR --> AI
IDX --> RT --> SV
SV --> DB
SV --> RD
SV --> FS
SV --> S3
SV --> AI
SV --> MSG
- La web llama al backend directamente desde el navegador, a la URL pública de Railway (
apps/web/src/lib/backend-url.ts). Solo el chatbot heredado pasa por un proxy de Next. - La generación de LinkedIn Ads llama a OpenAI desde Vercel (
apps/web/src/app/api/linkedin-ads/generate/route.ts), por fuera del backend.
| Líneas | Qué hace | Observación |
|---|---|---|
| 21–30 |
uncaughtException y unhandledRejection terminan el proceso |
Correcto como red de seguridad, pero no hay reporte de errores (Sentry) |
| 36–70 |
helmet y CORS con lista de orígenes (producción + CORS_ORIGIN) |
Falta trust proxy: detrás del proxy de Railway, el rate-limit y los logs no ven la IP real del visitante |
| 76–84 | Rate-limit general (100 por 15 min) y del chatbot (60 por minuto) | En memoria: cada instancia cuenta por separado |
| 91–100 | Swagger con swagger-jsdoc sobre ./src/routes/*.ts, servido por el mismo backend |
Solo 9 de 22 archivos de rutas tienen anotaciones |
| 110–128 | Crea 8 carpetas bajo ./uploads
|
Todo lo que se guarda ahí se pierde al redesplegar |
| 143–157 | Al arrancar, asegura el rol admin de una cuenta configurada |
Un arranque no debería modificar datos. Se reemplaza por el script scripts/set-admin.ts, que ya existe |
| 172–243 | Importa y monta los 22 routers. cuentas.routes se monta en la raíz /api (línea 232) |
Rutas genéricas como /api/process o /api/export ocupan nombres que la plataforma necesitará |
| 264–269 |
server.timeout de 15 minutos "para análisis IA extenso" |
El trabajo pesado debe ir a una cola, no a la petición HTTP |
| Dominio | Archivos principales | Persistencia | IA | Consumidor web |
|---|---|---|---|---|
| Autenticación |
routes/auth.routes.ts, controllers/auth.controller.ts (337 líneas), config/passport.ts, middleware/auth.ts
|
User en MongoDB. Refresco y restablecimiento en Redis (auth.controller.ts líneas 100, 136, 243) |
— | Login, registro, Google, recuperar |
| Administración |
routes/admin.routes.ts (todo el router con authorize('admin','manager'), línea 21), controllers/admin.controller.ts (535 líneas: pedidos, facturas, entregables, conversaciones, usuarios y contactos en un solo archivo) |
MongoDB | — | /admin/* |
| Contacto |
routes/contact.routes.ts, controllers/contact.controller.ts
|
Contact (new · read · responded) |
— | /contact |
| Chatbot |
routes/chatbot.routes.ts, services/chatbot-store.service.ts, models/ChatbotBot.ts (la API heredada se retiró el 14 de octubre de 2026) |
MongoDB: chatbot_bots, chatbot_bot_chunks y chatbot_turns (conversaciones del widget con TTL de 30 días) |
OpenAI opcional con tope mensual. Sin clave o sin presupuesto, modo extractivo BM25 con una nota. PDF y DOCX se leen con document-text.service
|
/demo/chatbot (tres modos) y /embed/chatbot/<id> (widget) |
| Documentos |
routes/document.routes.ts, controllers/document.controller.ts (633 líneas), services/document-ai.service.ts, services/storage.service.ts
|
Document con su embedding dentro del documento. Archivo en S3 solo si hay credenciales; si no, en disco (document.controller.ts líneas 74–85) |
Análisis y embeddings con text-embedding-ada-002. Similitud coseno calculada en memoria |
/demo/gestor-documentos (hooks/useDocuments.ts) |
| Portal |
routes/{project,orders,invoices,deliverables,messages,notifications}.routes.ts, services/project.service.ts (594 líneas) |
MongoDB. Adjuntos de pedidos en uploads/orders
|
— |
/dashboard/*, /admin/*
|
| Propuestas |
routes/quote.routes.ts, models/Quote.ts (pending · contacted · completed) |
MongoDB | — | Ninguno: requestQuote de lib/api.ts no se llama desde ninguna página |
| Chat heredado |
routes/chat.routes.ts, controllers/chat.controller.ts
|
ChatSession, ChatMessage
|
Ninguna: responde Echo: <mensaje>
|
Ninguno |
| Salud |
routes/{auditoria,cuentas,cups,liquidacion,reglas-facturacion,documentoConocimientoConfig}.routes.ts, 11 controladores, 28 servicios, 24 modelos |
MongoDB, PDFs y Excel en uploads/, decisiones de IA en data/decisiones-ia/*.json (sistema-aprendizaje.service.ts línea 78) |
gpt-4o, gpt-4o-mini, gpt-4-turbo, visión, text-embedding-3-small y un modelo de Anthropic de 2024 para reglas |
/demo/cuentas-medicas (/auditoria/*, /documentos-conocimiento/*), /liquidacion y /test (internas) |
| Sistema experto |
routes/expert-system.routes.ts, services/expert-system.service.ts, expert-rules.service.ts, excel-expert.service.ts
|
MongoDB | Embeddings de CUPS |
/demo/sistema-experto (vía components/DashboardAuditoria.tsx y BusquedaSemanticaCUPS.tsx: /expert/estadisticas, /cups/estadisticas, /cups/estadisticas-vectorizacion, /cups/buscar-semantica) |
| Contenido con IA |
routes/content-manager.routes.ts, services/content-manager.service.ts
|
— |
gpt-4o-mini (o OPENAI_MODEL) |
/demo/gestor-contenido (services/contentManagerService.ts) |
| Módulos en memoria |
src/modules/* (18 carpetas, 125 archivos) |
Arreglos en memoria | — | Ninguno: index.ts no los importa |
| Mensajería |
services/email.service.ts (SMTP), services/whatsapp.service.ts (3 proveedores), utils/notifications.ts (avisos dentro de la app) |
Notification |
— | Contacto, pedidos, restablecer contraseña |
| Qué | Dónde vive hoy | Qué pasa al redesplegar |
|---|---|---|
| Archivos subidos (documentos sin S3, pedidos, chatbot, cuentas médicas, Ley 100) |
./uploads/* (middleware/upload.ts líneas 106–121, routes/cuentas.routes.ts líneas 38–67, routes/liquidacion.routes.ts línea 20, controllers/auditoria.controller.ts línea 18, routes/chatbot.routes.ts línea 19) |
Se pierden; los registros de MongoDB quedan apuntando a archivos que ya no existen |
| Exportes de Excel |
./uploads/exports (excel.service.ts, liquidacion-automatizada.service.ts línea 417, process.controller.ts línea 177) |
Se pierden |
| Bots, documentos, fragmentos y conversaciones del constructor |
Map en memoria + data/chatbots/state.json (data/chatbot-store.ts líneas 19–20) |
Se pierden. Con 2 instancias, cada una tendría su propio estado |
| Decisiones y retroalimentación de la IA de auditoría |
data/decisiones-ia/*.json (además hay 30 archivos de ejemplo versionados en git) |
Se pierden los nuevos |
| Logs | Consola + 4 archivos en logs/ (utils/logger.ts líneas 49–70) |
Se pierden los archivos (la consola sí la guarda Railway) |
| Sesiones y restablecimiento de contraseña | Redis (config/redis.ts: si Redis falla, el envoltorio devuelve null en silencio) |
Sobreviven, pero si Redis no está, no se puede renovar la sesión |
| Rate-limit | Memoria del proceso | Se reinicia con cada despliegue |
-
Variables de entorno: se leen en 40 archivos con
process.env.X || 'valor'..env.exampleno lista varias que el código usa (JWT_REFRESH_SECRET,SMTP_*,TWILIO_*,FRONTEND_URL,ANTHROPIC_API_KEY) y sí lista otras sin uso (MONGO_URI). -
Dependencias declaradas sin uso:
-
zod: 0 importaciones, aunque Sistema de demos lo adopta como estándar de validación; -
@pinecone-database/pinecone: solo lo usaservices/embedding.service.ts, que nadie importa; -
pdf-poppler: sin uso.
-
-
Tres implementaciones de embeddings con dos modelos distintos:
-
document-ai.service.tsusaada-002; -
embeddings.service.tsusa3-small; -
embedding.service.tstambién usaada-002y está muerto.
-
-
Duplicación en salud:
- 3 controladores de auditoría (
auditoria,auditoria-medica,auditoria-modular); - 4 generadores de Excel;
- 5 extractores de PDF (
extraccion-dualtiene 1.017 líneas).
- 3 controladores de auditoría (
-
TypeScript:
tsconfig.jsonconstrict: false. -
Pruebas:
npm testcorre Jest sin configuración para TypeScript, y los 14 archivos de prueba del backend están ensrc/modules/*/__tests__, que no se montan. El CI (.github/workflows/ci.yml) levanta PostgreSQL y no corre (ver Diagnóstico). -
Servidores y scripts sueltos:
src/index-minimal.ts,test-db.js,test-simple.js,test-startup.js,src/test-pdf-processing.ts,src/verify-excel.ts. -
Restos de PostgreSQL:
src/db/migrations/*.sql,src/db/seeds/001_sample_projects.sql,packages/database/init.sqlysrc/config/database.ts.
Las tres demos que dependen de APIs reales del backend se ven vacías o con error en producción:



-
El estado vive en un disco que se borra.
- Archivos de clientes y prospectos, bots del chatbot, decisiones de la IA, exportes y logs se pierden en cada despliegue.
- Por la misma razón, no se pueden correr dos instancias.
-
Las sesiones dependen de Redis y admiten un solo dispositivo.
- Una clave de refresco por usuario: entrar desde el celular cierra la sesión del portátil.
- Si Redis falla, nadie puede renovar su sesión.
-
Los permisos son gruesos.
- Un solo filtro
admin/managerpara todo/api/admin. - No existen los roles
sales,prospectniclient. - La verificación de pertenencia (que un recurso sea del usuario que lo pide) es irregular.
- La autorización de todas las rutas se audita en la Fase 0 (sección 16).
- Un solo filtro
-
La IA no tiene control de costo.
- Los nombres de modelo aparecen escritos en 19 archivos.
- El gasto no se mide por función, visitante ni acceso, así que no se ve en ningún panel.
- Los límites de uso y de costo están repartidos y deben centralizarse (sección 16).
- La ruta de LinkedIn Ads gasta desde Vercel, por fuera del backend.
-
El trabajo pesado corre dentro de la petición HTTP.
- El servidor amplía los tiempos de espera a 15 minutos.
- Un despliegue o un reinicio a mitad del proceso pierde el trabajo.
- El navegador puede cortar la conexión antes de que termine.
-
La plataforma y el vertical de salud están mezclados.
- Salud es ≈ 65 % del código y tiene rutas montadas en la raíz
/api. - Muchos servicios están duplicados.
- Hay referencias a un pagador real en el código (ver Demo cuentas médicas).
- Salud es ≈ 65 % del código y tiene rutas montadas en la raíz
- Hay dos APIs de chatbot y tres de embeddings. El reposicionamiento RAG necesita un núcleo de ingesta, búsqueda y citas.
-
No existe el dominio comercial.
-
Contactes un buzón. -
Quotees un formulario sin uso. - No hay Lead, solicitud, acceso, bitácora ni outbox. Todo eso lo especifica Sistema de demos.
-
-
Los mensajes se envían "y ojalá lleguen".
- El controlador dispara email y WhatsApp sin esperar ni reintentar.
- No queda registro del envío.
- Hay 3 proveedores de WhatsApp a medio configurar.
-
Resuelto para los avisos al dueño en la rama
ccr-55ceecf7-dpc10i(pendiente de merge): registro por canal enContact.notificaciones, reintentos, prueba mensual, webhook firmado de WhatsApp y un solo proveedor por canal (Resend y Meta). Ver 3.5. - Los registros técnicos incluyen datos personales del formulario.
-
La configuración no se valida.
- Faltan variables en
.env.example. - Hay valores por defecto con datos de entorno dentro del código.
- Si falta una variable, el servidor arranca igual y falla después.
- Faltan variables en
-
No hay red de seguridad de calidad: sin pruebas que corran, sin CI válido, sin
stricty con dependencias desactualizadas. -
La documentación de la API es parcial. Además, el cliente de la web (
lib/api.ts,types/api.types.ts) está escrito a mano y se desalinea del backend. -
Hay código muerto que confunde.
- 18 módulos con 126 endpoints parecen un backend para las demos, pero no están montados.
- Además: copias
.bak, SQL, un chat de eco, rutas de prueba y servidores alternativos.
-
Las demos con backend real fallan en producción (capturas de arriba). No hay verificación de salud (
/ready), ni prueba de humo, ni alerta.
flowchart LR
U["Navegador"] --> PG["Web Next.js en Vercel: páginas, portal y panel"]
U --> MW["Middleware de Next: pase de demo"]
MW --> DP["Ruta /api/demo-pass"]
DP -->|"cabecera interna"| API
PG -->|"cookies httpOnly en el subdominio de la API"| API
subgraph RW["Railway"]
API["Servicio api: Express con createApp"]
WK["Servicio worker: colas BullMQ y jobs, Fase 2"]
end
subgraph PL["Plataforma, en capas"]
direction LR
P1["Autenticación, usuarios y permisos"]
P2["Leads, sistema de demos, privacidad, bitácora y métricas"]
P3["Portal: proyectos, pedidos, facturas, entregables y mensajes"]
P4["Propuestas y pagos, Fase 3"]
end
subgraph MD["Módulos de producto, Fase 2"]
direction LR
M1["rag-core: ingesta, búsqueda híbrida y citas"]
M2["chatbot y Prueba con tu documento"]
M3["documentos"]
M4["content-ai"]
M5["salud: cuentas médicas y motor de reglas"]
end
API --> PL
API --> MD
M2 --> M1
M3 --> M1
M5 --> M1
API --> OB["Outbox OutboundMessage"]
WK --> OB
OB --> CH["Email transaccional, WhatsApp Cloud API, Slack y avisos en la app"]
API --> GW["Pasarela de IA: cupos, tope mensual y AiUsage"]
WK --> GW
GW --> AI["OpenAI y Anthropic"]
API --> DB[("MongoDB con réplica: datos, sesiones, tokens y vectores")]
WK --> DB
API --> RD[("Redis: rate-limit, caché y colas")]
WK --> RD
API --> S3[("Almacenamiento de objetos privado")]
WK --> S3
API --> OBS["Logs JSON, Sentry, health y ready"]
Principios:
- El servidor decide. Toda regla de acceso, permiso, cupo y vigencia se evalúa en la API. La web solo oculta botones.
- Nada importante en disco. MongoDB guarda datos y estado; el almacenamiento de objetos, los archivos; el disco, solo temporales del trabajo en curso.
-
Plataforma en capas, productos en módulos.
- La plataforma comercial sigue la convención de Sistema de demos:
models/,routes/,controllers/,services/. - Cada producto vive en
src/modules/<producto>/, con sus rutas, modelos, esquemas y pruebas, y se puede apagar con una variable.
- La plataforma comercial sigue la convención de Sistema de demos:
-
Trabajo pesado fuera de la petición.
- Fase 1: jobs programados con
node-crondentro de la API. - Fase 2: colas en un worker aparte.
- Fase 1: jobs programados con
- Toda llamada a IA pasa por la pasarela. Así hay un solo lugar para cupos, topes, modelos y registro de costo.
- Todo mensaje sale por el outbox. Así hay un solo lugar para reintentos, idempotencia, horarios y bajas.
- El contrato se genera. Los esquemas zod validan las entradas, generan el OpenAPI y, de ahí, los tipos de la web.
| # | Tema | Decisión | Motivo |
|---|---|---|---|
| 1 | Forma del sistema |
Monolito modular: un repositorio y una imagen, con dos servicios (api y worker) desde la Fase 2 |
Equipo pequeño. Separar en microservicios multiplicaría despliegues sin beneficio. El módulo de salud queda listo para desplegarse aparte cuando un cliente lo pida |
| 2 | Base de datos | MongoDB con réplica. Recomendado MongoDB Atlas en la misma región que la API | Las transacciones de approve (Sistema de demos, sección 7.2) exigen réplica. Atlas aporta copias con restauración a un punto en el tiempo y Vector Search para el RAG sin sumar otra base |
| 3 | Archivos |
Almacenamiento de objetos compatible con S3: privado, cifrado, con URLs firmadas y ciclo de vida por prefijo. Se reutiliza @aws-sdk/client-s3, ya instalado |
El disco de Railway es efímero. Con S3_ENDPOINT opcional sirve también un proveedor compatible |
| 4 | Sesiones y tokens |
AuthSession y MagicLinkToken en MongoDB. Redis no participa del inicio de sesión |
Decisión ya tomada en Autenticación (tareas 8 y 9) y en Sistema de demos (decisión 6) |
| 5 | Redis | Opcional en las Fases 0 y 1 (rate-limit compartido y caché); obligatorio desde la Fase 2 (colas) | Si Redis cae, la API sigue funcionando: el rate-limit pasa a memoria |
| 6 | Jobs | Fase 1: node-cron + lease en JobRun + JOBS_ENABLED (decisión 8 de Sistema de demos). Fase 2: BullMQ para trabajo pesado y un worker.ts que también corre los jobs programados |
No se suma infraestructura antes de tiempo. Las colas llegan cuando hay ingesta RAG y lotes de salud |
| 7 | Mensajes | Outbox OutboundMessage (decisión 7 de Sistema de demos) con adaptadores por canal |
Reintentos, idempotencia, secuencias y registro de cada envío |
| 8 | Proveedor transaccional con API y webhooks de rebote, más SPF, DKIM y DMARC del dominio. SMTP solo en desarrollo | Las invitaciones con enlace mágico tienen que llegar a la bandeja de entrada. Hecho en parte (rama ccr-55ceecf7-dpc10i (pendiente de merge)): Resend por API con koptup.com verificado (SPF y DKIM); faltan el DMARC (acción del dueño) y los webhooks de rebote |
|
| 9 | WhatsApp Business Cloud API (Meta) como proveedor único en producción. Twilio queda como adaptador alternativo y UltraMsg se elimina | Las plantillas aprobadas por Meta son obligatorias para escribir fuera de la ventana de 24 h, incluso al equipo. Hecho (rama ccr-55ceecf7-dpc10i (pendiente de merge)), con una diferencia: Twilio también se eliminó (su sandbox vence a los 3 días) |
|
| 10 | IA |
Pasarela de IA propia (services/ai-gateway.service.ts): modelos por variable, medición de tokens y costo en AiUsage, cupos por visitante, usuario y grant, tope mensual global y por función, y apagado por función |
Las demos públicas usan modelos de pago: el costo necesita un solo punto de control y debe verse en el panel |
| 11 | Validación |
zod en todas las rutas nuevas y en las que se toquen; express-validator se retira por etapas |
Un solo lenguaje para validar, tipar y documentar (OpenAPI) |
| 12 | Documentación | OpenAPI generado desde zod (@asteasolutions/zod-to-openapi) y servido solo al equipo |
La especificación deja de depender de comentarios a mano |
| 13 | Núcleo RAG |
modules/rag-core/: ingesta (PDF, DOCX, TXT, URL), fragmentos con página, un modelo de embeddings, búsqueda híbrida (BM25 + vectorial) y citas |
Lo usan el chatbot, "Prueba con tu documento", el gestor documental y la consulta normativa de salud |
| 14 | Salud | Módulo aislado modules/salud/ (Fase 2), montado bajo /api/salud/*, con SALUD_ENABLED. Para clientes: instancia dedicada (API, worker, base y bucket propios) desde la misma imagen |
Datos de salud separados de la plataforma comercial. Es lo que propone Demo cuentas médicas |
| 15 | Código en memoria | Se eliminan los 18 módulos de src/modules/. Quedan en una etiqueta de git (archivo/modulos-en-memoria) |
Las demos usan datos simulados del frontend. Los productos reales se construirán sobre el núcleo multi-tenant de la Fase 4 |
| 16 | Arranque |
src/app.ts exporta createApp() y src/server.ts escucha el puerto. El arranque no modifica datos |
supertest necesita la app sin abrir puerto. Los cambios de datos van a scripts y migraciones |
| 17 | Versionado | Se mantiene /api sin versión. Los cambios incompatibles conviven una versión con cabeceras Deprecation y Sunset
|
La web y la API se despliegan juntas; no hay terceros consumiendo la API todavía |
Cada subsección dice qué hace el módulo, su estado, la decisión y los cambios. Las tareas citadas son las de la tabla de tareas de esta página, salvo que se diga otra página.
/api/auth·routes/auth.routes.ts,controllers/auth.controller.ts,config/passport.ts,middleware/auth.ts· 9 endpoints
- Qué hace: registro local, inicio de sesión, refresco, cierre de sesión, perfil, inicio con Google, recuperar y restablecer la contraseña.
-
Estado:
- JWT de acceso de 15 min, con el rol dentro del token (
middleware/auth.tslíneas 35–42). - Refresco JWT de 7 días guardado en Redis bajo una sola clave por usuario.
- El restablecimiento está bien resuelto (token aleatorio en hash, 1 h, un solo uso), pero también depende de Redis.
- Google crea las cuentas con rol
user(config/passport.tslíneas 60–64).
- JWT de acceso de 15 min, con el rol dentro del token (
- Decisión: refactorizar. Los endpoints se mantienen; cambian el almacenamiento de la sesión, los roles y la forma de entregar las credenciales.
-
Cambios:
- Roles
sales,prospectyclient, másaccountStatus,tokenVersiony la migraciónmigrate-roles.ts. Es la tarea 1 de Sistema de demos. -
AuthSessionen MongoDB, con rotación del refresco, detección de reutilización y máximo de sesiones por rol. Es la tarea 9 de Autenticación. - Cookies httpOnly y la API en un subdominio propio del dominio principal (tarea 8 de Autenticación).
-
authenticatelee la cookie. -
Authorization: Bearerse acepta solo en llamadas de servidor a servidor conINTERNAL_API_KEY.
-
-
MagicLinkTokencon los propósitosinvitacion,acceso,verificacionyreset. Reemplaza las claves de Redis. - Middlewares nuevos en
middleware/auth.ts:optionalAuthenticateyrequirePermission('<permiso>')sobreconfig/permissions.ts.authorize(...roles)queda como alias mientras se migran las rutas. - Endpoints nuevos:
POST /api/auth/logout-all,POST /api/auth/change-password,PATCH /api/me/profiley los de enlace mágico (sección 14). - El callback de Google fija las cookies, sin credenciales en la URL, y activa a los invitados cuyo email coincide (tarea 10 de Autenticación).
- Validación con zod, respuestas uniformes para no revelar qué cuentas existen y logs sin emails en claro.
- Roles
/api/admin·routes/admin.routes.ts,controllers/admin.controller.ts(535 líneas) · 13 endpoints
- Qué hace: pedidos (listar, cambiar estado, aprobar, rechazar, facturar), facturas, entregables, conversaciones, usuarios (listar y cambiar rol) y contactos.
-
Estado:
- Un solo controlador mezcla 6 recursos.
- Todo el router usa el mismo filtro de roles, así que
salesno podría ver contactos aunque se creara el rol.
- Decisión: refactorizar.
-
Cambios:
- Partir
admin.controller.tsenadmin-orders,admin-billing,admin-usersyadmin-contacts. Cada ruta con su propiorequirePermission. - Los recursos comerciales nuevos van en rutas de primer nivel (
/api/demo-requests,/api/leads…), no bajo/api/admin(decisión 16 de Sistema de demos). - Agregados que pide Panel de administración (sección 4.7):
-
PATCH /api/admin/users/:id/status; -
POST /api/admin/invoicesyPATCH /api/admin/invoices/:id/status; -
POST /api/admin/deliverables; -
GET /api/metrics/admin-home; - conteos del outbox y del gasto de IA en
GET /api/admin/jobs/health; - exportes CSV (Fase 2);
-
GET /api/admin/projects(Fase 3).
-
-
PATCH /api/admin/users/:id/rolevalida contra el enum, incrementatokenVersiony escribe enAuditLog. - Validación zod, 400/404 uniformes y una sola respuesta por petición (tarea 4 de Panel de administración).
- La promoción a administrador sale del arranque (
index.tslíneas 143–157) y queda solo enscripts/set-admin.ts, que deja registro enAuditLog.
- Partir
/api/contact·routes/contact.routes.ts,controllers/contact.controller.ts,models/Contact.ts· 3 endpoints (1 real y 2 de prueba)
- Qué hace: guarda el mensaje del formulario y avisa al equipo por WhatsApp y por email.
-
Estado:
- En
main,servicees obligatorio, así que "Prueba con tu documento" no encaja. La ramarag-reposicionamiento(hecho, pendiente de merge) lo resuelve conservices/lead.service.ts(registerLead), que comparten el formulario y la demo. - Los avisos se disparan sin esperar y sin reintento.
- El log escribe nombre, email y teléfono.
- Hay dos rutas de prueba en el mismo router.
-
Estado al 14 de octubre de 2026 (rama
ccr-55ceecf7-dpc10i(pendiente de merge)): las rutas de prueba se eliminaron (cambio 5) y el aviso al dueño queda registrado por canal con reintentos (sección 3.5). El cambio 4 (outbox con plantillas) sigue pendiente: los avisos salen porservices/owner-notify.service.ts, no por el outbox.
- En
-
Decisión: refactorizar.
POST /api/contactpasa a ser el canal único de Leads fuera del formulario de demo. -
Cambios:
- Upsert del
Leadpor email,ConsentRecordyContactconleadId,source(contactoodemo_rag),offeringSlugyplan. Es la tarea 4 de Sistema de demos. Hecho en la ramarag-reposicionamiento(pendiente de merge):Contact.sourcecon los valorescontact-formydemo-rag; la migración del Lead los normaliza acontactoydemo_rag. - Turnstile, honeypot y tiempo mínimo de llenado (sección 15 de Sistema de demos).
- "Prueba con tu documento" no pasa por
POST /api/contactni usa ticket: la API/api/demo-ragrecibe el email y la autorización junto con el archivo y registra el lead con el mismo servicio (sección 3.7). - Los avisos al equipo salen por el outbox, con plantillas y sin datos de contacto del prospecto en WhatsApp.
- Se eliminan las rutas de prueba de email y WhatsApp de este router (sección 15).
- Los logs registran solo el id del Lead, nunca el email.
- Upsert del
Es el bloque principal de la Fase 1 y está especificado en Sistema de demos:
- modelos: sección 5;
- estados: sección 6;
- API: sección 8;
- control de acceso: sección 10;
- tareas de backend: 1 a 15.
La sección 4 de esta página resume dónde vive cada pieza en el código y qué agrega el backend: pasarela de IA, almacenamiento y salud de los jobs.
services/email.service.ts(293 líneas),services/whatsapp.service.ts(376 líneas),utils/notifications.ts(221 líneas),models/Notification.ts
-
Qué hace:
- avisos de contacto y de pedidos;
- email de restablecimiento de contraseña;
- avisos dentro de la app (
Notificationcon 7 tipos).
-
Estado:
- SMTP con valores por defecto en el código.
- WhatsApp con 3 proveedores posibles (Twilio, Business API y UltraMsg).
- Cada controlador llama directamente al servicio.
- Decisión: refactorizar hacia la mensajería unificada de la sección 5.
-
Estado al 14 de octubre de 2026 (rama
ccr-55ceecf7-dpc10i(pendiente de merge)), avisos al dueño:-
Correo: Resend por API HTTPS (
RESEND_API_KEY) para todos los correos del backend; SMTP queda como respaldo con tiempos de espera, porque Railway Free, Trial y Hobby bloquean el SMTP saliente. DestinoNOTIFY_EMAIL(oADMIN_EMAIL), remitente por defectoKopTup <avisos@koptup.com>,Idempotency-Keyyreply_to. -
WhatsApp: solo WhatsApp Cloud API de Meta con la plantilla aprobada
koptup_aviso(2 variables), Graphv25.0configurable, lectura del JSON de error de Meta y respaldohello_world. Twilio y UltraMsg se eliminaron, también la dependenciatwilio. -
Registro y reintentos:
Contact.notificacionespor canal (estado, intentos, error, id del proveedor), job de reintentos cada 5 minutos, latido mensual y estado de salud enAppSettingavisos.estado. -
Panel y API:
GET /api/admin/notifications/status,POST /api/admin/notifications/test,POST /api/admin/contacts/:id/notifyy webhook firmadoPOST /api/webhooks/whatsapp(API › 4.15). - Sigue pendiente lo demás de la sección 5: outbox
OutboundMessage, plantillas editables, Slack y avisos en la app.
-
Correo: Resend por API HTTPS (
No existen hoy. Se crean con el sistema de demos (sección 12 de Sistema de demos) y se amplían en la sección 6 de esta página.
/api/chatbot·routes/chatbot.routes.ts,services/chatbot-store.service.ts,models/ChatbotBot.ts,services/chatbot-legacy-cleanup.service.ts· estado al 14 de octubre de 2026 en la ramaccr-55ceecf7-dpc10i(pendiente de merge)
-
Qué hace:
-
API de bots (los tres modos de
/demo/chatboty el widget/embed/chatbot/<id>): crear y configurar bots, subir documentos en base64 (TXT, MD, CSV, PDF y DOCX), indexar páginas web públicas (POST /bots/:id/urls), preguntar y ver conversaciones.- Recupera fragmentos con BM25 simplificado.
- Si hay clave de OpenAI y presupuesto, genera la respuesta con cita; si no, responde en modo extractivo con una nota que lo explica (en español o inglés según
lango el idioma del bot).
-
Bots de ejemplo compartidos (
POST /sample-bots): «Prueba el asistente» pide el bot de cada empresa de ejemplo; el servidor lo identifica por la huella de su contenido, lo crea una sola vez y lo reutiliza.GET /modelslo anuncia consharedSampleBots: true. -
API heredada "por sesión" (
/session,/upload,/message,/info,/config,/messages,/documents): retirada (responde 404). Al arrancar, el backend borra lo que dejó: la colecciónchatbots(guardaba preguntas y documentos sin vencimiento) yuploads/chatbot.
-
API de bots (los tres modos de
-
Estado:
- Bots, documentos, fragmentos y conversaciones en MongoDB (
chatbot_bots,chatbot_bot_chunks,chatbot_turns); las conversaciones del widget vencen a los 30 días (índice TTL). El archivo heredadostate.jsonse migra una vez y se borra. - Propiedad por token del navegador (
X-Bot-Owner-Token), tope de gasto mensual (CHATBOT_MONTHLY_BUDGET_USD) y cupos por IP: 180 consultas por minuto (sin contarGET /models), 60 preguntas por minuto y 30 bots nuevos por hora. - Reglas de respuesta con fuente (
GROUNDING_RULES) y la API/api/demo-ragde "Prueba con tu documento", con el pipeline compartido enservices/rag-pipeline.ts. - Los modelos están en una constante del archivo.
- Es el producto principal del reposicionamiento (Sistemas RAG, Reposicionamiento RAG).
- Bots, documentos, fragmentos y conversaciones en MongoDB (
- Decisión: refactorizar la API de bots y eliminar la heredada (eliminada el 14 de octubre de 2026).
-
Cambios:
-
Persistencia (Fase 0, tarea 10). Hecha en MongoDB (
ChatbotBot,ChatbotBotChunks,ChatbotTurn); los archivos no se guardan, solo su texto en fragmentos.- Migración idempotente desde
state.json. - Los bots de demo anónimos llevan
expiresAt.
- Migración idempotente desde
-
Pasarela de IA (Fase 0, tarea 5). Cupos por visitante (IP en hash y
sessionId), por grant y tope mensual de la demo (DEMO_MONTHLY_BUDGET_USD). El modelo se elige por variable de entorno. -
"Prueba con tu documento" (Fase 1, tarea 15). Hecho en la rama
rag-reposicionamiento(pendiente de merge) como API propia en/api/demo-rag. Contrato del backend, más abajo. -
Núcleo RAG (Fase 2, tarea 16).
- Ingesta real de PDF y DOCX con
pdf-parseymammoth, que ya son dependencias. - Fragmentos con número de página.
- Un solo modelo de embeddings.
- Búsqueda híbrida con Atlas Vector Search.
- Citas con página y fragmento.
- Ingesta de URLs con límites de tamaño, tiempo y tipo de contenido, y lista de dominios permitidos.
- Ingesta real de PDF y DOCX con
-
Grants (Fase 2). Un
DemoGrantdel chatbot amplía el cupo y crea un bot personalizado con el logo y el sector del prospecto (DemoGrant.customization). -
Retiro de la API heredada (Fase 2). Hecho: se eliminaron las 7 rutas,
chatbot.controller.ts,chatbot.service.ts,models/Chatbot.ts, los proxies de Next y la página/preview, con una limpieza única al arrancar (chatbot-legacy-cleanup.service.ts). -
SaaS real (Fase 4, tarea 25).
OrganizationytenantIden todas las consultas, medición de conversaciones y cobro recurrente.
-
Persistencia (Fase 0, tarea 10). Hecha en MongoDB (
Modelo de datos del chatbot (Fase 0, ampliado en la Fase 2):
erDiagram
Bot ||--o{ BotDocument : "tiene"
BotDocument ||--o{ BotChunk : "se divide en"
Bot ||--o{ BotMessage : "registra"
DemoGrant |o--o{ Bot : "personaliza"
Lead |o--o{ BotDocument : "sube en la demo"
Bot {
string botId UK
string ownerType "demo grant org"
objectid grantId FK
objectid orgId FK
string name
date expiresAt
}
BotDocument {
objectid id PK
string botId FK
string storageKey
number pages
string status "procesando listo error"
objectid leadId FK
date expiresAt
}
BotChunk {
objectid id PK
objectid docId FK
string botId FK
number page
string text
vector embedding "Fase 2"
}
BotMessage {
objectid id PK
string botId FK
string sessionId
string role
number costUsd
date createdAt
}
AiUsage {
objectid id PK
string feature
string actorKey
string model
number costUsd
date at
}
Contrato de "Prueba con tu documento".
El comportamiento lo define el dueño: PDF, DOCX o TXT de hasta 5 MB y 30 páginas; solo email y autorización Ley 1581; 10 preguntas por documento; 3 documentos por IP al día; borrado a la hora; tope mensual; respuestas con cita.
Está hecho en la rama rag-reposicionamiento (pendiente de merge) como una API propia montada en /api/demo-rag (routes/demo-rag.routes.ts y services/demo-rag.service.ts), con el pipeline del chatbot movido a services/rag-pipeline.ts. No usa ticket, cuenta ni POST /api/contact: el email y la autorización viajan con el archivo, y el lead se registra con services/lead.service.ts, el mismo servicio del formulario de contacto (Contact.source = demo-rag). La misma rama agregó a chatbot.routes.ts las reglas para responder solo con los documentos y citar (GROUNDING_RULES).
-
Encendido (falla cerrada): solo funciona con
DEMO_UPLOAD_ENABLED=true,OPENAI_API_KEYy Redis conectado. Si falta algo, o si el gasto del mes llegó aDEMO_MONTHLY_BUDGET_USD, la subida y las preguntas responden 503 y la web muestra "Agenda una demo con nosotros". - Almacenamiento: el archivo se recibe en memoria y nunca va a disco. El documento, sus fragmentos y su índice viven en un mapa en memoria con TTL de 1 hora y un barrido periódico; nada va a MongoDB ni al almacenamiento de objetos. Redis guarda solo el cupo diario por IP (con hash) y el gasto del mes.
-
Modelo y costo:
gpt-4o-minifijo. El costo de cada respuesta se calcula con el uso que informa OpenAI y se suma al gasto del mes (mes UTC). -
Límites extra: además del cupo diario, hay un límite de solicitudes por IP cada 10 minutos en la subida y en las preguntas. La IP real depende de
TRUST_PROXY_HOPS.
sequenceDiagram
autonumber
participant V as Visitante
participant W as Web Next
participant A as API demo-rag
participant R as Redis
participant O as OpenAI
participant D as MongoDB
W->>A: GET /api/demo-rag/status
A->>R: Lee el gasto del mes
A-->>W: Encendida o motivo, sin montos
V->>W: Archivo, email y autorización Ley 1581
W->>A: POST /api/demo-rag/documents con file, email y consent
A->>A: Encendido, formato, tamaño, email y autorización
A->>R: Reserva el cupo diario por IP
A->>A: Extrae el texto, cuenta páginas y arma el índice en memoria
alt Fuera de los límites
A->>R: Devuelve el cupo si el documento se rechaza
A-->>W: 4xx con un código estable y un mensaje claro
else Dentro de los límites
A->>D: Lead demo-rag por el servicio del formulario de contacto
A-->>W: 201 con docId, páginas, preguntas disponibles y vencimiento
end
V->>W: Pregunta
W->>A: POST /api/demo-rag/documents/:docId/questions
A->>A: Reserva la pregunta y busca los fragmentos
A->>O: Pregunta y fragmentos relevantes
O-->>A: Respuesta y uso de tokens
A->>R: Suma el costo al gasto del mes
A-->>W: Respuesta con citas por página o fragmento y preguntas restantes
Note over A: Un barrido periódico borra de memoria los documentos vencidos a la hora
| Endpoint | Acceso | Reglas |
|---|---|---|
GET /api/demo-rag/status |
público | Responde si la demo está encendida, el motivo si no lo está (disabled, unavailable o budget_exhausted) y los límites públicos. Nunca expone montos ni se guarda en caché |
POST /api/demo-rag/documents |
público, sin ticket |
multipart con file, email y consent=true. Valida extensión, tipo y contenido real del archivo, ≤ 5 MB y ≤ 30 páginas, email válido y autorización. Cupo de 3 documentos por IP al día. Responde 201 con el documento (docId, tipo, unidad de cita, páginas, preguntas usadas y restantes, vencimiento) y registra el lead demo-rag sin bloquear la respuesta |
GET /api/demo-rag/documents/:docId |
público | Estado del documento (preguntas usadas y vencimiento). 404 si ya se borró |
POST /api/demo-rag/documents/:docId/questions |
público |
{question} de hasta 500 caracteres. Máximo 10 preguntas por documento; una pregunta que falla no se descuenta. Responde {answer, citations[{page o fragment, excerpt}], notFound, questionsUsed, questionsRemaining}. Si no hay evidencia, responde "No encontré esa información en el documento." |
DELETE /api/demo-rag/documents/:docId |
público | Borra el documento de memoria antes de la hora. La web lo usa cuando el visitante sube otro documento |
Los errores responden {success: false, code, message} con códigos estables que la web traduce (por ejemplo invalid_format, file_too_large, too_many_pages, daily_limit, question_limit, budget_exhausted y demo_disabled).
Los eventos demo_upload y demo_start los emite la web con GA4, solo con consentimiento. Con la Fase 0, la pasarela de IA registra además el uso en AiUsage (feature = demo_doc) y el rate-limit pasa al almacén compartido; con la Fase 2, la demo usa el núcleo RAG (embeddings y búsqueda híbrida). El documento sigue sin guardarse fuera de la memoria.
/api/documents·routes/document.routes.ts,controllers/document.controller.ts(633 líneas),services/document-ai.service.ts,services/storage.service.ts,models/Document.ts· 11 endpoints
-
Qué hace:
- subir documentos, con análisis de IA (resumen, etiquetas, entidades) y embedding;
- listar, buscar por similitud, explicar, carpetas, favoritos, papelera y estadísticas.
-
Estado:
- S3 es opcional y, sin credenciales, el archivo queda en disco.
- La similitud se calcula en memoria sobre todos los documentos.
- Usa
ada-002, un modelo distinto del de CUPS. - En producción la demo muestra "Error al cargar documentos".
- Decisión: refactorizar. Sigue Gestor documental, tareas 1–4 y 8.
-
Cambios:
- Separar por espacio (
ownerType:muestra,grant,usuario) y verificar la pertenencia en cada consulta (Fase 0). -
Modo muestra público, de solo lectura, sobre un corpus sembrado con análisis y embeddings precalculados. Subir, OCR y "explicar" exigen un
DemoGranty respetan cupos (Fase 1). - Almacenamiento de objetos obligatorio (Fase 0). Los documentos de un grant revocado o vencido se borran en ≤ 1 h con el job
grant-cleanup. - La búsqueda pasa al núcleo RAG con un solo modelo de embeddings (Fase 2).
tenantIdy la escala de 50.000 documentos por cliente llegan en la Fase 4 (tarea 15 de Gestor documental). - En la semilla del catálogo,
gestor-documentosdebe llevarhasRealBackend = true(sección de integración).
- Separar por espacio (
/api/projects,/api/orders,/api/invoices,/api/deliverables,/api/messages,/api/notifications· 37 endpoints ·services/project.service.ts(594 líneas) y 10 modelos
-
Qué hace: es el portal del cliente que ya existe:
- proyectos con miembros, tareas y actividad (
ActivityLog); - pedidos con adjuntos y aprobación;
- facturas con descarga y registro de pago;
- entregables con aprobación y rechazo;
- conversaciones y avisos.
- proyectos con miembros, tareas y actividad (
-
Estado:
- Todas las rutas exigen sesión.
- La regla de pertenencia por rol (cliente, equipo) no está centralizada.
- Los adjuntos de pedidos van a
uploads/orders. - El estado
shippeddeOrderno aplica a software. - El pago de facturas no tiene pasarela detrás.
- Decisión: mantener y refactorizar.
-
Cambios:
- Política de pertenencia común:
clientve lo suyo, el equipo según su permiso. Con prueba de integración por recurso (Fase 1, tarea 22). - Adjuntos y entregables en el almacenamiento de objetos, con descarga por URL firmada de 5 minutos.
- Campos
Project.quoteIdyProject.leadIdpara trazar la conversión (sección 5.2 de Sistema de demos).Notification.typesumademo,leadyquote. - Hasta la Fase 3, el estado de pago de una factura lo registra el equipo (
PATCH /api/admin/invoices/:id/status). - Fase 3: adaptador de pagos (Wompi o PayU en COP, Stripe en USD) con webhooks firmados (tarea 24).
- Migración de
Order.status:shippedpasa acompleted, y se quita del enum. - Quién crea proyectos y qué ve un
prospecten el portal se define en Portal del cliente. El backend lo aplica con permisos.
- Política de pertenencia común:
/api/quotes·routes/quote.routes.ts,controllers/quote.controller.ts,models/Quote.ts· 1 endpoint
- Qué hace: un POST público que guarda nombre, email, servicio y descripción.
- Estado: ninguna página lo usa. El modelo no sirve para una propuesta real: no tiene ítems, montos, moneda ni vigencia.
-
Decisión:
- Fase 0, eliminar el POST público. Antes se confirma con 30 días de logs que nadie lo llama. La colección se conserva para la migración.
-
Fase 3, reconstruir como módulo de propuestas:
Quoteampliado, rutas de equipo y enlace público con token (sección 8.5 de Sistema de demos), PDF, aceptación y anticipo (tarea 24). - En la Fase 1, "Solicitar propuesta" solo crea una tarea (decisión 14 de Sistema de demos).
/api/auditoria(21),/apiraíz con/cuentas,/ley100,/process,/export(20),/api/cups(9),/api/liquidacion(8),/api/reglas-facturacion(8),/api/documentos-conocimiento(4) · 70 endpoints · ≈ 20.000 líneas
-
Qué hace: auditoría de cuentas médicas.
- Ingesta de PDF, extracción con IA (texto y visión), validaciones, motor de reglas y cálculo de glosas.
- Auditoría paso a paso, decisión asistida por un modelo grande y retroalimentación para el aprendizaje.
- Consulta de catálogos (CUPS, CIE-10, medicamentos, tarifarios).
- Liquidación de radicados y exportes en Excel.
-
Estado:
- Es el mayor activo técnico, pero:
- está fragmentado (3 controladores de auditoría, 5 extractores, 4 generadores de Excel);
- procesa dentro de la petición;
- guarda archivos en disco;
- tiene referencias a un pagador real;
- no tiene pruebas.
- La demo
/demo/cuentas-medicassolo usa/auditoria/*y/documentos-conocimiento/*. -
/cuentas,/ley100,/processy/exportno las llama ninguna página de la web (la página interna/testse retiró). -
/liquidaciony/reglas-facturacionsolo las llaman las páginas huérfanas/liquidacion. - Hay seeds (≈ 2.600 líneas en
db/seeds/) y scrapers de catálogos oficiales (≈ 2.100 líneas) que ningún script depackage.jsonejecuta.
- Es el mayor activo técnico, pero:
- Decisión: mover a un servicio aparte, por etapas. Se mantiene como producto vertical (Demo cuentas médicas).
- Cambios por fase:
| Fase | Cambio |
|---|---|
| 0 | Autenticación y autorización en todas las rutas del dominio; topes de IA (tarea 4 de esta página y tarea 1 de Demo cuentas médicas). Archivos y exportes al almacenamiento de objetos. Decisiones de IA en la colección AiDecision (con retención) en lugar de data/decisiones-ia/
|
| 1 |
requireDemoGrant('cuentas-medicas') en /auditoria/* y /documentos-conocimiento/*. Las rutas sin consumidor en el catálogo (/cuentas, /ley100, /process, /export, /liquidacion, /reglas-facturacion) quedan solo para el equipo (permiso salud.interno) hasta consolidarse. Verificación de salud y estados vacíos amables (tarea 4 de Demo cuentas médicas) |
| 2 | Consolidar en src/modules/salud/, con un solo flujo: ingesta → cola → extracción → validación → reglas → propuesta de glosa → revisión del auditor → reporte (tarea 8 de Demo cuentas médicas). Detalle: • montaje bajo /api/salud/*, con alias de las rutas viejas por una versión y cabecera Deprecation; • procesamiento en el worker, de modo que la API responde 202 con un jobId; • un solo generador de Excel; • convenios y tarifarios configurables ( ConvenioTarifa, Tarifario); • la consulta de Ley 100 ( rag.service.ts) pasa a ser la "Consulta normativa con citas" sobre el núcleo RAG; • SALUD_ENABLED decide si el módulo se carga |
| 3 y siguientes |
Instancia dedicada por cliente: la misma imagen desplegada con SALUD_ENABLED=true, con su propia base de datos, bucket y credenciales. La API comercial de KopTup no guarda datos de pacientes de clientes |
Además:
- Seeds y scrapers pasan a
apps/backend/tools/salud/(fuera del build), connpm run seed:saluddocumentado. -
models/CUPSEquivalencia.ts(sin uso) se elimina. - Los modelos maestros que hoy solo usan las seeds (
EPSMaestro,IPSMaestro,ConvenioTarifa,CuotaModeradora,Autorizacion) se conservan para la tarea 7 de Demo cuentas médicas.
/api/expert·routes/expert-system.routes.ts,controllers/expert-system.controller.ts,services/expert-system.service.ts(596),expert-rules.service.ts(450),excel-expert.service.ts(393) · 6 endpoints
- Qué hace: procesa facturas con reglas, genera Excel, lee y actualiza la configuración del motor y da estadísticas. Su demo también usa la búsqueda semántica de CUPS.
-
Estado:
- La configuración no se guarda por espacio.
- Las reglas están repartidas entre el código,
ReglaAuditoriayReglaFacturacion. - En producción la demo muestra todo en 0.
- Decisión: mover al módulo de salud como pestaña "Motor de reglas" (Demo sistema experto, tareas 4–6).
-
Cambios:
-
Fase 1:
requireDemoGrant('sistema-experto')en/api/expert/*y en/api/cups/estadisticas,/estadisticas-vectorizaciony/buscar-semantica. -
Fase 2: catálogo único de reglas versionado, configuración persistente por espacio con historial, y migración de los accesos a
cuentas-medicas. - Las rutas de importación y vectorización de CUPS (
/importar-*,/vectorizar,/revectorizar) son tareas de operación: soloadmin.
-
Fase 1:
/api/content·routes/content-manager.routes.ts,services/content-manager.service.ts· 5 endpoints (improve,change-tone,adjust-length,generate-versions,generate)
-
Qué hace: mejora, cambia el tono, ajusta la longitud y genera textos y versiones para la demo
/demo/gestor-contenido, ofertacms-headless(ver CMS headless). -
Estado: valida con
express-validator. El modelo se elige por variable o quedagpt-4o-mini. Sus límites de uso pasan a la pasarela de IA. - Decisión: mantener, detrás de la pasarela de IA.
-
Cambios:
-
Fase 0: pasarela de IA con cupo anónimo por IP (por defecto 10 acciones al día, configurable), cupo por grant (
quotas.aiActionsPerDay) y tope mensual por función (tarea 5). -
Fase 1: zod, límite de longitud de entrada y
hasRealBackend = trueen la semilla del catálogo (tarea 18). -
Fase 2: mover a
src/modules/content-ai/.
-
Fase 0: pasarela de IA con cupo anónimo por IP (por defecto 10 acciones al día, configurable), cupo por grant (
18 carpetas:
automation,code-review,crm,delivery,e-invoicing,e-signature,erp,helpdesk,hrms,lms,loyalty,moderation,pos,saas-platform,scraping,telemedicine,voice-ai,wms· 125 archivos · ≈ 3.440 líneas · 126 endpoints · 14 pruebas
-
Qué hace: CRUD sobre arreglos en memoria con datos de ejemplo (
_shared/crud.ts). -
Estado:
- Ninguno está montado en
index.ts, y ninguna demo los llama: todas usan datos simulados en el frontend. - Sus 14 pruebas no corren.
- Ninguno está montado en
-
Decisión: eliminar en la Fase 0.
- Se crea la etiqueta
archivo/modulos-en-memoriaantes de borrar. - Cuando una de esas soluciones se vuelva producto real (Fase 4 o por proyecto), se construye sobre el núcleo multi-tenant y no sobre estos arreglos.
- La carpeta
src/modules/queda libre para los módulos de producto de la Fase 2.
- Se crea la etiqueta
| Elemento | Qué es | Decisión |
|---|---|---|
routes/test.routes.ts |
Pruebas de mensajería |
Eliminar de producción. Se reemplaza por el script scripts/notify-smoke.ts, que se corre desde la consola de Railway |
Rutas de prueba de contact.routes.ts
|
Pruebas de proveedor | Eliminar |
/api/chat (chat.routes.ts, chat.controller.ts, ChatSession, ChatMessage) |
Chat anónimo que responde un eco |
Eliminar, junto con sendChatMessage y createChatSession de apps/web/src/lib/api.ts
|
Página web /test
|
Página interna que llama a APIs | Eliminar de la web de producción (ver Seguridad y calidad) |
Son parte de la Fase 1 (Sistema de demos, secciones 5, 13 y 14). En el backend agregan:
-
AuditLogyservices/audit.service.ts. Solo se agregan registros; ningún endpoint los edita ni los borra. -
PrivacyRequest,controllers/privacy.controller.tsy el jobprivacy-retention. -
controllers/metrics.controller.ts, conGET /api/metrics/demo-funnelyGET /api/metrics/admin-home.- Las consultas de agregación usan índices.
- El inicio del panel guarda una caché de 30 s en Redis o, si no hay Redis, en memoria.
Dónde vive cada pieza. Los nombres y archivos son los de Sistema de demos, sección 17.
| Capa | Archivos | Notas de esta página |
|---|---|---|
| Modelos | models/{Lead,LeadActivity,ConsentRecord,DemoCatalogItem,DemoRequest,DemoGrant,DemoEvent,MagicLinkToken,AuditLog,OutboundMessage,JobRun,PrivacyRequest}.ts |
Más los de esta página: AuthSession, AiUsage, AiDecision, WebhookEvent, Migration, y Bot, BotDocument, BotChunk, BotMessage
|
| Configuración |
config/permissions.ts, config/scoring.ts, config/env.ts, config/openapi.ts
|
permissions.ts también genera permissions.generated.ts para la web (tarea 3 de Panel de administración) |
| Middlewares |
middleware/auth.ts (authenticate, optionalAuthenticate, requirePermission), middleware/requireDemoGrant.ts, middleware/rateLimiter.ts (almacén compartido), middleware/validate.ts (zod), middleware/requestId.ts, middleware/internalApiKey.ts
|
requireDemoGrant acepta uno o varios slugs (por ejemplo, CUPS compartido entre dos demos) |
| Servicios |
demo-state, demo-access, lead-scoring, sla, magic-link, audit, turnstile, notification-dispatcher, email, whatsapp, storage, ai-gateway
|
ai-gateway y storage son nuevos de esta página |
| Rutas | routes/{demo-catalog,demo-requests,demo-grants,demo-access,demo-events,leads,metrics,audit-log,privacy,me,webhooks}.routes.ts |
Se registran en app.ts en el mismo orden de la tabla de la sección 14 |
| Jobs | jobs/{index,lease,outbox-dispatcher,grant-lifecycle,invitations-reminder,sla-monitor,usage-rollup,daily-digest,privacy-retention}.ts |
Esta página agrega grant-cleanup, exports-cleanup y ai-budget-watch (sección 6) |
| Plantillas |
templates/demo/*, templates/cuenta/*
|
Las de cuenta vienen de Autenticación, tarea 19 |
| Scripts | scripts/{seed-demo-catalog,migrate-roles,backfill-leads,set-admin}.ts |
Corren como migraciones registradas (sección 13) |
Pasarela de IA (services/ai-gateway.service.ts).
-
Interfaz:
ai.complete({feature, actor, model?, messages, maxTokens}),ai.embed({feature, actor, texts})yai.vision(...).-
featurepuede serchatbot,demo_doc,content,documents,salud,linkedinorules. -
actores{userId?, grantId?, ipHash?}.
-
-
Antes de llamar al proveedor:
- Verifica el cupo del actor, el tope mensual de la función y el global, y el interruptor de la función.
- Si algo no alcanza, lanza
AppError(429, 'presupuesto_agotado')olimite_excedido.
-
Después de llamar: registra
AiUsagecon los tokens, el costo en USD (tabla de precios enconfig/ai-pricing.ts), el modelo y la latencia. -
Modelos por variable de entorno:
-
AI_MODEL_FAST(respuestas de demo, contenido); -
AI_MODEL_SMART(auditoría, reglas); -
AI_MODEL_VISION; -
AI_EMBEDDING_MODEL(uno solo para todo).
-
-
Alertas: el job
ai-budget-watchavisa al admin al 80 % y al 100 % del tope.GET /api/admin/jobs/healthmuestra el gasto del mes por función. -
Ruta de LinkedIn Ads: se queda en Next con el pase de demo (Demo LinkedIn Ads, tarea 1), pero reporta su consumo con
POST /api/internal/ai-usage, con la cabecera interna. Así el tope mensual es uno solo.
flowchart TD
E1["Controladores y servicios"] -->|"enqueue con idempotencyKey"| OB[("OutboundMessage")]
J1["Jobs: secuencias, recordatorios, resumen diario"] --> OB
OB --> D["outbox-dispatcher cada minuto"]
D --> R{"Reglas: horario, 1 comercial por día, baja, condición de parada"}
R -->|"omitir o reprogramar"| OB
R -->|"enviar"| T["Render de plantilla con escape"]
T --> C1["Adaptador email: proveedor transaccional"]
T --> C2["Adaptador WhatsApp: Cloud API o Twilio"]
T --> C3["Adaptador in-app: Notification"]
T --> C4["Adaptador Slack: webhook"]
C1 --> ST["Estado: enviado, fallido con reintento, cancelado"]
C2 --> ST
C3 --> ST
C4 --> ST
WH["Webhooks de rebote y entrega, Fase 2"] --> ST
-
Una sola entrada.
notify.enqueue({channel, templateKey, to, vars, leadId?, userId?, grantId?, scheduledAt?, idempotencyKey}).- Ningún controlador vuelve a llamar a
emailServiceowhatsappServicedirectamente. - Se migran los envíos actuales: contacto, pedidos y restablecimiento de contraseña.
- Ningún controlador vuelve a llamar a
-
Adaptadores en
services/channels/, con la misma interfazsend(message) → {providerMessageId}. Son intercambiables por variable de entorno (EMAIL_PROVIDER,WHATSAPP_PROVIDER). -
Reglas (sección 11 de Sistema de demos):
- horario de 8:00 a 19:00, hora de Bogotá;
- máximo un mensaje comercial por Lead al día;
- condiciones de parada de las secuencias;
- lista de supresión: bajas y correos rebotados.
-
Datos personales:
- El WhatsApp al equipo nunca lleva el email ni el teléfono del prospecto. (La implementación de la rama
ccr-55ceecf7-dpc10i(pendiente de merge) se aparta de esta regla a pedido del dueño: el aviso por WhatsApp lleva nombre, empresa, servicio, email y un extracto del mensaje, nunca el teléfono./privacylo declara y nombra a Meta como encargado.) - El WhatsApp al prospecto solo sale con su autorización (
whatsappOptIn) y con una plantilla aprobada.
- El WhatsApp al equipo nunca lleva el email ni el teléfono del prospecto. (La implementación de la rama
-
Webhooks de proveedores (Fase 2):
POST /api/webhooks/emailyPOST /api/webhooks/whatsapp, con firma verificada. Marcan rebotes y entregas, y suprimen direcciones inválidas. (Hecho para WhatsApp en la ramaccr-55ceecf7-dpc10i(pendiente de merge): firmaX-Hub-Signature-256sobre el cuerpo crudo, estados entregado, leído y fallido porwamidy alerta por correo si falla; sin tablaWebhookEvent. El de correo sigue pendiente.) -
Salud: conteos de
programado,fallidoyenviadoen las últimas 24 h, enGET /api/admin/jobs/health, con alerta si hay más de 5 fallidos en una hora. -
Plantillas en el código, en español con "tú" e inglés cuando el Lead tenga
locale = en. En la Fase 2 se pueden editar desde el panel (MessageTemplate).
Fase 1: dentro de la API.
-
node-cronarranca después de conectar a MongoDB y solo siJOBS_ENABLED=true. - Cada job toma un lease en
JobRun, así que es seguro con varias instancias. - Son los 8 jobs de la sección 12 de Sistema de demos, más estos:
| Job | Frecuencia | Acción | Prioridad |
|---|---|---|---|
grant-cleanup |
Cada hora | Borra los datos y archivos de los espacios de demo (documentos, bots personalizados, lotes de salud) de grants revocado, o expirado hace más de 30 días |
P1 |
exports-cleanup |
Diario | Respaldo del ciclo de vida del bucket: borra los exportes de más de 7 días y sus referencias | P2 |
ai-budget-watch |
Cada hora | Suma AiUsage del mes por función y avisa al 80 % y al 100 % del tope |
P0 |
Fase 2: worker aparte con colas.
flowchart TD
subgraph APIS["Servicio api"]
R1["POST de procesamiento: lote de facturas, ingesta RAG, exporte"] --> Q1["Encola el trabajo y responde 202 con jobId"]
R2["GET del estado del trabajo"]
end
subgraph RDS["Redis"]
Q["Colas BullMQ: rag-ingest, salud-process, exports"]
end
subgraph WRK["Servicio worker"]
C1["Consumidores con concurrencia limitada y reintentos"]
C2["Jobs programados con node-cron y lease en JobRun"]
C3["outbox-dispatcher"]
end
Q1 --> Q
Q --> C1
C1 --> DB[("MongoDB: estado y resultado del trabajo")]
C1 --> S3[("Almacenamiento: archivos y exportes")]
R2 --> DB
C2 --> DB
C3 --> DB
C3 --> P["Proveedores de email y WhatsApp"]
-
Arranque.
src/worker.tscomparte modelos y servicios con la API. En Railway es un segundo servicio desde la misma imagen (node dist/worker.js), conJOBS_ENABLED=true. La API pasa aJOBS_ENABLED=false. -
Colas:
-
rag-ingest: documentos de bots y del gestor documental; -
salud-process: lotes de facturas; -
exports: Excel y PDF.
-
-
Comportamiento: cada trabajo guarda su estado en
ProcessingJob(encolado,procesando,listo,fallido, más progreso y resultado). Se reintenta 3 veces con espera creciente y tiene un tiempo máximo por tipo. -
Efecto en la API: al terminar la migración,
server.timeoutvuelve a los valores por defecto (index.tslíneas 264–269). Ninguna petición HTTP dura más de 30 s.
MongoDB.
-
Réplica obligatoria para las transacciones. En desarrollo,
docker-compose.dev.ymllevantamongo:7con--replSet rs0y un script de inicio. - Copias de seguridad diarias y restauración a un punto en el tiempo. La restauración se prueba una vez por trimestre y queda documentada en la página de operación.
- Un usuario de base por entorno, con permisos mínimos.
-
Índices de Sistema de demos (sección 5), más:
-
AuthSession {userId, revokedAt}y TTL enexpiresAt; -
AiUsage {feature, at}y{actorKey, at}, con TTL de 13 meses; -
BotChunk {botId}e índice vectorial de Atlas sobreembedding(Fase 2); -
Document {ownerType, ownerId, is_deleted}; -
WebhookEvent {provider, eventId}único.
-
-
Retenciones con TTL, alineadas con la sección 14 de Sistema de demos:
DemoEvent13 meses,BotMessagede demo 90 días yAiDecision24 meses. Los documentos de "Prueba con tu documento" no llegan a MongoDB: viven solo en memoria 1 hora (sección 3.7). -
Migraciones:
- archivos
scripts/migrations/NNN-nombre.ts, registrados en la colecciónMigration; - se ejecutan con
npm run migratecomo comando previo al despliegue en Railway; - todas idempotentes y con
--dry-run.
- archivos
Almacenamiento de objetos (services/storage.service.ts ampliado).
-
Bucket privado:
- acceso público bloqueado;
- cifrado del lado del servidor;
- versión desactivada para los prefijos temporales.
-
API del servicio:
put({prefix, key, body, contentType, expiresAt?}),getSignedUrl(key, 300),delete(key)ydeletePrefix(prefix). -
Validaciones de los archivos subidos:
- extensión y firma real del contenido;
- tamaño máximo por ruta;
- nombre generado por el servidor, nunca el del usuario.
-
Variables: se conservan las variables
AWS_*actuales, másS3_ENDPOINT(opcional) para un proveedor compatible. - Datos de salud de clientes reales: bucket dedicado por cliente (instancia dedicada).
| Prefijo | Contenido | Ciclo de vida | Antes en |
|---|---|---|---|
documents/{ownerType}/{ownerId}/ |
Gestor documental | Hasta que se borre; los de grants vencidos se borran con grant-cleanup
|
uploads/ o S3 opcional |
orders/{orderId}/ |
Adjuntos de pedidos | Indefinido | uploads/orders |
deliverables/{projectId}/ |
Entregables | Indefinido | — |
chatbot/{botId}/ |
Documentos de bots | Hasta que se borre el bot; los bots de demo vencen | Base64 en memoria y uploads/chatbot
|
salud/{spaceId}/ |
Lotes de facturas, soportes y documentos de conocimiento | Según el grant o el contrato |
uploads/cuentas-medicas, uploads/ley100
|
exports/ |
Excel y PDF generados | 7 días | uploads/exports |
quotes/ |
PDF de propuestas (Fase 3) | Indefinido | — |
Qué sale del disco:
| Hoy | Destino | Fase |
|---|---|---|
uploads/* |
Almacenamiento de objetos. Los temporales del procesamiento van a os.tmpdir() y se borran al terminar cada trabajo |
0 |
data/chatbots/state.json |
Bot, BotDocument, BotChunk y BotMessage
|
0 |
data/decisiones-ia/*.json |
AiDecision. Los 30 archivos versionados se quitan del repositorio y data/ entra al .gitignore
|
0 |
logs/*.log |
Salida estándar en JSON, que recoge Railway, más un destino de logs externo con retención | 0 |
| Refresco y restablecimiento en Redis |
AuthSession y MagicLinkToken en MongoDB |
0 (P1) |
| Rate-limit en memoria | Redis, con memoria como respaldo | 0 |
| Uso | Fase | Si Redis no está disponible |
|---|---|---|
Almacén del rate-limit (rate-limit-redis) por IP, email y usuario |
0 | Cae a memoria por instancia y deja una advertencia en el log. La API sigue respondiendo |
Cupo diario por IP y gasto del mes de "Prueba con tu documento" (hecho en la rama rag-reposicionamiento, pendiente de merge, con el cliente actual config/redis.ts) |
1 | La subida y las preguntas quedan apagadas (falla cerrada); la demo con el documento de ejemplo sigue funcionando |
| Caché: modos del catálogo (60 s), inicio del panel (30 s), tabla de precios de IA | 1 | Se calcula en cada petición |
| Colas BullMQ | 2 | El worker no procesa; la API responde 503 cola_no_disponible en los endpoints que encolan. Alerta inmediata |
| Sesiones, tokens de un solo uso | Nunca | — (viven en MongoDB) |
- En Railway: el plugin de Redis en la misma red privada, con una contraseña que solo conoce el entorno.
-
En desarrollo: el
redis:7-alpinededocker-compose.dev.yml. -
Cliente:
config/redis.tsse reescribe para que no esconda los errores en silencio.- Expone
isAvailable(). - Usa la conexión de
ioredisque comparte BullMQ. - Cada uso declara su respaldo.
- Expone
src/config/env.ts es el único lugar donde se lee process.env. Hay una regla de ESLint (no-restricted-properties) que impide usarlo en otros archivos. En producción, el servidor no arranca si falta una variable obligatoria o si una variable tiene un formato inválido. El mensaje de error nombra la variable, nunca su valor.
// apps/backend/src/config/env.ts (esqueleto)
import { z } from 'zod';
const list = z.string().default('').transform((s) => s.split(',').map((x) => x.trim()).filter(Boolean));
const flag = z.enum(['true', 'false']).default('false').transform((v) => v === 'true');
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().int().default(3001),
API_URL: z.string().url(),
FRONTEND_URL: z.string().url(),
CORS_ORIGIN: list,
TRUST_PROXY: z.coerce.number().int().default(1),
MONGODB_URI: z.string().min(1),
REDIS_URL: z.string().optional(),
JWT_SECRET: z.string().min(32),
COOKIE_DOMAIN: z.string().optional(),
INTERNAL_API_KEY: z.string().min(32),
TURNSTILE_SECRET_KEY: z.string().min(1),
OPENAI_API_KEY: z.string().optional(),
ANTHROPIC_API_KEY: z.string().optional(),
AI_MODEL_FAST: z.string().min(1),
AI_MODEL_SMART: z.string().min(1),
AI_EMBEDDING_MODEL: z.string().min(1),
AI_MONTHLY_BUDGET_USD: z.coerce.number().positive(),
DEMO_MONTHLY_BUDGET_USD: z.coerce.number().positive(),
JOBS_ENABLED: flag,
SALUD_ENABLED: flag,
API_DOCS_ENABLED: flag,
// ... almacenamiento, email, WhatsApp, avisos comerciales y observabilidad (tabla de abajo)
});
export type Env = z.infer<typeof EnvSchema>;
export function loadEnv(source: NodeJS.ProcessEnv = process.env): Env {
const parsed = EnvSchema.safeParse(source);
if (!parsed.success) {
const names = parsed.error.issues.map((i) => i.path.join('.')).join(', ');
throw new Error(`Configuración inválida o incompleta: ${names}`);
}
return parsed.data;
}
export const env = loadEnv();Además:
-
npm run env:examplegenera.env.exampledesde el esquema, solo con nombres y descripciones. - CI falla si el archivo no coincide con el esquema.
- Las variables de la web (
DEMO_PASS_SECRET,DEMO_GATE_ENABLED,NEXT_PUBLIC_BOOKING_URL,NEXT_PUBLIC_TURNSTILE_SITE_KEY) tienen su propio esquema enapps/web/src/config/env.ts.
| Grupo | Variables | Obligatoria en producción | Cambio |
|---|---|---|---|
| Núcleo |
NODE_ENV, PORT, API_URL, FRONTEND_URL, CORS_ORIGIN, TRUST_PROXY, LOG_LEVEL
|
Sí (salvo LOG_LEVEL) |
TRUST_PROXY y LOG_LEVEL son nuevas. La rama rag-reposicionamiento ya usa TRUST_PROXY_HOPS con el mismo fin (ver la nota de abajo) |
| Base de datos | MONGODB_URI |
Sí | Se elimina MONGO_URI (duplicada) |
| Redis | REDIS_URL |
Desde la Fase 1 para "Prueba con tu documento" (sin Redis queda apagada); para las colas, desde la Fase 2 | — |
| Autenticación |
JWT_SECRET, JWT_EXPIRES_IN, COOKIE_DOMAIN, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URL, INTERNAL_API_KEY
|
Sí (Google, si se usa) |
JWT_REFRESH_SECRET y JWT_REFRESH_EXPIRES_IN se retiran cuando el refresco sea opaco en AuthSession
|
| Anti-abuso |
TURNSTILE_SECRET_KEY, RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX_REQUESTS
|
Sí |
TURNSTILE_SECRET_KEY es nueva |
| IA |
OPENAI_API_KEY, ANTHROPIC_API_KEY, AI_MODEL_FAST, AI_MODEL_SMART, AI_MODEL_VISION, AI_EMBEDDING_MODEL, AI_MONTHLY_BUDGET_USD, DEMO_MONTHLY_BUDGET_USD, AI_DISABLED_FEATURES
|
Los topes y los modelos, sí |
OPENAI_MODEL se reemplaza por AI_MODEL_FAST. Se eliminan PINECONE_*
|
| Almacenamiento |
AWS_S3_BUCKET, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_ENDPOINT, MAX_FILE_SIZE
|
Sí (salvo S3_ENDPOINT) |
Pasa de opcional a obligatorio. ALLOWED_FILE_TYPES se reemplaza por una lista por ruta en el código |
EMAIL_PROVIDER, EMAIL_FROM, EMAIL_API_KEY o SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SECURE
|
Sí |
EMAIL_PROVIDER y EMAIL_API_KEY son nuevas. Sin valores por defecto en el código. Implementado en la rama ccr-55ceecf7-dpc10i (pendiente de merge) como RESEND_API_KEY (el proveedor se deduce: Resend si existe, si no SMTP), EMAIL_FROM y NOTIFY_EMAIL
|
|
WHATSAPP_PROVIDER, WHATSAPP_API_TOKEN, WHATSAPP_PHONE_NUMBER_ID, TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_WHATSAPP_NUMBER
|
Si WHATSAPP_PROVIDER está definido |
Se eliminan ULTRAMSG_*. ADMIN_WHATSAPP_NUMBER pasa a ser SALES_WHATSAPP_TO. Implementado en la rama ccr-55ceecf7-dpc10i (pendiente de merge): solo meta, sin TWILIO_* ni ULTRAMSG_*; se conserva ADMIN_WHATSAPP_NUMBER y se suman WHATSAPP_TEMPLATE_NAME, WHATSAPP_TEMPLATE_LANG, WHATSAPP_GRAPH_VERSION, WHATSAPP_HELLO_WORLD_FALLBACK, WHATSAPP_APP_SECRET, WHATSAPP_WEBHOOK_VERIFY_TOKEN, NOTIFY_HEARTBEAT_DAYS y NOTIFY_JOBS_ENABLED
|
|
| Comercial y demos |
SALES_NOTIFY_EMAILS, SALES_WHATSAPP_TO, SLACK_WEBHOOK_URL, PRIVACY_POLICY_VERSION, JOBS_ENABLED, BOOKING_WEBHOOK_SECRET
|
Las dos primeras y PRIVACY_POLICY_VERSION
|
ADMIN_EMAIL se elimina: los destinatarios van en SALES_NOTIFY_EMAILS y el rol admin se asigna con set-admin.ts
|
| Pagos (Fase 3) |
WOMPI_* o PAYU_*, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
|
En la Fase 3 | Nuevas |
| Observabilidad |
SENTRY_DSN, SENTRY_ENVIRONMENT
|
Sí | Nuevas |
| Módulos |
SALUD_ENABLED, API_DOCS_ENABLED
|
No | Nuevas |
| Solo scripts |
SEED_ADMIN_PASSWORD, SEED_CLIENT_PASSWORD
|
No | Salen del esquema del servidor; las lee solo el script de semilla de desarrollo |
Las variables que la rama rag-reposicionamiento (hecho, pendiente de merge) agrega al backend (DEMO_UPLOAD_ENABLED, DEMO_MONTHLY_BUDGET_USD, TRUST_PROXY_HOPS y DEMO_RAG_TTL_SECONDS, esta última solo para pruebas locales) y dónde se configura cada una están en Reposicionamiento RAG. El esquema zod las incorpora; como TRUST_PROXY_HOPS cumple la función de TRUST_PROXY, se deja un solo nombre.
apps/backend/
├── src/
│ ├── app.ts # createApp(): middlewares, rutas, 404 y errores (sin listen)
│ ├── server.ts # conecta MongoDB, escucha el puerto y arranca jobs si JOBS_ENABLED
│ ├── worker.ts # Fase 2: consumidores BullMQ y jobs programados
│ ├── config/ # env.ts, permissions.ts, scoring.ts, ai-pricing.ts, openapi.ts, cors.ts
│ ├── middleware/ # auth, requireDemoGrant, rateLimiter, validate, requestId, internalApiKey, upload, errorHandler
│ ├── schemas/ # esquemas zod por recurso; fuente de la validación y del OpenAPI
│ ├── models/ # plataforma: User, AuthSession, Lead, DemoRequest, DemoGrant, Project, Order...
│ ├── routes/ # un archivo por recurso de plataforma (demo-requests, leads, me, webhooks...)
│ ├── controllers/ # un controlador por recurso; sin lógica de negocio
│ ├── services/ # demo-access, demo-state, magic-link, audit, ai-gateway, storage, notification-dispatcher...
│ │ └── channels/ # adaptadores email, whatsapp, inapp, slack
│ ├── jobs/ # outbox-dispatcher, grant-lifecycle, grant-cleanup, ai-budget-watch...
│ ├── templates/ # demo/ y cuenta/ (ES y EN)
│ ├── modules/ # Fase 2: productos autocontenidos
│ │ ├── rag-core/ # ingesta, fragmentos, embeddings, búsqueda híbrida, citas
│ │ ├── chatbot/ # bots, demo-rag, conversaciones
│ │ ├── documents/ # gestor documental
│ │ ├── content-ai/ # asistente de contenido
│ │ └── salud/ # cuentas médicas, auditoría, CUPS, Ley 100, liquidación, reglas, motor de reglas
│ ├── scripts/ # set-admin, seed-demo-catalog, notify-smoke, migrations/NNN-*.ts
│ ├── utils/ # business-hours (festivos CO), email-risk, hash, money
│ └── __tests__/ # integración con supertest, factories y fixtures
├── tools/ # fuera del build: scrapers de catálogos oficiales y seeds de salud
├── openapi.json # generado en CI
└── jest.config.ts
Reglas:
- Cada módulo de
modules/expone unregister(app, deps)que monta sus rutas. - Un módulo no importa los internos de otro; solo su
index.ts. - Los controladores no tocan modelos: llaman a servicios.
- Las pruebas viven junto al código (
*.test.ts) o en__tests__/. -
tsconfig.jsonpasa astrict: truepararoutes/,controllers/,services/,schemas/ymodules/. Para el código heredado de salud se usa untsconfig.legacy.jsonhasta su consolidación.
flowchart TD
A["Petición"] --> B["requestId y logger"]
B --> C["helmet y CORS con lista de orígenes"]
C --> D["Parser JSON con límite de 1 MB"]
D --> E["rate-limit con almacén compartido"]
E --> F["authenticate u optionalAuthenticate"]
F --> G["requirePermission o requireDemoGrant"]
G --> H["validate con esquema zod"]
H --> I["Controlador"]
I --> J["Servicio de dominio"]
J --> K["Modelos y adaptadores"]
I --> L["Respuesta success y data"]
G -->|"401 o 403"| X["errorHandler: success false, message y code"]
H -->|"400"| X
J -->|"409 o 422"| X
| Tema | Convención |
|---|---|
| Respuestas |
{ success: true, data } o { success: false, message, code, errors? }, que es el formato actual |
| Códigos de error estables |
validacion, sin_sesion, sin_permiso, no_existe, transicion_invalida, regla_negocio, limite_excedido, presupuesto_agotado, cuenta_inactiva, email_no_verificado, demo_desactivada, cola_no_disponible. La web traduce por code (ES y EN); message es solo para logs y depuración |
| HTTP | 400 validación · 401 sin sesión · 403 sin permiso · 404 no existe · 409 transición o estado ya cambiado · 413 archivo grande · 422 regla de negocio · 429 límite o presupuesto · 503 dependencia caída |
| Paginación |
?page=1&limit=25 (máximo 100) y respuesta data: { items, total, page, limit }. Orden con ?sort=campo o -campo, solo sobre campos permitidos |
| Fechas y dinero | ISO 8601 en UTC. Los plazos hábiles se calculan en America/Bogota. El dinero va en enteros: COP en pesos, USD en centavos, siempre con currency
|
| Idempotencia | Las transiciones usan una precondición de estado (409 si ya cambió). Los POST de creación desde la web aceptan la cabecera Idempotency-Key, que se guarda 24 h |
| Llamadas externas | Siempre con tiempo máximo: Turnstile 3 s, consulta de MX 2 s, email 10 s, IA 60 s en la API (más en el worker) |
| Registros técnicos | Sin emails, teléfonos ni contenido de documentos. Se registran ids, requestId y la IP en hash |
| Cabeceras | Respuestas con X-Request-Id. Las rutas obsoletas devuelven Deprecation y Sunset. El dominio de la API envía X-Robots-Tag: noindex
|
-
Hoy:
swagger-jsdoclee comentarios desrc/routes/*.tsyswagger-ui-expresslos sirve desde el mismo backend. Solo 9 de 22 archivos tienen anotaciones, sin los esquemas de respuesta. El cliente de la web se escribe a mano. -
Objetivo:
-
Esquemas zod en
src/schemas/. Cada ruta nueva declarabody,query,paramsy la respuesta. El middlewarevalidate(schema)los usa para validar. -
Registro OpenAPI (
config/openapi.ts, con@asteasolutions/zod-to-openapi).- Cada esquema se registra con su ruta, método, permiso requerido (extensión
x-permission) y ejemplos. - Esquemas de seguridad: cookie de sesión y cabecera interna.
- Cada esquema se registra con su ruta, método, permiso requerido (extensión
-
npm run openapiescribeopenapi.json.- CI lo regenera y falla si hay diferencias sin confirmar.
- CI también falla si una ruta montada no tiene esquema (
scripts/check-routes.tscompara el inventario de Express con el OpenAPI).
-
Documentación interactiva en
/api/docs, solo siAPI_DOCS_ENABLED=true(desarrollo y staging). En producción la puede ver solo el equipo, con sesión deadminodeveloper. -
Cliente tipado para la web:
-
openapi-typescriptgeneraapps/web/src/types/api.generated.ts. -
lib/api.tsusa esos tipos ytypes/api.types.tsse retira por etapas. - Empieza por las rutas del sistema de demos (Fase 1).
-
-
Retiro de
swagger-jsdoccuando todas las rutas vivas tengan esquema (Fase 2). Las rutas de salud se documentan al consolidarse enmodules/salud.
-
Esquemas zod en
Logs.
-
winstoncon formato JSON a la salida estándar; se eliminan los 4 archivos delogs/. -
morganse reemplaza por un middleware de registro que guardarequestId, método, ruta (patrón, no la URL con datos), estado, duración,userIden hash yfeature. - Destino externo con 30 días de retención, configurado como log drain de Railway.
Errores. @sentry/node en la API y en el worker, con requestId, sin cuerpos de petición y con beforeSend que limpia los datos personales.
Salud del servicio.
| Endpoint | Qué revisa |
|---|---|
GET /health (vida) |
Responde 200 si el proceso atiende |
GET /ready (preparado) |
Ping a MongoDB, a Redis si está configurado, y acceso al bucket (con caché de 60 s). Responde 503 si algo obligatorio falla |
GET /api/admin/jobs/health |
Jobs, outbox, colas y gasto de IA |
- Railway usa
/readycomohealthcheckPathenrailway.json. - Un monitor externo revisa
/readycada minuto.
Alertas (email y Slack del equipo):
- más de 2 % de respuestas 5xx en 5 minutos;
-
/readycaído durante 2 minutos; - un job atrasado el doble de su intervalo;
- más de 5 mensajes fallidos en una hora;
- gasto de IA al 80 %.
Despliegue.
- Node.js LTS activo (24) en el CI, en Railway (
enginesy.nvmrc) y en Docker. Hoy el CI declara 18, que ya no recibe soporte. - Build:
tscynpm run openapi. Antes de desplegar se correnpm run migrate. - Dos servicios desde la misma imagen:
apiconnode dist/server.jsyworkerconnode dist/worker.js(Fase 2). - Reversión: si
/readyfalla después de desplegar, Railway vuelve a la versión anterior.
Entornos.
| Entorno | Web | API | Datos |
|---|---|---|---|
| Desarrollo | localhost:3000 |
localhost:3001 |
docker-compose.dev.yml (MongoDB con réplica y Redis), seeds |
| Staging | Vista previa de Vercel apuntando a la API de staging | Entorno staging de Railway |
Base y bucket propios, solo con datos de semilla. Nunca se copian datos personales de producción |
| Producción | Dominio principal | Subdominio propio de la API (tarea 8 de Autenticación) | MongoDB con réplica y copias, bucket privado |
Estado: Nuevo, Modificado, Se mantiene o Se elimina. El detalle de cuerpos, permisos y reglas de los endpoints del sistema de demos está en Sistema de demos (sección 8). La página que propone cada agregado aparece en la última columna.
Públicos
| Endpoint | Rol | Estado | Fase · origen |
|---|---|---|---|
GET /api/demo-catalog |
público | Nuevo: demos activas no privadas, con modo, medios y recorrido | 1 · Sistema de demos |
GET /api/demo-catalog/modes |
público | Nuevo: mapa {demoSlug: {mode, active}} para el middleware (caché de 60 s) |
1 · Sistema de demos |
POST /api/demo-requests |
público (Turnstile, honeypot, rate-limit) | Nuevo: crea o fusiona la solicitud; upsert del Lead, ConsentRecord, puntaje, SLA y avisos; responde {code}
|
1 · Sistema de demos |
POST /api/auth/magic-link/check |
público (token) | Nuevo: valida sin consumir; devuelve el nombre y el email enmascarado | 1 · Sistema de demos |
POST /api/auth/magic-link/consume |
público (token + contraseña) | Nuevo: consume de forma atómica, activa la cuenta y abre la sesión | 1 · Sistema de demos |
POST /api/auth/magic-link/request |
público (rate-limit) | Nuevo: enlace de invitación (72 h) o de acceso (15 min); siempre 200 | 1 · Sistema de demos |
GET /api/demo-access/:demoSlug |
sesión opcional o cabecera interna | Nuevo: {allowed, reason, mode, expiresAt, grantId, company}; registra open o denied. Fuente de verdad |
1 · Sistema de demos |
POST /api/demo-events |
con grant, o anónimo con consentimiento | Nuevo: lote de hasta 20 eventos | 1 · Sistema de demos |
GET /api/unsubscribe/:token |
público (token) | Nuevo: baja de comunicaciones comerciales | 1 · Sistema de demos |
POST /api/contact |
público | Modificado: upsert del Lead (contacto o demo_rag), conserva producto y plan, Turnstile. Comparte services/lead.service.ts con /api/demo-rag (hecho en la rama rag-reposicionamiento, pendiente de merge) |
1 · Sistema de demos y esta página |
GET /api/demo-rag/status, POST /api/demo-rag/documents, GET /api/demo-rag/documents/:docId, POST /api/demo-rag/documents/:docId/questions, DELETE /api/demo-rag/documents/:docId
|
público, sin ticket | Nuevo: "Prueba con tu documento" (sección 3.7). Hecho en la rama rag-reposicionamiento (pendiente de merge) |
1 · esta página |
POST /api/chatbot/bots, GET/PATCH/DELETE /api/chatbot/bots/:botId, POST /api/chatbot/bots/:botId/chat… (12 rutas) |
público con cupo; dueño o grant para editar | Modificado: persistencia en MongoDB, pasarela de IA, pertenencia por sessionId o grant |
0–2 · Chatbot RAG |
GET /api/content/* y POST /api/content/* (5 rutas) |
público con cupo; grant amplía | Modificado: pasarela de IA y zod | 0–1 · esta página |
GET /api/documents y lectura del corpus de muestra |
público (solo lectura) | Modificado: modo muestra | 1 · Gestor documental |
POST /api/webhooks/booking |
proveedor de agenda (firma) | Nuevo: LeadActivity de tipo reunion y parada de secuencias |
1 · Contacto |
POST /api/webhooks/email, POST /api/webhooks/whatsapp
|
proveedor (firma) | Nuevo: rebotes, entregas y supresión. GET y POST /api/webhooks/whatsapp hechos en la rama ccr-55ceecf7-dpc10i (pendiente de merge) (entregas y fallos de los avisos al dueño)
|
2 · esta página |
POST /api/webhooks/payments |
pasarela de pagos (firma) | Nuevo: confirma el anticipo y dispara la conversión | 3 · esta página |
GET /api/quotes/public/:token, POST .../accept, POST .../reject
|
público (token) | Nuevo: propuesta en línea | 3 · Sistema de demos |
POST /api/quotes (público heredado) |
público | Se elimina (sin consumidor) | 0 |
/api/chat/* (3 rutas) |
público | Se elimina (eco sin consumidor) | 0 |
Rutas de prueba (routes/test.routes.ts y las de prueba de contact.routes.ts) |
— | Se eliminan de producción | 0 |
Autenticación y cuenta
| Endpoint | Rol | Estado | Fase · origen |
|---|---|---|---|
POST /api/auth/register |
público | Modificado: crea prospect (después de la migración); Lead con canal portal; Turnstile |
1 |
POST /api/auth/login |
público | Modificado: rechaza invitado y suspendido; fija cookies |
0–1 |
POST /api/auth/refresh |
sesión | Modificado: AuthSession con rotación; compara tokenVersion y accountStatus
|
0 |
POST /api/auth/logout |
sesión | Modificado: revoca la AuthSession y borra las cookies |
0 |
POST /api/auth/logout-all |
sesión | Nuevo | 0 · Autenticación |
POST /api/auth/change-password |
sesión | Nuevo | 1 · Autenticación |
GET /api/auth/profile |
sesión | Modificado: rol, estado, permisos efectivos, leadId y verificación del email |
1 · Autenticación |
GET /api/auth/google, GET /api/auth/google/callback
|
público | Modificado: cookies, state firmado, activación de invitados |
1 · Autenticación |
POST /api/auth/forgot-password, POST /api/auth/reset-password
|
público | Modificado: MagicLinkToken con propósito reset, por el outbox; tokenVersion++
|
1 · Autenticación |
Portal (prospecto y cliente)
| Endpoint | Rol | Estado | Fase · origen |
|---|---|---|---|
GET /api/me/demos |
prospect, client | Nuevo: accesos con días restantes, recorrido y comercial | 1 · Sistema de demos |
POST /api/me/demo-requests |
autenticado y verificado | Nuevo: pedir otra demo sin captcha | 1 · Sistema de demos |
POST /api/me/demos/:grantId/actions |
prospect, client (dueño) | Nuevo: {type: extension · propuesta · llamada}; crea una tarea y avisa al comercial |
1 · Sistema de demos |
GET /api/me/privacy/export |
autenticado | Nuevo: exportar mis datos (auditado) | 1 · Sistema de demos |
POST /api/me/privacy/requests |
autenticado | Nuevo: ejercer derechos (Ley 1581) | 1 · Sistema de demos |
PATCH /api/me/consents |
autenticado | Nuevo: retirar la autorización de marketing | 1 · Sistema de demos |
PATCH /api/me/profile |
autenticado | Nuevo | 1 · Autenticación |
PATCH /api/me/notification-prefs |
equipo | Nuevo | 2 · Panel de administración |
/api/projects/* (10), /api/orders/* (5), /api/invoices/* (5), /api/deliverables/* (6), /api/messages/* (5), /api/notifications/* (6) |
client y equipo según permiso | Se mantienen con pertenencia centralizada; adjuntos al almacenamiento; el pago de facturas lo registra el equipo hasta la Fase 3 | 1 |
GET /api/me/quotes |
prospect, client | Nuevo: propuestas recibidas (/dashboard/propuestas) |
3 |
Equipo
| Endpoint | Rol | Estado | Fase · origen |
|---|---|---|---|
GET /api/demo-requests |
admin, sales, manager | Nuevo: bandeja con filtros (status, grade, demo, assignedTo, sla, source, q) y conteos |
1 · Sistema de demos |
GET /api/demo-requests/:id |
admin, sales, manager | Nuevo: detalle con Lead, desglose del puntaje, duplicados, accesos e historial | 1 |
PATCH /api/demo-requests/:id |
admin, sales | Nuevo: asignar, pasar a en_revision, nota interna |
1 |
POST /api/demo-requests/:id/approve |
admin, sales (privado solo admin) |
Nuevo: transaccional e idempotente | 1 |
POST /api/demo-requests/:id/reject |
admin, sales | Nuevo: nunca avisa si el motivo es spam | 1 |
POST /api/demo-requests/:id/reopen |
admin | Nuevo | 1 |
POST /api/demo-requests/bulk |
admin, sales | Nuevo: asignar o rechazar spam en lote | 2 |
GET /api/demo-requests?format=csv, GET /api/demo-grants?format=csv
|
admin (auditado) | Nuevo | 2 · Panel de administración |
GET /api/demo-grants, POST /api/demo-grants
|
admin, sales, manager / admin (privado), sales | Nuevo: lista con filtros e invitación directa | 1 |
GET /api/demo-grants/:id |
admin, sales, manager | Nuevo: detalle con línea de tiempo | 1 |
POST /api/demo-grants/:id/extend, /revoke, /convert, /resend-invite
|
admin, sales | Nuevo | 1 |
GET /api/demo-catalog/all, PATCH /api/demo-catalog/:slug
|
admin | Nuevo: catálogo completo y edición | 1 |
GET /api/leads, GET /api/leads/:id, PATCH /api/leads/:id
|
admin, sales (manager lee) | Nuevo | 1 |
POST /api/leads/:id/activities, PATCH /api/leads/:id/activities/:activityId
|
admin, sales | Nuevo | 1 |
POST /api/leads/:id/anonymize |
admin | Nuevo: anonimiza y revoca accesos | 1 |
GET /api/metrics/demo-funnel |
admin, sales, manager | Nuevo | 1 |
GET /api/metrics/admin-home |
admin, sales, manager | Nuevo: inicio del panel en una llamada | 1 · Panel de administración |
GET /api/audit-log |
admin | Nuevo | 1 |
GET /api/privacy-requests, PATCH /api/privacy-requests/:id
|
admin | Nuevo | 1 |
GET /api/admin/jobs/health |
admin, manager | Nuevo: jobs, outbox, colas y gasto de IA | 1 |
PATCH /api/admin/users/:id/role |
admin | Modificado: valida el enum, solo admin asigna admin o sales, tokenVersion++, AuditLog
|
1 |
PATCH /api/admin/users/:id/status |
admin | Nuevo: suspender y reactivar | 1 · Panel de administración |
GET /api/admin/contacts, PATCH /api/admin/contacts/:id/status
|
admin, sales | Modificado: permisos por ruta, origen y Lead | 1 |
GET /api/admin/users, /orders/*, /invoices, /deliverables, /conversations/*
|
según permiso | Modificado: controladores por recurso, zod, bitácora | 0–1 |
POST /api/admin/invoices, PATCH /api/admin/invoices/:id/status, POST /api/admin/deliverables
|
admin, manager | Nuevo | 1 · Panel de administración |
GET /api/admin/projects, GET /api/admin/projects/:id
|
admin, manager | Nuevo | 3 · Panel de administración |
GET /api/quotes, POST /api/quotes, PATCH /api/quotes/:id, POST /api/quotes/:id/send, POST /api/quotes/:id/convert
|
admin, sales (convert solo admin) |
Nuevo: el POST reemplaza al público heredado |
3 · Sistema de demos |
POST /api/internal/ai-usage |
cabecera interna | Nuevo: la ruta de LinkedIn Ads de Next reporta su consumo | 1 · esta página |
/api/auditoria/*, /api/documentos-conocimiento/*
|
staff o grant cuentas-medicas
|
Modificado: requireDemoGrant; pasan a /api/salud/* en la Fase 2 |
1–2 |
/api/expert/*, /api/cups/{estadisticas,estadisticas-vectorizacion,buscar-semantica}
|
staff o grant sistema-experto
|
Modificado: requireDemoGrant; importación y vectorización solo admin |
1–2 |
/api/cuentas/*, /api/ley100*, /api/process, /api/export, /api/liquidacion/*, /api/reglas-facturacion/*
|
staff (salud.interno) |
Modificado: solo equipo hasta consolidar en /api/salud/*
|
1–2 |
En Next (apps/web/src/app/api)
| Route handler | Estado | Fase |
|---|---|---|
GET /api/demo-pass |
Nuevo: emite y renueva la cookie del pase de demo (15 min) después de consultar demo-access con la cabecera interna |
1 · Sistema de demos |
POST /api/linkedin-ads/generate |
Modificado: exige el pase o rol de staff, cupo y tope; reporta el uso a POST /api/internal/ai-usage
|
0–1 · Demo LinkedIn Ads |
/api/chatbot/{config,info,message,messages,upload} |
Se eliminan junto con la API heredada | 2 · Chatbot RAG |
/api/trm |
Se elimina: hecho en la rama rag-reposicionamiento (pendiente de merge), con TRM de referencia fija |
1 |
Antes de borrar, se crea la etiqueta archivo/antes-de-limpieza. Después se comprueba que tsc, las pruebas y el build pasan, y que el inventario de rutas no perdió ninguna con consumidor.
| Elemento | Qué es | Evidencia | Acción | Fase |
|---|---|---|---|---|
src/modules/ (18 módulos, 125 archivos, ≈ 3.440 líneas, 126 endpoints, 14 pruebas) |
CRUD en memoria con datos de ejemplo |
index.ts no los importa; ninguna demo los llama |
Eliminar (con etiqueta de archivo) | 0 |
src/routes/*.ts.bak (5 archivos, 271 líneas) |
Copias de rutas | No se compilan ni se importan | Eliminar | 0 |
src/db/migrations/*.sql, src/db/seeds/001_sample_projects.sql, packages/database/init.sql, src/config/database.ts
|
Restos de PostgreSQL (≈ 450 líneas) | El backend usa solo MongoDB; database.ts exporta null
|
Eliminar. Quitar el servicio PostgreSQL del CI | 0 |
src/index-minimal.ts, test-db.js, test-simple.js, test-startup.js
|
Servidores y pruebas de conexión sueltas | No los usa package.json
|
Eliminar | 0 |
src/test-pdf-processing.ts, src/verify-excel.ts
|
Scripts de prueba manual dentro de src/ (se compilan) |
Nadie los importa | Mover a tools/dev/ o eliminar |
0 |
src/services/embedding.service.ts y la dependencia @pinecone-database/pinecone
|
Embeddings con Pinecone | Nadie importa el servicio | Eliminar ambos y las variables PINECONE_*
|
0 |
Dependencia pdf-poppler
|
Conversión de PDF | 0 importaciones | Eliminar | 0 |
src/models/CUPSEquivalencia.ts |
Modelo | 0 importaciones | Eliminar | 0 |
/api/chat (chat.routes.ts, chat.controller.ts, ChatSession, ChatMessage) y sus métodos en apps/web/src/lib/api.ts
|
Chat que responde un eco | Ninguna página llama a sendChatMessage
|
Eliminar | 0 |
POST /api/quotes (quote.routes.ts, submitQuote) y requestQuote en lib/api.ts
|
Formulario de cotización | Ninguna página llama a requestQuote
|
Desmontar (se conserva el modelo para migrar en la Fase 3) | 0 |
routes/test.routes.ts y las rutas de prueba de contacto |
Rutas de prueba | Sin consumidor | Eliminar; usar scripts/notify-smoke.ts
|
0 |
Promoción de rol en el arranque (index.ts líneas 143–157) |
Efecto lateral del arranque | — | Eliminar; usar scripts/set-admin.ts con bitácora |
0 |
ADMIN_SETUP.md |
Guía de operación | — | Reemplazar por la página de operación de la wiki (procedimiento con set-admin.ts) |
0 |
data/chatbots/, data/decisiones-ia/ (30 archivos versionados) y src/data/chatbot-store.ts
|
Estado en disco | Sección 7 | Migrar a MongoDB, quitar del repositorio y agregar data/ al .gitignore
|
0 |
Transportes de archivo de utils/logger.ts
|
Logs en disco | Sección 13 | Eliminar | 0 |
API heredada del chatbot (7 rutas), chatbot.controller.ts, chatbot.service.ts, models/Chatbot.ts, proxies de Next y /demo/chatbot/preview
|
Segunda API de chatbot (≈ 700 líneas en el backend) | Solo la usa una página huérfana | Eliminar | 2 |
services/document-ai.service.ts (embeddings), services/embeddings.service.ts
|
Dos implementaciones con modelos distintos | Sección "Configuración, dependencias y calidad" | Reemplazar por rag-core con un solo modelo |
2 |
| 3 controladores de auditoría, 5 extractores, 4 generadores de Excel | Duplicación en salud | Demo cuentas médicas | Consolidar en modules/salud
|
2 |
scripts/scrapers/*, scripts/run-all-scrapers.ts, db/seeds/*.seed.ts, db/seedMongoDB.ts
|
Herramientas offline (≈ 5.000 líneas) que hoy se compilan con la API | No las ejecuta ningún script de package.json
|
Mover a tools/ (fuera del build) con scripts npm run documentados |
2 |
src/controllers/README-AUDITORIA-CONTROLLERS.md, src/services/README-*.md, src/scripts/scrapers/README.md
|
Documentación interna dentro del código | — | Mover a docs/ o a la wiki y actualizarla |
1 |
express-validator |
Validación | 8 archivos | Reemplazar por zod en cada ruta que se toque; retirar al final | 2 |
swagger-jsdoc y comentarios @swagger
|
Documentación a mano | Sección 12 | Retirar cuando todo tenga esquema zod | 2 |
Resultado esperado: ≈ 5.300 líneas menos en la Fase 0. En la Fase 2, ≈ 30 % menos de código en salud por consolidación, y ninguna herramienta offline dentro del build.
La revisión detallada no se publica porque la wiki es pública. Estas son las tareas del backend. El plan completo está en Seguridad y calidad.
-
Auditar y exigir autenticación y autorización en el servidor en todas las rutas de la API:
- matriz de permisos;
- verificación de pertenencia;
- prueba de tabla rol × ruta en CI (tarea 4).
- Límites de costo y rate-limit en todos los endpoints de IA, con la pasarela de IA y un almacén compartido (tareas 5 y 6).
- Rotar credenciales. Ningún secreto ni dato personal en el código, en la documentación ni en el historial que se publique. Los secretos viven solo en las variables de cada entorno (tarea 7).
- Actualizar dependencias y quitar las que no se usan. Auditoría de dependencias en CI (tarea 7).
- Proteger webhooks y retirar las rutas de prueba en producción. Webhooks con firma verificada e idempotencia (tareas 7 y 23).
-
Archivos subidos:
- tipo verificado por contenido;
- tamaño máximo por ruta;
- nombres generados por el servidor;
- descarga solo por URL firmada (tarea 9).
-
Sesiones y origen:
- cookies httpOnly;
- verificación de
Originen las escrituras; - lista de orígenes CORS desde la configuración;
-
trust proxycorrecto (tarea 1 y Autenticación, tarea 8).
- Registros técnicos sin datos personales; errores sin detalles internos hacia el cliente (tarea 11).
- Documentación interactiva solo para el equipo (sección 12).
- Ingesta de URLs y documentos con límites de tamaño, tiempo, tipo y destino (sección 3.7).
Qué protege cada guardia del backend. Estas son las rutas reales que consume cada demo, verificadas en el código de apps/web:
Demo (demoSlug) |
Modo (semilla) | Rutas del backend que usa | Guardia objetivo |
|---|---|---|---|
cuentas-medicas |
privado |
/api/auditoria/*, /api/documentos-conocimiento/* (desde demo/cuentas-medicas/api.ts y page.tsx) |
requireDemoGrant('cuentas-medicas'). El staff pasa siempre |
sistema-experto |
privado |
/api/expert/estadisticas, /api/cups/estadisticas, /api/cups/estadisticas-vectorizacion, /api/cups/buscar-semantica (desde components/DashboardAuditoria.tsx y BusquedaSemanticaCUPS.tsx) |
requireDemoGrant(['sistema-experto', 'cuentas-medicas']) |
linkedin-ads |
solicitud |
Route handler de Next /api/linkedin-ads/generate
|
Pase de demo o demo-access, cupo y reporte a POST /api/internal/ai-usage
|
chatbot |
publico |
/api/chatbot/bots/*, /api/chatbot/models, /api/demo-rag/*
|
Sin grant: cupos por visitante y por documento, más el tope mensual. Un grant amplía el cupo y personaliza (Fase 2) |
gestor-documentos |
publico |
/api/documents/* |
Lectura del corpus de muestra sin grant. Subir, OCR y explicar, con grant y cupos |
gestor-contenido |
publico |
/api/content/* |
Cupo anónimo por IP; un grant lo amplía (quotas.aiActionsPerDay) |
Páginas internas /liquidacion y /test (fuera del catálogo) |
— |
/api/liquidacion/*, /api/reglas-facturacion/*, /api/cuentas/*, /api/ley100*
|
Solo staff (salud.interno) hasta consolidarse o eliminarse (Fase 2) |
| Las otras 22 demos | según la semilla | Ninguna: datos simulados en el frontend | No hace falta guardia de API; el control es comercial (sección 10.3 de Sistema de demos) |
Precisiones sobre Sistema de demos:
-
Sección 10.4. La búsqueda de CUPS la consume la demo del sistema experto, no la de cuentas médicas: esta usa un catálogo estático en el bundle (
contenido-cups-completo.ts). Por eso la guardia de CUPS acepta los dos slugs. -
cuentas.routes.tsyliquidacion.routes.ts. Se confirmó que ninguna demo del catálogo las usa. Quedan solo para el equipo, que es lo que la sección 10.4 dejaba pendiente de confirmar. -
Semilla del catálogo.
-
gestor-documentosygestor-contenidodeben llevarhasRealBackend = true, porque llaman a/api/documentsy/api/content. -
quotas.aiActionsPerDaydebe definirse también paragestor-contenidoychatbot. - El modo de acceso no cambia: siguen siendo
publico.
-
Cómo se comunica la web con el backend en el control de acceso:
-
GET /api/demo-access/:demoSlugacepta la sesión del usuario (cookie) o, desde/api/demo-pass, el token del usuario más la cabeceraINTERNAL_API_KEY.- Con la cabecera interna, el rate-limit se aplica por usuario, no por IP, porque todas las llamadas llegan desde Vercel.
- La respuesta es la misma en los dos casos.
-
GET /api/demo-catalog/modesresponde conCache-Control: public, max-age=60, que coincide con la caché del middleware.- Si el backend no responde en 300 ms, la web usa el mapa estático de
lib/demo-access.ts, generado desde la misma semilla. - Para
solicitudyprivado, falla cerrada.
- Si el backend no responde en 300 ms, la web usa el mapa estático de
-
requireDemoGrantreutilizaevaluateAccess(sección 10.1 de Sistema de demos).- Para staff, permite el paso sin registrar el evento.
- Para prospectos, registra
key_actioncuando la ruta está marcada como acción clave del catálogo.
-
PATCH /api/demo-catalog/:sluginvalida la caché de modos en Redis. En ≤ 60 s el cambio llega a todas las instancias de la API y al middleware.


-
SEO:
- el dominio de la API envía
X-Robots-Tag: noindex; -
GET /api/demo-catalog/modesalimenta elsitemap.tsde la web: las demossolicitudyprivadoquedan fuera; -
/health,/readyy/api/docsnunca aparecen en el sitemap.
- el dominio de la API envía
-
i18n:
- La API responde códigos estables (sección 11) y la web los traduce a ES y EN.
- Las plantillas de mensajes existen en español (con "tú") y en inglés.
- El idioma se toma de
User.localeoLead.locale, con español por defecto. - Fechas en UTC en la API y formateadas por la web. Plazos hábiles con festivos de Colombia (
utils/business-hours.ts).
-
Accesibilidad (emails):
- cada plantilla tiene versión en texto plano;
- botones con texto descriptivo ("Activar mi acceso", nunca "clic aquí");
- imágenes con
alt; - contraste suficiente.
-
Rendimiento:
-
Metas (p95, medidas con los logs de duración):
- lecturas del panel < 300 ms;
-
GET /api/demo-access< 150 ms; -
POST /api/demo-requests< 800 ms (incluye Turnstile y MX con tiempo máximo); - respuesta de "Prueba con tu documento" < 6 s.
-
Técnicas:
- consultas con
.lean()y proyección; - índices de la sección 7;
- paginación con máximo 100;
- caché de modos y del inicio del panel;
- JSON limitado a 1 MB (hoy 10 MB) y archivos solo por
multipart; - compresión activa;
- pool de conexiones de MongoDB según el número de instancias;
- nada pesado dentro de la petición desde la Fase 2.
- consultas con
-
Metas (p95, medidas con los logs de duración):
Tallas para 1 dev senior: S ≤ 2 días · M 3–5 días · L 1–2 semanas · XL más de 2 semanas. Las tareas de backend del sistema de demos ya están detalladas en Sistema de demos (tareas 1–15) y aquí aparecen agrupadas en la tarea 13.
| # | Tarea | Fase | Prioridad | Esfuerzo | Criterio de aceptación |
|---|---|---|---|---|---|
| 1 | Configuración validada: config/env.ts con zod como único lector de process.env (regla de ESLint), .env.example generado desde el esquema, sin valores por defecto con datos de entorno o personales en el código, trust proxy y CORS desde la configuración |
Fase 0 — Endurecimiento | P0 | S | Arrancar en modo producción sin MONGODB_URI o con un JWT_SECRET corto termina con un mensaje que nombra la variable (sin su valor). CI falla si .env.example no coincide con el esquema. Una búsqueda de process.env fuera de config/ no da resultados |
| 2 | Separar src/app.ts (createApp()) de src/server.ts; arranque sin efectos sobre los datos (la promoción a admin solo por set-admin.ts, con bitácora); /health y /ready; healthcheckPath en railway.json
|
Fase 0 — Endurecimiento | P0 | S | supertest importa la app sin abrir el puerto. /ready responde 503 con MongoDB caído. Arrancar el servidor no modifica ningún documento de users
|
| 3 | Base de pruebas y CI del backend: Jest con ts-jest, supertest, mongodb-memory-server en modo réplica, factories; el workflow corre lint, verificación de tipos, pruebas y build con Node LTS; strict: true en el código nuevo |
Fase 0 — Endurecimiento | P0 | M | Un PR con una prueba fallida queda en rojo. La transacción de una prueba de ejemplo se confirma y se revierte en memoria. El CI tarda menos de 10 minutos |
| 4 | Auditar y exigir autenticación y autorización en el servidor en todas las rutas: requirePermission, optionalAuthenticate, verificación de pertenencia y scripts/check-routes.ts que lista cada ruta montada con su política |
Fase 0 — Endurecimiento | P0 | L | El inventario generado no tiene ninguna ruta sin política declarada. La prueba de tabla (6 roles + anónimo × rutas) pasa en CI. Un cliente no puede leer recursos de otro en ninguna de las 37 rutas del portal |
| 5 | Pasarela de IA (services/ai-gateway.service.ts): modelos por variable de entorno, AiUsage con tokens y costo, cupos por visitante, usuario y grant, tope mensual global y por función, apagado por función, job ai-budget-watch; migrar las llamadas de chatbot, contenido, documentos y salud |
Fase 0 — Endurecimiento | P0 | M | Con un tope de prueba agotado, todos los endpoints de IA responden 429 presupuesto_agotado sin llamar al proveedor (comprobado con un doble del proveedor). Ningún nombre de modelo queda escrito fuera de config/. El gasto del mes aparece en jobs/health
|
| 6 | Rate-limit con almacén compartido (Redis) por IP, email y usuario, con respaldo en memoria y límites por ruta (autenticación, formularios, IA, eventos) | Fase 0 — Endurecimiento | P0 | S | Con 2 instancias, el límite por email se respeta aunque cambie la IP. Con Redis apagado, la API sigue respondiendo y registra la advertencia |
| 7 | Rotar credenciales, actualizar dependencias (Express, Mongoose, multer, SDKs), quitar las que no se usan y retirar de producción las rutas y páginas de prueba | Fase 0 — Endurecimiento | P0 | S |
npm audit --omit=dev sin vulnerabilidades altas ni críticas. El inventario de producción no contiene rutas de prueba. Lista de credenciales rotadas firmada en el checklist de operación (sin valores) |
| 8 | Limpieza de código muerto de la Fase 0 (sección 15): src/modules/, .bak, SQL, /api/chat, embedding.service.ts, servidores sueltos, POST público de quotes; etiqueta de archivo previa |
Fase 0 — Endurecimiento | P1 | S |
tsc, las pruebas y el build pasan. El build no contiene modules/ heredados. El inventario baja de 179 a 171 rutas, sin perder ninguna con consumidor (verificado contra las llamadas de apps/web) |
| 9 | Almacenamiento de objetos obligatorio: storage.service ampliado (privado, cifrado, URLs firmadas, prefijos con ciclo de vida, validación por contenido); migrar documentos, pedidos, entregables, chatbot, salud y exportes; temporales en os.tmpdir()
|
Fase 0 — Endurecimiento | P0 | M | Después de redesplegar, todos los archivos de prueba siguen disponibles. Ningún multer.diskStorage apunta a ./uploads. Un exporte de 8 días ya no existe. Un archivo con extensión .pdf que no es PDF se rechaza |
| 10 | Persistir el chatbot (Bot, BotDocument, BotChunk, BotMessage) y las decisiones de IA (AiDecision) en MongoDB; migración idempotente desde state.json; quitar data/ del repositorio |
Fase 0 — Endurecimiento | P0 | M | Reiniciar el backend no pierde bots, documentos ni conversaciones (prueba de integración). git ls-files apps/backend/data no devuelve archivos. Correr la migración dos veces no duplica nada |
| 11 | Observabilidad: logs JSON a la salida estándar con requestId y sin datos personales, Sentry en la API (y en el worker en la Fase 2), monitor externo de /ready y alertas de la sección 13 |
Fase 0 — Endurecimiento | P1 | S | Un error 500 provocado aparece en Sentry con su requestId. Los logs de un envío de contacto no contienen el email. Apagar MongoDB en staging dispara la alerta en ≤ 3 min |
| 12 | MongoDB con réplica (Atlas recomendado): transacciones, copias de seguridad con restauración probada, usuario por entorno, índices y TTL de la sección 7, docker-compose.dev.yml con réplica, entorno de staging |
Fase 0 — Endurecimiento | P0 | S |
approve en staging corre dentro de una transacción real. Restauración probada y documentada (objetivo de pérdida de datos ≤ 24 h). Staging no contiene datos personales de producción |
| 13 | Backend del sistema de demos: modelos, permisos, bitácora, Leads, catálogo, solicitudes, estados, API pública, de portal y de equipo, enlace mágico, demo-access, requireDemoGrant, outbox y jobs base (= tareas 1–15 de Sistema de demos) |
Fase 1 — Funnel y solicitud de demos | P0 | XL | Se cumplen los criterios de las tareas 1–15 de Sistema de demos y las pruebas de integración de su sección 18 pasan en CI |
| 14 |
Avance en la rama ccr-55ceecf7-dpc10i (pendiente de merge): los avisos al dueño (contacto, solicitudes y demo RAG) salen por Resend y por la plantilla de Meta, con registro por canal y reintentos en services/owner-notify.service.ts; los pedidos y el pipeline usan la misma plantilla. Falta el outbox. Mensajería unificada, segunda parte: adaptadores en services/channels/ (email transaccional con SPF, DKIM y DMARC, WhatsApp Cloud API, in-app, Slack) y migración de los envíos existentes (contacto, pedidos, restablecimiento) al outbox |
Fase 1 — Funnel y solicitud de demos | P0 | M | Ningún controlador llama directo a emailService ni a whatsappService. Un fallo simulado del proveedor se reintenta sin duplicar. Un correo de prueba llega con DKIM válido |
| 15 |
Hecho en la rama rag-reposicionamiento (pendiente de merge). "Prueba con tu documento": API /api/demo-rag sin ticket (estado, subida, consulta, preguntas y borrado), lead demo-rag con services/lead.service.ts, límites (5 MB, 30 páginas, 10 preguntas, 3 documentos por IP al día), documento solo en memoria con borrado a la hora, tope DEMO_MONTHLY_BUDGET_USD, encendido con DEMO_UPLOAD_ENABLED y citas por página o fragmento |
Fase 1 — Funnel y solicitud de demos | P0 | M | Un PDF de 30 páginas queda listo en < 20 s y la respuesta cita la página correcta. A la hora, el documento y sus fragmentos ya no existen. La pregunta 11 responde 429 con un mensaje claro. Con el tope agotado, no se llama al proveedor. Falta probarlo con la clave real en Railway |
| 16 | Núcleo RAG compartido (modules/rag-core/): ingesta de PDF, DOCX, TXT y URL con límites, fragmentos con página, un solo modelo de embeddings, búsqueda híbrida BM25 + vectorial con Atlas Vector Search y citas; lo usan el chatbot, "Prueba con tu documento", documentos y la consulta normativa de salud |
Fase 2 — Demos vendibles | P1 | L | Las 3 implementaciones de embeddings quedan reemplazadas. Un set de evaluación de 50 preguntas con respuesta conocida da ≥ 90 % de citas correctas y corre en CI |
| 17 | Documentos por espacio: corpus de muestra público de solo lectura, escritura con grant y cupos, pertenencia por usuario o grant, borrado de los documentos de grants vencidos (grant-cleanup) |
Fase 1 — Funnel y solicitud de demos | P1 | M | Sin grant se lista el corpus de muestra y "Subir" pide acceso. Con grant, al superar el cupo se ve "cupo agotado". Un grant revocado deja 0 documentos en ≤ 1 h |
| 18 | Gestor de contenido detrás de la pasarela de IA, con cupo anónimo por IP y cupo por grant, validación zod, límite de entrada y corrección de hasRealBackend en la semilla |
Fase 1 — Funnel y solicitud de demos | P1 | S | Un visitante anónimo agota su cupo diario y recibe un mensaje claro. El gasto del módulo aparece en AiUsage con feature = content
|
| 19 | Módulo salud aislado: consolidar auditoría, cuentas, CUPS, Ley 100, liquidación, reglas y motor de reglas en src/modules/salud/ bajo /api/salud/*, con alias de las rutas viejas, SALUD_ENABLED y herramientas offline en tools/salud/ (coordinado con la tarea 8 de Demo cuentas médicas) |
Fase 2 — Demos vendibles | P1 | XL | La demo usa un solo flujo y un solo generador de Excel. Las rutas viejas responden con Deprecation durante una versión. Con SALUD_ENABLED=false el backend arranca sin cargar el módulo y sin errores |
| 20 | Servicio worker (src/worker.ts) con BullMQ: colas rag-ingest, salud-process y exports, ProcessingJob con progreso, jobs programados movidos al worker y server.timeout en valores por defecto |
Fase 2 — Demos vendibles | P1 | L | Encolar un lote de 20 facturas responde 202 en < 1 s y el lote termina en el worker. Reiniciar la API no interrumpe el trabajo. Ninguna petición HTTP dura más de 30 s |
| 21 | OpenAPI desde zod: config/openapi.ts, npm run openapi en CI con verificación de diferencias, /api/docs solo para el equipo, cliente tipado api.generated.ts en la web para las rutas del sistema de demos |
Fase 1 — Funnel y solicitud de demos | P1 | M | CI falla si una ruta montada no tiene esquema o si openapi.json está desactualizado. La web compila usando los tipos generados en lib/api.ts para las rutas nuevas |
| 22 | Portal en el backend: política de pertenencia centralizada en proyectos, pedidos, facturas, entregables y mensajes; adjuntos con URL firmada; Project.quoteId y Project.leadId; tipos de aviso demo, lead y quote; endpoints de equipo de Panel de administración (sección 4.7) |
Fase 1 — Funnel y solicitud de demos | P1 | M | Las pruebas de integración confirman que un cliente no ve ni modifica recursos de otro. La descarga de un adjunto exige URL firmada, que vence a los 5 minutos. shipped ya no existe en Order
|
| 23 |
Avance en la rama ccr-55ceecf7-dpc10i (pendiente de merge): webhook de estados de WhatsApp con firma verificada (sin WebhookEvent; un estado nunca retrocede). Webhooks entrantes con firma e idempotencia (WebhookEvent): agenda (Fase 1), estado de email y WhatsApp (Fase 2) y pagos (Fase 3) |
Fase 1 — Funnel y solicitud de demos | P2 | M | Un webhook repetido no duplica efectos. Una firma inválida responde 401. Una reserva de prueba aparece en la ficha del Lead en < 1 minuto |
| 24 | Propuestas y anticipo: Quote ampliado y migración de los estados viejos, API de la sección 8.5 de Sistema de demos, PDF en el almacenamiento, adaptador de pagos (Wompi o PayU en COP, Stripe en USD) con webhooks y services/conversion.service.ts
|
Fase 3 — Propuestas y conversión | P1 | XL | En el entorno de pruebas del proveedor, una propuesta aceptada con anticipo pagado crea el Project, cambia el rol a client y pasa los accesos a convertido sin pasos manuales |
| 25 | Núcleo multi-tenant para el chatbot RAG: Organization, tenantId en todas sus consultas, medición de conversaciones y documentos contra el plan, cobro recurrente y aprovisionamiento |
Fase 4 — Productos SaaS reales | P1 | XL | Las pruebas de aislamiento entre 2 organizaciones pasan. Un cliente nuevo crea su cuenta, paga y publica su bot sin intervención de KopTup. La factura mensual coincide con el uso medido |
| Fase | Tareas | Días-dev aprox. | Nota |
|---|---|---|---|
| Fase 0 — Endurecimiento | 1–12 | ≈ 38 (≈ 8 semanas con 1 dev) | Las P0 de la Fase 0 (1–7, 9, 10 y 12) deben estar listas antes de activar el control de acceso en producción. El desarrollo de la Fase 1 puede empezar en paralelo con un segundo dev |
| Fase 1 — Funnel y solicitud de demos | 13–15, 17, 18, 21–23 | ≈ 56 (≈ 30 de ellos son las tareas 1–15 de Sistema de demos) | El frontend de Sistema de demos (≈ 24 días-dev) va aparte |
| Fase 2 — Demos vendibles | 16, 19, 20 | ≈ 35 | La tarea 19 depende de la 20 (colas) y de la 16 (consulta normativa) |
| Fase 3 — Propuestas y conversión | 24 | ≈ 20 | Requiere la decisión de pasarela de pagos (Comercial, marketing y legal) |
| Fase 4 — Productos SaaS reales | 25 | ≈ 30 o más | Empieza por el chatbot RAG (Roadmap) |
Total: ≈ 180 días-dev de backend (≈ 36 semanas con 1 dev). Con 2 devs, las Fases 0 y 1 caben en ≈ 10–11 semanas de calendario.
| Métrica | Meta inicial (a validar con 8 semanas de operación) |
|---|---|
| Pérdida de datos por despliegue | 0 archivos, bots o conversaciones perdidos (prueba de redespliegue en cada versión) |
Disponibilidad de la API (/ready) |
≥ 99,5 % mensual |
| Latencia p95 | Lecturas del panel < 300 ms · GET /api/demo-access < 150 ms · respuesta de "Prueba con tu documento" < 6 s |
| Rutas con política de permiso y prueba | 100 % de las rutas montadas |
| Gasto de IA | Nunca por encima del tope mensual; alerta al 80 % atendida en ≤ 1 día hábil; costo por Lead de demo_rag visible en el panel |
| Mensajes | ≥ 99 % de los mensajes programados enviados en < 2 min; 0 duplicados; tasa de rebote < 2 % |
| Calidad | Cobertura ≥ 70 % en services/ del sistema de demos y de la pasarela de IA; CI en verde en main ≥ 95 % de los días; CI < 10 min |
| Dependencias | 0 vulnerabilidades altas o críticas en producción |
| Código | ≈ 5.300 líneas menos en la Fase 0; 0 herramientas offline dentro del build en la Fase 2 |
| Recuperación | Reversión de un despliegue fallido en < 15 min; restauración de la base probada cada trimestre |
| Demos con backend real | 0 pantallas de error técnico en producción (prueba de humo diaria de cuentas-medicas, sistema-experto, gestor-documentos, gestor-contenido y chatbot) |
- Sistema de demos · Flujo del cliente · Panel de administración · Portal del cliente
- Autenticación · Contacto · Legal
- Sistemas RAG · Gestor documental · CMS headless · Demo cuentas médicas · Demo sistema experto · Demo LinkedIn Ads
- Diagnóstico · Visión de producto · Catálogo de productos · Seguridad y calidad · Comercial, marketing y legal · Roadmap
Inicio · Estado y próximos pasos · Correcciones pendientes
Documentación (lo que existe hoy)
- 00 · Índice
- 01 · Arquitectura · 02 · Mapa del sitio
- 03 · Páginas públicas
- 04 · Flujos del visitante
- 05 · Prospecto y cliente
- 06 · Manual del administrador
- 07 · Guía de demos (28)
- 08 · API · 09 · Modelos de datos
- 10 · Roles y permisos
- 11 · Operación y despliegue
- 12 · SEO, analítica y legal
- 13 · Glosario y preguntas
- 14 · Diagramas de flujo: negocio · administración · técnicos
Estrategia
Cliente y demos
Sitio
Productos
- 08 · Catálogo de productos
- Producto principal: Sistemas RAG
- Otras soluciones a medida:
- Ventas y atención: CRM · Helpdesk · Voice AI · Fidelización
- Finanzas y talento: ERP · Facturación · HRMS · Firma
- Comercio y logística: E-commerce · POS · WMS · Delivery · Reservas
- Datos y productividad: BI · Documentos · Automatización · Extracción de datos · CMS · Proyectos
- Salud y educación: Telemedicina · LMS
- Tecnología: Code review · QA · SaaS multi-tenant · VPN · Moderación
- Fuera del catálogo: Cuentas médicas · Motor de reglas · LinkedIn Ads
Plataforma
Roadmap