Plataforma técnica · Arquitectura
Arquitectura Hexagonal
Arquitectura Patrones
Esta página explica cómo el sistema aplica el patrón de arquitectura hexagonal (puertos y adaptadores) para mantener el dominio central aislado de la infraestructura. Los flujos completos (BFF, autenticación, multi-tenant) viven en la página de Arquitectura del Sistema.
Idea central
Section titled “Idea central”La arquitectura hexagonal organiza el código en tres capas concéntricas:
- Dominio central: servicios, motores de cálculo y modelos. Reglas de negocio puras.
- Puertos: interfaces que el dominio declara para hablar con el mundo exterior.
- Adaptadores: implementaciones concretas de esos puertos (Express, PostgreSQL, sistema de archivos, scrapers).
El dominio nunca importa código de los adaptadores. Los adaptadores implementan los puertos del dominio. Esto permite reemplazar la infraestructura (cambiar Express por Fastify, PostgreSQL por otra base, etc.) sin tocar las reglas de negocio.
Independencia del Dominio Central
Section titled “Independencia del Dominio Central”Las reglas que mantienen aislado al dominio:
- Sin importaciones de tipos
expressen la lógica de dominio. - Sin consultas directas a base de datos en los motores de cálculo (
PayrollEngine,TaxCalculator, etc.). - Sin operaciones del sistema de archivos en las reglas de negocio.
- Los servicios reciben dependencias por argumento (
Pool,Logger), no las construyen internamente.
Mapa de Capas
Section titled “Mapa de Capas”flowchart subgraph EX["Sistemas Externos"] WEB["Navegador Web"] DB["Base de Datos PostgreSQL"] FS["Sistema de Archivos<br/>(Almacenamiento PDF)"] end subgraph AD["Adaptadores"] FE["Adaptador Frontend (Proxy)<br/>Sevastopol"] API["Adaptador API (Rutas)<br/>Express"] DB_AD["Adaptador Base de Datos<br/>(Patrón Repository)"] FILE_AD["Adaptador de Archivos<br/>(PdfService)"] end subgraph PORTS["Puertos (Interfaces)"] HTTP_PORT["Puerto de Petición HTTP"] DATA_PORT["Puerto de Acceso a Datos"] STORAGE_PORT["Puerto de Almacenamiento"] end subgraph DOMAIN["Dominio Central"] SERVICES["Servicios de Dominio<br/>PayrollService<br/>EmployeeService<br/>ContractService"] CALC["Motores de Cálculo<br/>PayrollEngine<br/>TaxCalculator<br/>SocialLawsCalculator"] MODELS["Modelos de Dominio<br/>Employee<br/>Contract<br/>Payroll"] end WEB -->|HTTP| FE FE -->|Proxy| API API -->|Transformar| HTTP_PORT HTTP_PORT --> SERVICES SERVICES -->|Usa| CALC SERVICES -->|Usa| MODELS SERVICES -->|Consultar / Persistir| DATA_PORT SERVICES -->|Generar PDF| STORAGE_PORT DATA_PORT --> DB_AD STORAGE_PORT --> FILE_AD DB_AD --> DB FILE_AD --> FS
Puertos del Sistema
Section titled “Puertos del Sistema”| Puerto | Implementado por | Qué representa |
|---|---|---|
| Petición HTTP | Rutas Express + middleware | Entrada de comandos del mundo exterior (REST) al dominio. |
| Acceso a Datos | *Repository con pg.Pool | Lectura y persistencia de entidades de dominio. |
| Almacenamiento | PdfService con puppeteer | Generación y guardado de documentos (liquidaciones, finiquitos). |
| Tiempo / Indicadores | CommonDataService | Parámetros temporales (UF, USD, AFP) a la fecha de cálculo. |
Cada puerto es una “interfaz de uso” del dominio: el Service lo invoca; el adaptador decide cómo se cumple.
Adaptadores del Sistema
Section titled “Adaptadores del Sistema”Adaptador Frontend (Sevastopol)
Section titled “Adaptador Frontend (Sevastopol)”Sevastopol actúa como adaptador HTTP del lado del cliente. No habla con la base de datos: traduce eventos de UI (clicks, formularios) en peticiones HTTP que viajan al adaptador API del Orchestrator, vía un proxy local.
Adaptador API (Express)
Section titled “Adaptador API (Express)”Las rutas en orchestrator/src/routes/ traducen HTTP en llamadas al dominio. Su responsabilidad es:
- Parsear request (params, body, cookies).
- Verificar autenticación y autorización.
- Llamar al servicio de dominio correspondiente.
- Serializar la respuesta a JSON
snake_case.
router.post("/generar", authenticateToken, async (req, res) => { const pool = getTenantPool(await getDatabaseNameForUser(req.user, req)); const result = await PayrollService.generatePayroll(pool, req.body); res.json(result);});Adaptador de Base de Datos (Repository)
Section titled “Adaptador de Base de Datos (Repository)”Cada *Repository encapsula las consultas SQL contra PostgreSQL. El dominio no sabe que existe pg: recibe métodos como PayrollRepository.getPayrollContext(pool, input) que devuelven objetos del dominio.
Adaptador de Almacenamiento (PdfService)
Section titled “Adaptador de Almacenamiento (PdfService)”PdfService genera PDFs con Puppeteer y los guarda en LIQUIDACIONES_DIR. El dominio invoca PdfService.generatePayrollPdf(result) sin saber qué motor de renderizado se usa.
Cómo se traduce a las capas del Orchestrator
Section titled “Cómo se traduce a las capas del Orchestrator”| Capa hexagonal | Carpeta del Orchestrator | Ejemplo |
|---|---|---|
| Adaptador API | src/routes/ | payroll.ts, employees.ts |
| Adaptador Repository | src/domain/<contexto>/ | PayrollRepository.ts |
| Adaptador Almacenamiento | src/domain/common/ | PdfService.ts |
| Servicio de Dominio | src/domain/<contexto>/ | PayrollService.ts |
| Motor de Cálculo | src/domain/<contexto>/ | PayrollEngine.ts, TaxCalculator.ts |
| Modelos | src/domain/<contexto>/types | PayrollInput, PayrollResult |
Beneficios prácticos
Section titled “Beneficios prácticos”- Tests unitarios sin base de datos: el
PayrollEnginese prueba con inputs puros, sin Pool ni mocks complejos. - Reemplazo de infraestructura aislado: cambiar Puppeteer por otro generador de PDF solo afecta a
PdfService. - Razón de cambio única por archivo: un bug en cálculo de impuestos vive en
TaxCalculator, no en una ruta Express. - Onboarding más simple: un desarrollador nuevo puede leer el dominio sin entender Express ni el cliente PostgreSQL.