// Exam Mode Module class ExamMode extends BaseModule { constructor() { super(); this.examAnswers = {}; this.examTimer = null; this.examTimeLimit = 30; // minutes this.examStartTime = null; this.navigator = null; this.init(); } init() { this.setupEventListeners(); } setupEventListeners() { document.getElementById('start-exam')?.addEventListener('click', () => { this.startExam(); }); document.getElementById('prev-exam-btn')?.addEventListener('click', () => { this.previousQuestion(); }); document.getElementById('next-exam-btn')?.addEventListener('click', () => { this.nextQuestion(); }); document.getElementById('finish-exam')?.addEventListener('click', () => { this.showFinishConfirmation(); }); document.getElementById('retake-exam')?.addEventListener('click', () => { this.resetExam(); }); document.getElementById('review-answers')?.addEventListener('click', () => { this.showReviewMode(); }); // Keyboard navigation document.addEventListener('keydown', (e) => { this.handleKeyboard(e); }); // Modal event listeners this.setupModalListeners(); } setupModalListeners() { document.addEventListener('click', (e) => { if (e.target.id === 'confirm-finish') { this.finishExam(); this.hideConfirmModal(); } else if (e.target.id === 'confirmModalCancel' || e.target.id === 'confirmModalClose') { this.hideConfirmModal(); } else if (e.target.id === 'confirmBackdrop') { this.hideConfirmModal(); } }); } async startExam() { const questionCount = parseInt(document.getElementById('exam-questions').value); this.examTimeLimit = parseInt(document.getElementById('exam-time').value); try { const data = await this.fetchQuestions({ mode: 'random', count: questionCount }); this.questions = data; this.examAnswers = {}; this.currentIndex = 0; this.examStartTime = new Date(); this.initializeNavigator(); this.displayQuestion(); this.startTimer(); BaseModule.hide('exam-setup'); BaseModule.show('exam-content'); this.updateExamProgress(); } catch (error) { console.error('Error starting exam:', error); } } initializeNavigator() { if (!this.navigator) { this.navigator = new QuestionNavigator('question-navigator', { onQuestionSelect: (index) => { this.currentIndex = index; this.displayQuestion(); }, storagePrefix: 'exam_temp' // Don't persist exam navigation }); } this.navigator.setQuestions(this.questions); } displayQuestion() { if (this.currentIndex >= this.questions.length) return; const question = this.questions[this.currentIndex]; let html = this.createQuestionHTML(question, this.currentIndex, this.questions.length); html += this.createExamOptionsHTML(question.options); document.getElementById('exam-question-container').innerHTML = html; document.getElementById('question-progress').textContent = `${this.currentIndex + 1} de ${this.questions.length}`; // Setup option click handlers this.setupExamOptionHandlers(question); this.updateExamNavigation(); if (this.navigator) { this.navigator.setCurrentIndex(this.currentIndex); } } setupExamOptionHandlers(question) { const optionButtons = document.querySelectorAll('.option-enhanced'); optionButtons.forEach(button => { button.addEventListener('click', () => { this.playSound('click'); optionButtons.forEach(btn => btn.classList.remove('selected')); button.classList.add('selected'); const selectedAnswer = button.getAttribute('data-answer'); this.examAnswers[question.id] = selectedAnswer; if (this.navigator) { this.navigator.updateNavigator(); } this.updateExamProgress(); }); }); // Pre-select if already answered if (this.examAnswers[question.id]) { const selectedButton = document.querySelector(`[data-answer="${this.examAnswers[question.id]}"]`); if (selectedButton) { selectedButton.classList.add('selected'); } } } previousQuestion() { if (this.currentIndex > 0) { this.currentIndex--; this.displayQuestion(); } } nextQuestion() { if (this.currentIndex < this.questions.length - 1) { this.currentIndex++; this.displayQuestion(); } } updateExamNavigation() { document.getElementById('prev-exam-btn').disabled = this.currentIndex === 0; document.getElementById('next-exam-btn').disabled = this.currentIndex === this.questions.length - 1; } updateExamProgress() { const answeredCount = Object.keys(this.examAnswers).length; const remainingCount = this.questions.length - answeredCount; document.getElementById('answered-count').textContent = answeredCount; document.getElementById('remaining-count').textContent = remainingCount; } startTimer() { const endTime = new Date(this.examStartTime.getTime() + this.examTimeLimit * 60000); this.examTimer = setInterval(() => { const now = new Date(); const timeLeft = endTime - now; if (timeLeft <= 0) { clearInterval(this.examTimer); this.finishExam(); return; } const minutes = Math.floor(timeLeft / 60000); const seconds = Math.floor((timeLeft % 60000) / 1000); document.getElementById('timer').textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; // Change color when time is running out const timerElement = document.getElementById('timer'); if (minutes < 5) { timerElement.classList.remove('text-warning'); timerElement.classList.add('text-danger'); } }, 1000); } showFinishConfirmation() { const unansweredCount = this.questions.length - Object.keys(this.examAnswers).length; document.getElementById('unanswered-warning').textContent = unansweredCount; document.getElementById('confirmModal').style.display = 'block'; document.body.classList.add('modal-open'); } hideConfirmModal() { document.getElementById('confirmModal').style.display = 'none'; document.body.classList.remove('modal-open'); } finishExam() { clearInterval(this.examTimer); let correct = 0; let incorrect = 0; let unanswered = 0; this.questions.forEach(question => { if (this.examAnswers.hasOwnProperty(question.id)) { if (this.examAnswers[question.id] === question.correct) { correct++; } else { incorrect++; } } else { unanswered++; } }); const score = Math.round((correct / this.questions.length) * 100); const passed = score >= 70; // 70% to pass // Update global stats if (window.statsManager) { for (let i = 0; i < correct; i++) { window.statsManager.updateStats(true); } for (let i = 0; i < incorrect; i++) { window.statsManager.updateStats(false); } window.statsManager.updateExamScore(score); } // Show results this.showExamResults(correct, incorrect, unanswered, score, passed); } showExamResults(correct, incorrect, unanswered, score, passed) { document.getElementById('exam-correct').textContent = correct; document.getElementById('exam-incorrect').textContent = incorrect; document.getElementById('exam-unanswered').textContent = unanswered; document.getElementById('exam-score').textContent = score + '%'; const progressBar = document.getElementById('exam-progress-bar'); progressBar.style.width = score + '%'; const resultIcon = document.getElementById('result-icon'); const resultTitle = document.getElementById('result-title'); const passMessage = document.getElementById('pass-message'); if (passed) { progressBar.classList.remove('bg-danger'); progressBar.classList.add('bg-success'); resultIcon.innerHTML = ''; resultTitle.textContent = '¡Felicitaciones! Has Aprobado'; passMessage.classList.add('alert-success'); passMessage.textContent = 'Has obtenido una calificación aprobatoria. ¡Buen trabajo!'; passMessage.style.display = 'block'; if (window.Utils) { Utils.createConfetti(); Utils.playSound('complete'); } } else { progressBar.classList.remove('bg-success'); progressBar.classList.add('bg-danger'); resultIcon.innerHTML = ''; resultTitle.textContent = 'Examen No Aprobado'; passMessage.classList.add('alert-danger'); passMessage.textContent = 'Necesitas al menos 70% para aprobar. ¡Sigue estudiando y vuelve a intentarlo!'; passMessage.style.display = 'block'; } BaseModule.hide('exam-content'); BaseModule.show('exam-results'); } resetExam() { this.examAnswers = {}; this.currentIndex = 0; clearInterval(this.examTimer); BaseModule.hide('exam-results'); BaseModule.show('exam-setup'); } createExamOptionsHTML(options) { let html = '
'; options.forEach((option, index) => { const letter = String.fromCharCode(97 + index); // a, b, c, d html += ` `; }); html += '
'; return html; } showReviewMode() { this.reviewIndex = 0; this.setupReviewEventListeners(); this.displayReviewQuestion(); this.createReviewNavigator(); BaseModule.hide('exam-results'); BaseModule.show('exam-review'); } setupReviewEventListeners() { // Remove existing listeners to avoid duplicates document.getElementById('prev-review-btn')?.removeEventListener('click', this.prevReviewHandler); document.getElementById('next-review-btn')?.removeEventListener('click', this.nextReviewHandler); document.getElementById('back-to-results')?.removeEventListener('click', this.backToResultsHandler); // Create bound handlers this.prevReviewHandler = () => this.previousReviewQuestion(); this.nextReviewHandler = () => this.nextReviewQuestion(); this.backToResultsHandler = () => this.backToResults(); // Add event listeners document.getElementById('prev-review-btn')?.addEventListener('click', this.prevReviewHandler); document.getElementById('next-review-btn')?.addEventListener('click', this.nextReviewHandler); document.getElementById('back-to-results')?.addEventListener('click', this.backToResultsHandler); } displayReviewQuestion() { if (this.reviewIndex >= this.questions.length) return; const question = this.questions[this.reviewIndex]; const userAnswer = this.examAnswers[question.id]; const isCorrect = userAnswer === question.correct; const wasAnswered = userAnswer !== undefined; let html = `
Pregunta ${question.id}
${this.reviewIndex + 1} de ${this.questions.length} ${wasAnswered ? (isCorrect ? 'Correcta' : 'Incorrecta' ) : 'Sin responder' }
`; if (question.has_image && question.image) { html += `Imagen de la pregunta`; } html += `

${question.question}

`; html += '
'; question.options.forEach((option, index) => { const letter = String.fromCharCode(97 + index); // a, b, c, d const isUserAnswer = letter === userAnswer; const isCorrectAnswer = letter === question.correct; let optionClass = 'option-enhanced'; let badgeClass = 'bg-primary'; let iconHtml = ''; if (isCorrectAnswer) { optionClass += ' correct'; badgeClass = 'bg-success'; iconHtml = ''; } else if (isUserAnswer && !isCorrect) { optionClass += ' incorrect'; badgeClass = 'bg-danger'; iconHtml = ''; } else if (isUserAnswer) { optionClass += ' selected'; } html += `
${letter.toUpperCase()} ${option.replace(/^[a-d]\)\s*/, '')}
${iconHtml}
`; }); html += '
'; // Add explanation if available if (!wasAnswered) { html += `
No respondida: Esta pregunta no fue respondida durante el examen.
`; } else if (!isCorrect) { html += `
Tu respuesta: ${userAnswer?.toUpperCase()} | Respuesta correcta: ${question.correct.toUpperCase()}
`; } else { html += `
¡Correcto! Seleccionaste la respuesta correcta.
`; } html += '
'; document.getElementById('review-question-container').innerHTML = html; document.getElementById('review-progress').textContent = `${this.reviewIndex + 1} de ${this.questions.length}`; this.updateReviewNavigation(); this.updateReviewStats(); } createReviewNavigator() { const navigator = document.getElementById('review-navigator'); let html = ''; this.questions.forEach((question, index) => { const userAnswer = this.examAnswers[question.id]; const isCorrect = userAnswer === question.correct; const wasAnswered = userAnswer !== undefined; let buttonClass = 'btn btn-sm me-1 mb-1'; let iconHtml = ''; if (wasAnswered) { if (isCorrect) { buttonClass += ' btn-success'; iconHtml = ''; } else { buttonClass += ' btn-danger'; iconHtml = ''; } } else { buttonClass += ' btn-secondary'; iconHtml = ''; } if (index === this.reviewIndex) { buttonClass += ' active'; } html += ` `; }); navigator.innerHTML = html; // Add click handlers for navigator buttons navigator.querySelectorAll('button[data-review-index]').forEach(button => { button.addEventListener('click', () => { this.reviewIndex = parseInt(button.getAttribute('data-review-index')); this.displayReviewQuestion(); this.createReviewNavigator(); }); }); } updateReviewNavigation() { document.getElementById('prev-review-btn').disabled = this.reviewIndex === 0; document.getElementById('next-review-btn').disabled = this.reviewIndex === this.questions.length - 1; } updateReviewStats() { let correct = 0, incorrect = 0, unanswered = 0; this.questions.forEach(question => { const userAnswer = this.examAnswers[question.id]; if (userAnswer === undefined) { unanswered++; } else if (userAnswer === question.correct) { correct++; } else { incorrect++; } }); document.getElementById('review-correct-count').textContent = correct; document.getElementById('review-incorrect-count').textContent = incorrect; document.getElementById('review-unanswered-count').textContent = unanswered; } previousReviewQuestion() { if (this.reviewIndex > 0) { this.reviewIndex--; this.displayReviewQuestion(); this.createReviewNavigator(); } } nextReviewQuestion() { if (this.reviewIndex < this.questions.length - 1) { this.reviewIndex++; this.displayReviewQuestion(); this.createReviewNavigator(); } } backToResults() { BaseModule.hide('exam-review'); BaseModule.show('exam-results'); } handleKeyboard(e) { // Handle keyboard shortcuts during exam if (document.getElementById('exam-content') && document.getElementById('exam-content').style.display !== 'none') { const key = e.key.toLowerCase(); // Handle option selection (a, b, c, d) if (['a', 'b', 'c', 'd'].includes(key)) { e.preventDefault(); const optionButton = document.querySelector(`.option-enhanced[data-answer="${key}"]`); if (optionButton) { // Remove previous selection document.querySelectorAll('.option-enhanced').forEach(btn => btn.classList.remove('selected')); // Select the new option optionButton.classList.add('selected'); // Save the answer const question = this.questions[this.currentIndex]; this.examAnswers[question.id] = key; if (this.navigator) { this.navigator.updateNavigator(); } this.updateExamProgress(); this.playSound('click'); } } // Navigation keys else if (e.key === 'ArrowLeft' && this.currentIndex > 0) { this.previousQuestion(); } else if (e.key === 'ArrowRight' && this.currentIndex < this.questions.length - 1) { this.nextQuestion(); } } // Handle keyboard shortcuts during review else if (document.getElementById('exam-review') && document.getElementById('exam-review').style.display !== 'none') { if (e.key === 'ArrowLeft' && this.reviewIndex > 0) { this.previousReviewQuestion(); } else if (e.key === 'ArrowRight' && this.reviewIndex < this.questions.length - 1) { this.nextReviewQuestion(); } else if (e.key === 'Escape') { this.backToResults(); } } } } // Auto-initialize when DOM is ready document.addEventListener('DOMContentLoaded', function() { if (document.getElementById('exam-setup')) { window.examMode = new ExamMode(); } }); // Export for global use window.ExamMode = ExamMode;