// Funciones globales para la aplicación del Balotario // Configuración global const APP_CONFIG = { ANIMATION_DURATION: 300, CONFETTI_DURATION: 3000, SOUND_ENABLED: true, DARK_MODE: false }; // Utilidades generales class Utils { static showNotification(message, type = 'info', duration = 3000) { const notification = document.createElement('div'); notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`; notification.style.cssText = ` top: 20px; right: 20px; z-index: 9999; min-width: 300px; animation: slideInRight 0.3s ease-out; `; notification.innerHTML = ` ${message} `; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, duration); } static playSound(type) { if (!APP_CONFIG.SOUND_ENABLED) return; // Crear sonidos usando Web Audio API const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); const frequencies = { correct: 800, incorrect: 300, click: 600, complete: 1000 }; oscillator.frequency.setValueAtTime(frequencies[type] || 600, audioContext.currentTime); oscillator.type = 'sine'; gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + 0.1); } static createConfetti() { const confettiContainer = document.createElement('div'); confettiContainer.className = 'confetti'; document.body.appendChild(confettiContainer); const colors = ['#f39c12', '#e74c3c', '#3498db', '#27ae60', '#9b59b6']; for (let i = 0; i < 50; i++) { const confettiPiece = document.createElement('div'); confettiPiece.className = 'confetti-piece'; confettiPiece.style.cssText = ` left: ${Math.random() * 100}%; background: ${colors[Math.floor(Math.random() * colors.length)]}; animation-delay: ${Math.random() * 3}s; animation-duration: ${3 + Math.random() * 2}s; `; confettiContainer.appendChild(confettiPiece); } setTimeout(() => { confettiContainer.remove(); }, APP_CONFIG.CONFETTI_DURATION); } static formatTime(seconds) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; } static saveToLocalStorage(key, data) { try { localStorage.setItem(key, JSON.stringify(data)); } catch (e) { console.warn('No se pudo guardar en localStorage:', e); } } static loadFromLocalStorage(key, defaultValue = null) { try { const data = localStorage.getItem(key); return data ? JSON.parse(data) : defaultValue; } catch (e) { console.warn('No se pudo cargar de localStorage:', e); return defaultValue; } } } // Clase para manejar estadísticas class StatsManager { constructor() { this.stats = Utils.loadFromLocalStorage('balotario_stats', { totalAnswered: 0, correctAnswers: 0, incorrectAnswers: 0, accuracy: 0, studyTime: 0, examsTaken: 0, bestScore: 0, streak: 0, lastActivity: null }); } updateStats(isCorrect) { this.stats.totalAnswered++; if (isCorrect) { this.stats.correctAnswers++; this.stats.streak++; } else { this.stats.incorrectAnswers++; this.stats.streak = 0; } this.stats.accuracy = this.stats.totalAnswered > 0 ? Math.round((this.stats.correctAnswers / this.stats.totalAnswered) * 100) : 0; this.stats.lastActivity = new Date().toISOString(); this.saveStats(); return this.stats; } updateExamScore(score) { this.stats.examsTaken++; if (score > this.stats.bestScore) { this.stats.bestScore = score; } this.saveStats(); } addStudyTime(minutes) { this.stats.studyTime += minutes; this.saveStats(); } getStats() { return { ...this.stats }; } saveStats() { Utils.saveToLocalStorage('balotario_stats', this.stats); } resetStats() { this.stats = { totalAnswered: 0, correctAnswers: 0, incorrectAnswers: 0, accuracy: 0, studyTime: 0, examsTaken: 0, bestScore: 0, streak: 0, lastActivity: null }; this.saveStats(); } } // Clase para manejar el progreso circular class CircularProgress { constructor(element, options = {}) { this.element = element; this.options = { size: 120, strokeWidth: 8, color: '#3498db', backgroundColor: 'rgba(255,255,255,0.2)', ...options }; this.init(); } init() { const { size, strokeWidth } = this.options; const radius = (size - strokeWidth) / 2; const circumference = radius * 2 * Math.PI; this.element.innerHTML = `