Visión general de la arquitectura
Zentto es un ecosistema de productos alrededor de un ERP modular para empresas de
Latinoamérica. El núcleo es el monorepo zentto-web (API + micro-frontends), rodeado de
microservicios de plataforma (auth, payments, notify, geo, kyc…), verticales
standalone (hotel, medical, education, rental, tickets, inmobiliario, POS, restaurante…),
apps móviles (Capacitor y Expo) y web components reutilizables
(datagrid, board, gantt…). En total, la organización mantiene ~70 repositorios.
Todo despliega por CI/CD a un servidor de producción en Hetzner, con el ambiente dev en un
centro de datos local (Proxmox dellxeon).
Diagrama de infraestructura
Hetzner CX53] C --> D[Docker ~70 contenedores] D --> E[API ERP :4000
Node.js + Express] D --> F[Micro-apps Next.js
PM2 :3000-3017] D --> G[Microservicios
auth · payments · notify · crm · pulse · geo · kyc ...] D --> H[Verticales
hotel · medical · education · rental ...] E --> I[(PostgreSQL 16
en el host)] G --> I H --> I D --> J[Loki + Grafana
logs.zentto.net] style A fill:#6C63FF,color:#fff style B fill:#f59e0b,color:#fff style C fill:#10b981,color:#fff style E fill:#FF6584,color:#fff style I fill:#374151,color:#fff
Componentes principales
API (Express + TypeScript)
Servidor REST en web/api/, organizado en ~106 módulos de dominio
(src/modules/*). Toda la lógica de datos pasa por funciones PL/pgSQL —
cero SQL directo en TypeScript.
Micro-frontends (Next.js 16)
Micro-apps independientes en web/modular-frontend/apps/ (shell + 16 módulos
en producción), cada una en su puerto bajo PM2, con paquetes compartidos
@zentto/*.
PostgreSQL — motor único
PostgreSQL 16 en dev y producción. Cambios de esquema por migraciones Goose;
lógica en funciones PL/pgSQL (sqlweb-pg/). La arquitectura dual con SQL Server
se retiró en 2026 (detalle).
Contratos OpenAPI
Especificación 3.0.3 con ~880 paths en web/contracts/openapi.yaml, con
gate de cobertura en CI y Swagger UI protegido en la API
(detalle).
Estructura de directorios clave (zentto-web)
zentto-web/
├── web/
│ ├── api/ # API Express + TypeScript
│ │ ├── src/
│ │ │ ├── modules/ # ~106 módulos de dominio (*.routes.ts + *.service.ts)
│ │ │ ├── db/ # query.ts, pg-pool-manager, tenant-resolver
│ │ │ ├── middleware/ # auth, tenant, rbac, datetime, error-handler...
│ │ │ ├── auth/ config/ jobs/ lib/ utils/ webhooks/
│ │ │ └── app.ts # createApp(): middleware + montaje de rutas
│ │ ├── migrations/postgres/ # Migraciones Goose (fuente de verdad de BD)
│ │ ├── sqlweb-pg/ # Baseline + funciones plpgsql + seeds
│ │ └── sqlweb-mssql/ # T-SQL legacy congelado (no se toca)
│ ├── modular-frontend/
│ │ ├── apps/ # shell + micro-apps Next.js (una por módulo)
│ │ └── packages/ # shared-ui, shared-api, shared-auth, module-*...
│ ├── platform-client/ # SDK @zentto/platform-client
│ ├── datagateway-agent/ # Agente on-premise del Data Gateway
│ └── contracts/openapi.yaml # Contrato API (~880 paths)
├── docker/ # Dockerfile.api, Dockerfile.frontend, pm2.config.cjs
├── .github/workflows/ # 21 workflows (deploy prod/dev, tests, security...)
└── nginx/ # Referencia del reverse proxy (fuente: zentto-infra) Flujo de una solicitud típica
- El navegador solicita
https://app.zentto.net/inventario - Cloudflare (proxy + TLS) pasa la solicitud a Nginx en el servidor
- Nginx enruta al shell (:3000), que hace proxy interno a la micro-app de inventario
- La app Next.js hace fetch a
https://api.zentto.net/v1/inventario/...con la cookie de sesión httpOnly - En la API: CORS → CSRF por Origin → resolución de tenant por subdominio →
requireJwt(cookiezentto_token) → RBAC → normalización de fechas a UTC - El servicio del módulo llama la función PL/pgSQL vía
callSp(), dentro del pool del tenant y con elcompany_idfijado para Row-Level Security - La respuesta localiza las fechas al timezone de la empresa y vuelve como JSON
Principios de diseño
- Lógica en API, no en UI — el frontend es solo presentación
- Solo PostgreSQL — todo cambio de BD es migración Goose + función PL/pgSQL; nunca T-SQL
- SQL parametrizado siempre — cero SQL directo en TypeScript
- UTC-0 a fuego — la BD almacena UTC; el middleware localiza por empresa
- Contratos primero — OpenAPI se actualiza antes de codificar el frontend (gate en CI)
- Tablas con
<ZenttoDataGrid>— nunca<table>HTML a mano - Sin datos mock — las pantallas consumen hooks de API reales
- Apps nuevas standalone — los verticales no duplican módulos del ERP
- Deploy solo por CI/CD — rama
developer→ PR →main→ producción