Skip to main content

Permisos y roles

Hay dos mecanismos distintos que conviven. Confundirlos es el error clásico.

RolPermiso
Qué esROLE.name — uno por usuarioUn código de 3-5 letras dentro de un listado
Backendthis.hasPermission(BasicRoles.ADMIN, req)el listado de getUserAccess()
FrontendisAdmin() / hasRole(name) / guard requireRolepermissions().includes("CDDND") / guard requirePermission

El catálogo está en código, no en la base​

Nunca saques nombres de roles ni códigos de permiso de un SELECT

La fuente de verdad son dos enums de TypeScript; las migraciones los siembran en la base. Si mirás la base y hay algo distinto, el que está mal es el ambiente, no el enum.

Roles — GlobalRoles en src/migrations/RoleMigrations.ts:

RolNota
ADMINTodos los permisos
ADMIN_SALTATodos los permisos
ENCARGADO_DEPOSITOSubconjunto
ENCARGADO_REGIONALSubconjunto
CALLCENTEREl valor literal es "C.A.P", no "CALLCENTER"
MODERATORNinguno
USERNinguno

Permisos — PermissionCodes en src/migrations/RolesPermissionsMigration.ts. Ese mismo archivo tiene el mapa rol → permisos. Se persisten en configuration.CommonEntityDescriptions con ENTITYCODE = ROLEPERMISSIONS, y la asignación en ROLEPERMISSIONS.

Algunos de los que más aparecen en el código:

CódigoQué habilita
CCONConfigurar
CESSNEditar el número de serie de un supply
CCSPCambiar el producto de un supply
CAPS / CEPS / CDPSAgregar / editar serie / borrar un suministro del paciente
CDSUBorrar un supply
CDDNDBorrar un detalle de remito
CVAR / CVPT / CVSTVer autorizaciones / pacientes / estadísticas
Hay un tercer enum, BasicRoles

src/services/v1/RoleService.ts define BasicRoles con solo ADMIN | USER | GUEST="MODERATOR". Es el que toma hasPermission(). No es el catálogo: es un subset de conveniencia.

Cómo viaja al front​

GET /v1/access/me devuelve { user, role, permissions: string[] }.

En la consola web, services/user-access.service.ts lo guarda en signals y lo hidrata desde localStorage (clave oxi-user-access) en el constructor, para que el gateo funcione antes de que resuelva el request.

Expone hasPermission(code), hasRole(name), hasPermissionSignal(code) e isAdmin.

Cómo se gatea​

Backend, por rol:

const perm = await this.hasPermission(BasicRoles.ADMIN, request);
if (!perm.success) return perm; // es un CustomResponse, no un boolean

Front, rutas:

{ path: '…', canActivate: [requirePermission(PermissionCodes.CAN_VIEW_STATS)] }
{ path: '…', canActivate: [requireRole('ADMIN')] }

Front, dentro de una vista — hoy con un computed y el código como string:

canDeleteDetail = computed(() => this.userAccessService.permissions().includes("CDDND"));
No hay directiva *hasPermission en este front

Se gatea con @if sobre un computed. Y el enum PermissionCodes del front casi no se usa: lo usa el guard, pero las vistas escriben el string a mano. Si agregás un permiso, sumalo igual al enum src/app/enums/permission-codes.enum.ts — es el espejo documentado del enum del backend.

isAdmin compara el rol, no el permiso​

isAdmin es role.name === 'ADMIN' estricto, no el permiso CCON. La razón: CCON también lo tiene ADMIN_SALTA, y el módulo Configuración es solo para ADMIN.

El backend usa el mismo criterio. Si desalineás uno de los dos, el menú muestra una sección que la API rechaza.

Cachés​

El acceso se cachea en dos lugares:

  • Redis, clave ACCESSREG-<userId>, TTL CACHED_USERMETADATA_DB.
  • localStorage del navegador.

Un cambio de permisos tarda en verse en los dos. Si estás probando permisos, limpiá el localStorage y esperá el TTL de Redis (o reiniciá el backend, que lo vacía al arrancar).