RBAC Multitenant (Multi Empresa): Organigrama → Paquete → Rol
Un rol genérico no alcanza cuando un mismo sistema atiende a varios clientes: "editor" no dice nada si no dice editor de qué entidad. El mecanismo real que resuelve esto — grafo antes que roles, snapshot tipo Unix, cascada de templates — verificado línea por línea contra código y esquema real, no diseño aspiracional.
El Problema Real
Un Rol sin Relación es un Rol Huérfano
Los tutoriales genéricos de RBAC modelan el rol como si fuera propiedad exclusiva del usuario: "Juan es editor". En un sistema multitenant (multi empresa) esa frase está incompleta — Juan puede ser editor en la entidad A y no tener ningún acceso en la entidad B, con el mismo perfil y el mismo rol nominal. Si el rol se asigna sin atarlo a una relación explícita con la entidad, el sistema no tiene con qué decidir el alcance: o le da acceso a todo, o a nada.
El error común es diseñar primero el catálogo de roles y recién después preguntarse cómo se relacionan con el multitenant. El orden correcto es al revés: la relación existe primero, el rol se aplica sobre ella.
El Mecanismo
6 Pasos, en Orden, Verificados Contra el Código
Nombrado en español para que se entienda de corrido, con el nombre técnico real al lado — para quien va a implementarlo, no solo a leerlo.
Organigrama declara posición = relación + paquete
Cada posición del organigrama de la entidad resuelve un relation_kind + package_code vía position_id. El onboarding masivo de colaboradores usa la posición como override — nadie asigna un paquete suelto a mano. Si el relation_kind no tiene templates propios en el tenant, cae al tenant global como fallback; sin templates en ninguno de los dos, el vínculo se crea igual sin roles: no-op silencioso, no un fallo que bloquee el onboarding.
Grafo entidad-perfil, antes que cualquier rol
Dos tablas, no una: entity_relationships modela perfil↔entidad (scope_entity_id, relation_kind, position_id) y entity_link modela entidad↔entidad (relation_kind + vigencia valid_from/valid_to) — ahí vive la jerarquía y el contexto sobre el que se resuelve el alcance. Regla dura: asignar roles a un perfil sin esa relación lo deja huérfano — las queries del tenant filtran por scope, y sin grafo devuelven vacío aunque el perfil tenga roles ahí. Fuente: Entity/Graph.php en GitHub ↗
Paquetes de roles declaran qué se otorga
Un paquete (role_packages: código, label, scope_entity_id) no es un rol suelto: contiene una lista de roles (role_package_items) más las categorías de navegación y permisos de ruta que trae consigo (role_package_categories, role_package_route_permissions). Editar el paquete una vez cambia el criterio para todo el que lo tenga asignado, sin tocar perfil por perfil.
Desempaquetar es literal — y stateless en el sentido justo
RolePackageService::expand($packageCode, $scopeEntityId) resuelve el paquete a su lista de roles/categorías/permisos individuales en el momento, sin materializar un registro de "este perfil tiene este paquete", no existe una tabla profile_packages que haya que mantener sincronizada. Fuente: RolePackageService.php#L32 en GitHub ↗
Lo que persiste es profile_roles, con trazabilidad
Cada rol desempaquetado queda escrito en profile_roles, cada fila con source_package_code apuntando a qué paquete la originó — mismo modelo aditivo que los permisos tipo Unix. El paquete en sí se puede editar y re-expandir sin migrar estado viejo, y un rol agregado a mano (sin source_package_code) queda igual de trazable como "no vino de un paquete".
Detectar el desvío (drift) antes de reaplicar
diffPackageVsProfile() vuelve a correr el mismo expand() y lo compara contra lo que el perfil tiene realmente hoy en profile_roles — así se detecta el desvío (qué cambió el paquete desde la última vez) y se puede reaplicar sin duplicar roles ni pisar los que se agregaron a mano. Fuente: RolePackageService.php#L296 en GitHub ↗
Scope Transversal
Un Mecanismo, no un Feature de un Módulo
La verificación de rol acepta opcionalmente el scope de la entidad contra la que se está preguntando — permite chequear tanto contra el scope activo de la sesión como contra un scope explícito, para casos de gobernanza donde un administrador necesita validar permisos de un perfil distinto al propio.
Esto no es exclusivo de un módulo de búsqueda ni de un caso de uso puntual: aparece en la gestión de perfiles, en la administración de tenants, en la navegación, y en endpoints de entidades, captura de datos clínicos, autenticación y remuneraciones. Es el mecanismo transversal de scoping multitenant de toda la plataforma — cualquier dominio nuevo lo hereda, no lo reimplementa.
Identidad Multi-Perfil
Una Cuenta, Varias Caras Según el Tenant
El esquema real no vincula una cuenta a un único perfil: la relación es de muchos-a-uno en el sentido opuesto al que se suele asumir — una cuenta puede tener más de un perfil, cada uno atado a una entidad distinta. El login por defecto resuelve al perfil primario para no complicar el caso simple, pero el modelo de datos no restringe estructuralmente a uno solo.
La distinción importa para diseñar bien el onboarding de colaboradores que trabajan con más de un cliente o más de una unidad de negocio: la cuenta es la identidad estable y verificada una sola vez; los perfiles son las "caras" de esa identidad en cada tenant, cada uno con su propio grafo y su propio set de roles — sin duplicar la verificación de identidad por cada relación nueva.
Estado Real — Sin Inflar
Implementado, no Propuesto — con sus Límites Declarados
Los 6 pasos de este mecanismo están en producción, no son un diseño de referencia: se verificaron contra el código real y el esquema de base de datos, no contra documentación aspiracional. Lo que este mecanismo NO resuelve: filtrar resultados de un motor de búsqueda semántico (RAG) por rol es una capa aparte que se diseña sobre esta base, no algo que RBAC entregue automáticamente — y demostrar la "medida de seguridad apropiada" ante un requerimiento de la Ley 21.719 exige, además de esta arquitectura, la documentación del proceso, no solo el código funcionando.
Contenido relacionado
RBAC como Medida de Seguridad Apropiada (Ley 21.719)
El panorama completo: 3 capas, 6 scopes de ruta, y por qué RBAC responde el requisito legal de aislar datos por rol.
Ver el panorama completo - 6 Scopes de Ruta + CumplimientoProtección de Datos Personales para Empresas (Ley 21.719)
El checklist completo de la ley: este mecanismo es la evidencia técnica detrás del punto 4 (medidas de seguridad apropiadas).
Ver el checklist - Checklist Real de 7 EntregablesEstrategias de Búsqueda en RAG Empresarial
Cuando el sistema además incorpora búsqueda semántica, este mismo grafo de relación es la base para decidir qué puede ver cada usuario en los resultados.
Leer análisis - Sparse · Denso · Híbrido