Skip to content

Plataforma técnica · Orchestrator

Tenants

Orchestrator Command Multi-tenant

TenantService administra el ciclo de vida de las empresas (tenants) registradas en la plataforma SaaS. Es la primera ficha del modelo multi-tenant: sin un tenant en command.tenants, un usuario no puede ser asignado ni resolver su base de negocio.

CampoTipoObservaciones
idUUIDClave primaria.
rutstringIdentificador tributario chileno (único).
business_namestringRazón social.
trade_namestring?Nombre de fantasía.
emailstring?Contacto principal.
phonestring?Teléfono.
addressstring?Dirección, comuna, ciudad.
communestring?
citystring?
tax_regimestring?Régimen tributario (14A, 14D N°3, 14D N°8, etc.).
statusbooleanActivo/inactivo (no se borran físicamente en la práctica).
created_attimestamp
updated_attimestamp

La vista command.v_tenant_info agrega columnas derivadas usadas por el listado del Command Center:

Campo (derivado)Origen
total_databasesConteo de bases asociadas al tenant.
active_databasesBases con estado activo.
total_usersUsuarios en auth.users para el tenant.
active_usersUsuarios con sesión vigente.
flowchart LR
  R["routes/command/tenant.ts"]
  RDB["routes/command/tenant-db.ts"]
  S["TenantService"]
  REP["TenantRepository"]
  ADMIN["lib/db-admin.ts<br/>createTenantDatabase()"]
  CP[("centralPool<br/>nostromo_command")]
  PG[("PostgreSQL admin<br/>CREATE DATABASE")]

  R -- GET/POST/PUT/DELETE --> S
  S --> REP --> CP
  RDB -- POST /create --> ADMIN
  ADMIN --> PG

El provisionamiento de la base de datos del tenant es un paso separado y voluntario: routes/command/tenant-db.ts POST a /api/tenant-db/create invoca createTenantDatabase(tenant_id, db_name) desde lib/db-admin.ts, que ejecuta el CREATE DATABASE y registra la nueva base en nostromo_command.tenant_databases.

Lista todos los tenants (sin paginación dura — limita a 1000 vía SQL) o un detalle si se pasa ?id=<uuid>.

Terminal window
# Listar
curl -b "sid=$JWT" http://localhost:8000/api/tenant
# Detalle
curl -b "sid=$JWT" "http://localhost:8000/api/tenant?id=550e8400-e29b-41d4-a716-446655440000"

Cualquier usuario autenticado puede leer. El listado consume command.v_tenant_info para incluir los conteos derivados.

Crea un nuevo tenant. Requiere authorizeRoute (la matriz RBAC controla quién puede invocar este endpoint, típicamente SUPER_ADMIN).

POST /api/tenant
{
"rut": "60004317-7",
"business_name": "Albornoz Contadores SpA",
"trade_name": "Albornoz",
"email": "[email protected]",
"phone": "+56 9 1234 5678",
"address": "Av. Apoquindo 1234",
"commune": "Las Condes",
"city": "Santiago",
"tax_regime": "14D N°3",
"status": true
}

Validación vía express-validator: rut y business_name no vacíos, email válido. La unicidad del RUT la garantiza la constraint UNIQUE en la tabla.

Actualiza un tenant existente. Requiere SUPER_ADMIN (verificación adicional en el handler).

Terminal window
curl -X PUT -b "sid=$JWT" \
-H "Content-Type: application/json" \
-d '{"business_name":"Albornoz Contadores Ltda","status":true,...}' \
"http://localhost:8000/api/tenant?id=550e8400-..."

Elimina físicamente el registro. Requiere SUPER_ADMIN. Retorna 204 No Content.

Crea la base de datos PostgreSQL del tenant. Requiere SUPER_ADMIN y un db_name que cumpla el regex ^[a-zA-Z_][a-zA-Z0-9_]+$.

POST /api/tenant-db/create
{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"db_name": "nostromo_60004317"
}

Respuesta exitosa:

Response
{
"success": true,
"message": "Database created and schema applied",
"database_name": "nostromo_60004317"
}

La convención de nombres es nostromo_<rut_sin_dv>. Una vez creada, la base queda lista para sincronizar el plan de cuentas template — ver Plan de Cuentas Sync.

sequenceDiagram
  autonumber
  participant Admin as Super Admin
  participant API as Orchestrator
  participant Cmd as nostromo_command
  participant Tpl as accounting_template
  participant New as nostromo_60004317

  Admin->>API: POST /api/tenant (datos empresa)
  API->>Cmd: INSERT INTO command.tenants
  Cmd-->>API: tenant_id
  API-->>Admin: 201 { tenant_id }

  Admin->>API: POST /api/tenant-db/create
  API->>Cmd: INSERT INTO command.tenant_databases
  API->>New: CREATE DATABASE + esquemas
  Cmd-->>API: ok
  API-->>Admin: 200 { database_name }

  Admin->>API: POST /api/command/plan-cuentas/sync/:db
  API->>Tpl: SELECT plan_contable
  API->>New: INSERT plan_contable (faltantes)
  API-->>Admin: SyncResult

A partir de este momento el tenant tiene una base operativa con plan de cuentas inicial; lo que falta es asignar usuarios (auth.users.tenant_db = 'nostromo_60004317') para que tenantResolver.getDatabaseNameForUser resuelva al login.

  • TenantService no implementa hooks post-creación (notificación, seeding adicional, etc.). Si se necesita, agregar en createTenant() antes del return.
  • No hay endpoint para listar las bases de un tenant; esa info viene en v_tenant_info.total_databases. Si se requiere CRUD de tenant_databases, agregar en routes/command/tenant-db.ts.