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;
    }
}