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.

Esta página describe cómo se organizan y comunican las tres capas del sistema UZDI: el SPA de Vue 3, la API REST de NestJS y la base de datos PostgreSQL con schemas separados por dominio.

Resumen de la arquitectura

UZDI sigue una arquitectura de tres capas clara y desacoplada:
  1. Frontend SPA — Vue 3 + Vite, corre en el navegador del usuario (localhost:5174 en desarrollo).
  2. Backend REST API — NestJS, expone todos sus endpoints bajo el prefijo global /api/v1 (localhost:3000).
  3. Base de datos relacional — PostgreSQL con 6 schemas funcionales, triggers de auditoría automáticos y sin migraciones automáticas (TypeORM en modo synchronize: false).
┌─────────────────────────────┐
│   Browser (Vue 3 SPA)       │
│   Vite HMR · localhost:5174 │
│   axios + JWT interceptor   │
└────────────┬────────────────┘
             │ HTTP/REST (JSON)
             │ Authorization: Bearer <token>

┌─────────────────────────────┐
│   NestJS REST API           │
│   localhost:3000/api/v1     │
│   ValidationPipe            │
│   SanitizationPipe          │
│   RolesGuard (RBAC)         │
└────────────┬────────────────┘
             │ TypeORM queries

┌─────────────────────────────┐
│   PostgreSQL                │
│   6 schemas                 │
│   Audit triggers            │
│   Last-modified triggers    │
└─────────────────────────────┘

Frontend architecture

Tooling y entry point

El frontend se inicializa en UZDI_FRONT/src/main.ts:
import { createApp } from 'vue'
import './design-system.css'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')
HerramientaRol
Vite 8Bundler y servidor de desarrollo con HMR
Vue 3.5 Composition APIFramework reactivo con <script setup>
TypeScript 6Tipado estático en toda la codebase
vue-tscVerificación de tipos en tiempo de build

Router — 9 rutas de aplicación

