Ir al contenido

Arquitectura Tecnica

Zentto es un ERP modular multi-tenant, multi-pais y multi-almacen construido sobre una arquitectura de microservicios frontend con API centralizada y soporte dual de base de datos.

CapaTecnologia
APINode.js + Express + TypeScript
FrontendNext.js (App Router) + React + TypeScript
UIMUI + MUI X (DataGrid, DatePicker)
StateTanStack Query + Zustand
AuthJWT (API) + NextAuth (Frontend)
ValidacionZod (API + Frontend)
BD primariaSQL Server (mssql)
BD alternativaPostgreSQL 16 (pg)
CacheRedis (ioredis, opcional)
ContenedoresDocker + PM2
CI/CDGitHub Actions
Reverse proxyNginx
DatqBoxWeb/
web/
api/ # API Node + Express + TypeScript
src/
app.ts # Registro de rutas
index.ts # Entry point
config/ # Variables de entorno
db/ # Capa de abstraccion BD
query.ts # callSp, callSpOut, callSpTx
mssql.ts # Driver SQL Server
pg.ts # Driver PostgreSQL
middleware/ # JWT, RBAC, audit-trail, datetime
modules/ # 60+ modulos de negocio
_shared/ # Utilidades compartidas
clientes/
inventario/
contabilidad/
...
sqlweb/ # Scripts SQL Server
includes/sp/ # Stored Procedures (T-SQL)
run_all.sql # Master script (SSMS)
sqlweb-pg/ # Scripts PostgreSQL
includes/sp/ # Functions (PL/pgSQL)
run_all.sql # Master script (psql)
modular-frontend/ # Monorepo micro-frontends
apps/ # 15 micro-apps Next.js
shell/ # App host principal
ventas/
compras/
contabilidad/
inventario/
bancos/
nomina/
pos/
restaurante/
ecommerce/
auditoria/
crm/
manufactura/
flota/
logistica/
packages/ # Paquetes compartidos
shared-api/ # Cliente API, tipos, formateo
shared-auth/ # Autenticacion compartida
shared-ui/ # Componentes UI reutilizables
module-admin/ # Modulo administracion
module-bancos/ # Modulo bancos
module-contabilidad/ # Modulo contabilidad
module-compras/ # Modulo compras
module-crm/ # Modulo CRM
module-ecommerce/ # Modulo ecommerce
module-flota/ # Modulo flota
module-inventario/ # Modulo inventario
module-logistica/ # Modulo logistica
module-manufactura/ # Modulo manufactura
module-nomina/ # Modulo nomina
module-auditoria/ # Modulo auditoria
contracts/
openapi.yaml # Contrato API OpenAPI
docker/
Dockerfile.api # Imagen API
Dockerfile.frontend # Imagen Frontend (PM2)
pm2.config.cjs # Ecosistema PM2 (puertos 3000-3010)
.github/workflows/
deploy-api.yml # CI/CD API
deploy-frontend.yml # CI/CD Frontend

Zentto soporta dos motores de base de datos en paralelo. El switch se controla con una sola variable de entorno:

DB_TYPE=sqlserver # Microsoft SQL Server
DB_TYPE=postgres # PostgreSQL

La API es identica sin importar el motor activo. Los servicios llaman callSp('usp_NombreDelSP', params) y el helper resuelve internamente que driver usar.

La capa de abstraccion convierte automaticamente:

DireccionTransformacion
Parametros de entradaPascalCasep_snake_case (para PG)
Columnas de salidap_snake_casePascalCase (desde PG)
XML → JSONHeaderXmlHeaderJson (PG usa JSONB, no XML)

Tabla de traduccion SQL Server → PostgreSQL

Sección titulada «Tabla de traduccion SQL Server → PostgreSQL»
SQL ServerPostgreSQL
CREATE PROCEDURE usp_XCREATE OR REPLACE FUNCTION usp_x(...) RETURNS ... LANGUAGE plpgsql
@Param INTp_param INT
NVARCHAR(n)VARCHAR(n)
BITBOOLEAN
DATETIME / DATETIME2TIMESTAMP
INT IDENTITY(1,1)INT GENERATED ALWAYS AS IDENTITY
SYSUTCDATETIME()NOW() AT TIME ZONE 'UTC'
ISNULL(x, d)COALESCE(x, d)
OPENJSON(...)jsonb_array_elements(...)
FOR JSON PATHjson_agg(row_to_json(...))
BEGIN TRY...CATCHEXCEPTION WHEN OTHERS THEN
MERGE...WHEN NOT MATCHEDINSERT...ON CONFLICT DO NOTHING/UPDATE

Todo cambio de base de datos DEBE implementarse en AMBOS directorios. No hay excepciones. Checklist obligatorio:

  1. Crear/modificar SP en sqlweb/includes/sp/ (SQL Server)
  2. Crear/modificar funcion equivalente en sqlweb-pg/includes/sp/ (PostgreSQL)
  3. Si es tabla nueva: DDL en ambos directorios
  4. Si es seed: seed en ambos directorios
  5. Actualizar run_all.sql de ambos motores si hay archivo nuevo
  6. Probar con DB_TYPE=sqlserver y con DB_TYPE=postgres

