# 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