El router usa createWebHistory() (HTML5 history API) y define 10 rutas totales: una de login pública y 9 vistas protegidas bajo el layout /app/*.
// UZDI_FRONT/src/router/index.ts (extracto)
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/',      redirect: '/login' },
    { path: '/login', component: LoginView },
    {
      path: '/app',
      component: AppShell,          // Layout envolvente
      redirect: '/app/dashboard',
      children: [
        { path: 'dashboard',    component: DashboardView },
        { path: 'adolescentes', component: AdolescentesView },
        { path: 'expedientes',  component: ExpedientesView },
        { path: 'medidas',      component: MedidasView },
        { path: 'reportes',     component: ReportesView },
        { path: 'perfil',       component: PerfilView },
        { path: 'usuarios',     component: UsuariosView },
        { path: 'parametros',   component: ParametrosView },
      ],
    },
  ],
})
Un beforeEach guard verifica uzdi_token en localStorage y el tppr_id del usuario para controlar el acceso a rutas protegidas:
// Umbrales mínimos de rol por ruta
const ROUTE_MIN_ROL: Record<string, number> = {
  '/app/reportes':   2,  // Coordinador o superior
  '/app/usuarios':   3,  // Solo Administrador
  '/app/parametros': 3,  // Solo Administrador
}

AppShell — Layout global

AppShell.vue es el componente raíz de todas las vistas autenticadas. Provee:
  • Sidebar de 264px con navegación agrupada (General, Gestión, Análisis, Configuración) e íconos Lucide. Badges de conteo en tiempo real. Estado activo dinámico con gradiente azul y línea amarilla.
  • Topbar con breadcrumb, zona UZDI del usuario y campana de notificaciones.
  • Búsqueda global (Ctrl+K): overlay con filtrado en tiempo real sobre adolescentes y expedientes cargados desde la API.
  • Responsive: el sidebar se colapsa en móvil; un botón hamburger en la topbar abre un overlay.

Services layer

Toda comunicación con el backend pasa por una instancia axios centralizada en src/services/api.ts que inyecta automáticamente el token JWT en cada petición:
// src/services/api.ts (lógica conceptual)
const api = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL, // http://localhost:3000/api/v1
})

api.interceptors.request.use((config) => {
  const token = localStorage.getItem('uzdi_token')
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})
ServicioResponsabilidad
auth.service.tsLogin y cambio de contraseña
adolescente.service.tsCRUD /api/v1/adolescentes
expediente.service.tsCRUD /api/v1/expedientes
medida.service.tsCRUD /api/v1/medidas-socioeducativas
usuario.service.tsCRUD /api/v1/users
uzdi.service.tsGET/PATCH /api/v1/uzdi/:id
catalogo.service.tsCatálogos de referencia (etnias, UZDIs, etc.)

Composables

ComposableDescripción
useAuthEstado global de sesión: token, datos del usuario (tppr_id, nombre, UZDI), logout
useCatalogosCarga perezosa (lazy) de catálogos de referencia compartidos entre formularios

Backend architecture

Bootstrap y configuración global

El archivo UZDI_BACK/src/main.ts configura toda la aplicación antes de escuchar peticiones:
import { NestFactory }      from '@nestjs/core';
import { AppModule }        from './app.module';
import { ValidationPipe }   from '@nestjs/common';
import { SanitizationPipe } from './common/pipes/sanitization.pipe';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // CORS — solo acepta peticiones del frontend local
  app.enableCors({
    origin: 'http://localhost:5174',
    methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
  });

  // Prefijo global de versionamiento
  app.setGlobalPrefix('api/v1');

  // Pipe 1: Sanitización de strings con DOMPurify (corre primero)
  app.useGlobalPipes(new SanitizationPipe());

  // Pipe 2: Validación con class-validator
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,           // elimina propiedades no decoradas
    forbidNonWhitelisted: true,// lanza error si llegan propiedades extra
    transform: true,           // convierte payloads al tipo del DTO
  }));

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

Módulos de dominio

UZDI_BACK/src/
├── auth/                       # POST /api/v1/auth/login
│                               # POST /api/v1/auth/change-password
├── users/                      # CRUD /api/v1/users
├── adolescente/                # adolescente, expediente, medida-socioeducativa
│                               # infracción, GDO, etnia, estado-civil, nacionalidad
├── contexto-familiar/          # representante, tipo-parentesco,
│                               # adolescente-representante
├── estructura-institucional/   # UZDI, provincia, cantón
├── gestion-documental/         # documento-expediente, tipo-documento
├── seguridad/                  # persona, perfil, permiso, acción,
│                               # sesión, historial
├── talleres-actividades/       # taller, asistencia-taller
└── common/                     # SanitizationPipe, RolesGuard,
                                # MockAuthMiddleware

Global pipes — flujo de validación y sanitización

Cada petición entrante con un cuerpo JSON atraviesa dos pipes globales en cascada:
Request body (raw JSON)


  SanitizationPipe          ← DOMPurify en todos los campos string


  ValidationPipe            ← class-validator (whitelist, forbidNonWhitelisted, transform)


  Controller handler
El orden de registro importa: SanitizationPipe se registra primero para que los strings ya estén limpios cuando ValidationPipe los transforma a instancias de DTO.

TypeORM configuration

TypeORM se configura en AppModule con las siguientes opciones clave:
TypeOrmModule.forRoot({
  type: 'postgres',
  url: process.env.DATABASE_URL,
  autoLoadEntities: true,   // las entidades se registran por módulo
  synchronize: false,        // el esquema NUNCA se modifica automáticamente
})
synchronize: false es intencional. La estructura de la base de datos se controla exclusivamente a través del script DDL_UZDI_FINAL.sql. Esto garantiza que los schemas, triggers y constraints definidos en el DDL no sean sobreescritos por TypeORM.

Database architecture

Schemas de PostgreSQL

La base de datos uzdi_db está organizada en 6 schemas funcionales, cada uno correspondiente a un dominio del sistema:
SchemaDescripciónTablas principales
estructura_institucionalJerarquía geográfica e institucionaluzdi, provincia, canton
seguridadUsuarios, roles, permisos y auditoríapersona, perfil, permiso, accion, sesion, historial
adolescenteDatos del adolescente y su procesoadolescente, expediente, medida_socioeducativa, infraccion, etnia, estado_civil, nacionalidad
contexto_familiarEntorno familiar y representantesrepresentante, tipo_parentesco, adolescente_representante
talleres_actividadesTalleres formativos y asistenciataller, asistencia_taller
gestion_documentalDocumentos adjuntos a expedientesdocumento_expediente, tipo_documento

Trigger de auditoría — seguridad.fn_registrar_historial

Cada operación de escritura en cualquier tabla del sistema queda registrada automáticamente en seguridad.historial:
-- Trigger de auditoría (conceptual)
CREATE OR REPLACE FUNCTION seguridad.fn_registrar_historial()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO seguridad.historial (
    tabla, operacion, datos_anteriores, datos_nuevos,
    usuario_bd, fecha_hora
  ) VALUES (
    TG_TABLE_NAME,
    TG_OP,                         -- 'INSERT', 'UPDATE' o 'DELETE'
    row_to_json(OLD),              -- NULL en INSERT
    row_to_json(NEW),              -- NULL en DELETE
    current_user,
    now()
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Este trigger se aplica a todas las tablas de dominio, lo que garantiza trazabilidad completa de quién cambió qué y cuándo, sin ningún esfuerzo adicional en la capa de aplicación.

Trigger de última modificación — seguridad.fn_actualizar_fcmod

Un segundo trigger de base de datos actualiza automáticamente la columna fcMod (fecha de modificación) en cada fila que es modificada mediante UPDATE:
CREATE OR REPLACE FUNCTION seguridad.fn_actualizar_fcmod()
RETURNS TRIGGER AS $$
BEGIN
  NEW."fcMod" = now();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Esto elimina la necesidad de gestionar updatedAt desde la aplicación; la base de datos lo garantiza a nivel de trigger.

Security architecture

RBAC — Control de acceso basado en roles

El backend implementa RBAC mediante dos piezas del módulo common:
  • RolesGuard: un guard de NestJS que inspecciona el tppr_id del usuario autenticado y lo compara con los roles requeridos declarados en el decorador.
  • @Roles() decorator: decorador de metadata que se aplica en controladores o handlers individuales para indicar qué roles pueden acceder.
// Ejemplo de uso en un controlador
@Get()
@Roles(2)  // Mínimo Coordinador (tppr_id >= 2)
findAll() { ... }

@Delete(':id')
@Roles(3)  // Solo Administrador (tppr_id = 3)
remove(@Param('id') id: string) { ... }

MockAuthMiddleware (desarrollo)

Durante el desarrollo, MockAuthMiddleware inyecta un usuario simulado en el request para poder trabajar sin un token JWT real. Este middleware debe deshabilitarse en producción.
// common/middleware/mock-auth.middleware.ts (conceptual)
export class MockAuthMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    req['user'] = { id: 1, tppr_id: 3 }; // Simula un Administrador
    next();
  }
}

Hashing de contraseñas

Todas las contraseñas se almacenan como hashes bcrypt. El módulo auth nunca guarda ni compara contraseñas en texto plano:
// Registro / creación de usuario
const hash = await bcrypt.hash(password, 10);

// Login
const isValid = await bcrypt.compare(password, storedHash);
bcrypt versión 6.x incluye salt automático con factor de costo configurable. El sistema usa el valor por defecto de 10 rondas, que ofrece un balance adecuado entre seguridad y rendimiento para el volumen de usuarios esperado.

Build docs developers (and LLMs) love