327 lines
11 KiB
JavaScript
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;
|