Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Zapiony/PUCE_UZDI_2026/llms.txt

Use this file to discover all available pages before exploring further.

UZDI implements a Role-Based Access Control (RBAC) system that enforces authorization at two independent layers: the Vue 3 frontend router and the NestJS backend API. Every authenticated user carries a tppr_id integer (a foreign key to the seguridad.tipo_persona catalogue) that maps to a named role. The frontend router uses the numeric tppr_id to evaluate minimum-access thresholds per route, while the backend uses the string-valued Rol enum attached to the request user object and evaluated by RolesGuard before any controller handler runs.

Roles Overview

The seguridad.tipo_persona table seeds five person types on first run. Four of them are consumed by the RBAC system; the fifth (Otro) is a catch-all for non-system users.
Role (enum key)tppr_idSpanish display nameAccess level
TRABAJADOR_SOCIAL1Trabajador SocialTécnico — read-only on most modules
PSICOLOGO1PsicólogoTécnico — read-only on most modules
EDUCADOR1EducadorTécnico — read-only on most modules
JURIDICO1JurídicoTécnico — read-only on most modules
COORDINADOR2CoordinadorCan access Reportes in addition to Técnico routes
ADMINISTRADOR3AdministradorFull write access; can manage users and parameters
SUPERADMINISTRADOR3SuperadministradorSame as Administrador, reserved for platform-level operations
The tppr_id values seeded by the DDL are 1 = Técnico, 2 = Coordinador, 3 = Administrativo, 4 = Director, 5 = Otro. The frontend router guard uses tppr_id thresholds of 2 and 3 to gate protected routes. All four technical roles (Trabajador Social, Psicólogo, Educador, Jurídico) map to tppr_id = 1.

Frontend Route Guards

The Vue Router applies a beforeEach navigation guard that reads uzdi_user from localStorage and compares the user’s tppr_id against a static ROUTE_MIN_ROL map. Any navigation to a protected route where the user’s tppr_id is below the threshold is silently redirected to /app/dashboard.

Protected routes

