Appearance
Componentes y frontend
El frontend es una SPA Vue 3 con <script setup> y defineComponent según el módulo, Pinia para estado y Bootstrap 5 para estilos. Punto de entrada frontend/src/main.ts → monta App.vue, que renderiza <router-view/> más los overlays globales (ModalContainer, Toasts, LoadingOverlay, RouteProgress).
Árbol de componentes
Rutas (Vue Router)
frontend/src/router/index.ts, historial HTML5, componentes con carga diferida.
| Ruta | Nombre | Componente | Acceso | Permiso (meta.can) |
|---|---|---|---|---|
/auth/login | Login | pages/auth/LoginPage.vue | Público | — |
/auth/forgot-password | ForgotPassword | pages/auth/ForgotPasswordPage.vue | Público | — |
/auth/reset-password | ResetPassword | pages/auth/ResetPasswordPage.vue | Público | — |
/auth/verify-email | VerifyEmail | pages/auth/VerifyEmailPage.vue | Público | — |
/dashboard | Dashboard | pages/dashboard/DashboardPage.vue | Privada | dashboard:read |
/dashboard/vigilantes | Vigilantes | pages/dashboard/VigilantesPage.vue | Privada | vigilantes:read |
/dashboard/sedes | Sedes | pages/dashboard/SedesPage.vue | Privada | sedes:read |
/dashboard/sedes/:sedeId/edificios | Edificios | pages/dashboard/EdificiosPage.vue | Privada | edificios:read |
/dashboard/edificios/:edificioId/aulas | Aulas | pages/dashboard/AulasPage.vue | Privada | aulas:read |
/dashboard/convocatorias | Convocatorias | pages/dashboard/ConvocatoriasPage.vue | Privada | convocatorias:read |
/dashboard/convocatorias/:convocatoriaId/miembros | Miembros | pages/dashboard/MiembrosPage.vue | Privada | tribunalMiembros:read |
/dashboard/convocatorias/:convocatoriaId/ejercicios | Ejercicios | pages/dashboard/EjerciciosPage.vue | Privada | ejercicios:read |
/dashboard/templates | Templates | pages/dashboard/TemplatesPage.vue | Privada | templates:read |
/dashboard/configuracion | Configuracion | pages/dashboard/ConfiguracionPage.vue | Privada | configuracion:read |
/dashboard/users | Users | pages/dashboard/UsersPage.vue | Privada | users:read |
/forbidden | Forbidden | pages/errors/ForbiddenPage.vue | Pública | — |
/ | — | redirección a /dashboard | — | — |
/:pathMatch(.*)* | NotFound | pages/errors/NotFoundPage.vue | Pública | — |
Guards
beforeEach:- Si hay
accessTokenenlocalStoragey el store no está autenticado →authStore.initialize()(GET /auth/me). requiresAuthy sin sesión →Login.- Ruta
/auth/*con sesión →Dashboard. meta.canyuseAcl().can(permiso)falso →Forbidden.
- Si hay
afterEach: cierra la barra de progreso y fijadocument.title.
Control de acceso (ACL)
| Fichero | Contenido |
|---|---|
auth/permissions.ts | Catálogo Permissions con claves recurso:acción. |
auth/role-permissions.ts | Matriz rol→permisos. SUPERADMIN y ADMIN = ["*"]; SUPERVISOR tiene todos los permisos operativos (lectura y CRUD de catálogos, convocatorias, ejercicios, tribunal y asignaciones, además de ASIGNACIONES_READ y CONFIGURACION_READ), excepto TEMPLATES_* y USERS_*. En Configuración, el backend y ConfiguracionPage bloquean las claves de CLAVES_CONFIGURACION_SOLO_ADMIN. |
composables/useAcl.ts | can(permiso); los roles con "*" siempre pueden. |
Se usa en el guard del router, en Sidebar (menú desde config/sidebarMenu.ts) y en las páginas para ocultar botones de acción. El menú admite entradas externas (external: true), como Ayuda (tras Usuarios): en lugar de navegar ejecuta una acción que comprueba url_pagina_ayuda con configuracionStore.checkPaginaAyuda() y abre la página en una pestaña nueva sin cambiar de foco.
Estado global (Pinia)
frontend/src/stores/ contiene 23 stores:
| Store | Fichero | Responsabilidad |
|---|---|---|
auth | authStore.ts | Login/logout, initialize, cambio/recuperación de contraseña, usuario actual. En logout resetea el resto de stores. |
user | userStore.ts | Perfil propio, avatar y usuarios. |
summary | summaryStore.ts | KPIs del dashboard. |
sede | sedeStore.ts | CRUD de sedes, municipios/provincias, modales. |
edificio | edificioStore.ts | CRUD de edificios y asignación de plantas. |
aula | aulaStore.ts | CRUD de aulas y etiquetas. |
planta | plantaStore.ts | CRUD de plantas y asignación a edificios. |
tag | tagStore.ts | Etiquetas (scope vigilante/aula) y conflictos al borrar. |
convocatoria | convocatoriaStore.ts | CRUD y cambio de estado. |
miembro | miembroStore.ts | Tribunal: CRUD y renuncias. |
vigilante | vigilanteStore.ts | CRUD, bloqueo, historial, penalizaciones e importación. |
ejercicio | ejercicioStore.ts | Store central: CRUD, ubicaciones, grupos, llamamientos, confirmaciones, asignaciones, asistencia, notificaciones y documentos. |
tipoEjercicio | tipoEjercicioStore.ts | Catálogo de tipos de ejercicio. |
template | templateStore.ts | Plantillas de email. |
documentTemplate | documentTemplateStore.ts | Plantillas de documento. |
simulationStore | simulationStore.ts | Motor de asignación rápida: meta, configuración, propuesta, fijados, presets. |
configuracion | configuracionStore.ts | Parámetros, mapas de colores y comprobación de la URL de ayuda (checkPaginaAyuda). |
modalStore | modalStore.ts | Pila de modales dinámicos (openModal/closeModal). |
loadingStore | loadingStore.ts | Overlay de carga con contador. |
toastStore | toastStore.ts | Cola de notificaciones toast. |
sidebarStore | sidebarStore.ts | Estado abierto/colapsado del menú. |
routeProgressStore | routeProgressStore.ts | Barra de progreso de navegación. |
preferenciasStore | preferenciasStore.ts | Preferencias de UI (pestaña de asignación rápida). |
groupClipboardStore | groupClipboardStore.ts | Portapapeles de grupos de vigilantes. |
Flujo de datos
- No existe capa
services/: los stores invocan directamenteapi(utils/api.ts). - Cachés locales por store (p. ej.
edificioStore.sedeEdificios,aulaStore.edificioAulas). simulationStorepersiste presets y orden de reglas enlocalStorage.
Componentes clave
Base
| Componente | Responsabilidad |
|---|---|
base/BaseModal.vue | Modal genérico. El componente interno puede exponer footerButtons, footerLeftText y footerLeftIcon desde su setup para controlar el pie. Z-index apilable y gestión de foco/Escape. |
base/ModalContainer.vue | Renderiza los modales de modalStore. |
base/SkeletonBlock/Cards/Table.vue | Estados de carga. |
base/ErrorPage.vue | Marco de error. |
Ejercicios (components/ejercicios/modals/)
| Componente | Responsabilidad |
|---|---|
CreateEjercicioModal / EditEjercicioModal / DetailsEjercicioModal | Alta, edición y detalle de ejercicio. |
TipoEjercicioManagerModal / EditTipoEjercicioModal | Catálogo de tipos. |
GestionarUbicacionesModal (+ UbicacionSedeBody, UbicacionEdificioBody, EdificioUbicacionCard) | Configuración de sedes/edificios/aulas (calcular_aulas/manual, ratio/fijo), validación de impacto y export Excel cliente. |
GestionarVigilantesModal (+ GrupoVigilantesBody) | Selección de vigilantes, grupos, llamamientos, confirmaciones e importación/exportación. |
GestionarAsignacionesModal | Asignaciones y asistencia (responsables, anulaciones, penalizaciones), export PDF/XLSX y control de asistencia. |
QuickAssignModal + QuickAssignScopeSelector, QuickAssignRulesList, QuickAssignOptionsPanel, QuickAssignSlotsList, QuickAssignResultsPanel | Asignación rápida: alcance, reglas, opciones, propuesta y drag&drop. Usa simulationStore. |
RealizarNotificacionDialog / PreviewEmailModal | Redacción, adjuntos y previsualización de notificaciones. |
ConfirmarExportacionLlamamientoDialog / ExportVigilantesFieldsModal | Exportación Excel de llamamientos y vigilantes. |
ImportarConfirmacionesExcelModal | Importación de confirmaciones desde Excel. |
QuickSelectDialog | Selección rápida reutilizable. |
Correo
Las plantillas de correo (email_template) llevan el logo embebido como adjunto inline (<img src="cid:siva-logo" />). backend/src/utils/email-assets.ts expone getLogoAttachment() (public/images/logo.png como adjunto inline con Content-ID) y attachLogoIfNeeded(); backend/src/config/email.config.ts envuelve el sendMail del transporter para añadirlo automáticamente cuando el HTML contiene cid:siva-logo. En las vistas previas el cid: se sustituye por la URL remota /images/logo.png (email-template.service.ts en backend y frontend/src/utils/plantilla-email.ts en los diálogos de notificación), por lo que los assets de /public se sirven con Cross-Origin-Resource-Policy: cross-origin (backend/src/app.ts).
Otros dominios
components/vigilantes/modals/:CreateVigilanteModal,EditVigilanteModal,DetailsVigilanteModal,ToggleBloqueadoModal,TagManagerModal,EditTagModal,ImportVigilantesModal(+import/ImportConflictModal,ImportEditRowModal,SplitNameModal).components/sedes/modals/,edificios/modals/,aulas/modals/,convocatorias/modals/,miembros/modals/,users/modals/,templates/modals/: CRUD, detalle y cambios de estado por dominio.
Compartidos
shared/Breadcrumb, ConfirmDialog, DateRangeFilter, YearRangeFilter, SelectFilter, PaginationBar, TagBadge, LoadingOverlay, RouteProgress, Toasts.
Capa de API (utils/api.ts)
api: instancia Axios principal,baseURL = VITE_API_URL || "/api".refreshApi: instancia auxiliar sin interceptores, solo para el refresco.- Interceptor de request: añade
Authorization: Bearer <accessToken>salvo en los endpoints de auth; paraFormDataeliminaContent-Type. - Interceptor de response: ante
401intentaPOST /auth/refresh-token, encola peticiones concurrentes y reintenta; si el refresh falla, limpia los tokens y redirige a/auth/login. - Tokens: en
localStorage(accessToken,refreshToken).
Tipos (frontend/src/types/)
Interfaces por dominio: User/UserProfile, ModalOptions, quickAssign, ejercicio, ejercicio-ubicacion (+ -local), ejercicio-grupo-vigilante, vigilante (+ import), asignacion, llamamiento, notificacion, sede, edificio, aula, planta, convocatoria, miembro, tag, template, document-template, tipo-ejercicio, configuracion, summary, ubicacion-uso. El envoltorio común ApiResponse<T> está en types/index.ts.
Convenciones de UI
El sistema de diseño está descrito en DESIGN.md (paleta "Sala de Exámenes", tipografía Poppins, modales apilables). Requisito de accesibilidad WCAG 2.1 AA (RD 1112/2018), con foco visible, aria-label en botones icon-only y skip-link en el layout.