diff --git a/back/app/Http/Controllers/Administracion/AreaController.php b/back/app/Http/Controllers/Administracion/AreaController.php index d964139..4405078 100644 --- a/back/app/Http/Controllers/Administracion/AreaController.php +++ b/back/app/Http/Controllers/Administracion/AreaController.php @@ -15,7 +15,6 @@ class AreaController extends Controller { $query = Area::withCount(['cursos', 'procesos']); - // 🔍 Buscar por nombre o código if ($request->filled('search')) { $search = $request->search; $query->where(function ($q) use ($search) { @@ -24,7 +23,6 @@ class AreaController extends Controller }); } - // 🔄 Filtrar por estado if (!is_null($request->activo)) { $query->where('activo', $request->activo); } @@ -40,9 +38,6 @@ class AreaController extends Controller } - /** - * Crear área - */ public function store(Request $request) { $validator = Validator::make($request->all(), [ @@ -75,9 +70,6 @@ class AreaController extends Controller ], 201); } - /** - * Mostrar área - */ public function show($id) { $area = Area::with(['cursos', 'examenes'])->find($id); @@ -95,9 +87,6 @@ class AreaController extends Controller ]); } - /** - * Actualizar área - */ public function update(Request $request, $id) { $area = Area::find($id); @@ -127,7 +116,7 @@ class AreaController extends Controller 'nombre' => $request->nombre, 'codigo' => strtoupper($request->codigo), 'descripcion' => $request->descripcion, - 'activo' => $request->activo ?? $area->activo, // mantener el valor actual si no viene + 'activo' => $request->activo ?? $area->activo, ]); return response()->json([ @@ -137,9 +126,6 @@ class AreaController extends Controller ]); } - /** - * Activar / Desactivar área (NO elimina) - */ public function toggleEstado($id) { $area = Area::find($id); @@ -161,9 +147,6 @@ class AreaController extends Controller ]); } - /** - * Eliminar área (solo si no tiene cursos ni exámenes) - */ public function destroy($id) { $area = Area::with(['cursos', 'examenes'])->find($id); @@ -216,11 +199,8 @@ class AreaController extends Controller if ($validator->fails()) { return response()->json(['success' => false, 'errors' => $validator->errors()], 422); } - - // Sincronizar cursos $area->cursos()->sync($request->cursos); - // Recargar cursos $area->load('cursos:id,nombre,codigo'); return response()->json([ @@ -356,10 +336,8 @@ class AreaController extends Controller ], 422); } - // 🔄 Sincronizar procesos $area->procesos()->sync($request->procesos); - // 🔁 Recargar procesos vinculados $area->load('procesos:id,nombre,tipo_proceso'); return response()->json([ diff --git a/back/app/Http/Controllers/Administracion/CursoController.php b/back/app/Http/Controllers/Administracion/CursoController.php index 8cf6db1..2ba66c0 100644 --- a/back/app/Http/Controllers/Administracion/CursoController.php +++ b/back/app/Http/Controllers/Administracion/CursoController.php @@ -9,14 +9,12 @@ use Illuminate\Support\Facades\Validator; class CursoController extends Controller { - /** - * Listar cursos (con búsqueda, filtro y paginación) - */ + public function index(Request $request) { $query = Curso::query(); - // 🔍 Buscar por nombre o código + if ($request->filled('search')) { $search = $request->search; $query->where(function ($q) use ($search) { @@ -25,7 +23,6 @@ class CursoController extends Controller }); } - // 🔄 Filtrar por estado if ($request->filled('activo')) { $query->where('activo', $request->activo); } @@ -40,9 +37,7 @@ class CursoController extends Controller ]); } - /** - * Crear curso - */ + public function store(Request $request) { $validator = Validator::make($request->all(), [ @@ -73,9 +68,7 @@ class CursoController extends Controller ], 201); } - /** - * Mostrar curso - */ + public function show($id) { $curso = Curso::with('areas')->find($id); @@ -93,9 +86,7 @@ class CursoController extends Controller ]); } - /** - * Actualizar curso - */ + public function update(Request $request, $id) { $curso = Curso::find($id); @@ -133,9 +124,6 @@ class CursoController extends Controller ]); } - /** - * Activar / Desactivar curso - */ public function toggleEstado($id) { $curso = Curso::find($id); @@ -157,9 +145,7 @@ class CursoController extends Controller ]); } - /** - * Eliminar curso (solo si no tiene áreas ni preguntas asociadas) - */ + public function destroy($id) { $curso = Curso::with(['areas', 'preguntas'])->find($id); diff --git a/back/app/Http/Controllers/Administracion/ExamenesController.php b/back/app/Http/Controllers/Administracion/ExamenesController.php index b01a8b0..944024b 100644 --- a/back/app/Http/Controllers/Administracion/ExamenesController.php +++ b/back/app/Http/Controllers/Administracion/ExamenesController.php @@ -16,9 +16,6 @@ use Carbon\Carbon; class ExamenesController extends Controller { - /** - * Obtener exámenes de la academia - */ public function getExamenes(Request $request) { try { @@ -46,7 +43,6 @@ class ExamenesController extends Controller }]) ->latest(); - // Filtros if ($request->has('search')) { $search = $request->search; $query->where('titulo', 'like', "%{$search}%"); @@ -78,10 +74,7 @@ class ExamenesController extends Controller ], 500); } } - - /** - * Crear nuevo examen - */ + public function crearExamen(Request $request) { try { @@ -140,7 +133,6 @@ class ExamenesController extends Controller 'intentos_permitidos' => $request->intentos_permitidos, 'puntaje_minimo' => $request->puntaje_minimo, - // 👇 OJO AQUÍ 'preguntas_aleatorias' => $request->preguntas_aleatorias ?? 0, 'mezclar_opciones' => $request->mezclar_opciones ?? true, @@ -188,9 +180,6 @@ class ExamenesController extends Controller } } - /** - * Obtener detalles de un examen - */ public function getExamen($examenId) { try { @@ -223,7 +212,6 @@ class ExamenesController extends Controller ], 404); } - // Estadísticas del examen $estadisticas = DB::table('intentos_examen') ->where('examen_id', $examenId) ->where('estado', 'finalizado') @@ -252,9 +240,6 @@ class ExamenesController extends Controller } } - /** - * Actualizar examen - */ public function actualizarExamen(Request $request, $examenId) { try { @@ -338,9 +323,6 @@ class ExamenesController extends Controller } } - /** - * Eliminar examen - */ public function eliminarExamen($examenId) { try { @@ -371,7 +353,6 @@ class ExamenesController extends Controller ], 404); } - // Verificar si hay intentos realizados $tieneIntentos = $examen->intentos()->exists(); if ($tieneIntentos) { @@ -406,9 +387,6 @@ class ExamenesController extends Controller } } - /** - * Obtener resultados de un examen - */ public function getResultadosExamen($examenId) { try { @@ -455,8 +433,7 @@ class ExamenesController extends Controller ) ->orderBy('intentos_examen.porcentaje', 'desc') ->get(); - - // Estadísticas generales + $estadisticas = [ 'total_estudiantes' => $resultados->groupBy('estudiante_id')->count(), 'promedio' => $resultados->avg('porcentaje'), diff --git a/back/app/Http/Controllers/Administracion/PreguntaController.php b/back/app/Http/Controllers/Administracion/PreguntaController.php index fdda79f..be65b9c 100644 --- a/back/app/Http/Controllers/Administracion/PreguntaController.php +++ b/back/app/Http/Controllers/Administracion/PreguntaController.php @@ -87,7 +87,6 @@ class PreguntaController extends Controller return response()->json(['success' => false, 'message' => 'No autorizado'], 403); } - // Validación (igual que antes) $validator = Validator::make($request->all(), [ 'curso_id' => 'required|exists:cursos,id', 'enunciado' => 'required|string', @@ -107,21 +106,21 @@ class PreguntaController extends Controller return response()->json(['success' => false, 'errors' => $validator->errors()], 422); } - // Opciones + $opciones = is_string($request->opciones) ? json_decode($request->opciones, true) : $request->opciones; $opcionesValidas = array_map('trim', $opciones); - // Validar respuesta correcta + if (!in_array($request->respuesta_correcta, $opcionesValidas)) { return response()->json(['success' => false, 'errors' => ['respuesta_correcta' => ['La respuesta correcta debe estar entre las opciones']]] ,422); } - // Procesar imágenes del enunciado y devolver URLs completas + $imagenesUrls = []; if ($request->hasFile('imagenes')) { foreach ($request->file('imagenes') as $imagen) { $path = $imagen->store('preguntas/enunciados', 'public'); - $imagenesUrls[] = url(Storage::url($path)); // URL completa + $imagenesUrls[] = url(Storage::url($path)); } } @@ -130,11 +129,11 @@ class PreguntaController extends Controller if ($request->hasFile('imagenes_explicacion')) { foreach ($request->file('imagenes_explicacion') as $imagen) { $path = $imagen->store('preguntas/explicaciones', 'public'); - $imagenesExplicacionUrls[] = url(Storage::url($path)); // URL completa + $imagenesExplicacionUrls[] = url(Storage::url($path)); } } - // Crear pregunta + $pregunta = Pregunta::create([ 'curso_id' => $request->curso_id, 'enunciado' => $request->enunciado, @@ -190,7 +189,6 @@ class PreguntaController extends Controller return response()->json(['success' => false, 'errors' => $validator->errors()], 422); } - // Decodificar opciones $opciones = is_string($request->opciones) ? json_decode($request->opciones, true) : $request->opciones; $opcionesValidas = array_map('trim', $opciones); @@ -201,7 +199,7 @@ class PreguntaController extends Controller ], 422); } - // --- Imágenes del enunciado --- + $imagenesActuales = $request->input('imagenes_existentes', $pregunta->imagenes ?? []); if (is_string($imagenesActuales)) { $imagenesActuales = json_decode($imagenesActuales, true) ?? []; @@ -214,7 +212,6 @@ class PreguntaController extends Controller } } - // --- Imágenes de la explicación --- $imagenesExplicacionActuales = $request->input('imagenes_explicacion_existentes', $pregunta->imagenes_explicacion ?? []); if (is_string($imagenesExplicacionActuales)) { $imagenesExplicacionActuales = json_decode($imagenesExplicacionActuales, true) ?? []; @@ -227,7 +224,6 @@ class PreguntaController extends Controller } } - // Actualizar pregunta $pregunta->update([ 'curso_id' => $request->curso_id, 'enunciado' => $request->enunciado, @@ -269,7 +265,6 @@ class PreguntaController extends Controller ], 404); } - // Eliminar imágenes del storage si existen if ($pregunta->imagenes) { foreach ($pregunta->imagenes as $imagen) { Storage::disk('public')->delete($imagen); diff --git a/back/app/Http/Controllers/Administracion/ProcesoAdmisionController.php b/back/app/Http/Controllers/Administracion/ProcesoAdmisionController.php index d7e402f..5967f36 100644 --- a/back/app/Http/Controllers/Administracion/ProcesoAdmisionController.php +++ b/back/app/Http/Controllers/Administracion/ProcesoAdmisionController.php @@ -11,7 +11,7 @@ use Illuminate\Validation\Rule; class ProcesoAdmisionController extends Controller { - // GET /api/admin/procesos-admision?include_detalles=1&q=...&estado=...&publicado=1&page=1&per_page=15 + public function index(Request $request) { $q = ProcesoAdmision::query(); @@ -43,7 +43,6 @@ class ProcesoAdmisionController extends Controller return response()->json($q->paginate($perPage)); } - // GET /api/admin/procesos-admision/{id}?include_detalles=1 public function show(Request $request, $id) { $q = ProcesoAdmision::query(); @@ -55,7 +54,6 @@ class ProcesoAdmisionController extends Controller return response()->json($q->findOrFail($id)); } - // POST /api/admin/procesos-admision (multipart/form-data) public function store(Request $request) { $data = $request->validate([ @@ -88,20 +86,17 @@ class ProcesoAdmisionController extends Controller 'estado' => ['sometimes', Rule::in(['nuevo','publicado','en_proceso','finalizado','cancelado'])], - // Archivos (uploads) 'imagen' => ['nullable','image','mimes:jpg,jpeg,png,webp','max:10240'], 'banner' => ['nullable','image','mimes:jpg,jpeg,png,webp','max:10240'], 'brochure' => ['nullable','file','mimes:pdf','max:10240'], ]); - // slug auto si no viene if (empty($data['slug'])) { $data['slug'] = Str::slug($data['titulo']); } $data['publicado'] = $data['publicado'] ?? false; - // Guardar archivos if ($request->hasFile('imagen')) { $data['imagen_path'] = $request->file('imagen')->store('admisiones/procesos', 'public'); } @@ -117,7 +112,6 @@ class ProcesoAdmisionController extends Controller return response()->json($proceso, 201); } - // PATCH /api/admin/procesos-admision/{id} (multipart/form-data) public function update(Request $request, $id) { $proceso = ProcesoAdmision::findOrFail($id); @@ -152,18 +146,16 @@ class ProcesoAdmisionController extends Controller 'estado' => ['sometimes', Rule::in(['nuevo','publicado','en_proceso','finalizado','cancelado'])], - // Archivos 'imagen' => ['sometimes','nullable','image','mimes:jpg,jpeg,png,webp','max:10240'], 'banner' => ['sometimes','nullable','image','mimes:jpg,jpeg,png,webp','max:10240'], 'brochure' => ['sometimes','nullable','file','mimes:pdf','max:10240'], ]); - // slug auto si lo mandan vacío + if (array_key_exists('slug', $data) && empty($data['slug'])) { $data['slug'] = Str::slug($data['titulo'] ?? $proceso->titulo); } - // Reemplazo de archivos (borra anterior) if ($request->hasFile('imagen')) { if ($proceso->imagen_path) Storage::disk('public')->delete($proceso->imagen_path); $data['imagen_path'] = $request->file('imagen')->store('admisiones/procesos', 'public'); @@ -182,12 +174,11 @@ class ProcesoAdmisionController extends Controller return response()->json($proceso->fresh()); } - // DELETE /api/admin/procesos-admision/{id} + public function destroy($id) { $proceso = ProcesoAdmision::findOrFail($id); - // Borrar archivos asociados if ($proceso->imagen_path) Storage::disk('public')->delete($proceso->imagen_path); if ($proceso->banner_path) Storage::disk('public')->delete($proceso->banner_path); if ($proceso->brochure_path) Storage::disk('public')->delete($proceso->brochure_path); diff --git a/back/app/Http/Controllers/Administracion/ProcesoAdmisionDetalleController.php b/back/app/Http/Controllers/Administracion/ProcesoAdmisionDetalleController.php index 1b797c2..1b24b87 100644 --- a/back/app/Http/Controllers/Administracion/ProcesoAdmisionDetalleController.php +++ b/back/app/Http/Controllers/Administracion/ProcesoAdmisionDetalleController.php @@ -11,7 +11,7 @@ use Illuminate\Validation\Rule; class ProcesoAdmisionDetalleController extends Controller { - // GET /api/admin/procesos-admision/{procesoId}/detalles?tipo=requisitos + public function index(Request $request, $procesoId) { ProcesoAdmision::findOrFail($procesoId); @@ -25,7 +25,7 @@ class ProcesoAdmisionDetalleController extends Controller return response()->json($q->orderByDesc('id')->get()); } - // POST /api/admin/procesos-admision/{procesoId}/detalles (multipart/form-data) + public function store(Request $request, $procesoId) { ProcesoAdmision::findOrFail($procesoId); @@ -58,13 +58,13 @@ class ProcesoAdmisionDetalleController extends Controller return response()->json($detalle, 201); } - // GET /api/admin/detalles-admision/{id} + public function show($id) { return response()->json(ProcesoAdmisionDetalle::findOrFail($id)); } - // PATCH /api/admin/detalles-admision/{id} (multipart/form-data) + public function update(Request $request, $id) { $detalle = ProcesoAdmisionDetalle::findOrFail($id); @@ -98,7 +98,6 @@ class ProcesoAdmisionDetalleController extends Controller return response()->json($detalle->fresh()); } - // DELETE /api/admin/detalles-admision/{id} public function destroy($id) { $detalle = ProcesoAdmisionDetalle::findOrFail($id); diff --git a/back/app/Http/Controllers/Administracion/ProcesoController.php b/back/app/Http/Controllers/Administracion/ProcesoController.php index 70196b9..01a2407 100644 --- a/back/app/Http/Controllers/Administracion/ProcesoController.php +++ b/back/app/Http/Controllers/Administracion/ProcesoController.php @@ -12,14 +12,11 @@ use Illuminate\Support\Facades\Storage; class ProcesoController extends Controller { - /* ============================= - | LISTAR (INDEX) - ============================= */ + public function index(Request $request) { $query = Proceso::query(); - // 🔍 Filtros if ($request->filled('search')) { $query->where('nombre', 'like', '%' . $request->search . '%'); } @@ -36,7 +33,6 @@ class ProcesoController extends Controller $query->where('tipo_proceso', $request->tipo_proceso); } - // 📄 Paginación $procesos = $query ->orderBy('created_at', 'desc') ->paginate($request->get('per_page', 10)); @@ -44,9 +40,6 @@ class ProcesoController extends Controller return response()->json($procesos); } - /* ============================= - | CREAR (STORE) - ============================= */ public function store(Request $request) { $data = $request->validate([ @@ -82,9 +75,6 @@ class ProcesoController extends Controller ], 201); } - /* ============================= - | VER (SHOW) - ============================= */ public function show($id) { $proceso = Proceso::findOrFail($id); @@ -92,9 +82,7 @@ class ProcesoController extends Controller return response()->json($proceso); } - /* ============================= - | ACTUALIZAR (UPDATE) - ============================= */ + public function update(Request $request, $id) { $proceso = Proceso::findOrFail($id); @@ -122,7 +110,6 @@ class ProcesoController extends Controller 'tiempo_por_pregunta' => 'nullable|integer|min:1', ]); - // 🔄 Regenerar slug si cambia nombre if ($data['nombre'] !== $proceso->nombre) { $data['slug'] = Str::slug($data['nombre']) . '-' . uniqid(); } @@ -135,9 +122,7 @@ class ProcesoController extends Controller ]); } - /* ============================= - | ELIMINAR (DESTROY) - ============================= */ + public function destroy($id) { $proceso = Proceso::findOrFail($id); @@ -148,9 +133,7 @@ class ProcesoController extends Controller ]); } - /* ============================= - | TOGGLE ACTIVO - ============================= */ + public function toggleActivo($id) { $proceso = Proceso::findOrFail($id); diff --git a/back/app/Http/Controllers/Administracion/ReglaAreaProcesoController.php b/back/app/Http/Controllers/Administracion/ReglaAreaProcesoController.php index f80fdf9..691ffdd 100644 --- a/back/app/Http/Controllers/Administracion/ReglaAreaProcesoController.php +++ b/back/app/Http/Controllers/Administracion/ReglaAreaProcesoController.php @@ -11,18 +11,12 @@ use Illuminate\Support\Facades\DB; class ReglaAreaProcesoController extends Controller { - /** - * Mostrar cursos de un area_proceso con reglas existentes - */ - - - - public function areasProcesos() +ion areasProcesos() { $areasProcesos = DB::table('area_proceso as ap') ->leftJoin('reglas_area_proceso as r', 'ap.id', '=', 'r.area_proceso_id') - ->leftJoin('area_curso as ac', 'ap.area_id', '=', 'ac.area_id') // pivot area_curso - ->leftJoin('cursos as c', 'ac.curso_id', '=', 'c.id') // unimos los cursos reales + ->leftJoin('area_curso as ac', 'ap.area_id', '=', 'ac.area_id') + ->leftJoin('cursos as c', 'ac.curso_id', '=', 'c.id') ->join('areas as a', 'ap.area_id', '=', 'a.id') ->join('procesos as p', 'ap.proceso_id', '=', 'p.id') ->select( @@ -48,7 +42,7 @@ class ReglaAreaProcesoController extends Controller public function index($areaProcesoId) { - // Obtener el area_proceso y su proceso + $areaProceso = DB::table('area_proceso as ap') ->join('areas as a', 'a.id', '=', 'ap.area_id') ->join('procesos as p', 'p.id', '=', 'ap.proceso_id') @@ -61,19 +55,16 @@ public function index($areaProcesoId) return response()->json(['error' => 'AreaProceso no encontrado'], 404); } - // Traer todos los cursos del área (pivot area_curso) $cursos = DB::table('area_curso as ac') ->join('cursos as c', 'c.id', '=', 'ac.curso_id') ->where('ac.area_id', $areaProceso->area_id) ->select('c.id as curso_id', 'c.nombre as nombre') ->get(); - // Traer reglas existentes para este area_proceso $reglasExistentes = DB::table('reglas_area_proceso') ->where('area_proceso_id', $areaProcesoId) ->get(); - // Mapear cursos con reglas si existen $reglas = $cursos->map(function ($curso) use ($reglasExistentes) { $regla = $reglasExistentes->firstWhere('curso_id', $curso->curso_id); return [ @@ -109,7 +100,6 @@ public function store(Request $request, $areaProcesoId) 'ponderacion' => 'nullable|numeric|min:0|max:100', ]); - // 🔹 Cantidad total de preguntas del proceso (vía pivot) $areaProceso = DB::table('area_proceso as ap') ->join('procesos as p', 'ap.proceso_id', '=', 'p.id') ->where('ap.id', $areaProcesoId) @@ -124,7 +114,6 @@ public function store(Request $request, $areaProcesoId) $totalPreguntasProceso = $areaProceso->cantidad_pregunta; - // 🔹 Total ya asignado (excluyendo este curso si ya existe) $totalAsignado = DB::table('reglas_area_proceso') ->where('area_proceso_id', $areaProcesoId) ->where('curso_id', '!=', $request->curso_id) @@ -139,7 +128,6 @@ public function store(Request $request, $areaProcesoId) ], 422); } - // 🔹 Insertar o actualizar UNA regla DB::table('reglas_area_proceso')->updateOrInsert( [ 'area_proceso_id' => $areaProcesoId, @@ -177,7 +165,6 @@ public function update(Request $request, $reglaId) 'ponderacion' => 'nullable|numeric|min:0|max:100', ]); - // 🔹 Obtener la regla actual $regla = DB::table('reglas_area_proceso') ->where('id', $reglaId) ->first(); @@ -188,7 +175,6 @@ public function update(Request $request, $reglaId) ], 404); } - // 🔹 Obtener cantidad total de preguntas del proceso (vía pivot) $areaProceso = DB::table('area_proceso as ap') ->join('procesos as p', 'ap.proceso_id', '=', 'p.id') ->where('ap.id', $regla->area_proceso_id) @@ -203,7 +189,6 @@ public function update(Request $request, $reglaId) $totalPreguntasProceso = $areaProceso->cantidad_pregunta; - // 🔹 Total asignado EXCLUYENDO esta regla $totalAsignado = DB::table('reglas_area_proceso') ->where('area_proceso_id', $regla->area_proceso_id) ->where('id', '!=', $reglaId) @@ -218,7 +203,6 @@ public function update(Request $request, $reglaId) ], 422); } - // 🔹 Actualizar la regla DB::table('reglas_area_proceso') ->where('id', $reglaId) ->update([ @@ -241,9 +225,7 @@ public function update(Request $request, $reglaId) ]); } - /** - * Eliminar una regla - */ + public function destroy($reglaId) { $regla = ReglaAreaProceso::findOrFail($reglaId); @@ -272,7 +254,6 @@ public function storeMultiple(Request $request, $areaProcesoId) 'reglas.*.ponderacion' => 'nullable|numeric|min:0|max:100', ]); - // Obtener la cantidad total de preguntas del proceso a través del pivot $areaProceso = DB::table('area_proceso as ap') ->join('procesos as p', 'ap.proceso_id', '=', 'p.id') ->where('ap.id', $areaProcesoId) @@ -281,7 +262,6 @@ public function storeMultiple(Request $request, $areaProcesoId) $totalPreguntasProceso = $areaProceso->cantidad_pregunta ?? 0; - // Validar total de preguntas asignadas $totalNuevo = collect($request->reglas)->sum('cantidad_preguntas'); if ($totalNuevo > $totalPreguntasProceso) { return response()->json([ @@ -289,10 +269,8 @@ public function storeMultiple(Request $request, $areaProcesoId) ], 422); } - // Eliminar reglas existentes directamente en la tabla pivot DB::table('reglas_area_proceso')->where('area_proceso_id', $areaProcesoId)->delete(); - // Insertar las nuevas reglas $insertData = collect($request->reglas)->map(function ($r) use ($areaProcesoId) { return [ 'area_proceso_id' => $areaProcesoId, diff --git a/back/app/Http/Controllers/AuthController.php b/back/app/Http/Controllers/AuthController.php index a5e6f61..e0445f2 100644 --- a/back/app/Http/Controllers/AuthController.php +++ b/back/app/Http/Controllers/AuthController.php @@ -43,15 +43,14 @@ class AuthController extends Controller 'name' => strip_tags(trim($request->name)), 'email' => strtolower(trim($request->email)), 'password' => Hash::make($request->password), - 'email_verified_at' => null, // Para implementar verificación de email después + 'email_verified_at' => null, ]); $user->assignRole('administrador'); - // Registrar actividad + Log::info('Usuario registrado', ['user_id' => $user->id, 'email' => $user->email]); - // Crear token de acceso $token = $user->createToken('api_token', ['*'], now()->addHours(12))->plainTextToken; return response()->json([ @@ -66,7 +65,7 @@ class AuthController extends Controller ], 'token' => $token, 'token_type' => 'Bearer', - 'expires_in' => 12 * 60 * 60 // 12 horas en segundos + 'expires_in' => 12 * 60 * 60 ], 201); } catch (\Exception $e) { @@ -82,9 +81,6 @@ class AuthController extends Controller } } - /** - * Login de usuario - */ public function login(Request $request) { try { @@ -105,9 +101,8 @@ class AuthController extends Controller 'password' => $request->password ]; - // Intentar autenticación if (!Auth::attempt($credentials)) { - // Registrar intento fallido + Log::warning('Intento de login fallido', ['email' => $request->email]); return response()->json([ @@ -123,13 +118,10 @@ class AuthController extends Controller // return response()->json(['error' => 'Cuenta desactivada'], 403); // } - // Revocar tokens anteriores (opcional, para seguridad) $user->tokens()->delete(); - // Crear nuevo token con expiración $token = $user->createToken('api_token', ['*'], now()->addHours(12))->plainTextToken; - // Registrar login exitoso Log::info('Login exitoso', ['user_id' => $user->id]); return response()->json([ @@ -160,17 +152,13 @@ class AuthController extends Controller } } - /** - * Logout de usuario - */ + public function logout(Request $request) { try { if ($request->user()) { - // Registrar logout Log::info('Logout exitoso', ['user_id' => $request->user()->id]); - - // Revocar todos los tokens del usuario + $request->user()->tokens()->delete(); } @@ -189,9 +177,7 @@ class AuthController extends Controller } } - /** - * Obtener usuario actual - */ + public function me(Request $request) { try { @@ -225,9 +211,7 @@ class AuthController extends Controller } } - /** - * Refrescar token (opcional para implementar después) - */ + public function refresh(Request $request) { $user = $request->user(); diff --git a/back/app/Http/Controllers/ExamenController.php b/back/app/Http/Controllers/ExamenController.php index 1b4a5fc..72abdbb 100644 --- a/back/app/Http/Controllers/ExamenController.php +++ b/back/app/Http/Controllers/ExamenController.php @@ -71,7 +71,7 @@ public function areas(Request $request) return [ 'area_id' => $area->id, 'nombre' => $area->nombre, - 'area_proceso_id' => $pivot->id, // 🔥 CLAVE + 'area_proceso_id' => $pivot->id, ]; }); @@ -271,7 +271,7 @@ public function miExamenActual(Request $request) { $postulante = $request->user(); - // Obtenemos el examen más reciente junto con área y proceso usando joins + $examen = Examen::join('area_proceso', 'area_proceso.id', '=', 'examenes.area_proceso_id') ->join('areas', 'areas.id', '=', 'area_proceso.area_id') ->join('procesos', 'procesos.id', '=', 'area_proceso.proceso_id') @@ -280,14 +280,14 @@ public function miExamenActual(Request $request) 'examenes.id', 'examenes.pagado', 'examenes.tipo_pago', - 'examenes.intentos', // intentos del examen + 'examenes.intentos', 'areas.id as area_id', 'areas.nombre as area_nombre', 'procesos.id as proceso_id', 'procesos.nombre as proceso_nombre', - 'procesos.intentos_maximos as proceso_intentos_maximos' // intentos máximos del proceso + 'procesos.intentos_maximos as proceso_intentos_maximos' ) - ->latest('examenes.created_at') // solo el más reciente + ->latest('examenes.created_at') ->first(); if (!$examen) { @@ -321,14 +321,11 @@ public function miExamenActual(Request $request) -/** - * 2. GENERAR PREGUNTAS PARA EXAMEN (si no las tiene) - */ + public function generarPreguntas($examenId) { $examen = Examen::findOrFail($examenId); - // Verificar que el examen pertenece al usuario autenticado $postulante = request()->user(); if ($examen->postulante_id !== $postulante->id) { return response()->json([ @@ -337,7 +334,6 @@ public function generarPreguntas($examenId) ], 403); } - // Si YA tiene preguntas, no generar nuevas, solo confirmar éxito if ($examen->preguntasAsignadas()->exists()) { return response()->json([ 'success' => true, @@ -347,7 +343,6 @@ public function generarPreguntas($examenId) ]); } - // Si NO tiene preguntas, generar usando el servicio $resultado = $this->examenService->generarPreguntasExamen($examen); if (!$resultado['success']) { @@ -363,12 +358,6 @@ public function generarPreguntas($examenId) } - /** - * 4. INICIAR EXAMEN (marcar hora inicio) - */ -/** - * 4. INICIAR EXAMEN (marcar hora inicio e incrementar intentos) - */ public function iniciarExamen(Request $request) { $request->validate([ @@ -377,7 +366,6 @@ public function iniciarExamen(Request $request) $examen = Examen::findOrFail($request->examen_id); - // Verificar que el examen pertenece al usuario autenticado $postulante = $request->user(); if ($examen->postulante_id !== $postulante->id) { return response()->json([ @@ -386,7 +374,6 @@ public function iniciarExamen(Request $request) ], 403); } - // Obtener datos del área-proceso $areaProceso = \DB::table('area_proceso') ->join('procesos', 'area_proceso.proceso_id', '=', 'procesos.id') ->join('areas', 'area_proceso.area_id', '=', 'areas.id') @@ -400,7 +387,6 @@ public function iniciarExamen(Request $request) ) ->first(); - // Verificar que tenga preguntas if (!$examen->preguntasAsignadas()->exists()) { return response()->json([ 'success' => false, @@ -408,7 +394,6 @@ public function iniciarExamen(Request $request) ], 400); } - // Verificar límite de intentos if ($areaProceso && $examen->intentos >= $areaProceso->proceso_intentos_maximos) { return response()->json([ 'success' => false, @@ -416,15 +401,13 @@ public function iniciarExamen(Request $request) ], 403); } - // 🔥 Incrementar intento y marcar inicio $examen->increment('intentos'); $examen->update([ - 'hora_inicio' => now(), // Hora normal del servidor + 'hora_inicio' => now(), 'estado' => 'en_progreso' ]); - // Obtener preguntas completas $preguntas = $this->examenService->obtenerPreguntasExamen($examen); return response()->json([ @@ -454,7 +437,6 @@ public function iniciarExamen(Request $request) $preguntaAsignada = PreguntaAsignada::with(['examen', 'pregunta']) ->findOrFail($preguntaAsignadaId); - // Verificar que pertenece al usuario $postulante = $request->user(); if ($preguntaAsignada->examen->postulante_id !== $postulante->id) { return response()->json([ @@ -463,7 +445,6 @@ public function iniciarExamen(Request $request) ], 403); } - // Guardar respuesta $resultado = $this->examenService->guardarRespuesta( $preguntaAsignada, $request->respuesta @@ -472,14 +453,11 @@ public function iniciarExamen(Request $request) return response()->json($resultado); } - /** - * 6. FINALIZAR EXAMEN - */ + public function finalizarExamen($examenId) { $examen = Examen::findOrFail($examenId); - // Verificar que el examen pertenece al usuario autenticado $postulante = request()->user(); if ($examen->postulante_id !== $postulante->id) { return response()->json([ diff --git a/back/app/Http/Controllers/PostulanteAuthController.php b/back/app/Http/Controllers/PostulanteAuthController.php index 8617eec..1c782e1 100644 --- a/back/app/Http/Controllers/PostulanteAuthController.php +++ b/back/app/Http/Controllers/PostulanteAuthController.php @@ -252,7 +252,7 @@ public function misProcesos(Request $request) $procesos = ResultadoAdmision::select( 'procesos_admision.id', - 'procesos_admision.nombre', + 'procesos_admision.titulo', 'resultados_admision.puntaje', 'resultados_admision.apto' ) diff --git a/back/app/Models/Area.php b/back/app/Models/Area.php index fb8f7df..a249844 100644 --- a/back/app/Models/Area.php +++ b/back/app/Models/Area.php @@ -23,8 +23,6 @@ class Area extends Model 'updated_at' => 'datetime', ]; - /* ================= RELACIONES ================= */ - public function examenes() { @@ -36,7 +34,6 @@ class Area extends Model )->withTimestamps(); } - /* ================= SCOPES ================= */ public function scopeActivas($query) { diff --git a/back/app/Models/AreaAdmision.php b/back/app/Models/AreaAdmision.php index 9c9e433..1326f72 100644 --- a/back/app/Models/AreaAdmision.php +++ b/back/app/Models/AreaAdmision.php @@ -18,13 +18,7 @@ class AreaAdmision extends Model 'estado' => 'boolean' ]; - /* - |-------------------------------------------------------------------------- - | RELACIONES - |-------------------------------------------------------------------------- - */ - // Un área tiene muchos resultados public function resultados() { return $this->hasMany(ResultadoAdmision::class, 'idearea'); diff --git a/back/app/Models/Curso.php b/back/app/Models/Curso.php index 0e50e67..25a4630 100644 --- a/back/app/Models/Curso.php +++ b/back/app/Models/Curso.php @@ -32,7 +32,7 @@ class Curso extends Model } - // Curso → Preguntas + public function preguntas() { return $this->hasMany(Pregunta::class); diff --git a/back/app/Models/Examen.php b/back/app/Models/Examen.php index 7ab594f..1a18ae5 100644 --- a/back/app/Models/Examen.php +++ b/back/app/Models/Examen.php @@ -49,10 +49,10 @@ class Examen extends Model return $this->hasOneThrough( Area::class, AreaProceso::class, - 'id', // Foreign key on AreaProceso table... - 'id', // Foreign key on Area table... - 'area_proceso_id', // Local key on Examen table... - 'area_id' // Local key on AreaProceso table... + 'id', + 'id', + 'area_proceso_id', + 'area_id' ); } diff --git a/back/app/Models/Postulante.php b/back/app/Models/Postulante.php index 9b9838e..7a28818 100644 --- a/back/app/Models/Postulante.php +++ b/back/app/Models/Postulante.php @@ -13,9 +13,6 @@ class Postulante extends Authenticatable protected $table = 'postulantes'; - /** - * Campos asignables - */ protected $fillable = [ 'name', 'email', @@ -25,25 +22,19 @@ class Postulante extends Authenticatable 'last_activity' ]; - /** - * Campos ocultos al serializar - */ + protected $hidden = [ 'password', 'device_id', 'tokens' ]; - /** - * Casting - */ + protected $casts = [ 'last_activity' => 'datetime', ]; - /** - * Mutator para encriptar la contraseña automáticamente - */ + public function setPasswordAttribute($value) { $this->attributes['password'] = bcrypt($value); diff --git a/back/app/Models/Proceso.php b/back/app/Models/Proceso.php index 70db393..ecda2b7 100644 --- a/back/app/Models/Proceso.php +++ b/back/app/Models/Proceso.php @@ -101,9 +101,6 @@ class Proceso extends Model return true; } - /* ============================= - | EVENTS - ============================= */ protected static function booted() { @@ -118,10 +115,10 @@ public function areas() { return $this->belongsToMany( Area::class, - 'area_proceso' // nombre de la tabla pivot + 'area_proceso' ) - ->withPivot('id') // columnas extra de la tabla pivot que quieres acceder - ->withTimestamps(); // para manejar created_at y updated_at automáticamente + ->withPivot('id') + ->withTimestamps(); } diff --git a/back/app/Models/ProcesoAdmision.php b/back/app/Models/ProcesoAdmision.php index a91bb00..4c4af06 100644 --- a/back/app/Models/ProcesoAdmision.php +++ b/back/app/Models/ProcesoAdmision.php @@ -60,7 +60,7 @@ class ProcesoAdmision extends Model return $this->brochure_path ? Storage::disk('public')->url($this->brochure_path) : null; } - // Un proceso tiene muchos resultados + public function resultados() { return $this->hasMany(ResultadoAdmision::class, 'idproceso'); diff --git a/back/app/Models/ResultadoAdmision.php b/back/app/Models/ResultadoAdmision.php index 9ec8037..61d7650 100644 --- a/back/app/Models/ResultadoAdmision.php +++ b/back/app/Models/ResultadoAdmision.php @@ -37,11 +37,6 @@ class ResultadoAdmision extends Model 'respuestas' => 'array' ]; - /* - |-------------------------------------------------------------------------- - | RELACIONES - |-------------------------------------------------------------------------- - */ public function proceso() { diff --git a/back/app/Models/ResultadoAdmisionCarga.php b/back/app/Models/ResultadoAdmisionCarga.php index ded238b..7a7e468 100644 --- a/back/app/Models/ResultadoAdmisionCarga.php +++ b/back/app/Models/ResultadoAdmisionCarga.php @@ -10,131 +10,109 @@ class ResultadoAdmisionCarga extends Model protected $primaryKey = 'id'; - public $timestamps = false; // Solo tienes created_at + public $timestamps = false; protected $guarded = []; - // Uso guarded vacío porque tienes MUCHOS campos. - // Es más práctico que escribir todos en fillable. + protected $casts = [ 'puntaje_total' => 'decimal:2', 'puesto' => 'integer', - // ARITMETICA + 'correctas_aritmetica' => 'integer', 'blancas_aritmetica' => 'integer', 'puntaje_aritmetica' => 'decimal:2', 'porcentaje_aritmetica' => 'decimal:2', - // ALGEBRA 'correctas_algebra' => 'integer', 'blancas_algebra' => 'integer', 'puntaje_algebra' => 'decimal:2', 'porcentaje_algebra' => 'decimal:2', - // GEOMETRIA + 'correctas_geometria' => 'integer', 'blancas_geometria' => 'integer', 'puntaje_geometria' => 'decimal:2', 'porcentaje_geometria' => 'decimal:2', - // TRIGONOMETRIA 'correctas_trigonometria' => 'integer', 'blancas_trigonometria' => 'integer', 'puntaje_trigonometria' => 'decimal:2', 'porcentaje_trigonometria' => 'decimal:2', - // FISICA 'correctas_fisica' => 'integer', 'blancas_fisica' => 'integer', 'puntaje_fisica' => 'decimal:2', 'porcentaje_fisica' => 'decimal:2', - // QUIMICA 'correctas_quimica' => 'integer', 'blancas_quimica' => 'integer', 'puntaje_quimica' => 'decimal:2', 'porcentaje_quimica' => 'decimal:2', - // BIOLOGIA 'correctas_biologia_anatomia' => 'integer', 'blancas_biologia_anatomia' => 'integer', 'puntaje_biologia_anatomia' => 'decimal:2', 'porcentaje_biologia_anatomia' => 'decimal:2', - // PSICOLOGIA 'correctas_psicologia_filosofia' => 'integer', 'blancas_psicologia_filosofia' => 'integer', 'puntaje_psicologia_filosofia' => 'decimal:2', 'porcentaje_psicologia_filosofia' => 'decimal:2', - // GEOGRAFIA 'correctas_geografia' => 'integer', 'blancas_geografia' => 'integer', 'puntaje_geografia' => 'decimal:2', 'porcentaje_geografia' => 'decimal:2', - // HISTORIA 'correctas_historia' => 'integer', 'blancas_historia' => 'integer', 'puntaje_historia' => 'decimal:2', 'porcentaje_historia' => 'decimal:2', - // EDUCACION CIVICA 'correctas_educacion_civica' => 'integer', 'blancas_educacion_civica' => 'integer', 'puntaje_educacion_civica' => 'decimal:2', 'porcentaje_educacion_civica' => 'decimal:2', - // ECONOMIA 'correctas_economia' => 'integer', 'blancas_economia' => 'integer', 'puntaje_economia' => 'decimal:2', 'porcentaje_economia' => 'decimal:2', - // COMUNICACION 'correctas_comunicacion' => 'integer', 'blancas_comunicacion' => 'integer', 'puntaje_comunicacion' => 'decimal:2', 'porcentaje_comunicacion' => 'decimal:2', - // LITERATURA 'correctas_literatura' => 'integer', 'blancas_literatura' => 'integer', 'puntaje_literatura' => 'decimal:2', 'porcentaje_literatura' => 'decimal:2', - // RAZONAMIENTO MATEMATICO 'correctas_razonamiento_matematico' => 'integer', 'blancas_razonamiento_matematico' => 'integer', 'puntaje_razonamiento_matematico' => 'decimal:2', 'porcentaje_razonamiento_matematico' => 'decimal:2', - // RAZONAMIENTO VERBAL 'correctas_razonamiento_verbal' => 'integer', 'blancas_razonamiento_verbal' => 'integer', 'puntaje_razonamiento_verbal' => 'decimal:2', 'porcentaje_razonamiento_verbal' => 'decimal:2', - // INGLES 'correctas_ingles' => 'integer', 'blancas_ingles' => 'integer', 'puntaje_ingles' => 'decimal:2', 'porcentaje_ingles' => 'decimal:2', - // QUECHUA / AIMARA 'correctas_quechua_aimara' => 'integer', 'blancas_quechua_aimara' => 'integer', 'puntaje_quechua_aimara' => 'decimal:2', 'porcentaje_quechua_aimara' => 'decimal:2', ]; - /* - |-------------------------------------------------------------------------- - | RELACIONES - |-------------------------------------------------------------------------- - */ public function proceso() { diff --git a/front/src/components/Footer.vue b/front/src/components/Footer.vue index b78bcb6..4c841bf 100644 --- a/front/src/components/Footer.vue +++ b/front/src/components/Footer.vue @@ -2,7 +2,6 @@