You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
705 lines
29 KiB
Markdown
705 lines
29 KiB
Markdown
# ANÁLISIS TÉCNICO EXHAUSTIVO - PROYECTO DIRECCIÓN DE ADMISIÓN 2026
|
|
|
|
> Generado: 2026-04-08
|
|
|
|
---
|
|
|
|
## 1. ESTRUCTURA GENERAL DEL PROYECTO
|
|
|
|
```
|
|
/direccion_de_admision_2026/
|
|
├── back/ # Backend Laravel 12
|
|
│ ├── app/
|
|
│ │ ├── Models/ # 21 modelos Eloquent
|
|
│ │ ├── Http/Controllers/ # Controladores REST
|
|
│ │ └── Services/ # Lógica de negocio
|
|
│ ├── database/
|
|
│ │ ├── migrations/ # 9 migraciones
|
|
│ │ └── seeders/
|
|
│ ├── routes/api.php # Definición de endpoints API
|
|
│ ├── config/ # Configuraciones
|
|
│ ├── composer.json # Dependencias PHP
|
|
│ └── Dockerfile # Multi-stage build
|
|
├── front/ # Frontend Vue 3 + Vite
|
|
│ ├── src/
|
|
│ │ ├── views/ # Vistas por rol (admin, postulante)
|
|
│ │ ├── components/ # Componentes reutilizables
|
|
│ │ ├── store/ # 14 stores Pinia
|
|
│ │ ├── router/index.js # Rutas y guards
|
|
│ │ └── axios.js # Instancia HTTP autenticada
|
|
│ ├── package.json
|
|
│ ├── vite.config.js
|
|
│ └── Dockerfile
|
|
├── nginx/ # Configuración Nginx
|
|
├── mysql/ # Configuración MySQL
|
|
├── docker-compose.prod.yml # Orquestación producción
|
|
├── docker-compose.yml # Desarrollo local
|
|
└── .github/workflows/ # CI/CD
|
|
```
|
|
|
|
---
|
|
|
|
## 2. STACK TÉCNICO - VERSIONES EXACTAS
|
|
|
|
### Backend (Laravel 12)
|
|
|
|
| Librería | Versión | Propósito |
|
|
|----------|---------|----------|
|
|
| **laravel/framework** | ^12.0 | Framework principal |
|
|
| **laravel/sanctum** | ^4.2 | API tokens + autenticación |
|
|
| **spatie/laravel-permission** | ^6.24 | Gestión de roles/permisos |
|
|
| **simplesoftwareio/simple-qrcode** | ^4.2 | Generación códigos QR |
|
|
| **laravel/tinker** | ^2.10.1 | REPL para desarrollo |
|
|
|
|
**PHP:** ^8.2 (Dockerfile especifica 8.4-fpm-alpine)
|
|
**Base de datos:** MySQL 8.0
|
|
**ORM:** Eloquent (nativo)
|
|
**Autenticación:** Laravel Sanctum (tokens bearer)
|
|
|
|
### Frontend (Vue 3)
|
|
|
|
| Librería | Versión | Propósito |
|
|
|----------|---------|----------|
|
|
| **vue** | ^3.5.24 | Framework frontend |
|
|
| **vue-router** | ^4.6.4 | Enrutamiento SPA |
|
|
| **pinia** | ^3.0.4 | State management |
|
|
| **ant-design-vue** | ^4.2.6 | Componentes UI |
|
|
| **axios** | ^1.13.3 | Cliente HTTP |
|
|
| **chart.js** | ^4.5.1 | Gráficos |
|
|
| **vue-chartjs** | ^5.3.3 | Binding Vue para Chart.js |
|
|
| **@vueup/vue-quill** | ^1.2.0 | Editor WYSIWYG |
|
|
| **marked** | ^17.0.1 | Parser markdown |
|
|
| **markdown-it** | ^14.1.0 | Markdown avanzado |
|
|
| **dayjs** | ^1.11.19 | Utilidades fecha/hora |
|
|
| **vue-qrcode** | ^2.2.2 | Generador QR |
|
|
| **katex** | ^0.16.28 | Renderizado LaTeX |
|
|
|
|
**Build Tool:** Vite 7.2.4
|
|
**Package Manager:** npm
|
|
|
|
---
|
|
|
|
## 3. BACKEND - ARQUITECTURA LARAVEL 12
|
|
|
|
### 3.1 Modelos y Relaciones (21 modelos)
|
|
|
|
#### Modelos Núcleo de Admisión
|
|
|
|
| Modelo | Tabla | Relaciones Clave | Propósito |
|
|
|--------|-------|------------------|----------|
|
|
| **ProcesoAdmision** | procesos_admision | hasMany(ProcesoAdmisionDetalle), hasMany(ProcesoAdmisionResultadoArchivo), hasMany(ResultadoAdmision) | Gestiona procesos de admisión (convocatorias) |
|
|
| **ProcesoAdmisionDetalle** | proceso_admision_detalles | belongsTo(ProcesoAdmision) | Detalles informativos de procesos (requisitos, cronograma, etc.) |
|
|
| **ProcesoAdmisionResultadoArchivo** | proceso_admision_resultado_archivos | belongsTo(ProcesoAdmision) | Archivos de resultados organizados por slot/sedes |
|
|
| **ResultadoAdmision** | resultados_admision | belongsTo(ProcesoAdmision), belongsTo(AreaAdmision) | Resultados finales de admisión de postulantes |
|
|
| **Proceso** | procesos | belongsTo(Calificacion), belongsToMany(Area, via area_proceso) | Procesos de examen asociados a áreas |
|
|
| **Area** | areas | belongsToMany(Curso), belongsToMany(Examen), belongsToMany(Proceso) | Áreas temáticas (Biomedicas, Ingeniería) |
|
|
| **Curso** | cursos | belongsToMany(Area), hasMany(Pregunta) | Cursos dentro de áreas (Matemática, Comunicación) |
|
|
| **Pregunta** | preguntas | belongsTo(Curso) | Preguntas de examen con opciones múltiples |
|
|
| **PreguntaAsignada** | preguntas_asignadas | belongsTo(Examen) | Asociación pregunta-examen |
|
|
|
|
#### Modelos de Usuarios
|
|
|
|
| Modelo | Tabla | Relaciones | Propósito |
|
|
|--------|-------|-----------|----------|
|
|
| **User** | users | belongsToMany(Academia), hasMany(IntentoExamen) | Administradores/Staff (usa Spatie roles) |
|
|
| **Postulante** | postulantes | hasMany(Examen) | Postulantes que rinden exámenes |
|
|
| **AreaAdmision** | areas_admision | hasMany(ResultadoAdmision) | Áreas en contexto de admisión (diferente a Area de exámenes) |
|
|
|
|
#### Modelos de Evaluación
|
|
|
|
| Modelo | Tabla | Relaciones | Propósito |
|
|
|--------|-------|-----------|----------|
|
|
| **Examen** | examenes | belongsTo(Postulante), belongsTo(AreaProceso), belongsTo(Pago), hasMany(PreguntaAsignada) | Examen renderizado por postulante |
|
|
| **Calificacion** | calificaciones | hasMany(Proceso) | Esquemas de puntuación (puntos por correcta, incorrecta, nula) |
|
|
| **ResultadoExamen** | resultados_examenes | — | Resultados parciales de exámenes |
|
|
| **Pago** | pagos | belongsTo(Postulante) | Pagos de procesos que requieren arancel |
|
|
|
|
#### Modelos de Contenido
|
|
|
|
| Modelo | Tabla | Relaciones | Propósito |
|
|
|--------|-------|-----------|----------|
|
|
| **Noticia** | noticias | Standalone (SoftDeletes) | Noticias publicables en web pública |
|
|
| **Comunicado** | comunicados | hasMany(ComunicadoImagen) | Comunicados activos con imágenes |
|
|
| **ComunicadoImagen** | comunicado_imagenes | belongsTo(Comunicado) | Imágenes asociadas a comunicados |
|
|
|
|
#### Modelos Intermedios y Apoyo
|
|
|
|
| Modelo | Tabla | Propósito |
|
|
|--------|-------|----------|
|
|
| **ReglaAreaProceso** | reglas_area_proceso | Define reglas de asignación de preguntas por área-proceso |
|
|
| **ResultadoAdmisionCarga** | resultados_admision_carga | Almacena detalles de cursos en resultados de admisión |
|
|
|
|
### 3.2 Esquema de Base de Datos (30+ tablas)
|
|
|
|
```
|
|
areas -- Catálogo de áreas (Biomedicas, Ingenierías)
|
|
cursos -- Catálogo de cursos (Matemática, Comunicación)
|
|
area_curso -- M2M: área-curso
|
|
procesos -- Procesos de examen
|
|
area_proceso -- M2M: área-proceso (pivot con ID)
|
|
preguntas -- Preguntas de examen
|
|
preguntas_asignadas -- Preguntas asignadas a examen del postulante
|
|
examenes -- Exámenes rendidos
|
|
postulantes -- Usuarios que rinden exámenes
|
|
pagos -- Control de pagos
|
|
resultados_examenes -- Resultados de exámenes
|
|
|
|
procesos_admision -- Procesos de admisión (convocatorias)
|
|
proceso_admision_detalles -- Detalles de procesos (requisitos, cronograma)
|
|
proceso_admision_resultado_archivos -- Archivos de resultados por sede/slot
|
|
resultados_admision -- Resultados de admisión
|
|
areas_admision -- Áreas de admisión
|
|
|
|
noticias -- Artículos publicables
|
|
comunicados -- Comunicados activos
|
|
comunicado_imagenes -- Imágenes en comunicados
|
|
|
|
users -- Administradores
|
|
roles, permissions -- Spatie permission tables
|
|
model_has_roles, model_has_permissions
|
|
personal_access_tokens -- Tokens Sanctum
|
|
```
|
|
|
|
**Restricciones de Integridad:**
|
|
- `ON DELETE CASCADE` en relaciones examenes → postulantes, preguntas_asignadas → examenes
|
|
- `UNIQUE CONSTRAINTS` en `area_proceso(area_id, proceso_id)` y `area_curso`
|
|
- `UNIQUE KEY` en `proceso_admision_resultado_archivos(proceso_admision_id, orden)`
|
|
|
|
---
|
|
|
|
## 4. API REST - ENDPOINTS COMPLETOS (73 rutas)
|
|
|
|
### Autenticación Admin
|
|
```
|
|
POST /api/register
|
|
POST /api/login
|
|
POST /api/logout [auth:sanctum]
|
|
GET /api/me [auth:sanctum]
|
|
POST /api/refresh-token [auth:sanctum]
|
|
GET /api/user [auth:sanctum]
|
|
```
|
|
|
|
### Autenticación Postulante
|
|
```
|
|
POST /api/postulante/register
|
|
POST /api/postulante/login
|
|
POST /api/postulante/logout [auth:sanctum]
|
|
GET /api/postulante/me [auth:sanctum]
|
|
GET /api/postulante/pagos [auth:sanctum]
|
|
GET /api/postulante/mis-procesos [auth:sanctum]
|
|
GET /api/postulante/observacion [auth:sanctum]
|
|
GET /api/mis-procesos/{id}/avance [auth:sanctum]
|
|
```
|
|
|
|
### Procesos de Examen
|
|
```
|
|
GET /api/procesos [auth:sanctum]
|
|
POST /api/procesos [auth:sanctum]
|
|
GET /api/procesos/{id} [auth:sanctum]
|
|
PUT /api/procesos/{id} [auth:sanctum]
|
|
PATCH /api/procesos/{id}/toggle-activo [auth:sanctum]
|
|
DELETE /api/procesos/{id} [auth:sanctum]
|
|
```
|
|
|
|
### Admin - Áreas
|
|
```
|
|
GET /api/admin/areas [auth:sanctum]
|
|
POST /api/admin/areas [auth:sanctum]
|
|
GET /api/admin/areas/{id} [auth:sanctum]
|
|
PUT /api/admin/areas/{id} [auth:sanctum]
|
|
DELETE /api/admin/areas/{id} [auth:sanctum]
|
|
PATCH /api/admin/areas/{id}/toggle [auth:sanctum]
|
|
POST /api/admin/areas/{area}/vincular-cursos [auth:sanctum]
|
|
POST /api/admin/areas/{area}/desvincular-curso [auth:sanctum]
|
|
GET /api/admin/areas/{area}/cursos-disponibles [auth:sanctum]
|
|
POST /api/admin/areas/{area}/vincular-procesos [auth:sanctum]
|
|
GET /api/admin/areas/{area}/procesos-disponibles [auth:sanctum]
|
|
POST /api/admin/areas/{area}/desvincular-procesos [auth:sanctum]
|
|
```
|
|
|
|
### Admin - Cursos
|
|
```
|
|
GET /api/admin/cursos [auth:sanctum]
|
|
POST /api/admin/cursos [auth:sanctum]
|
|
GET /api/admin/cursos/{id} [auth:sanctum]
|
|
PUT /api/admin/cursos/{id} [auth:sanctum]
|
|
DELETE /api/admin/cursos/{id} [auth:sanctum]
|
|
PATCH /api/admin/cursos/{id}/toggle [auth:sanctum]
|
|
```
|
|
|
|
### Admin - Preguntas
|
|
```
|
|
GET /api/admin/cursos/{id}/preguntas [auth:sanctum]
|
|
POST /api/admin/preguntas [auth:sanctum]
|
|
GET /api/admin/preguntas/{id} [auth:sanctum]
|
|
PUT /api/admin/preguntas/{id} [auth:sanctum]
|
|
DELETE /api/admin/preguntas/{id} [auth:sanctum]
|
|
```
|
|
|
|
### Admin - Noticias
|
|
```
|
|
GET /api/admin/noticias [auth:sanctum]
|
|
GET /api/admin/noticias/{id} [auth:sanctum]
|
|
POST /api/admin/noticias [auth:sanctum]
|
|
PUT /api/admin/noticias/{id} [auth:sanctum]
|
|
DELETE /api/admin/noticias/{id} [auth:sanctum]
|
|
```
|
|
|
|
### Admin - Procesos de Admisión
|
|
```
|
|
GET /api/admin/procesos-admision [auth:sanctum]
|
|
POST /api/admin/procesos-admision [auth:sanctum]
|
|
GET /api/admin/procesos-admision/{id} [auth:sanctum]
|
|
PUT/PATCH /api/admin/procesos-admision/{id} [auth:sanctum]
|
|
DELETE /api/admin/procesos-admision/{id} [auth:sanctum]
|
|
|
|
GET /api/admin/procesos-admision/{id}/detalles [auth:sanctum]
|
|
POST /api/admin/procesos-admision/{id}/detalles [auth:sanctum]
|
|
GET /api/admin/detalles-admision/{id} [auth:sanctum]
|
|
PUT/PATCH /api/admin/detalles-admision/{id} [auth:sanctum]
|
|
DELETE /api/admin/detalles-admision/{id} [auth:sanctum]
|
|
```
|
|
|
|
### Admin - Resultados (Archivos)
|
|
```
|
|
GET /api/admin/proceso-resultado/{id}/archivos [auth:sanctum]
|
|
POST /api/admin/proceso-resultado/{id}/archivos [auth:sanctum]
|
|
DELETE /api/admin/proceso-resultado/archivos/{id} [auth:sanctum]
|
|
GET /api/proceso-resultado/{id}/archivos [público]
|
|
```
|
|
|
|
### Admin - Comunicados
|
|
```
|
|
GET /api/admin/comunicados [auth:sanctum]
|
|
POST /api/admin/comunicados [auth:sanctum]
|
|
PUT/PATCH /api/admin/comunicados/{id} [auth:sanctum]
|
|
DELETE /api/admin/comunicados/{id} [auth:sanctum]
|
|
PATCH /api/admin/comunicados/{id}/toggle-activo [auth:sanctum]
|
|
DELETE /api/admin/comunicados/imagenes/{id} [auth:sanctum]
|
|
```
|
|
|
|
### Examen Online
|
|
```
|
|
GET /api/examen/procesos [auth:sanctum]
|
|
GET /api/examen/areas [auth:sanctum]
|
|
GET /api/examen/actual [auth:sanctum]
|
|
POST /api/examen/crear [auth:sanctum]
|
|
POST /api/examen/{id}/generar-preguntas [auth:sanctum]
|
|
GET /api/examen/{id}/preguntas [auth:sanctum]
|
|
POST /api/examen/iniciar [auth:sanctum]
|
|
POST /api/examen/pregunta/{id}/responder [auth:sanctum]
|
|
POST /api/examen/{id}/finalizar [auth:sanctum]
|
|
POST /api/examen/{id}/calificar [auth:sanctum]
|
|
```
|
|
|
|
### Reglas de Examen
|
|
```
|
|
GET /api/area-proceso/areasprocesos [auth:sanctum]
|
|
GET /api/area-proceso/{id}/reglas [auth:sanctum]
|
|
POST /api/area-proceso/{id}/reglas [auth:sanctum]
|
|
POST /api/area-proceso/{id}/reglas/multiple [auth:sanctum]
|
|
PUT /api/reglas/{id} [auth:sanctum]
|
|
DELETE /api/reglas/{id} [auth:sanctum]
|
|
```
|
|
|
|
### Calificaciones
|
|
```
|
|
GET /api/calificaciones [auth:sanctum]
|
|
POST /api/calificaciones [auth:sanctum]
|
|
GET /api/calificaciones/{id} [auth:sanctum]
|
|
PUT /api/calificaciones/{id} [auth:sanctum]
|
|
DELETE /api/calificaciones/{id} [auth:sanctum]
|
|
```
|
|
|
|
### Postulantes (Admin)
|
|
```
|
|
GET /api/admin/postulantes [auth:sanctum]
|
|
PUT /api/admin/postulantes/{id} [auth:sanctum]
|
|
```
|
|
|
|
### Web Pública
|
|
```
|
|
GET /api/procesos-admision [público]
|
|
GET /api/noticias [público]
|
|
GET /api/noticias/{slug} [público]
|
|
GET /api/comunicados/activo [público]
|
|
GET /api/procesos-disponibles-preinscripcion [auth:sanctum]
|
|
```
|
|
|
|
---
|
|
|
|
## 5. AUTENTICACIÓN Y AUTORIZACIÓN
|
|
|
|
### Flujo Admin
|
|
1. `POST /api/login` con email/password
|
|
2. `AuthController` verifica credenciales, revoca tokens previos
|
|
3. Genera token con `createToken('api_token', ['*'], now()->addHours(12))`
|
|
4. Devuelve token + user + roles/permisos (Spatie)
|
|
|
|
### Flujo Postulante
|
|
1. `POST /api/postulante/login`
|
|
2. Token con expiración 1 hora
|
|
3. `Device-Id` único para detección multi-dispositivo
|
|
4. Guard separado: `postulante_token` en localStorage
|
|
|
|
### Roles (Spatie Permission)
|
|
- `administrador` — acceso a panel admin
|
|
- `superadmin` — acceso extendido
|
|
- Permisos granulares via `model_has_permissions`
|
|
|
|
---
|
|
|
|
## 6. FRONTEND - ARQUITECTURA VUE 3
|
|
|
|
### 6.1 Rutas (23 rutas principales)
|
|
|
|
```
|
|
/ (público) → WebPage (SPA pública)
|
|
/login-postulante → LoginView
|
|
/proceso-resultado → ProcesoResultado
|
|
/modalidades/cepreuna → Cepreuna
|
|
/modalidades/extraordinario → Extraordinario
|
|
/modalidades/general → General
|
|
|
|
/portal-postulante (requiresAuth)
|
|
├─ / → Dashboard postulante
|
|
├─ /test → Selector proceso/área
|
|
├─ /examen/:examenId → PreguntasExamen (examen online)
|
|
├─ /resultados/:examenId → Resultados (calificaciones)
|
|
├─ /mis-procesos → MisProcesos (admisión)
|
|
└─ /mis-procesos-estado → AvanceProceso
|
|
|
|
/admin/dashboard (requiresAuth + role:administrador)
|
|
├─ / → Dashboard admin
|
|
├─ /areas → AreasList (CRUD)
|
|
├─ /cursos → CursosList (CRUD)
|
|
├─ /cursos/:id/preguntas → PreguntasCursoView
|
|
├─ /procesos → ProcesosList (CRUD)
|
|
├─ /reglas → ReglasList (CRUD)
|
|
├─ /procesos-admision → ProcesosAdmisionList (CRUD)
|
|
├─ /procesos/:id/detalles → ProcesoAdmisionDetalles
|
|
├─ /lista-calificacion → CalificacionTest
|
|
├─ /lista-postulantes → ListPostulantes
|
|
├─ /noticias → NoticiasAdmin (CRUD)
|
|
└─ /comunicados → ComunicadosAdmin (CRUD)
|
|
|
|
/superadmin/dashboard → Dashboard superadmin
|
|
/unauthorized → 403
|
|
/:pathMatch(.*) → 404
|
|
```
|
|
|
|
**Guards:**
|
|
- `requiresAuth` — verifica token en localStorage, redirige a login
|
|
- `guest` — evita acceso a login si ya autenticado
|
|
- `role` — valida rol, redirige a `/403` si no aplica
|
|
|
|
### 6.2 Stores Pinia (14 stores)
|
|
|
|
| Store | API | Propósito principal |
|
|
|-------|-----|---------------------|
|
|
| `user.js` | Options | Auth admin, roles, persistencia localStorage |
|
|
| `postulanteStore.js` | Composition | Auth postulante, Device-Id |
|
|
| `examen.store.js` | Options | Flujo completo de examen online |
|
|
| `procesosAdmisionStore.js` | Options | CRUD procesos admisión (admin) |
|
|
| `area.store.js` | Options | CRUD áreas |
|
|
| `curso.store.js` | Options | CRUD cursos |
|
|
| `pregunta.store.js` | Options | CRUD preguntas |
|
|
| `proceso.store.js` | Options | CRUD procesos examen |
|
|
| `reglaAreaProceso.store.js` | Options | Reglas de asignación de preguntas |
|
|
| `noticiasStore.js` | Options | CRUD noticias (admin) |
|
|
| `noticiasPublicas.store.js` | Options | Noticias para web pública |
|
|
| `procesoAdmisionResultado.store.js` | Options | Upload/fetch archivos de resultados |
|
|
| `comunicadosStore.js` | Options | CRUD comunicados |
|
|
| `web.js` | Options | Estado web pública (procesos, comunicado activo) |
|
|
|
|
### 6.3 Instancias Axios
|
|
|
|
**`axios.js`** (Admin):
|
|
- `baseURL`: `VITE_API_URL`
|
|
- Request interceptor: inyecta `Bearer ${localStorage.token}`
|
|
- Response interceptor: 401 → clearAuth + redirect login; 403 → redirect /unauthorized
|
|
|
|
**`axiosPostulante.js`** (Postulante):
|
|
- Token en `postulante_token`
|
|
- Header `Device-Id` opcional
|
|
|
|
### 6.4 Componentes Principales
|
|
|
|
| Componente | Líneas aprox. | Función |
|
|
|-----------|--------------|---------|
|
|
| `ConvocatoriasSection.vue` | ~1,457 | Tarjetas de procesos, modal detalles, archivos multi-sede |
|
|
| `PreguntasExamen.vue` | ~598 | Examen online con timer, 1 pregunta/vez, Markdown+LaTeX |
|
|
| `ProcessSection.vue` | ~365 | Timeline visual del cronograma |
|
|
| `ComunicadoModal.vue` | ~207 | Modal carrusel de imágenes, auto-cierre por fecha |
|
|
| `HeroSection.vue` | — | Banner con card dinámica (v-if hayResultados) |
|
|
| `ProcesoResultado.vue` | — | Sección pública por proceso con archivos v-for |
|
|
| `ProcesosAdmisionList.vue` | — | Tabla admin con modales inline para CRUD + Resultados |
|
|
|
|
---
|
|
|
|
## 7. FLUJOS FUNCIONALES
|
|
|
|
### 7.1 Gestión de Procesos de Admisión
|
|
|
|
```
|
|
Admin crea ProcesoAdmision
|
|
├─ título, descripción, slug único
|
|
├─ Fechas: pre-inscripción → inscripción → examen → resultados
|
|
├─ Imágenes: imagen (JPG/PNG), banner, brochure PDF
|
|
├─ Links externos: pre-inscripción, inscripción, resultados, reglamento
|
|
├─ Estados: nuevo → publicado → en_proceso → finalizado → cancelado
|
|
└─ ProcesoAdmisionDetalle: requisitos, cronograma, etc.
|
|
|
|
Postulante visualiza en página pública
|
|
└─ GET /api/procesos-admision (solo publicados, latest first)
|
|
└─ ConvocatoriasSection: tarjetas + modal detalles
|
|
|
|
Admin gestiona resultados
|
|
└─ ProcesoAdmisionResultadoArchivo por sede/slot
|
|
├─ Upload PDF por slot (1-6) por proceso
|
|
└─ Descarga pública: GET /api/proceso-resultado/{id}/archivos
|
|
```
|
|
|
|
### 7.2 Examen Online (Flujo Completo)
|
|
|
|
```
|
|
1. Login postulante → Dashboard (procesos disponibles donde no ha rendido)
|
|
2. GET /api/examen/procesos → seleccionar proceso
|
|
3. GET /api/examen/areas?proceso_id={id} → seleccionar área
|
|
4. POST /api/examen/crear { area_proceso_id } → crea Examen (estado: pendiente)
|
|
5. POST /api/examen/{id}/generar-preguntas → selección aleatoria por ReglaAreaProceso
|
|
6. GET /api/examen/{id}/preguntas → lista sin respuesta_correcta visible
|
|
7. POST /api/examen/iniciar → hora_inicio = NOW, estado = en_curso
|
|
8. POST /api/examen/pregunta/{id}/responder { respuesta } → valida + puntúa (Calificacion)
|
|
9. POST /api/examen/{id}/finalizar → estado = finalizado, suma puntaje
|
|
10. POST /api/examen/{id}/calificar → computa estadísticas:
|
|
├─ total_correctas / incorrectas / nulas
|
|
├─ total_puntos, porcentaje_correctas
|
|
├─ calificacion_sobre_20
|
|
└─ correctas_por_curso (breakdown)
|
|
11. Resultados.vue: gráficos Chart.js por curso + ranking
|
|
```
|
|
|
|
**Restricciones:**
|
|
- Máximo `intentos_maximos` (campo de Proceso)
|
|
- Requiere `Pago` si `proceso.requiere_pago = true`
|
|
- Timer bloquea respuestas al expirar `duracion_minutos`
|
|
|
|
### 7.3 Gestión de Archivos de Resultados (Multi-sede)
|
|
|
|
```
|
|
Modelo: ProcesoAdmisionResultadoArchivo
|
|
Campos: proceso_admision_id, nombre, file_path, orden (UNIQUE por proceso)
|
|
|
|
Admin:
|
|
├─ Modal "Resultados" en ProcesosAdmisionList (6 slots pre-nombrados)
|
|
├─ Slots nombrados por: generarNombreSlot(orden, proceso) en store
|
|
│ slot 1 → "Resultados Sábado {fecha_examen1}"
|
|
│ slot 2 → "Resultados CONADIS"
|
|
│ slots 3-6 → slugs personalizados
|
|
└─ customRequest de Ant Design: requiere onSuccess()/onError()
|
|
|
|
Postulante:
|
|
├─ GET /api/proceso-resultado/{id}/archivos (público)
|
|
├─ archivosPorProceso en web.js store → mapa por procesoId
|
|
├─ fetchArchivosMultiples con Promise.allSettled (multi-sede)
|
|
└─ HeroSection: card con v-if="hayResultados" (≥1 archivo)
|
|
```
|
|
|
|
### 7.4 Gestión de Contenido (Noticias + Comunicados)
|
|
|
|
**Noticias:**
|
|
- CRUD admin con editor WYSIWYG (vue-quill)
|
|
- Campo `slug` único, `contenido` markdown
|
|
- Filtros: categoría, tag_color, destacado, publicado, orden
|
|
- Web pública: GET /api/noticias, GET /api/noticias/{slug}
|
|
|
|
**Comunicados:**
|
|
- Múltiples imágenes carrusel (ComunicadoImagen)
|
|
- Solo 1 activo a la vez: GET /api/comunicados/activo
|
|
- Fecha_inicio / fecha_fin de vigencia
|
|
- ComunicadoModal.vue: auto-cierre, botón de acción
|
|
|
|
---
|
|
|
|
## 8. INFRAESTRUCTURA Y DESPLIEGUE
|
|
|
|
### 8.1 Docker Compose (Producción)
|
|
|
|
| Servicio | Imagen | Puerto | RAM |
|
|
|----------|--------|--------|-----|
|
|
| **nginx** | nginx:alpine | 127.0.0.1:8080 | 64MB |
|
|
| **backend** | ghcr.io/…/backend:latest | 9000 (interno) | 1GB |
|
|
| **frontend** | ghcr.io/…/frontend:latest | interno | 64MB |
|
|
| **mysql** | mysql:8.0 | 127.0.0.1:3306 | 512MB |
|
|
|
|
**Volúmenes:** `mysql_data` (BD), `backend_storage` (archivos subidos)
|
|
**Red:** Bridge `admision_net`
|
|
**Health checks:** PHP ping (backend), mysqladmin ping (mysql)
|
|
|
|
### 8.2 Dockerfile Backend (Multi-Stage)
|
|
|
|
```dockerfile
|
|
# Stage 1: Composer (sin dev, optimizado)
|
|
FROM composer:2 AS vendor
|
|
RUN composer install --no-dev --optimize-autoloader --ignore-platform-reqs
|
|
|
|
# Stage 2: PHP 8.4-fpm-alpine
|
|
FROM php:8.4-fpm-alpine
|
|
# Extensiones: PDO MySQL, BCMath, MBString, GD, cURL, ZIP, XML, Intl
|
|
# OPcache: 128MB, 10k archivos, validate_timestamps=0
|
|
# Upload: 20MB archivos, 25MB POST
|
|
# PHP-FPM: 15 max_children, soporte 100+ concurrentes
|
|
# Entrypoint: config:cache + route:cache → php-fpm
|
|
```
|
|
|
|
### 8.3 Dockerfile Frontend
|
|
|
|
```dockerfile
|
|
FROM node:20-alpine AS build
|
|
RUN npm ci && npm run build
|
|
|
|
FROM nginx:alpine
|
|
COPY --from=build /app/dist /usr/share/nginx/html
|
|
```
|
|
|
|
### 8.4 Nginx
|
|
|
|
```nginx
|
|
# Frontend SPA
|
|
location / { try_files $uri $uri/ /index.html; }
|
|
|
|
# API (proxy a PHP-FPM backend)
|
|
location /api { proxy_pass http://backend:9000; }
|
|
|
|
# Storage público
|
|
location /storage { alias /var/www/html/storage/app/public; }
|
|
```
|
|
|
|
---
|
|
|
|
## 9. PATRONES DE DESARROLLO
|
|
|
|
### 9.1 Convenciones Backend
|
|
|
|
- Validación en controladores (`Validator::make`), no Form Requests
|
|
- Respuestas JSON estándar: `{ success, data/message, errors }`
|
|
- HTTP status codes semánticos (200, 201, 400, 401, 403, 422, 500)
|
|
- Accessors para URLs de storage: `getImagenUrlAttribute()`
|
|
- `$casts` en modelos para tipos (boolean, datetime, integer, array)
|
|
- `ON DELETE CASCADE` en FK críticas
|
|
- `->latest()` para orden cronológico inverso
|
|
|
|
### 9.2 Convenciones Frontend
|
|
|
|
- Componentes: PascalCase | Métodos/props: camelCase
|
|
- Stores Pinia Options API (mayoría) — patrón: fetch → state → computed
|
|
- Modales admin: inline en el mismo archivo `.vue` (patrón establecido)
|
|
- `axios` (admin) vs `axiosPostulante` (público/postulante) — instancias separadas
|
|
- Páginas públicas completas en `WebPageSections/navbarcontent/`
|
|
- Datos sensibles de auth solo en localStorage (no Pinia, no sessionStorage)
|
|
|
|
### 9.3 Patrones de Componentes
|
|
|
|
```vue
|
|
// Patrón típico: Store + Component
|
|
const store = useProcesoAdmisionStore()
|
|
onMounted(() => store.fetchProcesos({ page: 1 }))
|
|
|
|
// Patrón: Tabla paginada (AntD)
|
|
<a-table :dataSource="store.procesos" :pagination="pagination" :loading="store.loading" />
|
|
|
|
// Patrón: Modal inline (no rutas separadas)
|
|
<a-modal v-model:open="showModal" @ok="handleSubmit" />
|
|
```
|
|
|
|
### 9.4 Seguridad
|
|
|
|
- Bearer tokens con expiración (12h admin, 1h postulante)
|
|
- Validación siempre en servidor (no confiar en cliente)
|
|
- `BCRYPT_ROUNDS=12` para passwords
|
|
- `revoke tokens` al logout (`$user->tokens()->delete()`)
|
|
- Storage `public` disk — sin acceso a archivos privados vía HTTP
|
|
- CORS configurado via `config/cors.php`
|
|
|
|
---
|
|
|
|
## 10. OBSERVACIONES: FORTALEZAS Y MEJORAS
|
|
|
|
### Fortalezas
|
|
|
|
1. **Arquitectura modular** — separación clara backend/frontend, cada uno con Dockerfile propio
|
|
2. **Auth robusta** — Sanctum + Spatie, tokens con expiración, guards de router
|
|
3. **State management ordenado** — 14 stores Pinia con responsabilidades claras
|
|
4. **Multi-sede** — ProcesoAdmisionResultadoArchivo con orden y Promise.allSettled
|
|
5. **Docker optimizado** — multi-stage builds, healthchecks, límites RAM
|
|
6. **Rich content** — soporte Markdown + LaTeX en preguntas de examen
|
|
7. **Patrón de modales inline** — consistente en todo el admin
|
|
8. **Encoding correcto** — fix UTF-8 para tildes en archivos Windows-1252
|
|
|
|
### Mejoras Sugeridas
|
|
|
|
#### Backend
|
|
|
|
| Área | Problema | Solución recomendada |
|
|
|------|----------|---------------------|
|
|
| Form Requests | Validación no reutilizable | Crear clases `FormRequest` |
|
|
| Rate Limiting | Sin throttle | `ThrottleRequests` middleware |
|
|
| Tests | Sin cobertura visible | PHPUnit para flujos críticos (examen, auth) |
|
|
| N+1 Queries | Riesgo en relaciones | Usar `with()` en índices con relaciones |
|
|
| Caché | `database` driver (lento) | Redis para sesiones y caché |
|
|
| API Docs | Sin OpenAPI | `scribe-php` o `l5-swagger` |
|
|
| Servicios | Lógica en controllers | `PagoService`, `ExamenService` |
|
|
|
|
#### Frontend
|
|
|
|
| Área | Problema | Solución recomendada |
|
|
|------|----------|---------------------|
|
|
| TypeScript | Sin tipos | Migración gradual a TS |
|
|
| Tests | Sin cobertura | Vitest + Vue Test Utils |
|
|
| Composables | Lógica en componentes grandes | Extraer composables reutilizables |
|
|
| Lazy loading | Sin code splitting visible | Dynamic imports en rutas |
|
|
| Accesibilidad | No auditado | WCAG 2.1 audit |
|
|
| i18n | Solo español | `vue-i18n` si se requiere multiidioma |
|
|
|
|
#### Infraestructura
|
|
|
|
| Área | Problema | Solución recomendada |
|
|
|------|----------|---------------------|
|
|
| Backup | Sin automatización | Cron + mysqldump → S3/volumen |
|
|
| Logs | Solo internos | Centralizar (ELK/CloudWatch) |
|
|
| SSL | No visible | Let's Encrypt + Certbot |
|
|
| Secrets | .env en repo | Secret manager o .env.local ignorado |
|
|
| Monitoreo | Sin métricas | Prometheus + Grafana |
|
|
|
|
---
|
|
|
|
## 11. RESUMEN EJECUTIVO
|
|
|
|
### Stack Definitivo
|
|
- **Backend:** Laravel 12 + Sanctum + Spatie + MySQL 8.0
|
|
- **Frontend:** Vue 3.5 + Vite 7 + Pinia + Ant Design Vue 4
|
|
- **DevOps:** Docker Compose, Nginx, GitHub Actions
|
|
- **Base de datos:** 30+ tablas con integridad referencial
|
|
|
|
### Funcionalidades Implementadas
|
|
1. Procesos de Admisión multi-sede con archivos de resultados por slot
|
|
2. Exámenes online con timer, generación dinámica, calificación automática
|
|
3. Portal Admin con CRUD completo (áreas, cursos, preguntas, procesos, noticias)
|
|
4. Portal Postulante (dashboard, examen, resultados Chart.js, seguimiento admisión)
|
|
5. Página Web Pública (convocatorias, noticias, comunicados, descarga resultados)
|
|
|
|
### Métricas
|
|
- 73 endpoints API REST
|
|
- 21 modelos Eloquent
|
|
- 14 stores Pinia
|
|
- 23 rutas Vue Router
|
|
- 30+ tablas en BD
|
|
- Soporte 100+ usuarios concurrentes (PHP-FPM config)
|
|
|
|
### Estado del Proyecto
|
|
- **Etapa:** MVP con iteraciones activas (commits recientes)
|
|
- **Calidad:** Código limpio y consistente, sin tests automatizados
|
|
- **Seguridad:** Auth/Authz implementada, validación servidor
|
|
- **Escalabilidad:** Containerizado, DB con FK integridad, caché configurable
|