Permisos y roles
Hay dos mecanismos distintos que conviven. Confundirlos es el error clásico.
| Rol | Permiso | |
|---|---|---|
| Qué es | ROLE.name — uno por usuario | Un código de 3-5 letras dentro de un listado |
| Backend | this.hasPermission(BasicRoles.ADMIN, req) | el listado de getUserAccess() |
| Frontend | isAdmin() / hasRole(name) / guard requireRole | permissions().includes("CDDND") / guard requirePermission |
El catálogo está en código, no en la base
SELECTLa 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:
| Rol | Nota |
|---|---|
ADMIN | Todos los permisos |
ADMIN_SALTA | Todos los permisos |
ENCARGADO_DEPOSITO | Subconjunto |
ENCARGADO_REGIONAL | Subconjunto |
CALLCENTER | El valor literal es "C.A.P", no "CALLCENTER" |
MODERATOR | Ninguno |
USER | Ninguno |
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ódigo | Qué habilita |
|---|---|
CCON | Configurar |
CESSN | Editar el número de serie de un supply |
CCSP | Cambiar el producto de un supply |
CAPS / CEPS / CDPS | Agregar / editar serie / borrar un suministro del paciente |
CDSU | Borrar un supply |
CDDND | Borrar un detalle de remito |
CVAR / CVPT / CVST | Ver autorizaciones / pacientes / estadísticas |
BasicRolessrc/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"));
*hasPermission en este frontSe 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>, TTLCACHED_USERMETADATA_DB. localStoragedel 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).