main
parent 195d50de7f
commit b973eecc4f

@ -1,6 +1,8 @@
name: Build & Deploy
on:
push:
branches: [main]
workflow_dispatch:
inputs:
deploy:
@ -56,7 +58,7 @@ jobs:
deploy:
name: Deploy to VPS
needs: build
if: ${{ inputs.deploy }}
if: ${{ github.event_name == 'push' || inputs.deploy }}
runs-on: ubuntu-latest
steps:

@ -1,19 +1,60 @@
mysqldump: [Warning] Using a password on the command line interface can be insecure.
-- --------------------------------------------------------
-- Host: 127.0.0.1
-- Versión del servidor: 8.0.30 - MySQL Community Server - GPL
-- SO del servidor: Win64
-- HeidiSQL Versión: 12.1.0.6537
-- --------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
DROP TABLE IF EXISTS `area_curso`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `area_curso` (
-- Volcando estructura de base de datos para admision_2026
CREATE DATABASE IF NOT EXISTS `admision_2026` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `admision_2026`;
-- Volcando estructura para tabla admision_2026.areas
CREATE TABLE IF NOT EXISTS `areas` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`nombre` varchar(100) NOT NULL,
`codigo` varchar(20) NOT NULL,
`descripcion` varchar(500) DEFAULT NULL,
`activo` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `codigo` (`codigo`),
KEY `idx_areas_activo` (`activo`),
KEY `idx_areas_codigo` (`codigo`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Volcando datos para la tabla admision_2026.areas: ~2 rows (aproximadamente)
DELETE FROM `areas`;
INSERT INTO `areas` (`id`, `nombre`, `codigo`, `descripcion`, `activo`, `created_at`, `updated_at`) VALUES
(3, 'Biomedicas', 'BIO', NULL, 1, '2026-02-13 21:37:24', '2026-02-14 00:52:17'),
(4, 'Ingenierias', 'ING', NULL, 1, '2026-02-13 21:37:42', '2026-02-14 00:52:15');
-- Volcando estructura para tabla admision_2026.areas_admision
CREATE TABLE IF NOT EXISTS `areas_admision` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`nombre` varchar(150) NOT NULL,
`descripcion` text,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Volcando datos para la tabla admision_2026.areas_admision: ~0 rows (aproximadamente)
DELETE FROM `areas_admision`;
-- Volcando estructura para tabla admision_2026.area_curso
CREATE TABLE IF NOT EXISTS `area_curso` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`area_id` bigint unsigned NOT NULL,
`curso_id` bigint unsigned NOT NULL,
@ -24,12 +65,18 @@ CREATE TABLE `area_curso` (
KEY `fk_area_curso_curso` (`curso_id`) USING BTREE,
CONSTRAINT `fk_area_curso_area` FOREIGN KEY (`area_id`) REFERENCES `areas` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_area_curso_curso` FOREIGN KEY (`curso_id`) REFERENCES `cursos` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `area_proceso`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `area_proceso` (
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Volcando datos para la tabla admision_2026.area_curso: ~4 rows (aproximadamente)
DELETE FROM `area_curso`;
INSERT INTO `area_curso` (`id`, `area_id`, `curso_id`, `created_at`, `updated_at`) VALUES
(5, 4, 4, '2026-02-13 21:43:34', '2026-02-13 21:43:34'),
(6, 4, 3, '2026-02-13 21:43:34', '2026-02-13 21:43:34'),
(7, 3, 4, '2026-02-13 21:43:36', '2026-02-13 21:43:36'),
(8, 3, 3, '2026-02-13 21:43:36', '2026-02-13 21:43:36');
-- Volcando estructura para tabla admision_2026.area_proceso
CREATE TABLE IF NOT EXISTS `area_proceso` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`area_id` bigint unsigned NOT NULL,
`proceso_id` bigint unsigned NOT NULL,
@ -40,63 +87,58 @@ CREATE TABLE `area_proceso` (
KEY `fk_area_proceso_proceso` (`proceso_id`),
CONSTRAINT `fk_area_proceso_area` FOREIGN KEY (`area_id`) REFERENCES `areas` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_area_proceso_proceso` FOREIGN KEY (`proceso_id`) REFERENCES `procesos` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `areas`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `areas` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`nombre` varchar(100) NOT NULL,
`codigo` varchar(20) NOT NULL,
`descripcion` varchar(500) DEFAULT NULL,
`activo` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `codigo` (`codigo`),
KEY `idx_areas_activo` (`activo`),
KEY `idx_areas_codigo` (`codigo`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `areas_admision`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `areas_admision` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`nombre` varchar(150) NOT NULL,
`descripcion` text,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `cache`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `cache` (
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.area_proceso: ~2 rows (aproximadamente)
DELETE FROM `area_proceso`;
INSERT INTO `area_proceso` (`id`, `area_id`, `proceso_id`, `created_at`, `updated_at`) VALUES
(3, 4, 2, '2026-02-13 21:43:29', '2026-02-13 21:43:29'),
(4, 3, 2, '2026-02-13 21:43:32', '2026-02-13 21:43:32');
-- Volcando estructura para tabla admision_2026.cache
CREATE TABLE IF NOT EXISTS `cache` (
`key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`value` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`expiration` int NOT NULL,
PRIMARY KEY (`key`),
KEY `cache_expiration_index` (`expiration`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `cache_locks`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `cache_locks` (
-- Volcando datos para la tabla admision_2026.cache: ~0 rows (aproximadamente)
DELETE FROM `cache`;
-- Volcando estructura para tabla admision_2026.cache_locks
CREATE TABLE IF NOT EXISTS `cache_locks` (
`key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`owner` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`expiration` int NOT NULL,
PRIMARY KEY (`key`),
KEY `cache_locks_expiration_index` (`expiration`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `cursos`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `cursos` (
-- Volcando datos para la tabla admision_2026.cache_locks: ~0 rows (aproximadamente)
DELETE FROM `cache_locks`;
-- Volcando estructura para tabla admision_2026.calificaciones
CREATE TABLE IF NOT EXISTS `calificaciones` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`nombre` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`puntos_correcta` decimal(5,2) NOT NULL DEFAULT '10.00',
`puntos_incorrecta` decimal(5,2) NOT NULL DEFAULT '0.00',
`puntos_nula` decimal(5,2) NOT NULL DEFAULT '0.00',
`puntaje_maximo` decimal(8,2) NOT NULL DEFAULT '1000.00',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.calificaciones: ~0 rows (aproximadamente)
DELETE FROM `calificaciones`;
INSERT INTO `calificaciones` (`id`, `nombre`, `puntos_correcta`, `puntos_incorrecta`, `puntos_nula`, `puntaje_maximo`, `created_at`, `updated_at`) VALUES
(1, 'preu', 10.00, 0.00, 0.00, 1000.00, NULL, NULL);
-- Volcando estructura para tabla admision_2026.cursos
CREATE TABLE IF NOT EXISTS `cursos` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`nombre` varchar(100) NOT NULL,
`codigo` varchar(20) NOT NULL,
@ -108,12 +150,16 @@ CREATE TABLE `cursos` (
UNIQUE KEY `codigo` (`codigo`),
KEY `idx_cursos_activo` (`activo`),
KEY `idx_cursos_codigo` (`codigo`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `examenes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `examenes` (
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Volcando datos para la tabla admision_2026.cursos: ~2 rows (aproximadamente)
DELETE FROM `cursos`;
INSERT INTO `cursos` (`id`, `nombre`, `codigo`, `descripcion`, `activo`, `created_at`, `updated_at`) VALUES
(3, 'Matematica', 'MAT', NULL, 1, '2026-02-13 21:38:06', '2026-02-13 21:38:06'),
(4, 'Comunicacion', 'COM', NULL, 1, '2026-02-13 21:38:19', '2026-02-13 21:38:19');
-- Volcando estructura para tabla admision_2026.examenes
CREATE TABLE IF NOT EXISTS `examenes` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`postulante_id` bigint unsigned NOT NULL,
`area_proceso_id` bigint unsigned NOT NULL,
@ -121,20 +167,26 @@ CREATE TABLE `examenes` (
`tipo_pago` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`pago_id` decimal(20,6) DEFAULT '0.000000',
`intentos` int NOT NULL DEFAULT '0',
`estado` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pendiente',
`hora_inicio` timestamp NULL DEFAULT NULL,
`hora_fin` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
KEY `examenes_postulante_id_foreign` (`postulante_id`) USING BTREE,
KEY `examenes_area_proceso_id_foreign` (`area_proceso_id`) USING BTREE,
KEY `examenes_estado_index` (`estado`),
CONSTRAINT `examenes_area_proceso_id_foreign` FOREIGN KEY (`area_proceso_id`) REFERENCES `area_proceso` (`id`) ON DELETE CASCADE,
CONSTRAINT `examenes_postulante_id_foreign` FOREIGN KEY (`postulante_id`) REFERENCES `postulantes` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `failed_jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `failed_jobs` (
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.examenes: ~0 rows (aproximadamente)
DELETE FROM `examenes`;
INSERT INTO `examenes` (`id`, `postulante_id`, `area_proceso_id`, `pagado`, `tipo_pago`, `pago_id`, `intentos`, `estado`, `hora_inicio`, `hora_fin`, `created_at`, `updated_at`) VALUES
(21, 6, 4, 0, NULL, NULL, 1, 'calificado', '2026-02-17 18:42:13', '2026-02-17 18:42:25', '2026-02-17 18:42:11', '2026-02-17 18:42:19');
-- Volcando estructura para tabla admision_2026.failed_jobs
CREATE TABLE IF NOT EXISTS `failed_jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`connection` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
@ -145,11 +197,28 @@ CREATE TABLE `failed_jobs` (
PRIMARY KEY (`id`),
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `job_batches`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `job_batches` (
-- Volcando datos para la tabla admision_2026.failed_jobs: ~0 rows (aproximadamente)
DELETE FROM `failed_jobs`;
-- Volcando estructura para tabla admision_2026.jobs
CREATE TABLE IF NOT EXISTS `jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`queue` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`attempts` tinyint unsigned NOT NULL,
`reserved_at` int unsigned DEFAULT NULL,
`available_at` int unsigned NOT NULL,
`created_at` int unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `jobs_queue_index` (`queue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.jobs: ~0 rows (aproximadamente)
DELETE FROM `jobs`;
-- Volcando estructura para tabla admision_2026.job_batches
CREATE TABLE IF NOT EXISTS `job_batches` (
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`total_jobs` int NOT NULL,
@ -162,45 +231,23 @@ CREATE TABLE `job_batches` (
`finished_at` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`queue` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`attempts` tinyint unsigned NOT NULL,
`reserved_at` int unsigned DEFAULT NULL,
`available_at` int unsigned NOT NULL,
`created_at` int unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `jobs_queue_index` (`queue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `migrations` (
-- Volcando datos para la tabla admision_2026.job_batches: ~0 rows (aproximadamente)
DELETE FROM `job_batches`;
-- Volcando estructura para tabla admision_2026.migrations
CREATE TABLE IF NOT EXISTS `migrations` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`migration` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`batch` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
INSERT INTO `migrations` VALUES
(1,'0001_01_01_000000_create_users_table',1),
(2,'0001_01_01_000001_create_cache_table',1),
(3,'0001_01_01_000002_create_jobs_table',1),
(4,'2026_01_27_132900_create_personal_access_tokens_table',1),
(5,'2026_01_27_133609_create_permission_tables',1),
(6,'2026_02_15_051618_fix_unique_constraint_proceso_admision_detalles',2);
DROP TABLE IF EXISTS `model_has_permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_has_permissions` (
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.migrations: ~0 rows (aproximadamente)
DELETE FROM `migrations`;
-- Volcando estructura para tabla admision_2026.model_has_permissions
CREATE TABLE IF NOT EXISTS `model_has_permissions` (
`permission_id` bigint unsigned NOT NULL,
`model_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`model_id` bigint unsigned NOT NULL,
@ -208,11 +255,12 @@ CREATE TABLE `model_has_permissions` (
KEY `model_has_permissions_model_id_model_type_index` (`model_id`,`model_type`),
CONSTRAINT `model_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `model_has_roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_has_roles` (
-- Volcando datos para la tabla admision_2026.model_has_permissions: ~0 rows (aproximadamente)
DELETE FROM `model_has_permissions`;
-- Volcando estructura para tabla admision_2026.model_has_roles
CREATE TABLE IF NOT EXISTS `model_has_roles` (
`role_id` bigint unsigned NOT NULL,
`model_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`model_id` bigint unsigned NOT NULL,
@ -220,11 +268,47 @@ CREATE TABLE `model_has_roles` (
KEY `model_has_roles_model_id_model_type_index` (`model_id`,`model_type`),
CONSTRAINT `model_has_roles_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `pagos`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `pagos` (
-- Volcando datos para la tabla admision_2026.model_has_roles: ~0 rows (aproximadamente)
DELETE FROM `model_has_roles`;
INSERT INTO `model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES
(5, 'App\\Models\\User', 5);
-- Volcando estructura para tabla admision_2026.noticias
CREATE TABLE IF NOT EXISTS `noticias` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`titulo` varchar(220) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`slug` varchar(260) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`descripcion_corta` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`contenido` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`categoria` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'General',
`tag_color` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`imagen_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`imagen_url` varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`link_url` varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`link_texto` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 'Leer más',
`fecha_publicacion` datetime DEFAULT NULL,
`publicado` tinyint(1) NOT NULL DEFAULT '0',
`destacado` tinyint(1) NOT NULL DEFAULT '0',
`orden` int NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_noticias_slug` (`slug`),
KEY `idx_noticias_publicado_fecha` (`publicado`,`fecha_publicacion`),
KEY `idx_noticias_categoria` (`categoria`),
KEY `idx_noticias_destacado` (`destacado`),
KEY `idx_noticias_deleted` (`deleted_at`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.noticias: ~1 rows (aproximadamente)
DELETE FROM `noticias`;
INSERT INTO `noticias` (`id`, `titulo`, `slug`, `descripcion_corta`, `contenido`, `categoria`, `tag_color`, `imagen_path`, `imagen_url`, `link_url`, `link_texto`, `fecha_publicacion`, `publicado`, `destacado`, `orden`, `created_at`, `updated_at`, `deleted_at`) VALUES
(3, 'noticia 1', 'noticia-1', 'descripcion corta', 'contenido extenso de la noticia', 'noticias', 'blue', 'noticias/KzNdtcm035xM8NQwFad4VDbB3cEoqEfDTdQmwG9Z.png', NULL, NULL, 'Leer más', '2026-02-17 14:51:01', 1, 1, 0, '2026-02-17 19:51:49', '2026-02-17 19:51:49', NULL);
-- Volcando estructura para tabla admision_2026.pagos
CREATE TABLE IF NOT EXISTS `pagos` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`postulante_id` bigint unsigned NOT NULL,
`tipo_pago` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
@ -241,21 +325,23 @@ CREATE TABLE `pagos` (
KEY `pagos_postulante_id_foreign` (`postulante_id`) USING BTREE,
CONSTRAINT `pagos_postulante_id_foreign` FOREIGN KEY (`postulante_id`) REFERENCES `postulantes` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `password_reset_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `password_reset_tokens` (
-- Volcando datos para la tabla admision_2026.pagos: ~0 rows (aproximadamente)
DELETE FROM `pagos`;
-- Volcando estructura para tabla admision_2026.password_reset_tokens
CREATE TABLE IF NOT EXISTS `password_reset_tokens` (
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `permissions` (
-- Volcando datos para la tabla admision_2026.password_reset_tokens: ~0 rows (aproximadamente)
DELETE FROM `password_reset_tokens`;
-- Volcando estructura para tabla admision_2026.permissions
CREATE TABLE IF NOT EXISTS `permissions` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`guard_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
@ -264,11 +350,21 @@ CREATE TABLE `permissions` (
PRIMARY KEY (`id`),
UNIQUE KEY `permissions_name_guard_name_unique` (`name`,`guard_name`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `personal_access_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `personal_access_tokens` (
-- Volcando datos para la tabla admision_2026.permissions: ~4 rows (aproximadamente)
DELETE FROM `permissions`;
INSERT INTO `permissions` (`id`, `name`, `guard_name`, `created_at`, `updated_at`) VALUES
(1, 'ver-preguntas', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26'),
(2, 'crear-preguntas', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26'),
(3, 'editar-preguntas', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26'),
(4, 'eliminar-preguntas', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26'),
(5, 'ver-cursos', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26'),
(6, 'crear-cursos', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26'),
(7, 'editar-cursos', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26'),
(8, 'eliminar-cursos', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26');
-- Volcando estructura para tabla admision_2026.personal_access_tokens
CREATE TABLE IF NOT EXISTS `personal_access_tokens` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`tokenable_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`tokenable_id` bigint unsigned NOT NULL,
@ -283,12 +379,15 @@ CREATE TABLE `personal_access_tokens` (
UNIQUE KEY `personal_access_tokens_token_unique` (`token`),
KEY `personal_access_tokens_tokenable_type_tokenable_id_index` (`tokenable_type`,`tokenable_id`),
KEY `personal_access_tokens_expires_at_index` (`expires_at`)
) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `postulantes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `postulantes` (
) ENGINE=InnoDB AUTO_INCREMENT=75 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.personal_access_tokens: ~3 rows (aproximadamente)
DELETE FROM `personal_access_tokens`;
INSERT INTO `personal_access_tokens` (`id`, `tokenable_type`, `tokenable_id`, `name`, `token`, `abilities`, `last_used_at`, `expires_at`, `created_at`, `updated_at`) VALUES
(46, 'App\\Models\\User', 2, 'api_token', 'dc172c99f9d46ff643b75db1fce280cf28ca2184ccc31576bf170e5abe5c0bbe', '["*"]', '2026-02-13 21:33:51', '2026-02-14 09:33:46', '2026-02-13 21:33:46', '2026-02-13 21:33:51');
-- Volcando estructura para tabla admision_2026.postulantes
CREATE TABLE IF NOT EXISTS `postulantes` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
@ -301,12 +400,15 @@ CREATE TABLE `postulantes` (
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
UNIQUE KEY `dni` (`dni`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `preguntas`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `preguntas` (
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Volcando datos para la tabla admision_2026.postulantes: ~0 rows (aproximadamente)
DELETE FROM `postulantes`;
INSERT INTO `postulantes` (`id`, `name`, `email`, `password`, `dni`, `device_id`, `last_activity`, `created_at`, `updated_at`) VALUES
(6, 'Elmer Yujra Condori', 'elmer26@gmail.com', '$2y$12$wqDpRA9Ek6mKjsnBWAOvlOn0yUdWV1eln1MaLmuZptWorTvEWG6t6', '73903851', NULL, NULL, '2026-02-13 18:40:08', '2026-02-17 19:02:44');
-- Volcando estructura para tabla admision_2026.preguntas
CREATE TABLE IF NOT EXISTS `preguntas` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`curso_id` bigint unsigned NOT NULL,
`enunciado` longtext NOT NULL,
@ -324,18 +426,27 @@ CREATE TABLE `preguntas` (
KEY `idx_preguntas_curso` (`curso_id`),
KEY `idx_preguntas_activo` (`activo`),
CONSTRAINT `fk_preguntas_curso` FOREIGN KEY (`curso_id`) REFERENCES `cursos` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `preguntas_asignadas`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `preguntas_asignadas` (
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Volcando datos para la tabla admision_2026.preguntas: ~7 rows (aproximadamente)
DELETE FROM `preguntas`;
INSERT INTO `preguntas` (`id`, `curso_id`, `enunciado`, `enunciado_adicional`, `opciones`, `respuesta_correcta`, `explicacion`, `imagenes_explicacion`, `imagenes`, `nivel_dificultad`, `activo`, `created_at`, `updated_at`) VALUES
(7, 4, '$$ 2x + 7 = 19 $$', '$$ 2x + 7 = 19 $$', '["$$ 2x + 7 = 19 $$", "$$ 2x + 7 ="]', '$$ 2x + 7 =', '$$ 2x + 7 = 19 $$', '[]', '[]', 'medio', 1, '2026-02-13 21:40:16', '2026-02-13 21:40:16'),
(8, 4, 'Resolver la siguiente integral:\r\n$$\r\n\\int_0^2 x^3\\,dx = \\left[\\frac{x^4}{4}\\right]_0^2 = \\frac{16}{4} = 4\r\n$$', 'Resolver la siguiente integral:\r\n$$\r\n\\int_0^2 x^3\\,dx = \\left[\\frac{x^4}{4}\\right]_0^2 = \\frac{16}{4} = 4\r\n$$', '["FEWEFWEFWEFW", "FEEFW"]', 'FEEFW', 'FEWEFWFEEFWResolver la siguiente integral:\r\n$$\r\n\\int_0^2 x^3\\,dx = \\left[\\frac{x^4}{4}\\right]_0^2 = \\frac{16}{4} = 4\r\n$$', '[]', '["http://127.0.0.1:8000/storage/preguntas/enunciados/7eiL0SNx7z4EpgfK8PNxe09ocxt0GBR8O7dddPVq.png"]', 'medio', 1, '2026-02-13 21:41:34', '2026-02-16 08:00:54'),
(9, 4, 'FEWEFWEFWEFWEFWEFW', NULL, '["$$ \\\\int_0^2 x^3 \\\\, dx $$", "$$ \\\\int_0^2 x^3 \\\\,$$"]', '$$ \\int_0^2 x^3 \\,$$', 'FEEFWEFW', '[]', '[]', 'medio', 1, '2026-02-13 21:41:55', '2026-02-16 08:02:02'),
(10, 4, 'Resolver la siguiente integral:\r\n$$\r\n\\int_0^2 x^3\\,dx = \\left[\\frac{x^4}{4}\\right]_0^2 = \\frac{16}{4} = 4\r\n$$', 'twtwewteWETTWEtwe', '["twetwe", "wttwe", "Resolver la siguiente integral:\\n$$\\n\\\\int_0^2 x^3\\\\,dx = \\\\left[\\\\frac{x^4}{4}\\\\right]_0^2 = \\\\frac{16}{4} = 4\\n$$"]', 'wttwe', 'twtwtwtWETWTEWTEW', '[]', '["http://127.0.0.1:8000/storage/preguntas/enunciados/y26OCN43bCkYNXbK7mx5i675diFvKkD0alAw0Eqo.png"]', 'medio', 1, '2026-02-13 21:42:11', '2026-02-16 08:01:10'),
(11, 3, 'WTTWWTETWTWEETW', 'TWEETWETWTWE', '["TEWETW", "ETWETWTWE", "TWETW"]', 'TWETW', 'ETWETWETW', '[]', '[]', 'medio', 1, '2026-02-13 21:42:43', '2026-02-13 21:42:43'),
(12, 3, 'RWWRRWERWE', 'FWEFWEWEFFWE', '["FWEEFWEFW", "FEWEFW", "FEWEFWEFW"]', 'FEWEFWEFW', 'FWFWEFWEFEW', '[]', '[]', 'medio', 1, '2026-02-13 21:42:58', '2026-02-13 21:42:58'),
(13, 3, 'EFWEFWEFWEFW', 'FEWFEW', '["EFWEFWEW", "EFWEFWEE", "EFWEFW"]', 'EFWEFW', 'EFWEFW', '[]', '[]', 'medio', 1, '2026-02-13 21:43:21', '2026-02-13 21:43:21');
-- Volcando estructura para tabla admision_2026.preguntas_asignadas
CREATE TABLE IF NOT EXISTS `preguntas_asignadas` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`examen_id` bigint unsigned NOT NULL,
`pregunta_id` bigint unsigned NOT NULL,
`orden` int NOT NULL,
`respuesta_usuario` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Clave elegida (A, B, C, D) o texto si es abierta',
`es_correcta` tinyint(1) DEFAULT NULL COMMENT '1 correcta, 0 incorrecta, NULL no respondida',
`respuesta_usuario` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Clave elegida (A, B, C, D) o texto si es abierta',
`es_correcta` tinyint(1) NOT NULL DEFAULT '2' COMMENT '1 correcta, 0 incorrecta, 2 blanco',
`estado` enum('pendiente','respondida','anulada') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pendiente',
`puntaje` decimal(5,2) NOT NULL DEFAULT '0.00',
`respondida_at` timestamp NULL DEFAULT NULL,
@ -347,36 +458,17 @@ CREATE TABLE `preguntas_asignadas` (
KEY `idx_preg_asig_estado` (`estado`) USING BTREE,
CONSTRAINT `fk_preg_asig_examen` FOREIGN KEY (`examen_id`) REFERENCES `examenes` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_preg_asig_pregunta` FOREIGN KEY (`pregunta_id`) REFERENCES `preguntas` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `proceso_admision_detalles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `proceso_admision_detalles` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`proceso_admision_id` bigint unsigned NOT NULL,
`tipo` enum('requisitos','pagos','vacantes','cronograma') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`titulo_detalle` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`descripcion` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`listas` json DEFAULT NULL,
`meta` json DEFAULT NULL,
`url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`imagen_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`imagen_path_2` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_proceso_tipo` (`proceso_admision_id`,`tipo`),
KEY `idx_detalles_lookup` (`proceso_admision_id`,`tipo`),
CONSTRAINT `fk_detalles_proceso` FOREIGN KEY (`proceso_admision_id`) REFERENCES `procesos_admision` (`id`) ON DELETE CASCADE,
CONSTRAINT `proceso_admision_detalles_chk_1` CHECK (json_valid(`listas`)),
CONSTRAINT `proceso_admision_detalles_chk_2` CHECK (json_valid(`meta`))
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `procesos`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `procesos` (
) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.preguntas_asignadas: ~3 rows (aproximadamente)
DELETE FROM `preguntas_asignadas`;
INSERT INTO `preguntas_asignadas` (`id`, `examen_id`, `pregunta_id`, `orden`, `respuesta_usuario`, `es_correcta`, `estado`, `puntaje`, `respondida_at`, `created_at`, `updated_at`) VALUES
(45, 21, 11, 1, 'TEWETW', 0, 'respondida', 0.00, '2026-02-17 18:42:15', '2026-02-17 18:42:12', '2026-02-17 18:42:25'),
(46, 21, 13, 2, 'EFWEFWEW', 0, 'respondida', 0.00, '2026-02-17 18:42:17', '2026-02-17 18:42:13', '2026-02-17 18:42:25'),
(47, 21, 9, 3, '$$ \\int_0^2 x^3 \\, dx $$', 0, 'respondida', 0.00, '2026-02-17 18:42:19', '2026-02-17 18:42:13', '2026-02-17 18:42:25');
-- Volcando estructura para tabla admision_2026.procesos
CREATE TABLE IF NOT EXISTS `procesos` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`nombre` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`descripcion` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
@ -404,12 +496,15 @@ CREATE TABLE `procesos` (
KEY `idx_examenes_publico` (`publico`),
KEY `idx_examenes_tipo_simulacro` (`tipo_simulacro`),
KEY `idx_examenes_tipo_proceso` (`tipo_proceso`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `procesos_admision`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `procesos_admision` (
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.procesos: ~1 rows (aproximadamente)
DELETE FROM `procesos`;
INSERT INTO `procesos` (`id`, `nombre`, `descripcion`, `estado`, `duracion`, `intentos_maximos`, `requiere_pago`, `precio`, `calificacion_id`, `slug`, `tipo_simulacro`, `tipo_proceso`, `activo`, `publico`, `fecha_inicio`, `fecha_fin`, `tiempo_por_pregunta`, `cantidad_pregunta`, `created_at`, `updated_at`) VALUES
(2, 'test1', 'prueba de admision', 'borrador', 5, 1, 0, NULL, 1, 'test1-698f5330e6eb2', 'admision', 'test', 1, 1, NULL, NULL, NULL, 10, '2026-02-13 21:37:04', '2026-02-17 13:42:01');
-- Volcando estructura para tabla admision_2026.procesos_admision
CREATE TABLE IF NOT EXISTS `procesos_admision` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`titulo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`subtitulo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
@ -443,11 +538,36 @@ CREATE TABLE `procesos_admision` (
KEY `idx_procesos_publico` (`publicado`,`estado`),
KEY `idx_procesos_fechas` (`fecha_inicio_inscripcion`,`fecha_examen1`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `reglas_area_proceso`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `reglas_area_proceso` (
-- Volcando datos para la tabla admision_2026.procesos_admision: ~0 rows (aproximadamente)
DELETE FROM `procesos_admision`;
-- Volcando estructura para tabla admision_2026.proceso_admision_detalles
CREATE TABLE IF NOT EXISTS `proceso_admision_detalles` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`proceso_admision_id` bigint unsigned NOT NULL,
`tipo` enum('requisitos','pagos','vacantes','cronograma') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`titulo_detalle` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`descripcion` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`listas` json DEFAULT NULL,
`meta` json DEFAULT NULL,
`url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`imagen_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`imagen_path_2` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_detalles_lookup` (`proceso_admision_id`,`tipo`),
CONSTRAINT `fk_detalles_proceso` FOREIGN KEY (`proceso_admision_id`) REFERENCES `procesos_admision` (`id`) ON DELETE CASCADE,
CONSTRAINT `proceso_admision_detalles_chk_1` CHECK (json_valid(`listas`)),
CONSTRAINT `proceso_admision_detalles_chk_2` CHECK (json_valid(`meta`))
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.proceso_admision_detalles: ~4 rows (aproximadamente)
DELETE FROM `proceso_admision_detalles`;
-- Volcando estructura para tabla admision_2026.reglas_area_proceso
CREATE TABLE IF NOT EXISTS `reglas_area_proceso` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`area_proceso_id` bigint unsigned NOT NULL,
`curso_id` bigint unsigned NOT NULL,
@ -462,12 +582,18 @@ CREATE TABLE `reglas_area_proceso` (
KEY `curso_id` (`curso_id`),
CONSTRAINT `reglas_area_proceso_ibfk_1` FOREIGN KEY (`area_proceso_id`) REFERENCES `area_proceso` (`id`),
CONSTRAINT `reglas_area_proceso_ibfk_2` FOREIGN KEY (`curso_id`) REFERENCES `cursos` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `resultados_admision`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `resultados_admision` (
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Volcando datos para la tabla admision_2026.reglas_area_proceso: ~0 rows (aproximadamente)
DELETE FROM `reglas_area_proceso`;
INSERT INTO `reglas_area_proceso` (`id`, `area_proceso_id`, `curso_id`, `cantidad_preguntas`, `orden`, `nivel_dificultad`, `ponderacion`, `created_at`, `updated_at`) VALUES
(10, 3, 4, 1, 1, 'medio', 5.00, '2026-02-13 21:46:46', '2026-02-13 21:46:46'),
(11, 3, 3, 2, 2, 'medio', 5.00, '2026-02-13 21:47:33', '2026-02-13 21:47:33'),
(14, 4, 3, 2, 1, 'medio', 5.00, '2026-02-13 21:48:33', '2026-02-13 21:48:33'),
(15, 4, 4, 1, 2, 'medio', 5.00, '2026-02-13 21:48:33', '2026-02-13 21:48:33');
-- Volcando estructura para tabla admision_2026.resultados_admision
CREATE TABLE IF NOT EXISTS `resultados_admision` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`dni` varchar(20) NOT NULL,
`paterno` varchar(100) NOT NULL,
@ -496,11 +622,12 @@ CREATE TABLE `resultados_admision` (
CONSTRAINT `fk_resultado_area_admision` FOREIGN KEY (`idearea`) REFERENCES `areas_admision` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_resultado_proceso_admision` FOREIGN KEY (`idproceso`) REFERENCES `procesos_admision` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `resultados_admision_carga`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `resultados_admision_carga` (
-- Volcando datos para la tabla admision_2026.resultados_admision: ~0 rows (aproximadamente)
DELETE FROM `resultados_admision`;
-- Volcando estructura para tabla admision_2026.resultados_admision_carga
CREATE TABLE IF NOT EXISTS `resultados_admision_carga` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`dni` varchar(20) DEFAULT NULL,
`paterno` varchar(100) DEFAULT NULL,
@ -591,23 +718,43 @@ CREATE TABLE `resultados_admision_carga` (
CONSTRAINT `fk_carga_area` FOREIGN KEY (`idearea`) REFERENCES `areas_admision` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_carga_proceso` FOREIGN KEY (`idproceso`) REFERENCES `procesos_admision` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `role_has_permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `role_has_permissions` (
`permission_id` bigint unsigned NOT NULL,
`role_id` bigint unsigned NOT NULL,
PRIMARY KEY (`permission_id`,`role_id`),
KEY `role_has_permissions_role_id_foreign` (`role_id`),
CONSTRAINT `role_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE,
CONSTRAINT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `roles` (
-- Volcando datos para la tabla admision_2026.resultados_admision_carga: ~0 rows (aproximadamente)
DELETE FROM `resultados_admision_carga`;
-- Volcando estructura para tabla admision_2026.resultados_examenes
CREATE TABLE IF NOT EXISTS `resultados_examenes` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`postulante_id` bigint unsigned NOT NULL,
`examen_id` bigint unsigned NOT NULL,
`total_puntos` decimal(8,3) NOT NULL,
`correctas_por_curso` json NOT NULL,
`incorrectas_por_curso` json DEFAULT NULL,
`preguntas_totales_por_curso` json DEFAULT NULL,
`total_correctas` int NOT NULL,
`total_incorrectas` int NOT NULL,
`total_nulas` int NOT NULL,
`porcentaje_correctas` double(5,2) NOT NULL,
`calificacion_sobre_20` double(5,2) NOT NULL,
`orden_merito` int DEFAULT NULL,
`probabilidad_ingreso` float DEFAULT NULL,
`programa_recomendado` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
KEY `resultados_examenes_postulante_id_foreign` (`postulante_id`) USING BTREE,
KEY `resultados_examenes_examen_id_foreign` (`examen_id`) USING BTREE,
CONSTRAINT `resultados_examenes_examen_id_foreign` FOREIGN KEY (`examen_id`) REFERENCES `examenes` (`id`) ON DELETE CASCADE,
CONSTRAINT `resultados_examenes_postulante_id_foreign` FOREIGN KEY (`postulante_id`) REFERENCES `postulantes` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.resultados_examenes: ~0 rows (aproximadamente)
DELETE FROM `resultados_examenes`;
INSERT INTO `resultados_examenes` (`id`, `postulante_id`, `examen_id`, `total_puntos`, `correctas_por_curso`, `incorrectas_por_curso`, `preguntas_totales_por_curso`, `total_correctas`, `total_incorrectas`, `total_nulas`, `porcentaje_correctas`, `calificacion_sobre_20`, `orden_merito`, `probabilidad_ingreso`, `programa_recomendado`, `created_at`, `updated_at`) VALUES
(11, 6, 21, 0.000, '{"Matematica": "0 de 2", "Comunicacion": "0 de 1"}', '{"Matematica": 2, "Comunicacion": 1}', '{"Matematica": 2, "Comunicacion": 1}', 0, 3, 0, 0.00, 0.00, 1, NULL, NULL, '2026-02-17 18:42:25', '2026-02-17 18:42:25');
-- Volcando estructura para tabla admision_2026.roles
CREATE TABLE IF NOT EXISTS `roles` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`guard_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
@ -616,11 +763,46 @@ CREATE TABLE `roles` (
PRIMARY KEY (`id`),
UNIQUE KEY `roles_name_guard_name_unique` (`name`,`guard_name`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `sessions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sessions` (
-- Volcando datos para la tabla admision_2026.roles: ~0 rows (aproximadamente)
DELETE FROM `roles`;
INSERT INTO `roles` (`id`, `name`, `guard_name`, `created_at`, `updated_at`) VALUES
(4, 'usuario', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26'),
(5, 'administrador', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26'),
(6, 'superadmin', 'web', '2026-02-13 21:27:26', '2026-02-13 21:27:26');
-- Volcando estructura para tabla admision_2026.role_has_permissions
CREATE TABLE IF NOT EXISTS `role_has_permissions` (
`permission_id` bigint unsigned NOT NULL,
`role_id` bigint unsigned NOT NULL,
PRIMARY KEY (`permission_id`,`role_id`),
KEY `role_has_permissions_role_id_foreign` (`role_id`),
CONSTRAINT `role_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE,
CONSTRAINT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Volcando datos para la tabla admision_2026.role_has_permissions: ~0 rows (aproximadamente)
DELETE FROM `role_has_permissions`;
INSERT INTO `role_has_permissions` (`permission_id`, `role_id`) VALUES
(1, 5),
(2, 5),
(3, 5),
(4, 5),
(5, 5),
(6, 5),
(7, 5),
(8, 5),
(1, 6),
(2, 6),
(3, 6),
(4, 6),
(5, 6),
(6, 6),
(7, 6),
(8, 6);
-- Volcando estructura para tabla admision_2026.sessions
CREATE TABLE IF NOT EXISTS `sessions` (
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` bigint unsigned DEFAULT NULL,
`ip_address` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
@ -631,11 +813,12 @@ CREATE TABLE `sessions` (
KEY `sessions_user_id_index` (`user_id`),
KEY `sessions_last_activity_index` (`last_activity`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `users` (
-- Volcando datos para la tabla admision_2026.sessions: ~0 rows (aproximadamente)
DELETE FROM `sessions`;
-- Volcando estructura para tabla admision_2026.users
CREATE TABLE IF NOT EXISTS `users` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
@ -646,15 +829,15 @@ CREATE TABLE `users` (
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Volcando datos para la tabla admision_2026.users: ~1 rows (aproximadamente)
DELETE FROM `users`;
INSERT INTO `users` (`id`, `name`, `email`, `email_verified_at`, `password`, `remember_token`, `created_at`, `updated_at`) VALUES
(5, 'Elmer Admin', 'elmer20@gmail.com', NULL, '$2y$12$dFWOcwAPv3v3oQzeO/JJbOyP7IgfI6uMSk3XIpWYTOSNxMf9WhqFm', NULL, '2026-02-13 21:36:09', '2026-02-13 21:36:09');
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;

@ -1,6 +1,6 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_KEY=base64:wEHrK9znRe1M/jdkBIM7b+TihyUtHygp8hKeqE57eB4=
APP_DEBUG=true
APP_URL=http://localhost:8000

@ -152,23 +152,6 @@ class ProcesoAdmisionController extends Controller
]);
// Campos nullable: si vienen en el request (aunque sea vacíos/null), incluirlos en $data
$nullableFields = [
'subtitulo','descripcion','tipo_proceso','modalidad',
'fecha_publicacion',
'fecha_inicio_preinscripcion','fecha_fin_preinscripcion',
'fecha_inicio_inscripcion','fecha_fin_inscripcion',
'fecha_examen1','fecha_examen2','fecha_resultados',
'fecha_inicio_biometrico','fecha_fin_biometrico',
'link_preinscripcion','link_inscripcion','link_resultados','link_reglamento',
];
foreach ($nullableFields as $field) {
if ($request->has($field) && !array_key_exists($field, $data)) {
$data[$field] = null;
}
}
if (array_key_exists('slug', $data) && empty($data['slug'])) {
$data['slug'] = Str::slug($data['titulo'] ?? $proceso->titulo);
}
@ -192,16 +175,6 @@ class ProcesoAdmisionController extends Controller
}
public function publicados()
{
$procesos = ProcesoAdmision::where('publicado', true)
->with(['detalles' => fn($d) => $d->orderBy('id', 'asc')])
->orderByDesc('id')
->get();
return response()->json($procesos);
}
public function destroy($id)
{
$proceso = ProcesoAdmision::findOrFail($id);

@ -1,33 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('proceso_admision_detalles', function (Blueprint $table) {
// Eliminar el unique incorrecto (solo proceso_admision_id)
$table->dropUnique('uq_proceso_modalidad_tipo');
// Crear el unique correcto: un detalle por tipo por proceso
$table->unique(['proceso_admision_id', 'tipo'], 'uq_proceso_tipo');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('proceso_admision_detalles', function (Blueprint $table) {
$table->dropUnique('uq_proceso_tipo');
$table->unique('proceso_admision_id', 'uq_proceso_modalidad_tipo');
});
}
};

@ -199,9 +199,6 @@ Route::middleware(['auth:postulante'])->group(function () {
});
// Ruta pública (sin auth) - procesos publicados para la web
Route::get('/procesos-admision/publicados', [ProcesoAdmisionController::class, 'publicados']);
Route::middleware('auth:sanctum')->prefix('admin')->group(function () {
// PROCESOS

@ -1,156 +0,0 @@
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\ProcesoAdmision;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ProcesoAdmisionPublicadosTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// Ensure the procesos_admision table exists
$this->artisan('migrate', ['--path' => 'database/migrations']);
// Create table if doesn't exist (for testing with SQLite)
\Illuminate\Support\Facades\Schema::dropIfExists('procesos_admision');
\Illuminate\Support\Facades\Schema::create('procesos_admision', function ($table) {
$table->id();
$table->string('titulo');
$table->string('subtitulo')->nullable();
$table->text('descripcion')->nullable();
$table->string('slug', 120)->unique();
$table->string('tipo_proceso', 60)->nullable();
$table->string('modalidad', 50)->nullable();
$table->boolean('publicado')->default(false);
$table->datetime('fecha_publicacion')->nullable();
$table->datetime('fecha_inicio_preinscripcion')->nullable();
$table->datetime('fecha_fin_preinscripcion')->nullable();
$table->datetime('fecha_inicio_inscripcion')->nullable();
$table->datetime('fecha_fin_inscripcion')->nullable();
$table->datetime('fecha_examen1')->nullable();
$table->datetime('fecha_examen2')->nullable();
$table->datetime('fecha_resultados')->nullable();
$table->datetime('fecha_inicio_biometrico')->nullable();
$table->datetime('fecha_fin_biometrico')->nullable();
$table->string('imagen_path', 500)->nullable();
$table->string('banner_path', 500)->nullable();
$table->string('brochure_path', 500)->nullable();
$table->string('link_preinscripcion', 500)->nullable();
$table->string('link_inscripcion', 500)->nullable();
$table->string('link_resultados', 500)->nullable();
$table->string('link_reglamento', 500)->nullable();
$table->enum('estado', ['nuevo', 'publicado', 'en_proceso', 'finalizado', 'cancelado'])->default('nuevo');
$table->timestamps();
});
}
/** @test */
public function publicados_endpoint_returns_only_published_processes(): void
{
// Create published processes
$published1 = ProcesoAdmision::create([
'titulo' => 'Proceso Publicado 1',
'slug' => 'proceso-publicado-1',
'publicado' => true,
'estado' => 'publicado',
]);
$published2 = ProcesoAdmision::create([
'titulo' => 'Proceso Publicado 2',
'slug' => 'proceso-publicado-2',
'publicado' => true,
'estado' => 'en_proceso',
]);
// Create unpublished processes
ProcesoAdmision::create([
'titulo' => 'Proceso No Publicado 1',
'slug' => 'proceso-no-publicado-1',
'publicado' => false,
'estado' => 'nuevo',
]);
ProcesoAdmision::create([
'titulo' => 'Proceso No Publicado 2',
'slug' => 'proceso-no-publicado-2',
'publicado' => false,
'estado' => 'finalizado',
]);
// Call the publicados endpoint
$response = $this->getJson('/api/procesos-admision/publicados');
// Assert response is successful
$response->assertStatus(200);
// Assert only published processes are returned
$response->assertJsonCount(2);
// Assert the returned processes are the published ones
$responseData = $response->json();
$returnedTitles = array_column($responseData, 'titulo');
$this->assertContains('Proceso Publicado 1', $returnedTitles);
$this->assertContains('Proceso Publicado 2', $returnedTitles);
$this->assertNotContains('Proceso No Publicado 1', $returnedTitles);
$this->assertNotContains('Proceso No Publicado 2', $returnedTitles);
}
/** @test */
public function publicados_endpoint_returns_empty_array_when_no_published_processes(): void
{
// Create only unpublished processes
ProcesoAdmision::create([
'titulo' => 'Proceso No Publicado',
'slug' => 'proceso-no-publicado',
'publicado' => false,
'estado' => 'nuevo',
]);
$response = $this->getJson('/api/procesos-admision/publicados');
$response->assertStatus(200);
$response->assertJsonCount(0);
$response->assertJson([]);
}
/** @test */
public function publicados_endpoint_returns_processes_ordered_by_id_desc(): void
{
// Create published processes
$first = ProcesoAdmision::create([
'titulo' => 'Primer Proceso',
'slug' => 'primer-proceso',
'publicado' => true,
]);
$second = ProcesoAdmision::create([
'titulo' => 'Segundo Proceso',
'slug' => 'segundo-proceso',
'publicado' => true,
]);
$third = ProcesoAdmision::create([
'titulo' => 'Tercer Proceso',
'slug' => 'tercer-proceso',
'publicado' => true,
]);
$response = $this->getJson('/api/procesos-admision/publicados');
$response->assertStatus(200);
$responseData = $response->json();
// Should be ordered by id desc (newest first)
$this->assertEquals('Tercer Proceso', $responseData[0]['titulo']);
$this->assertEquals('Segundo Proceso', $responseData[1]['titulo']);
$this->assertEquals('Primer Proceso', $responseData[2]['titulo']);
}
}

@ -1,213 +0,0 @@
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use App\Models\ProcesoAdmision;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
class ProcesoAdmisionUpdateNullableTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->artisan('migrate', ['--path' => 'database/migrations']);
// Create procesos_admision table
\Illuminate\Support\Facades\Schema::dropIfExists('procesos_admision');
\Illuminate\Support\Facades\Schema::create('procesos_admision', function ($table) {
$table->id();
$table->string('titulo');
$table->string('subtitulo')->nullable();
$table->text('descripcion')->nullable();
$table->string('slug', 120)->unique();
$table->string('tipo_proceso', 60)->nullable();
$table->string('modalidad', 50)->nullable();
$table->boolean('publicado')->default(false);
$table->datetime('fecha_publicacion')->nullable();
$table->datetime('fecha_inicio_preinscripcion')->nullable();
$table->datetime('fecha_fin_preinscripcion')->nullable();
$table->datetime('fecha_inicio_inscripcion')->nullable();
$table->datetime('fecha_fin_inscripcion')->nullable();
$table->datetime('fecha_examen1')->nullable();
$table->datetime('fecha_examen2')->nullable();
$table->datetime('fecha_resultados')->nullable();
$table->datetime('fecha_inicio_biometrico')->nullable();
$table->datetime('fecha_fin_biometrico')->nullable();
$table->string('imagen_path', 500)->nullable();
$table->string('banner_path', 500)->nullable();
$table->string('brochure_path', 500)->nullable();
$table->string('link_preinscripcion', 500)->nullable();
$table->string('link_inscripcion', 500)->nullable();
$table->string('link_resultados', 500)->nullable();
$table->string('link_reglamento', 500)->nullable();
$table->enum('estado', ['nuevo', 'publicado', 'en_proceso', 'finalizado', 'cancelado'])->default('nuevo');
$table->timestamps();
});
}
protected function authenticateUser(): void
{
$user = User::factory()->create();
Sanctum::actingAs($user);
}
/** @test */
public function update_sets_nullable_fields_to_null_when_empty_values_provided(): void
{
$this->authenticateUser();
// Create a process with filled nullable fields
$proceso = ProcesoAdmision::create([
'titulo' => 'Proceso Test',
'slug' => 'proceso-test',
'subtitulo' => 'Subtitulo Original',
'descripcion' => 'Descripcion Original',
'tipo_proceso' => 'ordinario',
'modalidad' => 'presencial',
'fecha_publicacion' => '2026-01-15 10:00:00',
'fecha_inicio_preinscripcion' => '2026-01-20 08:00:00',
'fecha_fin_preinscripcion' => '2026-01-25 18:00:00',
'link_preinscripcion' => 'https://example.com/preinscripcion',
'link_inscripcion' => 'https://example.com/inscripcion',
]);
// Verify initial values
$this->assertEquals('Subtitulo Original', $proceso->subtitulo);
$this->assertEquals('Descripcion Original', $proceso->descripcion);
$this->assertEquals('ordinario', $proceso->tipo_proceso);
$this->assertNotNull($proceso->fecha_publicacion);
$this->assertNotNull($proceso->link_preinscripcion);
// Update with empty values for nullable fields
$response = $this->patchJson("/api/admin/procesos-admision/{$proceso->id}", [
'subtitulo' => '',
'descripcion' => null,
'tipo_proceso' => '',
'fecha_publicacion' => null,
'link_preinscripcion' => '',
]);
$response->assertStatus(200);
// Refresh the model from database
$proceso->refresh();
// Assert nullable fields are now null
$this->assertNull($proceso->subtitulo);
$this->assertNull($proceso->descripcion);
$this->assertNull($proceso->tipo_proceso);
$this->assertNull($proceso->fecha_publicacion);
$this->assertNull($proceso->link_preinscripcion);
// Assert other fields remain unchanged
$this->assertEquals('Proceso Test', $proceso->titulo);
$this->assertEquals('presencial', $proceso->modalidad);
$this->assertEquals('https://example.com/inscripcion', $proceso->link_inscripcion);
}
/** @test */
public function update_sets_date_fields_to_null_when_empty(): void
{
$this->authenticateUser();
$proceso = ProcesoAdmision::create([
'titulo' => 'Proceso Con Fechas',
'slug' => 'proceso-con-fechas',
'fecha_inicio_inscripcion' => '2026-02-01 08:00:00',
'fecha_fin_inscripcion' => '2026-02-15 18:00:00',
'fecha_examen1' => '2026-02-20 09:00:00',
'fecha_examen2' => '2026-02-21 09:00:00',
'fecha_resultados' => '2026-03-01 12:00:00',
]);
// Update dates to empty/null
$response = $this->patchJson("/api/admin/procesos-admision/{$proceso->id}", [
'fecha_inicio_inscripcion' => null,
'fecha_fin_inscripcion' => '',
'fecha_examen1' => null,
'fecha_examen2' => '',
'fecha_resultados' => null,
]);
$response->assertStatus(200);
$proceso->refresh();
// All date fields should be null
$this->assertNull($proceso->fecha_inicio_inscripcion);
$this->assertNull($proceso->fecha_fin_inscripcion);
$this->assertNull($proceso->fecha_examen1);
$this->assertNull($proceso->fecha_examen2);
$this->assertNull($proceso->fecha_resultados);
}
/** @test */
public function update_sets_link_fields_to_null_when_empty(): void
{
$this->authenticateUser();
$proceso = ProcesoAdmision::create([
'titulo' => 'Proceso Con Links',
'slug' => 'proceso-con-links',
'link_preinscripcion' => 'https://example.com/preinscripcion',
'link_inscripcion' => 'https://example.com/inscripcion',
'link_resultados' => 'https://example.com/resultados',
'link_reglamento' => 'https://example.com/reglamento',
]);
// Update links to empty
$response = $this->patchJson("/api/admin/procesos-admision/{$proceso->id}", [
'link_preinscripcion' => '',
'link_inscripcion' => null,
'link_resultados' => '',
'link_reglamento' => null,
]);
$response->assertStatus(200);
$proceso->refresh();
// All link fields should be null
$this->assertNull($proceso->link_preinscripcion);
$this->assertNull($proceso->link_inscripcion);
$this->assertNull($proceso->link_resultados);
$this->assertNull($proceso->link_reglamento);
}
/** @test */
public function update_does_not_affect_fields_not_in_request(): void
{
$this->authenticateUser();
$proceso = ProcesoAdmision::create([
'titulo' => 'Proceso Original',
'slug' => 'proceso-original',
'subtitulo' => 'Subtitulo que no debe cambiar',
'descripcion' => 'Descripcion que no debe cambiar',
'link_preinscripcion' => 'https://example.com/link',
]);
// Update only titulo (subtitulo, descripcion, link not in request)
$response = $this->patchJson("/api/admin/procesos-admision/{$proceso->id}", [
'titulo' => 'Titulo Actualizado',
]);
$response->assertStatus(200);
$proceso->refresh();
// Updated field
$this->assertEquals('Titulo Actualizado', $proceso->titulo);
// Fields not in request should remain unchanged
$this->assertEquals('Subtitulo que no debe cambiar', $proceso->subtitulo);
$this->assertEquals('Descripcion que no debe cambiar', $proceso->descripcion);
$this->assertEquals('https://example.com/link', $proceso->link_preinscripcion);
}
}

@ -1,5 +1,5 @@
<!doctype html>
<html lang="es">
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="" />

2084
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -6,13 +6,9 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest",
"test:run": "vitest run"
"preview": "vite preview"
},
"dependencies": {
"@fontsource/lora": "^5.2.8",
"@fontsource/merriweather": "^5.2.11",
"@vueup/vue-quill": "^1.2.0",
"ant-design-vue": "^4.2.6",
"axios": "^1.13.3",
@ -33,9 +29,6 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"@vue/test-utils": "^2.4.6",
"happy-dom": "^15.11.7",
"vite": "^7.2.4",
"vitest": "^2.1.8"
"vite": "^7.2.4"
}
}

@ -63,7 +63,7 @@
.footer-col li,
.footer-text,
.footer-bottom {
font-family: "Lora", serif;
font-family: "Times New Roman", Times, serif;
}

@ -7,7 +7,7 @@
@virtual-tour="openVirtualTour"
/>
<ProcessSection :proceso="procesoPrincipal" />
<ProcessSection />
<ConvocatoriasSection/>
@ -27,9 +27,9 @@
</template>
<script setup>
import { ref, computed, onMounted, markRaw } from "vue"
import { ref, markRaw } from "vue"
import { message } from "ant-design-vue"
import { useProcesoAdmisionStore } from "../store/procesosAdmisionStore"
import NavbarModerno from '../components/nabvar.vue'
import FooterModerno from '../components/footer.vue'
@ -52,6 +52,7 @@ const scrollToConvocatoria = () => {
}
const openVirtualTour = () => {
// pon aquí tu URL real
window.open("https://example.com", "_blank", "noopener,noreferrer")
}
@ -61,8 +62,4 @@ const openVirtualTour = () => {
.main-content {
min-height: 100vh;
}
.detalle-modal-imagen {
text-align: center;
}
</style>
</style>

@ -69,7 +69,6 @@
</a-collapse>
</div>
</div>
</section>
</template>

@ -127,7 +127,7 @@
<div class="main-card-media">
<a-image
:src="principal.imagen_url || '/images/extra.jpg'"
src="/images/extra.jpg"
alt="Convocatoria"
:preview="true"
class="convocatoria-image"
@ -237,10 +237,7 @@ import {
const store = useWebAdmisionStore()
defineEmits(["show-modal"])
const principal = computed(() => props.procesos[0] || {})
const secundarios = computed(() => props.procesos.slice(1))
const emit = defineEmits(["show-modal", "open-preinscripcion"])
const modalVisible = ref(false)
const detallesSeleccionados = ref([])
@ -542,13 +539,6 @@ const abrirPorTipo = (tipo) => {
margin-top: 12px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #999;
font-size: 1.1rem;
}
@media (max-width: 992px) {
.section-title {
font-size: 2.1rem;

@ -93,7 +93,7 @@ defineEmits(["scroll-to-convocatoria", "virtual-tour"]);
.hero,
.hero * {
font-family: "Lora", serif;
font-family: "Times New Roman", Times, serif;
}
.hero {

@ -273,7 +273,7 @@ const normalizeTagColor = (c) => {
radial-gradient(1000px 420px at 95% 10%, #f3f0ff 0%, transparent 55%),
#fbfcff;
overflow: hidden;
font-family: "Lora", serif;
font-family: "Times New Roman", Times, serif;
}
.news-section::before {

@ -1,6 +1,6 @@
<!-- components/process/ProcessSection.vue -->
<template>
<section v-if="proceso" class="process-section" aria-labelledby="process-title">
<section class="process-section" aria-labelledby="process-title">
<div class="section-container">
<div class="section-header">
<h2 id="process-title" class="section-title">{{ tituloProceso }}</h2>
@ -360,7 +360,7 @@ const tareasHoy = computed(() => {
.process-section {
padding: 30px 0;
background: #ffffff;
font-family: "Lora", serif;
font-family: "Times New Roman", Times, serif;
}
.section-container {

@ -93,7 +93,7 @@ const selectArea = (area) => {
.areas-section {
padding: 80px 0;
background: #f5f5f5;
font-family: "Lora", serif;
font-family: "Times New Roman", Times, serif;
}
.section-container {

@ -49,7 +49,7 @@
.stats-section {
position: relative;
padding: 70px 0;
font-family: "Lora", serif;
font-family: "Times New Roman", Times, serif;
color: #fff;
background: linear-gradient(135deg, #1a237e 0%, #283593 100%);
overflow: hidden;

@ -230,7 +230,7 @@ onUnmounted(() => {
.logo-text span,
.drawer-logo-text h3,
.drawer-logo-text span {
font-family: "Lora", serif;
font-family: "Times New Roman", Times, serif;
}
.modern-header {

@ -8,13 +8,6 @@ import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
import 'katex/dist/katex.min.css'
// Fuentes locales
import '@fontsource/lora/400.css'
import '@fontsource/lora/600.css'
import '@fontsource/lora/700.css'
import '@fontsource/merriweather/400.css'
import '@fontsource/merriweather/700.css'
const app = createApp(App)
const pinia = createPinia()

@ -1,5 +1,4 @@
import { defineStore } from 'pinia'
import axios from 'axios'
import api from '../axios'
export const useProcesoAdmisionStore = defineStore('procesoAdmision', {
@ -8,7 +7,6 @@ export const useProcesoAdmisionStore = defineStore('procesoAdmision', {
error: null,
procesos: [],
procesosPublicados: [],
pagination: {
current_page: 1,
per_page: 15,
@ -31,22 +29,6 @@ export const useProcesoAdmisionStore = defineStore('procesoAdmision', {
},
async fetchProcesosPublicados() {
this.loading = true
this.error = null
try {
const baseURL = import.meta.env.VITE_API_URL
const { data } = await axios.get(`${baseURL}/procesos-admision/publicados`)
this.procesosPublicados = Array.isArray(data) ? data : []
return true
} catch (err) {
this._setError(err)
return false
} finally {
this.loading = false
}
},
async fetchProcesos(params = {}) {
this.loading = true
this.error = null

@ -611,35 +611,35 @@ function buildFormData() {
const fields = {
titulo: formState.titulo,
subtitulo: formState.subtitulo ?? '',
descripcion: formState.descripcion ?? '',
subtitulo: formState.subtitulo || null,
descripcion: formState.descripcion || null,
slug: formState.slug,
tipo_proceso: formState.tipo_proceso ?? '',
modalidad: formState.modalidad ?? '',
tipo_proceso: formState.tipo_proceso || null,
modalidad: formState.modalidad || null,
publicado: formState.publicado ? 1 : 0,
fecha_publicacion: formState.fecha_publicacion ?? '',
fecha_inicio_preinscripcion: formState.fecha_inicio_preinscripcion ?? '',
fecha_fin_preinscripcion: formState.fecha_fin_preinscripcion ?? '',
fecha_inicio_inscripcion: formState.fecha_inicio_inscripcion ?? '',
fecha_fin_inscripcion: formState.fecha_fin_inscripcion ?? '',
fecha_examen1: formState.fecha_examen1 ?? '',
fecha_examen2: formState.fecha_examen2 ?? '',
fecha_resultados: formState.fecha_resultados ?? '',
fecha_inicio_biometrico: formState.fecha_inicio_biometrico ?? '',
fecha_fin_biometrico: formState.fecha_fin_biometrico ?? '',
link_preinscripcion: formState.link_preinscripcion ?? '',
link_inscripcion: formState.link_inscripcion ?? '',
link_resultados: formState.link_resultados ?? '',
link_reglamento: formState.link_reglamento ?? '',
fecha_publicacion: formState.fecha_publicacion || null,
fecha_inicio_preinscripcion: formState.fecha_inicio_preinscripcion || null,
fecha_fin_preinscripcion: formState.fecha_fin_preinscripcion || null,
fecha_inicio_inscripcion: formState.fecha_inicio_inscripcion || null,
fecha_fin_inscripcion: formState.fecha_fin_inscripcion || null,
fecha_examen1: formState.fecha_examen1 || null,
fecha_examen2: formState.fecha_examen2 || null,
fecha_resultados: formState.fecha_resultados || null,
fecha_inicio_biometrico: formState.fecha_inicio_biometrico || null,
fecha_fin_biometrico: formState.fecha_fin_biometrico || null,
link_preinscripcion: formState.link_preinscripcion || null,
link_inscripcion: formState.link_inscripcion || null,
link_resultados: formState.link_resultados || null,
link_reglamento: formState.link_reglamento || null,
estado: formState.estado
}
Object.entries(fields).forEach(([k, v]) => {
if (v === undefined) return
if (v === null || v === undefined || v === '') return
fd.append(k, v)
})

@ -1,313 +0,0 @@
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { h } from 'vue'
import ConvocatoriasSection from '../../src/components/WebPageSections/ConvocatoriasSection.vue'
// Mock Ant Design Vue components
const mockAntComponents = {
'a-badge': {
template: '<span class="a-badge"><slot /></span>',
props: ['count']
},
'a-card': {
template: '<div class="a-card"><slot /></div>'
},
'a-tag': {
template: '<span class="a-tag" :class="color"><slot /></span>',
props: ['color']
},
'a-divider': {
template: '<hr class="a-divider" />'
},
'a-button': {
template: '<button class="a-button" @click="$emit(\'click\')"><slot /><slot name="icon" /></button>',
props: ['type', 'size', 'ghost'],
emits: ['click']
},
'a-image': {
template: '<img class="a-image" :src="src" :alt="alt" />',
props: ['src', 'alt', 'preview']
},
'a-empty': {
template: '<div class="a-empty"><slot /></div>',
props: ['description']
}
}
// Mock icons
const mockIcons = {
FileTextOutlined: { template: '<span class="icon file-text" />' },
DollarOutlined: { template: '<span class="icon dollar" />' },
TeamOutlined: { template: '<span class="icon team" />' },
CalendarOutlined: { template: '<span class="icon calendar" />' },
FormOutlined: { template: '<span class="icon form" />' }
}
const createWrapper = (props = {}) => {
return mount(ConvocatoriasSection, {
props,
global: {
stubs: {
...mockAntComponents,
...mockIcons
}
}
})
}
describe('ConvocatoriasSection', () => {
describe('when no procesos are provided', () => {
it('renders empty state message', () => {
const wrapper = createWrapper({ procesos: [] })
expect(wrapper.find('.empty-state').exists()).toBe(true)
expect(wrapper.text()).toContain('No hay convocatorias vigentes en este momento')
})
it('does not render main or secondary cards', () => {
const wrapper = createWrapper({ procesos: [] })
expect(wrapper.find('.main-convocatoria-card').exists()).toBe(false)
expect(wrapper.find('.secondary-list').exists()).toBe(false)
})
})
describe('when one proceso is provided', () => {
const singleProceso = [
{
id: 1,
titulo: 'Admisión Ordinaria 2026',
subtitulo: 'Proceso regular de admisión',
descripcion: 'Descripción del proceso de admisión',
estado: 'publicado',
fecha_inicio_inscripcion: '2026-02-01',
fecha_fin_inscripcion: '2026-02-28',
link_preinscripcion: 'https://example.com/preinscripcion'
}
]
it('renders main convocatoria card with correct title', () => {
const wrapper = createWrapper({ procesos: singleProceso })
expect(wrapper.find('.main-convocatoria-card').exists()).toBe(true)
expect(wrapper.find('h3').text()).toBe('Admisión Ordinaria 2026')
})
it('renders the "Principal" badge on main card', () => {
const wrapper = createWrapper({ procesos: singleProceso })
expect(wrapper.find('.card-badge').exists()).toBe(true)
expect(wrapper.find('.card-badge').text()).toBe('Principal')
})
it('renders action buttons for requisitos, pagos, vacantes, and cronograma', () => {
const wrapper = createWrapper({ procesos: singleProceso })
const actionButtons = wrapper.findAll('.action-btn')
expect(actionButtons.length).toBe(4)
const buttonTexts = actionButtons.map(btn => btn.text())
expect(buttonTexts).toContain('Requisitos')
expect(buttonTexts).toContain('Pagos')
expect(buttonTexts).toContain('Vacantes')
expect(buttonTexts).toContain('Cronograma')
})
it('does not render secondary cards list when only one proceso', () => {
const wrapper = createWrapper({ procesos: singleProceso })
expect(wrapper.find('.secondary-list').exists()).toBe(false)
})
it('displays the estado tag with correct label', () => {
const wrapper = createWrapper({ procesos: singleProceso })
const statusTag = wrapper.find('.status-tag')
expect(statusTag.exists()).toBe(true)
expect(statusTag.text()).toBe('Abierto') // 'publicado' maps to 'Abierto'
})
it('displays inscription dates when provided', () => {
const wrapper = createWrapper({ procesos: singleProceso })
expect(wrapper.text()).toContain('Inscripciones:')
})
})
describe('when multiple procesos are provided', () => {
const multipleProcesos = [
{
id: 1,
titulo: 'Proceso Principal',
descripcion: 'Descripción del proceso principal',
estado: 'publicado'
},
{
id: 2,
titulo: 'Proceso Secundario 1',
descripcion: 'Descripción secundario 1',
estado: 'en_proceso'
},
{
id: 3,
titulo: 'Proceso Secundario 2',
descripcion: 'Descripción secundario 2',
estado: 'nuevo'
}
]
it('renders main card for first proceso', () => {
const wrapper = createWrapper({ procesos: multipleProcesos })
expect(wrapper.find('.main-convocatoria-card').exists()).toBe(true)
expect(wrapper.find('h3').text()).toBe('Proceso Principal')
})
it('renders secondary cards for remaining procesos', () => {
const wrapper = createWrapper({ procesos: multipleProcesos })
expect(wrapper.find('.secondary-list').exists()).toBe(true)
const secondaryCards = wrapper.findAll('.secondary-convocatoria-card')
expect(secondaryCards.length).toBe(2)
})
it('renders correct titles in secondary cards', () => {
const wrapper = createWrapper({ procesos: multipleProcesos })
const secondaryTitles = wrapper.findAll('.secondary-title')
expect(secondaryTitles[0].text()).toBe('Proceso Secundario 1')
expect(secondaryTitles[1].text()).toBe('Proceso Secundario 2')
})
it('renders estado tags with correct colors for different estados', () => {
const wrapper = createWrapper({ procesos: multipleProcesos })
const statusTags = wrapper.findAll('.status-tag')
// First is main card (publicado), then secondary cards
expect(statusTags[0].text()).toBe('Abierto') // publicado
expect(statusTags[1].text()).toBe('En Proceso') // en_proceso
expect(statusTags[2].text()).toBe('PRÓXIMAMENTE') // nuevo
})
})
describe('emits events correctly', () => {
const procesos = [
{
id: 1,
titulo: 'Proceso Test',
estado: 'publicado'
},
{
id: 2,
titulo: 'Proceso Secundario',
estado: 'publicado'
}
]
it('emits show-modal event with correct payload when action button is clicked', async () => {
const wrapper = createWrapper({ procesos })
const actionButtons = wrapper.findAll('.action-btn')
await actionButtons[0].trigger('click') // Requisitos button
expect(wrapper.emitted('show-modal')).toBeTruthy()
expect(wrapper.emitted('show-modal')[0]).toEqual([
{ procesoId: 1, tipo: 'requisitos' }
])
})
it('emits show-modal for pagos button', async () => {
const wrapper = createWrapper({ procesos })
const actionButtons = wrapper.findAll('.action-btn')
await actionButtons[1].trigger('click') // Pagos button
expect(wrapper.emitted('show-modal')[0]).toEqual([
{ procesoId: 1, tipo: 'pagos' }
])
})
it('emits show-modal for vacantes button', async () => {
const wrapper = createWrapper({ procesos })
const actionButtons = wrapper.findAll('.action-btn')
await actionButtons[2].trigger('click') // Vacantes button
expect(wrapper.emitted('show-modal')[0]).toEqual([
{ procesoId: 1, tipo: 'vacantes' }
])
})
it('emits show-modal for cronograma button', async () => {
const wrapper = createWrapper({ procesos })
const actionButtons = wrapper.findAll('.action-btn')
await actionButtons[3].trigger('click') // Cronograma button
expect(wrapper.emitted('show-modal')[0]).toEqual([
{ procesoId: 1, tipo: 'cronograma' }
])
})
})
describe('estado mapping', () => {
const testEstadoCases = [
{ estado: 'publicado', expectedLabel: 'Abierto' },
{ estado: 'en_proceso', expectedLabel: 'En Proceso' },
{ estado: 'nuevo', expectedLabel: 'PRÓXIMAMENTE' },
{ estado: 'finalizado', expectedLabel: 'FINALIZADO' },
{ estado: 'cancelado', expectedLabel: 'CANCELADO' }
]
testEstadoCases.forEach(({ estado, expectedLabel }) => {
it(`maps estado "${estado}" to label "${expectedLabel}"`, () => {
const wrapper = createWrapper({
procesos: [{ id: 1, titulo: 'Test', estado }]
})
expect(wrapper.find('.status-tag').text()).toBe(expectedLabel)
})
})
})
describe('description display', () => {
it('displays descripcion when provided', () => {
const wrapper = createWrapper({
procesos: [{
id: 1,
titulo: 'Test',
descripcion: 'Esta es la descripción del proceso',
estado: 'publicado'
}]
})
expect(wrapper.find('.convocatoria-desc').text()).toBe('Esta es la descripción del proceso')
})
it('falls back to subtitulo when descripcion is not provided', () => {
const wrapper = createWrapper({
procesos: [{
id: 1,
titulo: 'Test',
subtitulo: 'Este es el subtítulo',
estado: 'publicado'
}]
})
expect(wrapper.find('.convocatoria-desc').text()).toBe('Este es el subtítulo')
})
it('shows default text when neither descripcion nor subtitulo is provided', () => {
const wrapper = createWrapper({
procesos: [{
id: 1,
titulo: 'Test',
estado: 'publicado'
}]
})
expect(wrapper.find('.convocatoria-desc').text()).toBe('Proceso de admisión')
})
})
})

@ -1,396 +0,0 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount, shallowMount } from '@vue/test-utils'
import { ref, computed, nextTick } from 'vue'
import { setActivePinia, createPinia } from 'pinia'
// Since WebPage.vue has many dependencies, we'll test the showModal logic in isolation
// by extracting the logic into a testable unit
describe('WebPage showModal function', () => {
// Simulate the showModal function logic from WebPage.vue
const tipoLabels = {
requisitos: 'Requisitos',
pagos: 'Pagos',
vacantes: 'Vacantes',
cronograma: 'Cronograma'
}
const createShowModal = (procesosPublicados) => {
const detalleModal = ref({
titulo: '',
descripcion: '',
imagen_url: null,
imagen_url_2: null,
listas: []
})
const detalleModalVisible = ref(false)
const showModal = ({ procesoId, tipo }) => {
const proceso = procesosPublicados.value.find(p => p.id === procesoId)
if (!proceso) return
const detalle = proceso.detalles?.find(d => d.tipo === tipo)
if (detalle) {
detalleModal.value = {
titulo: detalle.titulo_detalle || tipoLabels[tipo] || tipo,
descripcion: detalle.descripcion || '',
imagen_url: detalle.imagen_url || null,
imagen_url_2: detalle.imagen_url_2 || null,
listas: detalle.listas || []
}
} else {
detalleModal.value = {
titulo: `${tipoLabels[tipo] || tipo} - ${proceso.titulo}`,
descripcion: '',
imagen_url: null,
imagen_url_2: null,
listas: []
}
}
detalleModalVisible.value = true
}
return { showModal, detalleModal, detalleModalVisible }
}
describe('when proceso has matching detalle', () => {
it('displays detalle information when detalle exists for the tipo', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Admisión 2026',
detalles: [
{
tipo: 'requisitos',
titulo_detalle: 'Requisitos de Admisión',
descripcion: 'Lista de requisitos necesarios para postular',
imagen_url: 'https://example.com/requisitos.jpg',
imagen_url_2: 'https://example.com/requisitos2.jpg',
listas: ['DNI vigente', 'Certificado de estudios', 'Foto carnet']
}
]
}
])
const { showModal, detalleModal, detalleModalVisible } = createShowModal(procesosPublicados)
showModal({ procesoId: 1, tipo: 'requisitos' })
expect(detalleModalVisible.value).toBe(true)
expect(detalleModal.value.titulo).toBe('Requisitos de Admisión')
expect(detalleModal.value.descripcion).toBe('Lista de requisitos necesarios para postular')
expect(detalleModal.value.imagen_url).toBe('https://example.com/requisitos.jpg')
expect(detalleModal.value.imagen_url_2).toBe('https://example.com/requisitos2.jpg')
expect(detalleModal.value.listas).toEqual(['DNI vigente', 'Certificado de estudios', 'Foto carnet'])
})
it('uses tipo label as title when titulo_detalle is not provided', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Admisión 2026',
detalles: [
{
tipo: 'pagos',
descripcion: 'Información de pagos'
}
]
}
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
showModal({ procesoId: 1, tipo: 'pagos' })
expect(detalleModal.value.titulo).toBe('Pagos')
expect(detalleModal.value.descripcion).toBe('Información de pagos')
})
it('handles detalle with vacantes tipo correctly', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Proceso Ordinario',
detalles: [
{
tipo: 'vacantes',
titulo_detalle: 'Vacantes Disponibles',
descripcion: '100 vacantes para todas las carreras',
listas: ['Medicina: 20', 'Ingeniería: 30', 'Derecho: 50']
}
]
}
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
showModal({ procesoId: 1, tipo: 'vacantes' })
expect(detalleModal.value.titulo).toBe('Vacantes Disponibles')
expect(detalleModal.value.listas).toHaveLength(3)
})
it('handles detalle with cronograma tipo correctly', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Admisión Extraordinaria',
detalles: [
{
tipo: 'cronograma',
titulo_detalle: 'Cronograma de Actividades',
descripcion: 'Fechas importantes del proceso',
imagen_url: 'https://example.com/cronograma.png'
}
]
}
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
showModal({ procesoId: 1, tipo: 'cronograma' })
expect(detalleModal.value.titulo).toBe('Cronograma de Actividades')
expect(detalleModal.value.imagen_url).toBe('https://example.com/cronograma.png')
})
})
describe('when proceso does not have matching detalle', () => {
it('creates default modal data with proceso title when no detalle found', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Admisión 2026',
detalles: [] // No detalles
}
])
const { showModal, detalleModal, detalleModalVisible } = createShowModal(procesosPublicados)
showModal({ procesoId: 1, tipo: 'requisitos' })
expect(detalleModalVisible.value).toBe(true)
expect(detalleModal.value.titulo).toBe('Requisitos - Admisión 2026')
expect(detalleModal.value.descripcion).toBe('')
expect(detalleModal.value.imagen_url).toBeNull()
expect(detalleModal.value.imagen_url_2).toBeNull()
expect(detalleModal.value.listas).toEqual([])
})
it('creates default modal for pagos when no detalle', () => {
const procesosPublicados = ref([
{
id: 2,
titulo: 'Proceso Especial',
detalles: [
{ tipo: 'requisitos', titulo_detalle: 'Requisitos' } // Only requisitos, no pagos
]
}
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
showModal({ procesoId: 2, tipo: 'pagos' })
expect(detalleModal.value.titulo).toBe('Pagos - Proceso Especial')
})
it('handles proceso with undefined detalles array', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Proceso Sin Detalles'
// No detalles property at all
}
])
const { showModal, detalleModal, detalleModalVisible } = createShowModal(procesosPublicados)
showModal({ procesoId: 1, tipo: 'vacantes' })
expect(detalleModalVisible.value).toBe(true)
expect(detalleModal.value.titulo).toBe('Vacantes - Proceso Sin Detalles')
})
})
describe('when proceso is not found', () => {
it('does not open modal when procesoId does not match any proceso', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Admisión 2026',
detalles: []
}
])
const { showModal, detalleModal, detalleModalVisible } = createShowModal(procesosPublicados)
showModal({ procesoId: 999, tipo: 'requisitos' }) // Non-existent ID
expect(detalleModalVisible.value).toBe(false)
})
it('does not modify modal data when proceso not found', () => {
const procesosPublicados = ref([
{ id: 1, titulo: 'Admisión 2026', detalles: [] }
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
// Set initial state
const initialState = { ...detalleModal.value }
showModal({ procesoId: 999, tipo: 'requisitos' })
expect(detalleModal.value).toEqual(initialState)
})
})
describe('modal data handling edge cases', () => {
it('handles empty strings in detalle properties', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Test Proceso',
detalles: [
{
tipo: 'requisitos',
titulo_detalle: '',
descripcion: '',
imagen_url: '',
listas: []
}
]
}
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
showModal({ procesoId: 1, tipo: 'requisitos' })
// Empty titulo_detalle should fall back to tipo label
expect(detalleModal.value.titulo).toBe('Requisitos')
expect(detalleModal.value.descripcion).toBe('')
})
it('handles null values in detalle properties', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Test Proceso',
detalles: [
{
tipo: 'pagos',
titulo_detalle: 'Pagos',
descripcion: null,
imagen_url: null,
imagen_url_2: null,
listas: null
}
]
}
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
showModal({ procesoId: 1, tipo: 'pagos' })
expect(detalleModal.value.descripcion).toBe('')
expect(detalleModal.value.imagen_url).toBeNull()
expect(detalleModal.value.listas).toEqual([])
})
it('correctly maps all tipo labels', () => {
const testCases = [
{ tipo: 'requisitos', expectedLabel: 'Requisitos' },
{ tipo: 'pagos', expectedLabel: 'Pagos' },
{ tipo: 'vacantes', expectedLabel: 'Vacantes' },
{ tipo: 'cronograma', expectedLabel: 'Cronograma' }
]
testCases.forEach(({ tipo, expectedLabel }) => {
const procesosPublicados = ref([
{ id: 1, titulo: 'Test', detalles: [] }
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
showModal({ procesoId: 1, tipo })
expect(detalleModal.value.titulo).toBe(`${expectedLabel} - Test`)
})
})
it('handles unknown tipo by using tipo as label', () => {
const procesosPublicados = ref([
{ id: 1, titulo: 'Test', detalles: [] }
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
showModal({ procesoId: 1, tipo: 'unknown_tipo' })
expect(detalleModal.value.titulo).toBe('unknown_tipo - Test')
})
})
describe('multiple procesos scenarios', () => {
it('correctly finds the right proceso among multiple', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Proceso Uno',
detalles: [{ tipo: 'requisitos', titulo_detalle: 'Requisitos Uno' }]
},
{
id: 2,
titulo: 'Proceso Dos',
detalles: [{ tipo: 'requisitos', titulo_detalle: 'Requisitos Dos' }]
},
{
id: 3,
titulo: 'Proceso Tres',
detalles: [{ tipo: 'requisitos', titulo_detalle: 'Requisitos Tres' }]
}
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
showModal({ procesoId: 2, tipo: 'requisitos' })
expect(detalleModal.value.titulo).toBe('Requisitos Dos')
})
it('handles showing different tipos for different procesos', () => {
const procesosPublicados = ref([
{
id: 1,
titulo: 'Proceso A',
detalles: [
{ tipo: 'requisitos', titulo_detalle: 'Requisitos A' },
{ tipo: 'pagos', titulo_detalle: 'Pagos A' }
]
},
{
id: 2,
titulo: 'Proceso B',
detalles: [
{ tipo: 'vacantes', titulo_detalle: 'Vacantes B' }
]
}
])
const { showModal, detalleModal } = createShowModal(procesosPublicados)
// Show pagos for proceso 1
showModal({ procesoId: 1, tipo: 'pagos' })
expect(detalleModal.value.titulo).toBe('Pagos A')
// Then show vacantes for proceso 2
showModal({ procesoId: 2, tipo: 'vacantes' })
expect(detalleModal.value.titulo).toBe('Vacantes B')
})
})
})

@ -1,166 +0,0 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import axios from 'axios'
import { useProcesoAdmisionStore } from '../../src/store/procesosAdmisionStore'
// Mock axios
vi.mock('axios')
describe('procesosAdmisionStore', () => {
let store
beforeEach(() => {
setActivePinia(createPinia())
store = useProcesoAdmisionStore()
vi.stubEnv('VITE_API_URL', 'http://localhost:8000/api')
})
afterEach(() => {
vi.clearAllMocks()
vi.unstubAllEnvs()
})
describe('fetchProcesosPublicados', () => {
it('should fetch and update procesosPublicados state with returned data', async () => {
const mockProcesos = [
{
id: 1,
titulo: 'Proceso Ordinario 2026',
slug: 'proceso-ordinario-2026',
publicado: true,
estado: 'publicado',
detalles: [
{ tipo: 'requisitos', titulo_detalle: 'Requisitos', descripcion: 'Lista de requisitos' }
]
},
{
id: 2,
titulo: 'Proceso Extraordinario 2026',
slug: 'proceso-extraordinario-2026',
publicado: true,
estado: 'en_proceso',
detalles: []
}
]
axios.get.mockResolvedValueOnce({ data: mockProcesos })
// Initial state should be empty
expect(store.procesosPublicados).toEqual([])
expect(store.loading).toBe(false)
// Call the action
const result = await store.fetchProcesosPublicados()
// Verify the result
expect(result).toBe(true)
expect(store.procesosPublicados).toEqual(mockProcesos)
expect(store.procesosPublicados).toHaveLength(2)
expect(store.loading).toBe(false)
expect(store.error).toBeNull()
})
it('should set loading to true while fetching', async () => {
let loadingDuringFetch = false
axios.get.mockImplementation(() => {
loadingDuringFetch = store.loading
return Promise.resolve({ data: [] })
})
await store.fetchProcesosPublicados()
expect(loadingDuringFetch).toBe(true)
})
it('should handle empty response array', async () => {
axios.get.mockResolvedValueOnce({ data: [] })
const result = await store.fetchProcesosPublicados()
expect(result).toBe(true)
expect(store.procesosPublicados).toEqual([])
expect(store.error).toBeNull()
})
it('should handle non-array response by setting empty array', async () => {
axios.get.mockResolvedValueOnce({ data: null })
const result = await store.fetchProcesosPublicados()
expect(result).toBe(true)
expect(store.procesosPublicados).toEqual([])
})
it('should set error state when fetch fails', async () => {
const errorMessage = 'Network Error'
axios.get.mockRejectedValueOnce(new Error(errorMessage))
const result = await store.fetchProcesosPublicados()
expect(result).toBe(false)
expect(store.error).toBe(errorMessage)
expect(store.loading).toBe(false)
})
it('should handle API error response with message', async () => {
axios.get.mockRejectedValueOnce({
response: {
data: { message: 'Server error occurred' },
status: 500
}
})
const result = await store.fetchProcesosPublicados()
expect(result).toBe(false)
expect(store.error).toBe('Server error occurred')
})
it('should call the correct API endpoint', async () => {
axios.get.mockResolvedValueOnce({ data: [] })
await store.fetchProcesosPublicados()
expect(axios.get).toHaveBeenCalledTimes(1)
expect(axios.get).toHaveBeenCalledWith(
expect.stringContaining('/procesos-admision/publicados')
)
})
it('should clear previous error before fetching', async () => {
// Set an error first
store.error = 'Previous error'
axios.get.mockResolvedValueOnce({ data: [] })
await store.fetchProcesosPublicados()
expect(store.error).toBeNull()
})
it('should update state with processes containing detalles', async () => {
const mockProcesos = [
{
id: 1,
titulo: 'Proceso con Detalles',
slug: 'proceso-con-detalles',
publicado: true,
detalles: [
{ tipo: 'requisitos', titulo_detalle: 'Requisitos', descripcion: 'Requisito 1' },
{ tipo: 'pagos', titulo_detalle: 'Pagos', descripcion: 'Pago info' },
{ tipo: 'vacantes', titulo_detalle: 'Vacantes', descripcion: '100 vacantes' },
{ tipo: 'cronograma', titulo_detalle: 'Cronograma', descripcion: 'Fechas importantes' }
]
}
]
axios.get.mockResolvedValueOnce({ data: mockProcesos })
await store.fetchProcesosPublicados()
expect(store.procesosPublicados[0].detalles).toHaveLength(4)
expect(store.procesosPublicados[0].detalles[0].tipo).toBe('requisitos')
})
})
})

@ -1,14 +0,0 @@
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'happy-dom',
globals: true,
include: ['tests/**/*.{test,spec}.{js,ts}'],
coverage: {
reporter: ['text', 'json', 'html'],
},
},
})

@ -57,7 +57,7 @@ docker exec admision_2026_db mysql -uroot -proot -e "SELECT 'MySQL OK'"
## Paso 3: Importar la base de datos
```bash
docker exec -i admision_2026_db mysql -uroot -proot admision_2026 < admision_2026_estructura.sql
docker exec -i admision_2026_db mysql -uroot -proot admision_2026 < admision_2006-vI.sql
```
Verificar tablas importadas:
@ -66,7 +66,7 @@ Verificar tablas importadas:
docker exec admision_2026_db mysql -uroot -proot admision_2026 -e "SHOW TABLES;"
```
Debe mostrar ~31 tablas (users, postulantes, areas, cursos, examenes, procesos_admision, etc.)
Debe mostrar 34 tablas (users, postulantes, areas, cursos, examenes, procesos_admision, etc.)
> **Nota:** El dump ya incluye los registros en la tabla `migrations`, por lo que no es necesario ejecutar `php artisan migrate`.
@ -208,6 +208,6 @@ Estara disponible en: **http://localhost:5173**
## Notas importantes
- **No modificar** `composer.lock` a menos que todos los del equipo acuerden (requiere PHP 8.4+)
- El dump SQL (`admision_2026_estructura.sql`) incluye la estructura de todas las tablas + registros en `migrations`. No es necesario ejecutar `php artisan migrate` a menos que se hayan agregado nuevas migraciones al proyecto.
- El dump SQL (`admision_2006-vI.sql`) incluye la estructura de todas las tablas + registros en `migrations`. No es necesario ejecutar `php artisan migrate` a menos que se hayan agregado nuevas migraciones al proyecto.
- Si el puerto 3306 esta ocupado (por otro MySQL local), detener ese servicio primero o cambiar el puerto en `docker-compose.yml`
- Los archivos `.env` no se suben al repositorio (estan en `.gitignore`)

@ -230,7 +230,7 @@ docker compose --env-file .env.prod -f docker-compose.prod.yml up -d mysql
Esperar ~10 segundos y luego importar:
```bash
docker exec -i admision_prod_db mysql -uroot -pTU_PASSWORD admision_2026 < admision_2026_estructura.sql
docker exec -i admision_prod_db mysql -uroot -pTU_PASSWORD admision_2026 < admision_2006-vI.sql
```
Verificar:

@ -1,37 +0,0 @@
I've created all the unit tests for the 5 requested cases. Here's a summary:
Backend Tests (Laravel/PHPUnit)
1. back/tests/Feature/ProcesoAdmisionPublicadosTest.php
• Tests that /api/procesos-admision/publicados returns only processes with publicado=true
• Verifies empty array returned when no published processes exist
• Confirms ordering by id descending
2. back/tests/Feature/ProcesoAdmisionUpdateNullableTest.php
• Tests that update method sets nullable fields to null when empty values are provided
• Covers date fields, link fields, and text fields
• Verifies fields not in request remain unchanged
Frontend Tests (Vitest)
3. front/tests/store/procesosAdmisionStore.test.js
• Tests fetchProcesosPublicados action fetches and updates state
• Covers loading states, error handling, and API endpoint verification
4. front/tests/components/ConvocatoriasSection.test.js
• Tests rendering of main card for first process and secondary cards for remaining
• Verifies estado label mapping, action button events, and empty state
5. front/tests/components/WebPage.test.js
• Tests showModal function displays correct detalle information
• Covers all tipo cases (requisitos, pagos, vacantes, cronograma)
• Handles edge cases: missing proceso, empty detalles, null values
To Run Tests
Backend:
cd back && php artisan test
bash
Frontend (after installing dependencies):
cd front && npm install && npm test
bash
Loading…
Cancel
Save