Plataforma técnica · Arquitectura
Arquitectura del Sistema
Arquitectura
Esta página entrega la visión integral de la arquitectura del ecosistema Nostromo: servicios, patrones de comunicación, stack tecnológico, organización de dominios y decisiones clave.
Visión del Negocio
Section titled “Visión del Negocio”El sistema es una plataforma SaaS multi-tenant de gestión de remuneraciones, RRHH y contabilidad chilena. Implementa el cumplimiento de la legislación laboral local: cálculo de remuneraciones, seguridad social (AFP, ISAPRE, AFC), retención de impuestos y operaciones contables. Cada empresa cliente opera como un tenant aislado, con su información en una base de datos propia.
Servicios del Sistema
Section titled “Servicios del Sistema”| Servicio | Tecnología | Puerto | Propósito |
|---|---|---|---|
| Sevastopol | Astro 5.x + SolidJS | 4321 | Capa de presentación, enrutamiento de vistas, componentes UI |
| Orchestrator | Node.js 20+ / Express 5.x | 8000 | Lógica de negocio, acceso a datos, servicios API |
| Mother | PostgreSQL 16 | 5432 | Persistencia multi-tenant |
| Nostromo | Python 3.11+ (ETL) | — | Cargas masivas desde SII, Banco Central y Previred |
Límites de Servicios
Section titled “Límites de Servicios”flowchart TD
A[Cliente Navegador<br/>Agente de Usuario]
A -->|HTTP GET /command| B
subgraph FE["Servicio Frontend :4321"]
B[Páginas Astro<br/>src/pages/*.astro]
B --> C[Renderiza SSR<br/>view router / Carga Dinámica de Vistas]
C --> D[Carga<br/>Islands SolidJS<br/>src/components/islands/*]
D --> E[Llamadas API]
E --> F[Proxy API<br/>src/pages/api/*]
end
F -->|Proxy con cookie sid| G
subgraph BE["Servicio Orchestrator :8000"]
G[App Express<br/>src/app.ts]
G --> H[authenticateToken<br/>src/middleware/auth.ts]
H --> I[authorizeRoute<br/>src/lib/rbac.ts]
I --> J[Manejadores de Rutas<br/>src/routes/*]
J --> K[Servicios de Dominio<br/>src/domain/*]
end
K -->|centralPool| DB1[(nostromo_command<br/>Central)]
K -->|commonPool| DB2[(nostromo_common<br/>Parámetros)]
K -->|getTenantPool| DB3[(nostromo_*<br/>DBs Tenant)] Flujo de Peticiones Autenticadas
Section titled “Flujo de Peticiones Autenticadas”El siguiente diagrama muestra cómo fluye una petición API autenticada típica a través del sistema:
sequenceDiagram
participant N as Navegador
participant V as Sevastopol
participant P as Proxy API
participant A as Orchestrator
participant M as Auth Middleware
participant R as RBAC
participant D as PostgreSQL
N->>V: Click en navegación
V->>V: Dispara sidebar:navigate
V->>P: authenticatedFetch('/api/employees')
Note right of V: Cookie sid adjunta
P->>A: HTTP con cookie sid
A->>M: Verifica JWT + sesión
M->>D: Consulta sessions
D-->>M: Sesión válida
M->>R: Verifica permisos
R->>R: rbac.canAccess(role, route)
alt Autorizado
R->>A: Ejecuta handler
A->>D: Query SQL
D-->>A: Datos
A-->>P: JSON (snake_case)
P-->>V: Respuesta
V->>N: Re-renderiza UI
else No autorizado
R-->>A: 403 Forbidden
A-->>P: Error
P-->>V: Error
V->>N: Mensaje de error
end Arquitectura Frontend (Sevastopol)
Section titled “Arquitectura Frontend (Sevastopol)”Sevastopol usa Astro con Islands Architecture de SolidJS para hidratación selectiva e interactividad.
| Componente | Tecnología | Versión |
|---|---|---|
| Framework | Astro (SSR) | 5.x |
| UI Library | SolidJS (Islands) | 1.x |
| Estilos | TailwindCSS | 3.x |
| Build Tool | Vite | 6.x |
Directorysrc/
Directorypages/
- command.astro (Contenedor principal)
Directoryapi/ (Proxies al backend)
- …
Directorycomponents/
Directoryatoms/ (Input, Button, Modal)
- …
Directorymolecules/ (StatCard, StatusBadge)
- …
Directoryorganisms/ (Sidebar, PageHeader)
- …
Directoryislands/ (Componentes interactivos)
- …
Directorytemplates/ (IslandBase.tsx)
- …
Directorylib/
- authFetch.ts
- view-router.ts
Directoryconfig/
- menu.config.ts
Componentes Principales
Section titled “Componentes Principales”flowchart TD
A[command.astro<br/>Contenedor Principal]
A -->|Inicializa| B
subgraph GV["Gestión de Vistas"]
B[view-router.ts<br/>Cargador dinámico]
M[menu.config.ts<br/>Estructura de navegación]
B -->|Lee| M
end
B -->|import dinámico| I1
B -->|import dinámico| I2
B -->|import dinámico| I3
subgraph IS["Islands SolidJS"]
I1[Remuneraciones<br/>PayrollViewIsland<br/>EmployeesViewIsland]
I2[Administración<br/>TenantsViewIsland<br/>CompanyViewIsland]
I3[Contabilidad<br/>OperationsViewIsland]
end
I1 & I2 & I3 -->|Envuelve en| C
subgraph BC["Biblioteca de Componentes"]
C[IslandBase.tsx]
C --> O[Organismos]
O --> MO[Moléculas]
MO --> AT[Átomos]
end Mecanismo del View Router
Section titled “Mecanismo del View Router”- Usuario hace clic en elemento de navegación en
Sidebar.astro. - Sidebar dispara
CustomEvent("sidebar:navigate", detail: viewKey). - View router escucha el evento en
view-router.ts. - Router realiza
import()dinámico del módulo island correspondiente. - Función
render()de SolidJS monta el island en#command-view. - Router emite
CustomEvent("view:state")para actualizar estado activo.
Arquitectura Backend (Orchestrator)
Section titled “Arquitectura Backend (Orchestrator)”Orchestrator implementa Diseño Orientado al Dominio (DDD) con contextos delimitados claros y una arquitectura por capas.
| Componente | Tecnología |
|---|---|
| Runtime | Node.js 20+ |
| Framework | Express 5.x |
| Lenguaje | TypeScript 5.x |
| Base de Datos | PostgreSQL (node-postgres) |
| Documentos | Puppeteer, docxtemplater |
Directorysrc/
- app.ts (Express factory)
- server.ts (HTTP bootstrap)
Directorylib/
- db.ts (Pool management)
- rbac.ts (Control de acceso)
Directorymiddleware/
- auth.ts (JWT validation)
Directoryroutes/
Directorycommand/ (tenant, users, auth)
- …
Directoryremuneraciones/ (payroll, employees)
- …
Directorycommon/ (parameters, afc)
- …
Directoryadmin/ (chart-of-accounts)
- …
Directorydomain/
Directorypayroll/ (Service, Repository, Calculators)
- …
Directorycommon/ (CommonDataService, PdfService)
- …
Estructura de Capas
Section titled “Estructura de Capas”flowchart TD
subgraph PE["Punto de Entrada"]
S["server.ts"] --> A["app.ts"]
end
subgraph R["Capa de Rutas"]
R1[command/]
R2[remuneraciones/]
R3[common/]
R4[admin/]
end
subgraph MW["Middleware"]
M1["auth.ts<br/>authenticateToken()"]
M2["rbac.ts<br/>canAccess()"]
end
subgraph D["Capa de Dominio"]
D1[PayrollService<br/>PayrollRepository]
D2[CommonDataService<br/>PdfService]
D3[ChartOfAccountsService]
end
subgraph DB["Base de Datos"]
DB1["db.ts<br/>centralPool / commonPool / getTenantPool()"]
end
A --> R1 & R2 & R3 & R4
A --> M1 & M2
R1 & R2 --> D1
R3 --> D2
R4 --> D3
D1 & D2 & D3 --> DB1 Inicialización de Express
Section titled “Inicialización de Express”- Middleware de seguridad:
helmet()para headers HTTP seguros. - CORS: configurado para puertos 4320–4322 con credenciales.
- Logging:
morgan('combined')para registro de peticiones. - Parsing de body:
express.json()con límite de 2 MB. - Parsing de cookies:
cookieParser()para cookies de sesión.
Registro de Rutas
Section titled “Registro de Rutas”| Prefijo | Módulo | Propósito |
|---|---|---|
/api/tenant | command/tenant.ts | Gestión de tenants (SUPER_ADMIN) |
/api/admin | command/users.ts | Gestión de usuarios |
/api/sessions | command/sessions.ts | Monitoreo de sesiones |
/api/parameters | common/parameters.ts | Indicadores económicos |
/api/remuneraciones/payroll | remuneraciones/payroll.ts | Liquidaciones |
/api/employees | remuneraciones/employees.ts | CRUD empleados |
/api/admin/chart-of-accounts | admin/chart-of-accounts.ts | Plan de cuentas |
Organización de Dominios
Section titled “Organización de Dominios”Dominio de Remuneraciones
Section titled “Dominio de Remuneraciones”Clases clave:
PayrollService: orquesta el flujo de generación de liquidaciones.PayrollRepository: recopilación de contexto y persistencia.SocialLawsCalculator: cálculos de AFP, salud y seguro de cesantía.HealthPlanCalculator: lógica FONASA vs ISAPRE.TaxCalculator: impuesto progresivo (Impuesto 2da Categoría).
Patrón de recopilación de contexto:
// Consultas paralelas para optimizar rendimientoconst [attendanceRes, indicatorsRes, afpCode, taxRes] = await Promise.all([ this.getAttendanceData(db, contractRes.employeeId, periodStr), CommonDataService.getIndicators(periodStr), this.getAfpCode(db, contractId), CommonDataService.getTaxBrackets(periodStr),]);Dominio Común
Section titled “Dominio Común”Proporciona parámetros regulatorios compartidos:
CommonDataService: consultas de parámetros temporales.PdfService: generación de documentos usando Puppeteer.
Patrones de Diseño API
Section titled “Patrones de Diseño API”Convenciones RESTful
Section titled “Convenciones RESTful”- Query params:
snake_case(ej.?empleado_id=123). - Body: JSON en
snake_case. - Auth: Cookie
sidcon token JWT.
{ "field_name": "value", "nested_object": { "snake_case_key": 123 }}{ "success": false, "error": "ERROR_CODE", "message": "Descripción legible"}Patrones de Rutas
Section titled “Patrones de Rutas”| Método | Patrón | Propósito |
|---|---|---|
GET | /api/resource | Listar todos (con filtros opcionales) |
GET | /api/resource/:id | Obtener por ID |
POST | /api/resource | Crear nuevo |
PUT/PATCH | /api/resource/:id | Actualizar existente |
DELETE | /api/resource/:id | Eliminar (con ?force=true opcional) |
router.get("/", async (req, res) => { /* Listar */ });router.get("/vista/:id", async (req, res) => { /* Obtener por ID */ });router.get("/:id/details", async (req, res) => { /* Obtener detalles */ });router.post("/generar", async (req, res) => { /* Crear/Generar */ });router.patch("/:id", async (req, res) => { /* Actualizar */ });router.delete("/:id", async (req, res) => { /* Eliminar */ });Capa de Acceso a Datos
Section titled “Capa de Acceso a Datos”Gestión de Connection Pools
Section titled “Gestión de Connection Pools”El sistema usa tres tipos de connection pools:
flowchart LR
subgraph PS["Pools Singleton"]
CP[centralPool]
CMP[commonPool]
end
subgraph PD["Pools Dinámicos"]
F["getTenantPool(dbName)"]
M[Map en caché]
F --> M
end
subgraph DB["Bases de Datos"]
DB1[(nostromo_command)]
DB2[(nostromo_common)]
DB3[(nostromo_6000431)]
DB4[(nostromo_7000123)]
end
CP --> DB1
CMP --> DB2
M --> DB3
M --> DB4 Resolución de Tenant
Section titled “Resolución de Tenant”- Extraer
tenant_idde los parámetros de consulta o de la sesión del usuario. - Mapear a nombre de base de datos:
nostromo_<rut_sin_dv>. - Recuperar o crear connection pool (cacheado en
Map<string, Pool>). - Ejecutar consulta en la base de datos específica del tenant.
const tenantDb = await getDatabaseNameForUser(req.user, req);const pool = getTenantPool(tenantDb);const result = await pool.query(sql, params);Patrones de Comunicación
Section titled “Patrones de Comunicación”Frontend a Backend
Section titled “Frontend a Backend”Sevastopol usa un patrón de proxy para comunicarse con Orchestrator:
- Proxy de desarrollo: Vite hace proxy de
/api/*ahttp://localhost:8000. - Páginas proxy API:
sevastopol/src/pages/api/*implementan proxies simples. - Fetch autenticado: la utilidad
authenticatedFetch()adjunta la cookiesid.
// sevastopol/src/pages/api/admin/chart-of-accounts.tsimport { createProxy } from "@/lib/proxyUtils";export const { GET, POST, PUT, DELETE } = createProxy("/api/admin/chart-of-accounts");Flujo de Autenticación
Section titled “Flujo de Autenticación”- Usuario inicia sesión vía
/api/auth/login. - Orchestrator valida credenciales contra
nostromo_command.users. - Crea sesión en tabla
sessions. - Retorna cookie
sidcon token JWT (HttpOnly). - Todas las peticiones subsecuentes incluyen la cookie.
- Middleware
authenticateTokenvalida token y sesión.
Patrones Arquitectónicos Clave
Section titled “Patrones Arquitectónicos Clave”Separación de Responsabilidades
Section titled “Separación de Responsabilidades”| Capa | Responsabilidad | Ejemplo |
|---|---|---|
| Presentación | Renderizado UI, interacción | view-router.ts, Islands SolidJS |
| API Gateway | Enrutamiento, auth, RBAC | app.ts, rbac.ts |
| Servicio | Orquestación de lógica | PayrollService.generatePayroll() |
| Repositorio | Acceso a datos | PayrollRepository.getPayrollContext() |
| Calculador | Lógica de negocio pura | SocialLawsCalculator.calculate() |
Diseño Orientado al Dominio
Section titled “Diseño Orientado al Dominio”Cada dominio es autocontenido:
Directorysrc/domain/chart-of-accounts/
- types.ts (Account, CreateAccountDTO)
- ChartOfAccountsService.ts (Lógica de negocio)
Directorysrc/routes/admin/
- chart-of-accounts.ts (Rutas HTTP)
Patrón Repository con Mapeo
Section titled “Patrón Repository con Mapeo”private static mapRow(row: any): LegalRep { return { fecha_desde: row.fecha_nombramiento, // Mapea DB a dominio fecha_hasta: row.fecha_termino, vigente: row.activo, // ... otros campos };}Configuración de Entorno
Section titled “Configuración de Entorno”Variables Requeridas
Section titled “Variables Requeridas”| Variable | Servicio | Propósito |
|---|---|---|
PORT | Orchestrator | Puerto HTTP (8000) |
PGHOST | Orchestrator | Host PostgreSQL |
PGPORT | Orchestrator | Puerto PostgreSQL (5432) |
PGUSER | Orchestrator | Usuario de BD |
PGPASSWORD | Orchestrator | Contraseña de BD |
COMMAND_DB | Orchestrator | Base central (nostromo_command) |
COMMON_DB | Orchestrator | Base de parámetros (nostromo_common) |
JWT_SECRET | Orchestrator | Clave de firma de token (256+ bits) |
CORS_ORIGINS | Orchestrator | Lista de orígenes permitidos (separados por ,) |
LIQUIDACIONES_DIR | Orchestrator | Ruta de almacenamiento PDF |
PUBLIC_API_URL | Sevastopol | URL del Orchestrator (http://localhost:8000) |
Arquitectura de Despliegue
Section titled “Arquitectura de Despliegue”localhost:4321 ← Sevastopol (Vite dev server) ↓ (proxy /api/*)localhost:8000 ← Orchestrator (nodemon) ↓localhost:5432 ← PostgreSQL (Docker)- Sevastopol: build estático (
npm run build) servido por Nginx/CDN. - Orchestrator: proceso Node.js detrás de proxy reverso (Nginx/Caddy).
- PostgreSQL: servidor dedicado con connection pooling (PgBouncer).