RouteRequired tppr_idMinimum role
/app/reportes≥ 2Coordinador
/app/usuarios≥ 3Administrador
/app/parametros≥ 3Administrador
All other /app/* routes (dashboard, adolescentes, expedientes, medidas, perfil) are accessible to every authenticated user regardless of role.

Router guard code

// UZDI_FRONT/src/router/index.ts

// tppr_id mínimo requerido por ruta (1=Técnico, 2=Coordinador, 3=Administrador)
const ROUTE_MIN_ROL: Record<string, number> = {
  '/app/reportes':   2,
  '/app/usuarios':   3,
  '/app/parametros': 3,
}

router.beforeEach((to) => {
  const isLoggedIn = !!localStorage.getItem('uzdi_token')

  if (to.path.startsWith('/app') && !isLoggedIn) return '/login'
  if (to.path === '/login' && isLoggedIn) return '/app/dashboard'

  if (isLoggedIn) {
    const user = JSON.parse(
      localStorage.getItem('uzdi_user') ?? 'null'
    ) as Record<string, unknown> | null
    const tppr = (user?.tppr_id as number) ?? 1

    const entry = Object.entries(ROUTE_MIN_ROL).find(
      ([path]) => to.path.startsWith(path)
    )
    if (entry && tppr < entry[1]) return '/app/dashboard'
  }
})
The guard also enforces the unauthenticated redirect: any visit to /app/** without a valid uzdi_token in localStorage is sent to /login, and a logged-in user visiting /login is redirected back to /app/dashboard.

Backend Endpoint Guards

On the API side, RBAC is enforced through a @Roles() decorator and a RolesGuard that inspects request.user.rol on every incoming request.

@Roles() decorator

// UZDI_BACK/src/common/decorators/roles.decorator.ts

import { SetMetadata } from '@nestjs/common';
import { Rol } from '../enums/rol.enum';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: Rol[]) => SetMetadata(ROLES_KEY, roles);
The decorator stores a list of Rol enum values as metadata on the route handler or controller class using NestJS’s SetMetadata.

RolesGuard

// UZDI_BACK/src/common/guards/roles.guard.ts

import {
  Injectable, CanActivate, ExecutionContext, ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
import { Rol } from '../enums/rol.enum';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<Rol[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!requiredRoles) {
      return true;
    }

    const { user } = context.switchToHttp().getRequest();

    if (!user || !user.rol) {
      throw new ForbiddenException('No tienes un rol asignado o no has iniciado sesión.');
    }

    const hasRole = requiredRoles.includes(user.rol);
    if (!hasRole) {
      throw new ForbiddenException(
        `Acceso denegado. Se requiere alguno de estos roles: ${requiredRoles.join(', ')}`
      );
    }

    return true;
  }
}
RolesGuard uses Reflector.getAllAndOverride so that a method-level @Roles() annotation takes precedence over a class-level one. If no @Roles() annotation exists on either the handler or the class, the guard returns true (public endpoint).

Example: AdolescenteController

The adolescente controller applies @UseGuards(RolesGuard) at the class level and restricts each HTTP verb separately:
// UZDI_BACK/src/adolescente/adolescente/adolescente.controller.ts

@Controller('adolescente')
@UseGuards(RolesGuard)
export class AdolescenteController {

  // Write operations are restricted to admin roles only
  @Post()
  @Roles(Rol.ADMINISTRADOR, Rol.SUPERADMINISTRADOR)
  create(@Body() createAdolescenteDto: CreateAdolescenteDto) {
    return this.adolescenteService.create(createAdolescenteDto);
  }

  // All technical staff can list adolescents
  @Get()
  @Roles(
    Rol.ADMINISTRADOR,
    Rol.SUPERADMINISTRADOR,
    Rol.TRABAJADOR_SOCIAL,
    Rol.PSICOLOGO,
    Rol.EDUCADOR,
    Rol.JURIDICO,
  )
  findAll() {
    return this.adolescenteService.findAll();
  }

  // No @Roles on findOne — guard returns true (accessible to any authenticated user)
  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.adolescenteService.findOne(+id);
  }

  @Patch(':id')
  @Roles(Rol.ADMINISTRADOR, Rol.SUPERADMINISTRADOR)
  update(@Param('id') id: string, @Body() updateAdolescenteDto: UpdateAdolescenteDto) {
    return this.adolescenteService.update(+id, updateAdolescenteDto);
  }

  @Delete(':id')
  @Roles(Rol.ADMINISTRADOR, Rol.SUPERADMINISTRADOR)
  remove(@Param('id') id: string) {
    return this.adolescenteService.remove(+id);
  }
}
This pattern — broad read access for all technical roles, write access locked to ADMINISTRADOR / SUPERADMINISTRADOR — is the standard convention across the UZDI domain modules.

Rol Enum Reference

The enum lives in UZDI_BACK/src/common/enums/rol.enum.ts and defines the exact string values that must appear in request.user.rol for guard evaluation to succeed.
Enum keyString value
Rol.ADMINISTRADOR'Administrador'
Rol.PSICOLOGO'Psicólogo'
Rol.TRABAJADOR_SOCIAL'Trabajador Social'
Rol.EDUCADOR'Educador'
Rol.JURIDICO'Jurídico'
Rol.SUPERADMINISTRADOR'Superadministrador'
// UZDI_BACK/src/common/enums/rol.enum.ts

export enum Rol {
  ADMINISTRADOR      = 'Administrador',
  PSICOLOGO          = 'Psicólogo',
  TRABAJADOR_SOCIAL  = 'Trabajador Social',
  EDUCADOR           = 'Educador',
  JURIDICO           = 'Jurídico',
  SUPERADMINISTRADOR = 'Superadministrador',
}
The string values use Spanish characters (e.g., 'Psicólogo', 'Jurídico'). Any JWT payload or mock user object must use these exact strings — including accents — for RolesGuard comparisons to pass.

Development: MockAuthMiddleware

In local development, the NestJS application wires MockAuthMiddleware globally across all routes via AppModule. This middleware inspects the incoming Authorization header: when a Bearer token is present, it injects a synthetic request.user object so that protected endpoints can be exercised without a real authentication workflow.
MockAuthMiddleware is applied with consumer.apply(MockAuthMiddleware).forRoutes('*') in AppModule.configure(). It only sets request.user when the request already carries an Authorization: Bearer <token> header — requests without that header leave req.user unset. To test a specific role in development, send any bearer token and update the mock user’s rol field inside the middleware to one of the Rol enum string values (e.g., 'Administrador'). Remove or conditionally disable this middleware before deploying to any shared environment.

Build docs developers (and LLMs) love