Plataforma técnica · Orchestrator
API del Orchestrator
API REST
Propósito
Section titled “Propósito”El Orchestrator expone una API REST plana bajo /api/* sobre Node.js + Express. Cada dominio del ecosistema (administración, remuneraciones, operaciones, declaraciones, etc.) tiene un router dedicado en orchestrator/src/routes/ y una página de documentación equivalente en esta sección.
Esta página es el hub de la API: muestra los patrones REST comunes, las convenciones de respuesta y autenticación, y enlaza a cada página de dominio.
Arquitectura de Flujo de Solicitudes
Section titled “Arquitectura de Flujo de Solicitudes”Cada request HTTP a la API atraviesa una pipeline determinista: autenticación, autorización RBAC, resolución de tenant, lógica de dominio y respuesta. El middleware de Sevastopol inyecta la cookie sid antes del proxy local, que reenvía al Orchestrator en localhost:8000.
flowchart LR
A[Solicitud HTTP] --> B[authenticateToken]
B -->|req.user| C[authorizeRoute · RBAC]
C -->|permitido| D[getTenantPool]
D -->|pool| E[Route handler]
E -->|Service layer| F[Repository]
F -->|SQL| G[(PostgreSQL)]
G -->|filas| F --> E -->|JSON snake_case| H[Response]
C -.->|403| H
B -.->|401| H Patrón Backend-for-Frontend
Section titled “Patrón Backend-for-Frontend”Sevastopol nunca habla directo con localhost:8000. Toda llamada cruza el proxy local en sevastopol/src/pages/api/*, que adjunta la cookie sid y reenvía al Orchestrator. Esto centraliza CSP, headers de seguridad y rotación de credenciales en una sola capa.
sequenceDiagram
participant I as Island (SolidJS)
participant AF as authenticatedFetch
participant API as Sevastopol /api/*
participant O as Orchestrator
participant DB as PostgreSQL
I->>AF: fetch('/api/employees')
AF->>AF: Adjunta cookie sid
AF->>API: HTTP request
API->>O: Reenvía a :8000 con cookie
O->>O: authenticateToken + authorizeRoute
O->>DB: Query (tenant pool)
DB-->>O: Filas
O-->>API: JSON snake_case
API-->>AF: Respuesta
AF-->>I: Datos parseados Convenciones
Section titled “Convenciones”Naming
Section titled “Naming”- URLs:
kebab-case, prefijo/api, sin versionado en path (la API es interna). - Payloads JSON:
snake_case, replicando los nombres de columnas para evitar transformación en el frontend. - TypeScript interno:
camelCase. La conversión ocurre solo en los DTOs de borde.
Autenticación
Section titled “Autenticación”| Mecanismo | Uso |
|---|---|
Cookie sid (HttpOnly) | Toda mutación y casi toda lectura. Inyectada por el middleware de Sevastopol. |
Header X-Internal-Token | Endpoints /api/internal/* consumidos por jean_d_arc en build. |
Authorization: Bearer | Reservado para integraciones externas autorizadas (no usado hoy). |
Códigos de estado
Section titled “Códigos de estado”| Código | Caso de uso | Ejemplo |
|---|---|---|
200 | GET o PUT exitoso | Recurso obtenido o actualizado |
201 | POST exitoso | Recurso creado |
204 | DELETE exitoso | Sin contenido |
400 | Entrada inválida | Campos requeridos faltantes, RUT mal formado |
401 | No autorizado | Sin cookie sid o JWT inválido |
403 | Prohibido | Cookie válida pero rol sin permiso para la ruta |
404 | No encontrado | Recurso no existe en el tenant |
409 | Conflicto | Duplicado, estado incompatible |
500 | Error interno | Fallo en DB o lógica no esperado |
Respuestas de error
Section titled “Respuestas de error”{ "success": false, "error": "Mensaje descriptivo", "code": "VALIDATION_ERROR"}| Código | HTTP | Descripción |
|---|---|---|
UNAUTHORIZED | 401 | Cookie ausente o expirada |
FORBIDDEN | 403 | Sin permiso RBAC para la ruta |
NOT_FOUND | 404 | Recurso no existe en el tenant |
VALIDATION_ERROR | 400 | Payload inválido |
INTERNAL_ERROR | 500 | Error de servidor |
Paginación
Section titled “Paginación”Endpoints que listan recursos aceptan page y limit (default limit=50).
curl -X GET "http://localhost:8000/api/employees?page=2&limit=20" -b cookies.txt{ "success": true, "data": [ /* ... */ ], "pagination": { "page": 2, "limit": 20, "total": 150, "pages": 8 }}Multi-tenant
Section titled “Multi-tenant”El Orchestrator es multi-tenant: cada request resuelve un tenant_id desde la sesión y usa getTenantPool(tenantId) para obtener una conexión a la base PostgreSQL del tenant (nostromo_<rut_sin_dv>).
- Las bases compartidas (
nostromo_common,nostromo_command) se acceden con pools dedicados (commonPool,centralPool). - Cambiar de tenant requiere
PUT /api/tenanty emite un nuevo JWT en la cookie. - Ver Mother Database para el modelo de aislamiento por base.