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.

RolesGlobalRoles 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

PermisosPermissionCodes 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).