File "EvaluacionControlador.php"
Full Path: C:/wamp64/www/Formaciones/Controladores/EvaluacionControlador.php
File size: 17.43 KB
MIME-type: text/x-php
Charset: utf-8
<?php
// Controladores/EvaluacionControlador.php
require_once 'Modelos/EvaluacionModelo.php';
require_once 'Modelos/CursoModelo.php';
class EvaluacionControlador {
private $modelo;
private $db;
public function __construct($db) {
$this->db = $db;
$this->modelo = new EvaluacionModelo($db);
if (!esta_autenticado() || !in_array(usuario_actual()['rol'], ['admin', 'instructor'])) {
header("Location: index.php");
exit;
}
}
public function index() {
// Lógica para CREAR si viene por POST
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'create_quiz') {
$curso_id = $_POST['course_id'];
$titulo = $_POST['title'];
$intentos = $_POST['attempts_allowed'];
if ($this->modelo->crearCabecera($curso_id, $titulo, $intentos)) {
$_SESSION['mensaje'] = "Evaluación creada. Ahora puedes añadir preguntas.";
} else {
$_SESSION['error'] = "Error al crear la evaluación.";
}
header("Location: index.php?r=instructor/quizzes");
exit;
}
// Lógica para LISTAR
$evaluaciones = $this->modelo->listarTodas();
$modeloCurso = new CursoModelo($this->db);
$cursos = $modeloCurso->listarTodos();
require_once 'Vistas/plantilla/encabezado.php';
require_once 'Vistas/instructor/evaluaciones.php';
require_once 'Vistas/plantilla/pie.php';
}
public function borrar() {
$id = $_GET['id'] ?? null;
if ($id && $this->modelo->eliminar($id)) {
$_SESSION['mensaje'] = "Evaluación eliminada correctamente.";
}
header("Location: index.php?r=instructor/quizzes");
exit;
}
// El método de edición usualmente abre una vista aparte para gestionar preguntas
public function editar() {
$quiz_id = (int)($_GET['id'] ?? 0);
// --- LÓGICA PARA GUARDAR PREGUNTA ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'add_question') {
$qtext = trim($_POST['question_text'] ?? '');
$qtype = $_POST['question_type'] ?? 'mcq';
$points = (int)$_POST['points'];
// Guardar la pregunta base
$question_id = $this->modelo->agregarPregunta($quiz_id, $qtext, $qtype, $points);
// Si es de opción múltiple, guardar las opciones
if ($question_id && $qtype === 'mcq' && isset($_POST['options'])) {
$correcta = (int)$_POST['correct_option'];
foreach ($_POST['options'] as $index => $texto_opcion) {
if (!empty(trim($texto_opcion))) {
$es_correcta = ($index === $correcta) ? 1 : 0;
$this->modelo->agregarOpcion($question_id, $texto_opcion, $es_correcta);
}
}
}
header("Location: index.php?r=instructor/quizzes/edit&id=" . $quiz_id);
exit;
}
// --- LÓGICA PARA MOSTRAR LA VISTA ---
$quiz = $this->modelo->obtenerPorId($quiz_id);
$preguntas = $this->db->query("SELECT * FROM questions WHERE quiz_id = $quiz_id ORDER BY id ASC");
require_once 'Vistas/plantilla/encabezado.php';
require_once 'Vistas/instructor/editEvaluacion.php';
require_once 'Vistas/plantilla/pie.php';
}
public function borrarPregunta($id, $quiz_id) {
$this->modelo->eliminarPregunta($id);
header("Location: index.php?r=instructor/quizzes/edit&id=" . $quiz_id);
exit;
}
/**
* Resultados con filtros avanzados y paginación
*/
public function verResultados() {
$quiz_id = $_GET['id'] ?? null;
$busqueda = $_GET['s'] ?? '';
// --- NUEVOS FILTROS ---
$filtro_eval = $_GET['eval'] ?? '';
$fecha_desde = $_GET['fd'] ?? '';
$fecha_hasta = $_GET['fh'] ?? '';
$estado = $_GET['est'] ?? '';
// --- LÓGICA DE PAGINACIÓN ---
$por_pagina = 20;
$pagina_actual = isset($_GET['p']) ? (int)$_GET['p'] : 1;
if ($pagina_actual < 1) $pagina_actual = 1;
$inicio = ($pagina_actual - 1) * $por_pagina;
// --- OBTENCIÓN DE DATOS FILTRADOS ---
$total_registros = $this->modelo->contarResultados($busqueda, $quiz_id, $filtro_eval, $fecha_desde, $fecha_hasta, $estado);
$total_paginas = ceil($total_registros / $por_pagina);
$resultados = $this->modelo->obtenerResultadosPaginados($busqueda, $por_pagina, $inicio, $quiz_id, $filtro_eval, $fecha_desde, $fecha_hasta, $estado);
// --- LISTA DE EVALUACIONES/CURSOS PARA EL FILTRO DROPDOWN ---
$evaluaciones_lista = $this->modelo->obtenerEvaluacionesYCursos();
// --- CARGA DE VISTAS ---
require_once 'Vistas/plantilla/encabezado.php';
require_once 'Vistas/instructor/resultados.php';
require_once 'Vistas/plantilla/pie.php';
}
/**
* Obtener detalle de respuestas de un intento (AJAX/JSON)
*/
public function verDetalleIntento() {
header('Content-Type: application/json; charset=utf-8');
$attempt_id = (int)($_GET['attempt_id'] ?? 0);
if (!$attempt_id) {
echo json_encode(['error' => 'ID de intento no válido']);
exit;
}
$respuestas = $this->modelo->obtenerRespuestasIntento($attempt_id);
// Enriquecer cada respuesta con todas las opciones de la pregunta
foreach ($respuestas as &$resp) {
$resp['opciones'] = $this->modelo->obtenerOpcionesPregunta($resp['question_id']);
}
unset($resp);
// Calcular resumen
$total = count($respuestas);
$correctas = count(array_filter($respuestas, fn($r) => $r['is_correct'] == 1));
$incorrectas = $total - $correctas;
echo json_encode([
'attempt_id' => $attempt_id,
'total_preguntas' => $total,
'correctas' => $correctas,
'incorrectas' => $incorrectas,
'respuestas' => $respuestas
], JSON_UNESCAPED_UNICODE);
exit;
}
/**
* Ver diploma de un estudiante específico (modo instructor/admin).
* Recibe user_id y course_id por GET.
*/
public function verDiplomaInstructor() {
$user_id = (int)($_GET['user_id'] ?? 0);
$course_id = (int)($_GET['course_id'] ?? 0);
if (!$user_id || !$course_id) {
$_SESSION['error'] = 'Parámetros del diploma no válidos.';
header("Location: index.php?r=instructor/resultados");
exit;
}
// Obtener datos de completación del curso para este usuario
$result = $this->modelo->obtenerCompletacionCursoDiploma($user_id, $course_id);
if (!$result) {
$_SESSION['error'] = 'No se encontró información del curso o del estudiante.';
header("Location: index.php?r=instructor/resultados");
exit;
}
// Verificar que el estudiante aprobó
if ($result['quizzes_passed'] == 0) {
$_SESSION['error'] = 'El estudiante aún no ha aprobado la evaluación de este curso.';
header("Location: index.php?r=instructor/resultados");
exit;
}
// Preparar datos para el diploma (igual que EvaluacionAsigControlador::diploma)
$nombre_estudiante = !empty($result['name']) ? $result['name'] : 'Nombre del Estudiante';
$nombre_profesional = !empty($result['namePro']) ? $result['namePro'] : 'Nombre del Instructor';
$cargo_estudiante = !empty($result['cargo']) ? $result['cargo'] : 'OPERARIO';
$cargo_profesional = !empty($result['cargoPro']) ? $result['cargoPro'] : 'INSTRUCTOR';
// Capitalizar nombres
$nombre_estudiante = mb_convert_case($nombre_estudiante, MB_CASE_TITLE, "UTF-8");
$nombre_profesional = mb_convert_case($nombre_profesional, MB_CASE_TITLE, "UTF-8");
// Preparar fecha
$meses = ['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'];
$fecha_aprobacion = $result['submitted_at'] ?? date('Y-m-d H:i:s');
$timestamp_aprobacion = strtotime(substr($fecha_aprobacion, 0, 10));
$dia = date('d', $timestamp_aprobacion);
$mes = $meses[date('n', $timestamp_aprobacion) - 1];
$anio = date('Y', $timestamp_aprobacion);
$fecha_text = $dia . ' del mes de ' . $mes . ' de ' . $anio;
// Limpiar buffer
while (ob_get_level()) {
ob_end_clean();
}
require_once 'Vistas/evaluaciones/diploma.php';
exit;
}
/**
* Ver firma digital de un estudiante (modo instructor/admin).
* Recibe attempt_id por GET. NO requiere course_assignments.
*/
public function verFirmaInstructor() {
$attempt_id = (int)($_GET['attempt_id'] ?? 0);
if (!$attempt_id) {
$_SESSION['error'] = 'ID de intento no válido.';
header("Location: index.php?r=instructor/resultados");
exit;
}
// Consultar directamente el intento con firma (sin verificar course_assignments)
$stmt = $this->db->prepare(
"SELECT qa.*, u.name as estudiante, u.cedula,
q.title as quiz_title, c.title as course_title
FROM quiz_attempts qa
JOIN users u ON u.id = qa.user_id
JOIN quizzes q ON q.id = qa.quiz_id
JOIN courses c ON c.id = q.course_id
WHERE qa.id = ? AND qa.submitted_at IS NOT NULL"
);
$stmt->bind_param('i', $attempt_id);
$stmt->execute();
$attempt = $stmt->get_result()->fetch_assoc();
if (!$attempt) {
$_SESSION['error'] = 'Intento no encontrado.';
header("Location: index.php?r=instructor/resultados");
exit;
}
require_once 'Vistas/plantilla/encabezado.php';
require_once 'Vistas/instructor/ver_firma.php';
require_once 'Vistas/plantilla/pie.php';
exit;
}
/**
* Exportar a Excel con filtros avanzados Y detalle de preguntas.
* Genera 2 hojas: "Resumen" y "Detalle Preguntas".
*/
public function excel() {
$quiz_id = $_GET['id'] ?? null;
$busqueda = $_GET['s'] ?? '';
$filtro_eval = $_GET['eval'] ?? '';
$fecha_desde = $_GET['fd'] ?? '';
$fecha_hasta = $_GET['fh'] ?? '';
$estado = $_GET['est'] ?? '';
// Traer todos los filtrados sin límite de página
$resultados = $this->modelo->obtenerResultadosPaginados('', 5000, 0, $quiz_id, $filtro_eval, $fecha_desde, $fecha_hasta, $estado);
header('Content-Type: application/vnd.ms-excel; charset=utf-8');
header('Content-Disposition: attachment; filename=Resultados_Evaluaciones_Detalle.xls');
header('Pragma: no-cache');
header('Expires: 0');
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
echo "<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"\n";
echo " xmlns:x=\"urn:schemas-microsoft-com:office:excel\"\n";
echo " xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\"\n";
echo " xmlns:html=\"http://www.w3.org/TR/REC-html40\">\n";
// ========== HOJA 1: RESUMEN ==========
echo " <Worksheet ss:Name=\"Resumen\">\n";
echo " <Table>\n";
echo " <Row>\n";
foreach (['Estudiante', 'Cedula', 'Evaluacion', 'Curso', 'Puntaje (%)', 'Fecha', 'Estado'] as $header) {
echo " <Cell><Data ss:Type=\"String\">" . h($header) . "</Data></Cell>\n";
}
echo " </Row>\n";
if ($resultados) {
while ($r = $resultados->fetch_assoc()) {
echo " <Row>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['estudiante']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['cedula']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['evaluacion']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['curso']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"Number\">" . h($r['score']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['completed_at']) . "</Data></Cell>\n";
$aprobado = $r['score'] >= 70 ? 'APROBADO' : 'REPROBADO';
echo " <Cell><Data ss:Type=\"String\">" . h($aprobado) . "</Data></Cell>\n";
echo " </Row>\n";
}
}
echo " </Table>\n";
echo " </Worksheet>\n";
// ========== HOJA 2: DETALLE DE PREGUNTAS ==========
echo " <Worksheet ss:Name=\"Detalle Preguntas\">\n";
echo " <Table>\n";
echo " <Row>\n";
foreach (['Estudiante', 'Cedula', 'Evaluacion', 'Curso', 'N° Pregunta', 'Pregunta', 'Tipo',
'Respuesta Seleccionada', 'Respuesta Correcta', 'Resultado', 'Puntos Obtenidos'] as $header) {
echo " <Cell><Data ss:Type=\"String\">" . h($header) . "</Data></Cell>\n";
}
echo " </Row>\n";
// Re-consultar para recorrer de nuevo (ya se consumió el resultset)
$resultados2 = $this->modelo->obtenerResultadosPaginados('', 5000, 0, $quiz_id, $filtro_eval, $fecha_desde, $fecha_hasta, $estado);
if ($resultados2) {
while ($r = $resultados2->fetch_assoc()) {
$attempt_id = (int)$r['id'];
$respuestas = $this->modelo->obtenerRespuestasIntento($attempt_id);
if (empty($respuestas)) {
// Si no hay respuestas, escribir una fila indicándolo
echo " <Row>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['estudiante']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['cedula']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['evaluacion']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['curso']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">-</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">Sin respuestas registradas</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">-</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">-</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">-</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">-</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"Number\">0</Data></Cell>\n";
echo " </Row>\n";
continue;
}
$num_pregunta = 1;
foreach ($respuestas as $resp) {
$esCorrecta = $resp['is_correct'] == 1;
$resultadoTexto = $esCorrecta ? 'Correcta' : 'Incorrecta';
$puntosOtorgados = $esCorrecta ? (float)$resp['awarded_points'] : 0;
echo " <Row>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['estudiante']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['cedula']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['evaluacion']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($r['curso']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"Number\">" . $num_pregunta . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($resp['question_text']) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($resp['question_type'] === 'mcq' ? 'Opcion Multiple' : 'Abierta') . "</Data></Cell>\n";
// Respuesta seleccionada
if ($resp['question_type'] === 'mcq') {
$sel = !empty($resp['selected_option_text']) ? $resp['selected_option_text'] : '(Sin seleccion)';
} else {
$sel = !empty($resp['answer_text']) ? $resp['answer_text'] : '(Sin respuesta)';
}
echo " <Cell><Data ss:Type=\"String\">" . h($sel) . "</Data></Cell>\n";
// Respuesta correcta
$correcta_text = !empty($resp['correct_option_text']) ? $resp['correct_option_text'] : '-';
echo " <Cell><Data ss:Type=\"String\">" . h($correcta_text) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"String\">" . h($resultadoTexto) . "</Data></Cell>\n";
echo " <Cell><Data ss:Type=\"Number\">" . $puntosOtorgados . "</Data></Cell>\n";
echo " </Row>\n";
$num_pregunta++;
}
}
}
echo " </Table>\n";
echo " </Worksheet>\n";
echo "</Workbook>\n";
exit;
}
}