Los tres helpers en web/api/src/db/query.ts son la unica forma de interactuar con la BD:

Ejecuta un stored procedure y retorna las filas resultantes como array.

const rows = await callSp<Customer>('usp_Master_Customer_List', {
CompanyId: 1,
Search: 'acme',
Page: 1,
PageSize: 50
});

Ejecuta un SP con parametros de salida. Retorna { rows, output }.

const { rows, output } = await callSpOut<SalesDoc>(
'usp_AR_SalesDocument_List',
{ CompanyId: 1, Page: 1, PageSize: 50 },
['TotalCount']
);
// output.TotalCount = 142

Ejecuta multiples SPs dentro de una transaccion atomica.

await callSpTx([
{ sp: 'usp_AR_SalesDocument_Create', params: { ... } },
{ sp: 'usp_Inv_Stock_Decrease', params: { ... } },
]);

Formato: usp_[Schema]_[Entity]_[Action]

ComponenteDescripcionEjemplo
usp_Prefijo obligatorio-
SchemaEsquema logico (ar, ap, acct, master…)AR
EntityEntidad principalSalesDocument
ActionOperacion (List, Get, Create, Update, Void)List

Ejemplos completos:

  • usp_AR_SalesDocument_List — listar documentos de venta
  • usp_Master_Customer_Create — crear cliente
  • usp_Acct_JournalEntry_Post — contabilizar asiento

Las listas retornan un parametro de salida TotalCount para paginacion:

  • SQL Server: @TotalCount INT OUTPUT
  • PostgreSQL: columna "TotalCount" en RETURNS TABLE

Las operaciones de escritura retornan resultado y mensaje:

  • SQL Server: @Resultado INT OUTPUT, @Mensaje NVARCHAR(500) OUTPUT
  • PostgreSQL: RETURNS TABLE("ok" BOOLEAN, "mensaje" VARCHAR)

Zentto es multi-tenant por diseno. Cada tabla de negocio tiene:

  • CompanyId INT — identificador de empresa
  • BranchId INT — identificador de sucursal

El scope activo se obtiene del token JWT y se inyecta automaticamente en cada consulta mediante getActiveScope().

Las tablas de configuracion viven en el schema cfg:

  • cfg.Company — empresas registradas
  • cfg.Branch — sucursales por empresa
  • cfg.AppSetting — configuraciones por empresa

Zentto soporta multiples paises con reglas fiscales diferenciadas:

  • cfg.Company.CountryCode — codigo ISO del pais (VE, ES, CO, MX, US)
  • Plugins fiscales por pais en web/api/src/modules/fiscal/
  • Retenciones, libros fiscales y formatos de factura especificos por jurisdiccion

Paises actualmente soportados:

PaisCodigoFuncionalidades fiscales
VenezuelaVEISLR, IVA, retenciones, impresoras fiscales
EspanaESVerifactu, SII
ColombiaCOFacturacion electronica DIAN
MexicoMXCFDI
USAUSSales tax

El sistema soporta multiples almacenes con control granular:

  • inv.Warehouse — catalogo de almacenes
  • WarehouseId en todas las operaciones de stock
  • Zonas y ubicaciones dentro de cada almacen (inventario avanzado)
  • Transferencias entre almacenes
  • Stock por ubicacion con trazabilidad de lotes y seriales

Todas las fechas se almacenan en UTC-0. No hay excepciones.

  • API: middleware datetime.ts convierte request → UTC, response → timezone local
  • Frontend: hook useTimezone() + funciones formatDate/formatDateTime/toDateOnly
  • SQL: SYSUTCDATETIME() (SQL Server) / NOW() AT TIME ZONE 'UTC' (PostgreSQL)
  • GETDATE() esta prohibido — 0 ocurrencias en el codebase

Toda tabla de negocio incluye columnas de auditoria:

ColumnaTipoDescripcion
CreatedAtTIMESTAMPFecha de creacion (UTC)
UpdatedAtTIMESTAMPFecha de ultima modificacion (UTC)
CreatedByINTUserId del creador
UpdatedByINTUserId del ultimo editor
IsActiveBOOLEANBorrado logico

Las integraciones entre modulos (contabilidad, auditoria, notificaciones, inventario) usan el patron best-effort: nunca bloquean la operacion principal.

// Ejemplo: venta genera asiento contable
try {
await generateAccountingEntry(saleData);
} catch {
// Best-effort: nunca bloquea la venta
}

Esto garantiza que:

  1. La operacion principal siempre se completa
  2. Un fallo en contabilidad/auditoria/notificaciones no afecta al usuario
  3. Los errores se registran silenciosamente para revision posterior

Modulos que usan best-effort:

  • Contabilidad automatica (asientos desde ventas, compras, bancos, etc.)
  • Auditoria (registro en audit.AuditLog)
  • Notificaciones (envio via Zentto Notify)
  • Movimientos de inventario (desde POS, manufactura, logistica)