Arquitectura del Backend CRM

Fases 22.0–48. Estado: DESPLEGADO (VPS en vivo, 68+ endpoints, arquitectura modular). Última actualización: 2026-04-28 (Fase 48 — Descomposición de Arquitectura)

Resumen

Browser (:18888)
  │
  ├── Archivos estáticos ──► Nginx ──► Docker frontend (React + Nginx)
  │
  ├── /api/crm/* ────► Nginx ──► Bun Master Bot (:19210)
  │                                 ├── crmAuthMiddleware (HMAC-SHA256)
  │                                 └── shared/routes/router.ts → 17 módulos de dominio
  │
  ├── /api/auth/* ───► Nginx ──► Bun (:19210) → master-bot/routes/auth.ts
  ├── /api/cli/*  ───► Nginx ──► Bun (:19210) → master-bot/routes/cli.ts
  ├── /api/internal/* ► Bun (:19210, solo loopback) → master-bot/routes/internal.ts
  │
  ├── /api/sse/* ────► Nginx (sin buffer) ──► Bun (:19210)
  │
  └── /ws/terminal/* ► Nginx (upgrade) ──► Bun (:19210) → master-bot/routes/websocket.ts

Estructura de Módulos (Fase 48)

API Server (master-bot/api-server.ts, 196 líneas) — dispatcher delgado:

Rutas CRM (shared/routes/router.ts, 512 líneas) — tabla de dispatch para 17 módulos de dominio:

Flujo de Autenticación

Usuario ──► LoginOverlay (frontend)
         │
         ├── Email/contraseña → POST /api/auth/login
         └── OAuth → /api/auth/{google,github} → callback
         │
         ▼
    Auth exitosa → token HMAC-SHA256
         │
         ├── payload = { sub: userId, iat, exp: +24h }
         ├── signature = HMAC-SHA256(base64url(payload), CRM_SECRET)
         └── token = base64url(payload) + "." + signature
         │
         ▼
    localStorage('crm-token') → Todas las llamadas API: Authorization: Bearer <token>
         │
         ▼
    Token vence → 401 → se requiere login

Archivos Clave

Archivo Rol
shared/auth.ts Generación/verificación de token, email/OAuth, restablecimiento de contraseña, verificación de email, CORS, middleware
shared/vault.ts Almacenamiento cifrado AES-256-GCM de secretos (CRM_SECRET se crea automáticamente)
frontend/src/crm/components/LoginOverlay.jsx UI de login (email, OAuth, restablecimiento de contraseña, verificación de email)

Propiedades de Seguridad

Rutas de CRM API

Todas las rutas bajo /api/crm/ requieren Authorization: Bearer <token>.

GET /api/crm/projects

Lista todos los proyectos registrados con estado de salud en vivo. Incluye master como pseudo-proyecto (DEBT-6).

Campo Fuente
name, type, bot_username config/bot_registry.json (master: entrada virtual, type: "system")
healthy Health check HTTP (hijos: puerto del hijo, master: /api/master/health)
tmux_alive tmux has-session -t <name> (master: citadel-master)
status Archivo heartbeat o derivado de health+tmux (master: healthy/degraded/down)

El master siempre se antepone como primera entrada en el array de respuesta.

GET /api/crm/projects/:name

Tarjeta de detalle completo del proyecto.

Campo Fuente
manifest Texto plano de <cwd>/MANIFEST.md
heartbeat JSON de state/heartbeat_<name>.json
skills[] Nombres de archivos <cwd>/skills/*.md (excluyendo _*.md)
healthy, tmux_alive Igual que el endpoint de lista

GET /api/crm/projects/:name/logs

Tail de logs JSONL estructurados.

Parámetro Por defecto Máx.
lines 100 500
category dialog system, dialog, error

Fuente: /var/log/citadel/<name>/<category>-YYYY-MM-DD.log

Lee los archivos de log más recientes primero, parsea JSONL, devuelve en orden cronológico.

GET /api/crm/projects/:name/files

Listado de directorio con protección contra path traversal.

Parámetro Por defecto Descripción
path / Ruta relativa dentro del cwd del proyecto

Seguridad: resolve(base, requested) debe startsWith(resolve(base)). Violación → 403.

Entradas de respuesta: { name, type: "file"|"directory", size?, modified }, ordenadas con directorios primero.

GET /api/crm/projects/:name/skills

Skills instalados con atribución de fuente.

Fuente Detección
file <cwd>/skills/*.md (excluyendo _*.md)
manifest MANIFEST.mdskills[] + library_skills[] (parse JSON)

Deduplicado: la fuente de archivo tiene prioridad sobre el manifest.

POST /api/crm/projects/:name/restart (Fase 22.3)

Reiniciar un child bot via watchdog.

Aspecto Detalle
Método Solo POST (GET devuelve 405)
Auth Token HMAC-SHA256 (igual que otras rutas CRM)
Cooldown 30 segundos por proyecto (devuelve 429 si es muy frecuente)
Mecanismo Llama a restartChild() desde watchdog.ts
Respuesta { ok: true } o { error: "..." }

GET /api/crm/projects/:name/metrics (Fase 22.1)

Timeseries de métricas de calidad para gráficos sparkline.

Parámetro Por defecto Descripción
days 7 Número de días a agregar

Fuente: /var/log/citadel/<name>/quality-events.log → conteos de éxito/fallo por día.

WS /ws/terminal/:name (Fase 22.3)

Terminal WebSocket que transmite contenido del panel tmux.

Aspecto Detalle
Auth Query param ?token= (verificado via verifyToken().valid)
Modo Solo lectura por defecto; ?mode=interactive habilita send-keys
Poll tmux capture-pane -p -e -t {session} -S -80 cada 200ms
Delta Solo envía cuando el contenido cambia respecto al anterior

Seguridad: Protección contra Path Traversal (Fase 22.3, DEBT-7)

Todos los endpoints validan nombres de proyectos antes de procesar:

const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
export function isValidProjectName(name: string): boolean {
  return name.length > 0 && name.length <= 64 && SAFE_NAME_RE.test(name);
}

Aplicado en 3 puntos de entrada: routeCrmRequest(), routeSseRequest(), manejador de upgrade WebSocket en bot.ts. Nombres inválidos → 403 Forbidden.

Integridad de Datos: Escrituras Atómicas (Fase 22.3, DEBT-2)

Las escrituras de registro en onboarding.ts usan un patrón atómico:

async function atomicWriteJson(filePath: string, data: unknown): Promise<void> {
  const tmp = `${filePath}.tmp.${process.pid}`;
  await Bun.write(tmp, JSON.stringify(data, null, 2));
  Bun.spawn(["mv", tmp, filePath]); // rename atómico POSIX
}

Archivos Clave

Archivo Rol Líneas
shared/crm-routes.ts 55+ handlers + router routeCrmRequest() + isValidProjectName() ~4800
shared/sage.ts Sage Worker: runSageAnalysis(), runBenchmark(), juez LLM, pruebas A/B ~550
shared/db.ts SQLite SSOT: userQueries, projectQueries, skillQueries, benchmarkQueries, chatQueries ~600
shared/auth.ts Generación y verificación de token, CORS, middleware ~100
master-bot/bot.ts Bun.serve: router HTTP + manejador de terminal WebSocket ~1160
master-bot/onboarding.ts atomicWriteJson() para escrituras de registro ~850

Proxy Nginx

Archivo: infra/nginx/citadel-crm.conf — reemplaza la configuración legacy citadel-os.

Tres upstreams:

Upstream Puerto Servicio
crm_backend 19210 Bun Master Bot (CRM API, health)
phaser_frontend 18889 Docker Phaser office (legacy)
mcp_bridge 19200 Docker MCP state-bridge (legacy)
Location Backend Auth Configuración especial
/ phaser_frontend basic auth Docker Phaser frontend
/api/crm/ crm_backend token (sin basic) timeout 30s, keepalive
/api/master/health crm_backend ninguna Health check público
/api/sse/ crm_backend token (sin basic) proxy_buffering off, timeout 1h (Fase 22.1)
/ws/ crm_backend token (sin basic) Upgrade WebSocket, timeout 1h (Fase 22.1)
/api/ mcp_bridge ninguna (sin basic) Legacy state API + SSE
/health mcp_bridge ninguna Health del bridge legacy

Todas las locations reenvían X-Real-IP, X-Forwarded-For, X-Forwarded-Proto.

Rutas bloqueadas: /.* (dotfiles), /config/, /state/, /scripts/.

Nota: La prioridad de locations de Nginx asegura que /api/crm/ y /api/sse/ coincidan antes del catch-all /api/ (MCP legacy).

Integración en Master Bot

master-bot/bot.ts Bun.serve (:19210):

async fetch(req) {
  /api/master/health  →  público, sin auth
  /api/crm/*          →  preflight CORS → crmAuthMiddleware → routeCrmRequest()
  *                   →  404
}

El router despacha a funciones handler de shared/crm-routes.ts. Los headers CORS se inyectan en cada respuesta CRM (incluidos errores 401).

Resumen de Endpoints (Todas las Fases)

Fase Funcionalidad Endpoint Estado
22.0 Lista de proyectos GET /api/crm/projects DONE
22.0 Detalle de proyecto GET /api/crm/projects/:name DONE
22.0 Tail de logs GET /api/crm/projects/:name/logs DONE
22.0 Listado de archivos GET /api/crm/projects/:name/files DONE
22.0 Listado de skills GET /api/crm/projects/:name/skills DONE
22.1 Streaming SSE de logs GET /api/sse/logs/:name DONE
22.1 Historial de métricas GET /api/crm/projects/:name/metrics DONE
22.3 Reinicio de proyecto POST /api/crm/projects/:name/restart DONE
22.3 Terminal WebSocket WS /ws/terminal/:name DONE
24.5 CRUD de specs GET/POST /api/crm/projects/:name/specs DONE
24.5 Rol activo GET/POST /api/crm/projects/:name/active-role DONE
25 Bundle de skills GET /api/crm/projects/:name/skills-bundle DONE
25 Sincronización de learnings GET/POST /api/crm/projects/:name/learnings DONE
26 CRUD de workers GET/POST /api/crm/projects/:name/workers DONE
32 Guardar wiki PUT /api/crm/projects/:name/wiki/save DONE
32 Guardar/eliminar skills PUT/DELETE /api/crm/projects/:name/skills/* DONE
33 Configuración de cuenta GET/PUT /api/crm/account/settings DONE
33 Crear proyecto POST /api/crm/projects/create DONE
34 CRUD de issues POST/GET/PUT /api/mcp/issues/:project DONE
34 Wiki MCP PUT /api/mcp/wiki/:project DONE
34 Sincronización de roadmap GET/PUT /api/mcp/roadmap/:project DONE
34 Init CLI GET /api/cli/init/:project/:mode DONE
35 Ingest de log de terminal POST /api/crm/projects/:name/terminal/log DONE
36 Chat Cloud PM POST /api/crm/projects/:name/chat DONE
36.6 Generación de skill POST /api/crm/projects/:name/skills/generate DONE
36.7 Listado de notebooks GET /api/crm/projects/:name/notebooks DONE

| 40.14 | Roadmap (contenido) | GET /api/mcp/roadmap/:project (+ campo content) | DONE |

Total: 46+ endpoints (REST + SSE + WebSocket + CLI/MCP)

NotebookLM Bridge (Fase 36.3)

Servicio Python FastAPI separado en :19213 (solo localhost). No forma parte de la CRM API de Bun.

Endpoint Método Propósito
/query POST Búsqueda semántica via Google NotebookLM
/sync POST Encolar fuente para sincronización asíncrona (fire-and-forget)
/notebooks/init POST Crear notebook para nuevo proyecto
/health GET Estado de auth, recuento de notebooks, estadísticas de cola

El CRM de Bun dispara llamadas fire-and-forget al bridge en:

Deuda Técnica

Ver docs/backlog/technical-debt.md para el registro completo. Elementos clave:

ID Título Estado
DEBT-2 Escrituras JSON atómicas RESUELTO (Fase 22.3)
DEBT-3 Token Docker integrado → auth de sesión RESUELTO (Fase 31)
DEBT-6 Punto ciego CRM del master bot RESUELTO (Fase 22.3)
DEBT-7 Defensa contra path traversal RESUELTO (Fase 22.3)

Mantenido por Rick (Orquestrador). Actualizado tras la finalización de cada fase.