Documentación pública

03 · Multi-tenant

MAG-03-TNT · Multi-tenant

Una plataforma. Miles de empresas. Cero mezcla de datos.

Roustix fue diseñado como una plataforma SaaS multi-tenant desde su arquitectura. Todas las solicitudes, recursos y permisos existen dentro del contexto de un tenant, garantizando el aislamiento completo entre clientes.

El desarrollador nunca trabaja directamente con múltiples bases de datos ni necesita indicar qué tenant consultar. Ese contexto se obtiene automáticamente a partir del JWT o de la sesión web autenticada.


1 · ¿Qué es un tenant?

Un tenant es el concepto arquitectónico de aislamiento en Roustix. En Roustix, un tenant representa una empresa cliente (Empresa en el modelo de datos).

Cada tenant posee:

  • usuarios
  • sedes
  • activos
  • inventario
  • órdenes de trabajo
  • compras
  • ventas
  • configuraciones
  • permisos
  • auditoría

Todo pertenece a un único tenant.

Tenant
│
├── Usuarios
├── Activos
├── Inventario
├── Compras
├── Ventas
├── Reportes
└── Configuración

2 · Principio de aislamiento

Todos los datos deben pertenecer exactamente a un tenant.

Nunca existen registros «globales» de operación.

Tenant A (Empresa A)         Tenant B (Empresa B)
│                            │
├── Activos                  ├── Activos
├── Inventario               ├── Inventario
└── OTs                      └── OTs

No existe comunicación entre ambos espacios.


3 · ¿Cómo conoce Roustix el tenant?

  1. El cliente inicia sesión
  2. El servidor genera un JWT
  3. Dentro del JWT viaja el contexto
{
  "empresa_id": 4,
  "empresa_slug": "empresa-xyz",
  "rol": "admin"
}
  1. Cada petición posterior utiliza ese contexto:
Authorization: Bearer eyJhbGc...

El cliente nunca necesita enviar empresa_id, tenant ni company en parámetros o body.

Ver MAG-02 · Autenticación JWT.


4 · Flujo completo

4.1 · Vista integrador

Usuario
     │
     ▼
Login
     │
     ▼
JWT
     │
     ▼
Middleware Tenant
     │
     ▼
empresa_id = 4
     │
     ▼
Consulta filtrada
     │
     ▼
Respuesta

4.2 · Ciclo interno Flask

Request
      │
      ▼
tenant_required
      │
      ▼
validar JWT / sesión
      │
      ▼
g.user_id
g.empresa_id
g.user_rol
      │
      ▼
require_module()
      │
      ▼
rol_required()
      │
      ▼
View

5 · Filtrado automático

Todas las consultas deben ejecutarse filtrando por tenant (empresa_id).

Ejemplo conceptual:

Machine.query.filter_by(empresa_id=current_empresa_id())

En Roustix (patrón oficial):

query_tenant(Machine).order_by(Machine.codigo).all()

SQL equivalente generado:

SELECT *
FROM machines
WHERE empresa_id = 4
ORDER BY codigo;

Nunca:

Machine.query.all()

ni

SELECT * FROM machines;

sin contexto.


6 · Recurso de otro tenant

Si un usuario intenta acceder a un recurso de otro tenant:

GET /api/v1/maintenance/assets/152

y el activo pertenece a otro tenant, Roustix responde:

404 Not Found

No 403 Forbidden.

¿Por qué? Porque el recurso no debe revelar su existencia fuera del tenant. Mantener la existencia del recurso oculta reduce la superficie de ataque y evita enumeración de recursos entre tenants.

Error estándar

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "Activo no encontrado"
  }
}

Formato completo → MAG-06 · Manejo de errores.

Implementación: verificar_pertenencia(obj) antes de responder.


7 · Tenant en la aplicación web

La aplicación Flask utiliza el mismo principio.

La sesión contiene el usuario. El middleware resuelve el contexto del tenant:

  • tenant (empresa_id, empresa_slug)
  • plan
  • módulos activos
  • permisos del usuario

El resto de la aplicación trabaja únicamente con ese contexto.

No existen pantallas «globales» para usuarios normales.


8 · Cadena Tenant → Permisos

Toda acción en Roustix atraviesa esta cadena:

Usuario autenticado
        │
        ▼
     Tenant
        │
        ▼
      Plan
        │
        ▼
    Módulos
        │
        ▼
   Permisos
        │
        ▼
     Acción
EslabónPregunta
Tenant¿Contexto de empresa válido?
Plan¿El plan contratado permite esta capacidad?
Módulos¿maintenance / inventory activos?
Permisos¿El rol del usuario puede ejecutar la acción?

Ejemplo: tenant con módulos maintenance + inventory · usuario admin puede leer activos · operador no puede eliminar activos.

Implementación: empresa_tiene_modulo() · require_module() · @rol_required.


9 · Superadministrador

Existe un único rol con visión transversal: superadmin.

Este rol pertenece a Roustix Platform. Puede:

  • administrar tenants (empresas cliente)
  • gestionar planes
  • soporte
  • auditoría
  • impersonación controlada

Toda acción queda registrada.

Endpoints de plataforma fuera del contrato MAG público por tenant.


10 · Multi-sede

Las sedes no son tenants.

Jerarquía oficial:

Tenant (Empresa)
│
├── Sede Norte
├── Sede Centro
└── Sede Sur
SeparaciónCampo
Entre tenantsempresa_id
Entre sedes (mismo tenant)sede_id

Las sedes nunca reemplazan al tenant.

Estado: 🟡 Multi-sede en evolución.


11 · Arquitectura de datos

Tenant (Empresa)
│
├── empresa_id
├── slug
├── plan
├── estado
└── modulos_activos

Activo (machines)
│
├── id
├── empresa_id
└── ...

Inventario (productos)
│
├── id
├── empresa_id
└── ...

OT (work_orders)
│
├── id
├── empresa_id
└── ...

Toda entidad operativa incorpora empresa_id como parte de su modelo.


12 · Buenas prácticas

#Regla
1Nunca confiar en un empresa_id enviado por el cliente
2El contexto siempre proviene del JWT o la sesión
3Toda consulta debe filtrar por empresa_id
4Nunca devolver recursos de otro tenant
5Registrar accesos administrativos entre tenants
6Cache key incluye empresa_id
7Un token por usuario/servicio — no compartir entre clientes

13 · Regla de Oro

Si una consulta no contiene empresa_id, es un bug.

Sin excepciones en código de producción.


14 · Errores comunes

❌ Incorrecto✅ Correcto
Machine.query.get(id)query_tenant(Machine).filter_by(id=id).first()
empresa_id = request.json["empresa_id"]empresa_id = current_empresa_id()
SELECT * FROM machines;SELECT * FROM machines WHERE empresa_id = :empresa_id;
?empresa_id=99 en query stringTenant solo desde JWT / sesión
Cache global sin clave tenantCache key: tenant:{empresa_id}:...

15 · Implementación actual

ComponenteEstado
JWT con empresa_id✅ Implementado
Filtrado query_tenant✅ Implementado
verificar_pertenencia✅ Implementado
Roles por tenant✅ Implementado
require_module()✅ Implementado
Multi-sede🟡 En evolución
Impersonación auditada📋 Roadmap

16 · Relación con otros documentos

DocumentoRol
MAG-02 · Autenticación JWTObtención del contexto del tenant
MAG-04 · Recursos RESTRecursos filtrados automáticamente

Filosofía del capítulo

El desarrollador nunca elige el tenant. Roustix lo determina automáticamente. Así garantizamos que cada empresa cliente vea únicamente sus propios datos, sin excepciones.