login2026
parent
87e72bc029
commit
489cfd8f6b
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Administracion;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Calificacion;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class CalificacionController extends Controller
|
||||||
|
{
|
||||||
|
// ✅ Listar todas
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$calificaciones = Calificacion::all();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $calificaciones
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Guardar nueva
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'nombre' => 'required|string|max:255',
|
||||||
|
'puntos_correcta' => 'required|numeric',
|
||||||
|
'puntos_incorrecta' => 'required|numeric',
|
||||||
|
'puntos_nula' => 'required|numeric',
|
||||||
|
'puntaje_maximo' => 'required|numeric',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$calificacion = Calificacion::create($request->all());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Calificación creada correctamente',
|
||||||
|
'data' => $calificacion
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Mostrar una
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
$calificacion = Calificacion::find($id);
|
||||||
|
|
||||||
|
if (!$calificacion) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'No encontrada'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $calificacion
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Actualizar
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$calificacion = Calificacion::find($id);
|
||||||
|
|
||||||
|
if (!$calificacion) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'No encontrada'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'nombre' => 'required|string|max:255',
|
||||||
|
'puntos_correcta' => 'required|numeric',
|
||||||
|
'puntos_incorrecta' => 'required|numeric',
|
||||||
|
'puntos_nula' => 'required|numeric',
|
||||||
|
'puntaje_maximo' => 'required|numeric',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$calificacion->update($request->all());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Calificación actualizada correctamente',
|
||||||
|
'data' => $calificacion
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Eliminar
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$calificacion = Calificacion::find($id);
|
||||||
|
|
||||||
|
if (!$calificacion) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'No encontrada'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$calificacion->delete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Calificación eliminada correctamente'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,465 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace App\Http\Controllers\Administracion;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Academia;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Examen;
|
|
||||||
use App\Models\Pregunta;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
|
|
||||||
class ExamenesController extends Controller
|
|
||||||
{
|
|
||||||
|
|
||||||
public function getExamenes(Request $request)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$user = auth()->user();
|
|
||||||
|
|
||||||
if (!$user->hasRole('administrador')) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'No autorizado'
|
|
||||||
], 403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$academia = Academia::where('admin_academia_id', $user->id)->first();
|
|
||||||
|
|
||||||
if (!$academia) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Academia no encontrada'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = $academia->examenes()
|
|
||||||
->withCount(['preguntas', 'intentos' => function($query) {
|
|
||||||
$query->where('estado', 'finalizado');
|
|
||||||
}])
|
|
||||||
->latest();
|
|
||||||
|
|
||||||
if ($request->has('search')) {
|
|
||||||
$search = $request->search;
|
|
||||||
$query->where('titulo', 'like', "%{$search}%");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->has('publicado')) {
|
|
||||||
$query->where('publicado', $request->publicado);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->has('tipo')) {
|
|
||||||
$query->where('tipo', $request->tipo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$examenes = $query->paginate(15);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'data' => $examenes
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('Error obteniendo exámenes', [
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Error al cargar los exámenes'
|
|
||||||
], 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function crearExamen(Request $request)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$user = auth()->user();
|
|
||||||
|
|
||||||
if (!$user->hasRole('administrador')) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'No autorizado'
|
|
||||||
], 403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$academia = Academia::where('admin_academia_id', $user->id)->first();
|
|
||||||
|
|
||||||
if (!$academia) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Academia no encontrada'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$validator = Validator::make($request->all(), [
|
|
||||||
'titulo' => 'required|string|max:255',
|
|
||||||
'descripcion' => 'nullable|string',
|
|
||||||
'tipo' => 'required|in:practica,simulacro,evaluacion',
|
|
||||||
'dificultad' => 'required|in:facil,medio,dificil,avanzado',
|
|
||||||
|
|
||||||
'duracion_minutos' => 'required|integer|min:1|max:480',
|
|
||||||
'intentos_permitidos' => 'required|integer|min:1|max:10',
|
|
||||||
'puntaje_minimo' => 'required|numeric|min:0|max:100',
|
|
||||||
'preguntas_aleatorias' => 'boolean',
|
|
||||||
'mostrar_resultados' => 'boolean',
|
|
||||||
'mostrar_respuestas' => 'boolean',
|
|
||||||
'publicado' => 'boolean',
|
|
||||||
'fecha_inicio' => 'nullable|date',
|
|
||||||
'fecha_fin' => 'nullable|date|after_or_equal:fecha_inicio',
|
|
||||||
'configuracion' => 'nullable|array'
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($validator->fails()) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'errors' => $validator->errors()
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
|
|
||||||
DB::beginTransaction();
|
|
||||||
|
|
||||||
$examen = Examen::create([
|
|
||||||
'academia_id' => $academia->id,
|
|
||||||
'titulo' => $request->titulo,
|
|
||||||
'descripcion' => $request->descripcion,
|
|
||||||
'tipo' => $request->tipo,
|
|
||||||
'dificultad' => $request->dificultad,
|
|
||||||
'duracion_minutos' => $request->duracion_minutos,
|
|
||||||
'intentos_permitidos' => $request->intentos_permitidos,
|
|
||||||
'puntaje_minimo' => $request->puntaje_minimo,
|
|
||||||
|
|
||||||
'preguntas_aleatorias' => $request->preguntas_aleatorias ?? 0,
|
|
||||||
|
|
||||||
'mezclar_opciones' => $request->mezclar_opciones ?? true,
|
|
||||||
'mostrar_resultados' => $request->mostrar_resultados ?? true,
|
|
||||||
'mostrar_respuestas' => $request->mostrar_respuestas ?? false,
|
|
||||||
'mostrar_explicaciones' => $request->mostrar_explicaciones ?? false,
|
|
||||||
|
|
||||||
'activar_timer' => $request->activar_timer ?? true,
|
|
||||||
'permitir_navegacion' => $request->permitir_navegacion ?? true,
|
|
||||||
'permitir_revisar' => $request->permitir_revisar ?? true,
|
|
||||||
|
|
||||||
'publicado' => $request->publicado ?? false,
|
|
||||||
'fecha_inicio' => $request->fecha_inicio,
|
|
||||||
'fecha_fin' => $request->fecha_fin,
|
|
||||||
'orden' => $request->orden ?? 1,
|
|
||||||
|
|
||||||
'configuracion' => $request->configuracion ?? []
|
|
||||||
]);
|
|
||||||
|
|
||||||
DB::commit();
|
|
||||||
|
|
||||||
Log::info('Examen creado por admin', [
|
|
||||||
'academia_id' => $academia->id,
|
|
||||||
'examen_id' => $examen->id,
|
|
||||||
'admin_id' => $user->id
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Examen creado exitosamente',
|
|
||||||
'data' => $examen
|
|
||||||
], 201);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
DB::rollBack();
|
|
||||||
|
|
||||||
Log::error('Error creando examen', [
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Error al crear el examen'
|
|
||||||
], 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExamen($examenId)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$user = auth()->user();
|
|
||||||
|
|
||||||
if (!$user->hasRole('administrador')) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'No autorizado'
|
|
||||||
], 403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$academia = Academia::where('admin_academia_id', $user->id)->first();
|
|
||||||
|
|
||||||
if (!$academia) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Academia no encontrada'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$examen = Examen::where('academia_id', $academia->id)
|
|
||||||
->with(['preguntas'])
|
|
||||||
->find($examenId);
|
|
||||||
|
|
||||||
if (!$examen) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Examen no encontrado'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$estadisticas = DB::table('intentos_examen')
|
|
||||||
->where('examen_id', $examenId)
|
|
||||||
->where('estado', 'finalizado')
|
|
||||||
->selectRaw('COUNT(*) as total_intentos')
|
|
||||||
->selectRaw('AVG(porcentaje) as promedio')
|
|
||||||
->selectRaw('SUM(CASE WHEN aprobado = 1 THEN 1 ELSE 0 END) as aprobados')
|
|
||||||
->first();
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'data' => [
|
|
||||||
'examen' => $examen,
|
|
||||||
'estadisticas' => $estadisticas
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('Error obteniendo examen', [
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Error al cargar el examen'
|
|
||||||
], 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function actualizarExamen(Request $request, $examenId)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$user = auth()->user();
|
|
||||||
|
|
||||||
if (!$user->hasRole('administrador')) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'No autorizado'
|
|
||||||
], 403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$academia = Academia::where('admin_academia_id', $user->id)->first();
|
|
||||||
|
|
||||||
if (!$academia) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Academia no encontrada'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$examen = Examen::where('academia_id', $academia->id)->find($examenId);
|
|
||||||
|
|
||||||
if (!$examen) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Examen no encontrado'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$validator = Validator::make($request->all(), [
|
|
||||||
'titulo' => 'sometimes|string|max:255',
|
|
||||||
'descripcion' => 'nullable|string',
|
|
||||||
'tipo' => 'sometimes|in:practica,simulacro,evaluacion',
|
|
||||||
'duracion_minutos' => 'sometimes|integer|min:1|max:480',
|
|
||||||
'intentos_permitidos' => 'sometimes|integer|min:1|max:10',
|
|
||||||
'puntaje_minimo' => 'sometimes|numeric|min:0|max:100',
|
|
||||||
'preguntas_aleatorias' => 'boolean',
|
|
||||||
'mostrar_resultados' => 'boolean',
|
|
||||||
'mostrar_respuestas' => 'boolean',
|
|
||||||
'publicado' => 'boolean',
|
|
||||||
'fecha_inicio' => 'nullable|date',
|
|
||||||
'fecha_fin' => 'nullable|date|after_or_equal:fecha_inicio',
|
|
||||||
'configuracion' => 'nullable|array'
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($validator->fails()) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'errors' => $validator->errors()
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
|
|
||||||
$examen->update($request->only([
|
|
||||||
'titulo', 'descripcion', 'tipo', 'duracion_minutos', 'intentos_permitidos',
|
|
||||||
'puntaje_minimo', 'preguntas_aleatorias', 'mostrar_resultados',
|
|
||||||
'mostrar_respuestas', 'publicado', 'fecha_inicio', 'fecha_fin', 'configuracion'
|
|
||||||
]));
|
|
||||||
|
|
||||||
Log::info('Examen actualizado por admin', [
|
|
||||||
'academia_id' => $academia->id,
|
|
||||||
'examen_id' => $examen->id,
|
|
||||||
'admin_id' => $user->id
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Examen actualizado exitosamente',
|
|
||||||
'data' => $examen
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('Error actualizando examen', [
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Error al actualizar el examen'
|
|
||||||
], 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function eliminarExamen($examenId)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$user = auth()->user();
|
|
||||||
|
|
||||||
if (!$user->hasRole('administrador')) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'No autorizado'
|
|
||||||
], 403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$academia = Academia::where('admin_academia_id', $user->id)->first();
|
|
||||||
|
|
||||||
if (!$academia) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Academia no encontrada'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$examen = Examen::where('academia_id', $academia->id)->find($examenId);
|
|
||||||
|
|
||||||
if (!$examen) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Examen no encontrado'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tieneIntentos = $examen->intentos()->exists();
|
|
||||||
|
|
||||||
if ($tieneIntentos) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'No se puede eliminar un examen con intentos realizados'
|
|
||||||
], 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
$examen->delete();
|
|
||||||
|
|
||||||
Log::info('Examen eliminado por admin', [
|
|
||||||
'academia_id' => $academia->id,
|
|
||||||
'examen_id' => $examenId,
|
|
||||||
'admin_id' => $user->id
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Examen eliminado exitosamente'
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('Error eliminando examen', [
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Error al eliminar el examen'
|
|
||||||
], 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getResultadosExamen($examenId)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$user = auth()->user();
|
|
||||||
|
|
||||||
if (!$user->hasRole('administrador')) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'No autorizado'
|
|
||||||
], 403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$academia = Academia::where('admin_academia_id', $user->id)->first();
|
|
||||||
|
|
||||||
if (!$academia) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Academia no encontrada'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$examen = Examen::where('academia_id', $academia->id)->find($examenId);
|
|
||||||
|
|
||||||
if (!$examen) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Examen no encontrado'
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$resultados = DB::table('intentos_examen')
|
|
||||||
->join('users', 'intentos_examen.user_id', '=', 'users.id')
|
|
||||||
->where('intentos_examen.examen_id', $examenId)
|
|
||||||
->where('intentos_examen.estado', 'finalizado')
|
|
||||||
->select(
|
|
||||||
'users.id as estudiante_id',
|
|
||||||
'users.name as estudiante_nombre',
|
|
||||||
'users.email as estudiante_email',
|
|
||||||
'intentos_examen.numero_intento',
|
|
||||||
'intentos_examen.porcentaje',
|
|
||||||
'intentos_examen.aprobado',
|
|
||||||
'intentos_examen.tiempo_utilizado',
|
|
||||||
'intentos_examen.finalizado_en'
|
|
||||||
)
|
|
||||||
->orderBy('intentos_examen.porcentaje', 'desc')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
$estadisticas = [
|
|
||||||
'total_estudiantes' => $resultados->groupBy('estudiante_id')->count(),
|
|
||||||
'promedio' => $resultados->avg('porcentaje'),
|
|
||||||
'aprobados' => $resultados->where('aprobado', true)->count(),
|
|
||||||
'reprobados' => $resultados->where('aprobado', false)->count(),
|
|
||||||
'tiempo_promedio' => $resultados->avg('tiempo_utilizado')
|
|
||||||
];
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'data' => [
|
|
||||||
'resultados' => $resultados,
|
|
||||||
'estadisticas' => $estadisticas
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('Error obteniendo resultados', [
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Error al cargar los resultados'
|
|
||||||
], 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Administracion;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Postulante;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class PostulanteController extends Controller
|
||||||
|
{
|
||||||
|
public function obtenerPostulantes(Request $request)
|
||||||
|
{
|
||||||
|
$query = Postulante::query();
|
||||||
|
|
||||||
|
if ($request->buscar) {
|
||||||
|
$query->where(function ($q) use ($request) {
|
||||||
|
$q->where('name', 'like', "%{$request->buscar}%")
|
||||||
|
->orWhere('email', 'like', "%{$request->buscar}%")
|
||||||
|
->orWhere('dni', 'like', "%{$request->buscar}%");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$postulantes = $query->orderBy('id', 'desc')
|
||||||
|
->paginate(20);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $postulantes
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actualizarPostulante(Request $request, $id)
|
||||||
|
{
|
||||||
|
$postulante = Postulante::findOrFail($id);
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|unique:postulantes,email,' . $postulante->id,
|
||||||
|
'dni' => 'required|string|max:20|unique:postulantes,dni,' . $postulante->id,
|
||||||
|
'password' => 'nullable|string|min:6'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$postulante->update([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'dni' => $request->dni,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 🔹 Solo si envían nueva contraseña
|
||||||
|
if ($request->filled('password')) {
|
||||||
|
$postulante->password = $request->password;
|
||||||
|
$postulante->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Postulante actualizado correctamente',
|
||||||
|
'data' => $postulante
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ResultadoExamen extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'resultados_examenes';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'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',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function postulante()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Postulante::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function examen()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Examen::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<a-card :bordered="false" class="main-card">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header">
|
||||||
|
<h2>Gestión de Calificaciones</h2>
|
||||||
|
<a-button type="primary" @click="openModal()">
|
||||||
|
Nueva Calificación
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabla -->
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="calificaciones"
|
||||||
|
:loading="loading"
|
||||||
|
row-key="id"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<template #actions="{ record }">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="link" @click="openModal(record)">
|
||||||
|
Editar
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-popconfirm
|
||||||
|
title="¿Seguro de eliminar?"
|
||||||
|
@confirm="eliminar(record.id)"
|
||||||
|
>
|
||||||
|
<a-button type="link" danger>
|
||||||
|
Eliminar
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
:title="form.id ? 'Editar Calificación' : 'Nueva Calificación'"
|
||||||
|
@ok="guardar"
|
||||||
|
@cancel="cerrarModal"
|
||||||
|
ok-text="Guardar"
|
||||||
|
>
|
||||||
|
<a-form layout="vertical">
|
||||||
|
<a-form-item label="Nombre">
|
||||||
|
<a-input v-model:value="form.nombre" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="Puntos Correcta">
|
||||||
|
<a-input-number v-model:value="form.puntos_correcta" style="width:100%" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="Puntos Incorrecta">
|
||||||
|
<a-input-number v-model:value="form.puntos_incorrecta" style="width:100%" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="Puntos Nula">
|
||||||
|
<a-input-number v-model:value="form.puntos_nula" style="width:100%" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="Puntaje Máximo">
|
||||||
|
<a-input-number v-model:value="form.puntaje_maximo" style="width:100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
|
import api from '../../../axios'
|
||||||
|
|
||||||
|
const calificaciones = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const modalVisible = ref(false)
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
id: null,
|
||||||
|
nombre: '',
|
||||||
|
puntos_correcta: 0,
|
||||||
|
puntos_incorrecta: 0,
|
||||||
|
puntos_nula: 0,
|
||||||
|
puntaje_maximo: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: 'Nombre', dataIndex: 'nombre' },
|
||||||
|
{ title: 'Correcta', dataIndex: 'puntos_correcta' },
|
||||||
|
{ title: 'Incorrecta', dataIndex: 'puntos_incorrecta' },
|
||||||
|
{ title: 'Nula', dataIndex: 'puntos_nula' },
|
||||||
|
{ title: 'Máximo', dataIndex: 'puntaje_maximo' },
|
||||||
|
{ title: 'Acciones', key: 'actions', slots: { customRender: 'actions' } }
|
||||||
|
]
|
||||||
|
|
||||||
|
const cargar = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const { data } = await api .getAll()
|
||||||
|
calificaciones.value = data.data
|
||||||
|
} catch (error) {
|
||||||
|
message.error('Error al cargar')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openModal = (record = null) => {
|
||||||
|
if (record) {
|
||||||
|
form.value = { ...record }
|
||||||
|
} else {
|
||||||
|
form.value = {
|
||||||
|
id: null,
|
||||||
|
nombre: '',
|
||||||
|
puntos_correcta: 0,
|
||||||
|
puntos_incorrecta: 0,
|
||||||
|
puntos_nula: 0,
|
||||||
|
puntaje_maximo: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const cerrarModal = () => {
|
||||||
|
modalVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const guardar = async () => {
|
||||||
|
try {
|
||||||
|
if (form.value.id) {
|
||||||
|
await api .update(form.value.id, form.value)
|
||||||
|
message.success('Actualizado correctamente')
|
||||||
|
} else {
|
||||||
|
await api .create(form.value)
|
||||||
|
message.success('Creado correctamente')
|
||||||
|
}
|
||||||
|
|
||||||
|
cerrarModal()
|
||||||
|
cargar()
|
||||||
|
} catch (error) {
|
||||||
|
message.error('Error al guardar')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eliminar = async (id) => {
|
||||||
|
try {
|
||||||
|
await api .delete(id)
|
||||||
|
message.success('Eliminado correctamente')
|
||||||
|
cargar()
|
||||||
|
} catch (error) {
|
||||||
|
message.error('Error al eliminar')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
cargar()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page-wrapper {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,201 @@
|
|||||||
|
<template>
|
||||||
|
<div class="postulantes-container">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header">
|
||||||
|
<div>
|
||||||
|
<h2 class="page-title">Gestión de Postulantes</h2>
|
||||||
|
<p class="page-subtitle">Administra los usuarios registrados</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="search"
|
||||||
|
placeholder="Buscar por nombre, email o DNI"
|
||||||
|
style="width: 300px"
|
||||||
|
@search="fetchPostulantes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabla -->
|
||||||
|
<a-card>
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="postulantes"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
row-key="id"
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
|
||||||
|
<!-- Nombre -->
|
||||||
|
<template v-if="column.key === 'name'">
|
||||||
|
<strong>{{ record.name }}</strong>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<template v-else-if="column.key === 'email'">
|
||||||
|
{{ record.email }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- DNI -->
|
||||||
|
<template v-else-if="column.key === 'dni'">
|
||||||
|
<a-tag color="blue">{{ record.dni }}</a-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Última actividad -->
|
||||||
|
<template v-else-if="column.key === 'last_activity'">
|
||||||
|
{{ record.last_activity ?? 'Sin actividad' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Acciones -->
|
||||||
|
<template v-else-if="column.key === 'acciones'">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="text" @click="editar(record)">
|
||||||
|
Editar
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<!-- Modal Editar -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="showModal"
|
||||||
|
title="Editar Postulante"
|
||||||
|
@ok="guardarCambios"
|
||||||
|
@cancel="resetForm"
|
||||||
|
>
|
||||||
|
<a-form layout="vertical">
|
||||||
|
<a-form-item label="Nombre">
|
||||||
|
<a-input v-model:value="form.name" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="Email">
|
||||||
|
<a-input v-model:value="form.email" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="DNI">
|
||||||
|
<a-input v-model:value="form.dni" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="Nueva contraseña (opcional)">
|
||||||
|
<a-input-password v-model:value="form.password" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import api from '../../../axios'
|
||||||
|
|
||||||
|
const postulantes = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const search = ref('')
|
||||||
|
const showModal = ref(false)
|
||||||
|
const editingId = ref(null)
|
||||||
|
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
dni: '',
|
||||||
|
password: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: 'Nombre', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: 'Email', dataIndex: 'email', key: 'email' },
|
||||||
|
{ title: 'DNI', dataIndex: 'dni', key: 'dni' },
|
||||||
|
{ title: 'Última actividad', dataIndex: 'last_activity', key: 'last_activity' },
|
||||||
|
{ title: 'Acciones', key: 'acciones', align: 'center' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const fetchPostulantes = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const { data } = await api.get('/admin/postulantes', {
|
||||||
|
params: {
|
||||||
|
page: pagination.current,
|
||||||
|
buscar: search.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
postulantes.value = data.data.data
|
||||||
|
pagination.total = data.data.total
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
message.error('Error al cargar postulantes')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const editar = (record) => {
|
||||||
|
editingId.value = record.id
|
||||||
|
form.name = record.name
|
||||||
|
form.email = record.email
|
||||||
|
form.dni = record.dni
|
||||||
|
form.password = ''
|
||||||
|
showModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const guardarCambios = async () => {
|
||||||
|
try {
|
||||||
|
await api.put(`/admin/postulantes/${editingId.value}`, form)
|
||||||
|
message.success('Postulante actualizado correctamente')
|
||||||
|
showModal.value = false
|
||||||
|
fetchPostulantes()
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.response?.data?.message || 'Error al actualizar')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTableChange = (pag) => {
|
||||||
|
pagination.current = pag.current
|
||||||
|
fetchPostulantes()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
showModal.value = false
|
||||||
|
editingId.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(fetchPostulantes)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.postulantes-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue