Files
driving-academy/static/js/modules/practice-mode.js
2025-10-26 23:39:49 -05:00

327 lines
11 KiB
JavaScript

// Practice Mode Module
class PracticeMode extends BaseModule {
constructor() {
super();
this.sessionStats = {
correct: 0,
incorrect: 0,
total: 0
};
this.answered = false;
this.init();
}
init() {
this.setupEventListeners();
}
setupEventListeners() {
document.getElementById('start-practice')?.addEventListener('click', () => {
this.startPractice();
});
document.getElementById('new-session')?.addEventListener('click', () => {
this.resetSession();
});
document.getElementById('skip-question')?.addEventListener('click', () => {
this.nextQuestion();
});
document.getElementById('next-question')?.addEventListener('click', () => {
this.nextQuestion();
});
document.getElementById('practice-again')?.addEventListener('click', () => {
this.resetSession();
this.startPractice();
});
}
async startPractice() {
const count = parseInt(document.getElementById('question-count').value);
this.showLoading();
BaseModule.hide('welcome-message');
BaseModule.hide('practice-content');
BaseModule.hide('practice-complete');
try {
const data = await this.fetchQuestions({
mode: 'random',
count: count
});
this.questions = data;
this.currentIndex = 0;
this.resetSession();
this.displayQuestion();
this.hideLoading();
BaseModule.show('practice-content');
} catch (error) {
this.hideLoading();
console.error('Error in startPractice:', error);
}
}
displayQuestion() {
if (this.currentIndex >= this.questions.length) {
this.showResults();
return;
}
const question = this.questions[this.currentIndex];
this.answered = false;
let html = `
<div class="question-card-enhanced fade-in">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0 text-gradient">Pregunta ${question.id}</h5>
<span class="badge bg-success">${this.currentIndex + 1} de ${this.questions.length}</span>
</div>
`;
if (question.has_image && question.image) {
html += `<img src="${question.image}" class="question-image" alt="Imagen de la pregunta">`;
}
html += `<p class="lead mb-4">${question.question}</p>`;
html += '<div class="options-container">';
question.options.forEach((option, index) => {
const letter = String.fromCharCode(97 + index); // a, b, c, d
html += `
<button class="option-enhanced" data-answer="${letter}">
<div class="d-flex align-items-center">
<span class="badge bg-primary me-3">${letter.toUpperCase()}</span>
<span>${option.replace(/^[a-d]\)\s*/, '')}</span>
</div>
</button>
`;
});
html += '</div>';
html += '<div id="feedback" class="mt-3" style="display: none;"></div>';
html += '</div>';
document.getElementById('question-container').innerHTML = html;
document.getElementById('question-counter').textContent = `${this.currentIndex + 1} de ${this.questions.length}`;
// Setup option click handlers
this.setupOptionHandlers(question);
BaseModule.show('skip-question');
BaseModule.hide('next-question');
// Setup keyboard shortcuts
this.setupKeyboardHandlers();
}
setupOptionHandlers(question) {
const optionButtons = document.querySelectorAll('.option-enhanced');
optionButtons.forEach(button => {
button.addEventListener('click', () => {
if (this.answered) return;
this.playSound('click');
button.classList.add('selected');
const selectedAnswer = button.getAttribute('data-answer');
this.checkAnswer(selectedAnswer, question);
});
});
}
setupKeyboardHandlers() {
document.removeEventListener('keydown', this.practiceKeyHandler);
document.addEventListener('keydown', this.practiceKeyHandler.bind(this));
}
practiceKeyHandler(e) {
if (this.answered) return;
const key = e.key.toLowerCase();
if (['a', 'b', 'c', 'd'].includes(key)) {
e.preventDefault();
const optionButton = document.querySelector(`.option-enhanced[data-answer="${key}"]`);
if (optionButton) {
optionButton.click();
}
}
}
checkAnswer(selectedAnswer, question) {
if (this.answered) return;
this.answered = true;
const isCorrect = selectedAnswer === question.correct;
// Update session stats
this.sessionStats.total++;
if (isCorrect) {
this.sessionStats.correct++;
this.playSound('correct');
} else {
this.sessionStats.incorrect++;
this.playSound('incorrect');
}
this.updateSessionStats();
// Update global stats
if (window.statsManager) {
window.statsManager.updateStats(isCorrect);
}
// Show feedback
this.showFeedback(isCorrect, question);
BaseModule.hide('skip-question');
BaseModule.show('next-question');
}
showFeedback(isCorrect, question) {
const optionButtons = document.querySelectorAll('.option-enhanced');
optionButtons.forEach(button => {
const answer = button.getAttribute('data-answer');
if (answer === question.correct) {
button.classList.add('correct');
} else if (button.classList.contains('selected') && !isCorrect) {
button.classList.add('incorrect');
}
button.style.pointerEvents = 'none';
});
let feedbackHtml = '';
if (isCorrect) {
feedbackHtml = `
<div class="alert alert-success border-0 shadow-sm">
<div class="d-flex align-items-center">
<i class="fas fa-check-circle fa-2x text-success me-3"></i>
<div>
<h6 class="mb-1">¡Excelente!</h6>
<p class="mb-0">Has seleccionado la respuesta correcta.</p>
</div>
</div>
</div>
`;
} else {
feedbackHtml = `
<div class="alert alert-danger border-0 shadow-sm">
<div class="d-flex align-items-center">
<i class="fas fa-times-circle fa-2x text-danger me-3"></i>
<div>
<h6 class="mb-1">Respuesta incorrecta</h6>
<p class="mb-0">La respuesta correcta es: <strong>${question.correct.toUpperCase()})</strong></p>
</div>
</div>
</div>
`;
}
const feedbackElement = document.getElementById('feedback');
feedbackElement.innerHTML = feedbackHtml;
feedbackElement.style.display = 'none';
feedbackElement.style.opacity = '0';
feedbackElement.style.display = 'block';
// Fade in effect
setTimeout(() => {
feedbackElement.style.transition = 'opacity 0.3s ease';
feedbackElement.style.opacity = '1';
}, 10);
}
nextQuestion() {
document.removeEventListener('keydown', this.practiceKeyHandler);
this.currentIndex++;
this.displayQuestion();
}
updateSessionStats() {
document.getElementById('session-correct').textContent = this.sessionStats.correct;
document.getElementById('session-incorrect').textContent = this.sessionStats.incorrect;
const accuracy = this.sessionStats.total > 0 ?
Math.round((this.sessionStats.correct / this.sessionStats.total) * 100) : 0;
document.getElementById('session-accuracy').textContent = accuracy + '%';
// Update progress bar
const progressBar = document.getElementById('session-progress');
progressBar.style.width = accuracy + '%';
// Change bar color based on accuracy
progressBar.classList.remove('bg-danger', 'bg-warning', 'bg-success', 'bg-light');
if (accuracy >= 80) {
progressBar.classList.add('bg-success');
} else if (accuracy >= 60) {
progressBar.classList.add('bg-warning');
} else if (accuracy > 0) {
progressBar.classList.add('bg-danger');
} else {
progressBar.classList.add('bg-light');
}
}
resetSession() {
this.sessionStats = {
correct: 0,
incorrect: 0,
total: 0
};
this.updateSessionStats();
}
showResults() {
const accuracy = this.sessionStats.total > 0 ?
Math.round((this.sessionStats.correct / this.sessionStats.total) * 100) : 0;
document.getElementById('final-correct').textContent = this.sessionStats.correct;
document.getElementById('final-incorrect').textContent = this.sessionStats.incorrect;
document.getElementById('final-accuracy').textContent = accuracy + '%';
const finalProgress = document.getElementById('final-progress');
finalProgress.style.width = accuracy + '%';
// Change final progress bar color
finalProgress.classList.remove('bg-danger', 'bg-warning', 'bg-success');
if (accuracy >= 80) {
finalProgress.classList.add('bg-success');
if (window.Utils) {
Utils.createConfetti();
Utils.playSound('complete');
Utils.showNotification('¡Excelente trabajo! Has obtenido una puntuación sobresaliente.', 'success');
}
} else if (accuracy >= 60) {
finalProgress.classList.add('bg-warning');
if (window.Utils) {
Utils.playSound('complete');
Utils.showNotification('¡Buen trabajo! Sigue practicando para mejorar.', 'warning');
}
} else {
finalProgress.classList.add('bg-danger');
if (window.Utils) {
Utils.showNotification('Sigue estudiando. La práctica hace al maestro.', 'info');
}
}
BaseModule.hide('practice-content');
BaseModule.show('practice-complete');
}
}
// Auto-initialize when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
if (document.getElementById('practice-content')) {
window.practiceMode = new PracticeMode();
}
});
// Export for global use
window.PracticeMode = PracticeMode;