Spaces:
Running
Running
| // SoberanIA Argentina - Revolutionary Fintech Application | |
| class SoberanIAApp { | |
| constructor() { | |
| this.data = { | |
| usuarios_actuales: 1247500, | |
| redistribuido_total: 45320780000, | |
| peso_d_circulacion: 32654892300, | |
| jubilados_beneficiados: 872500, | |
| comercios_adheridos: 28750, | |
| pool_sorteos: 12500000, | |
| soberania_porcentaje: 45, | |
| provincias: [ | |
| { nombre: "Buenos Aires", usuarios: 450000, beneficiarios: 315000, penetracion: 2.6 }, | |
| { nombre: "CABA", usuarios: 120000, beneficiarios: 84000, penetracion: 3.9 }, | |
| { nombre: "Córdoba", usuarios: 98000, beneficiarios: 68600, penetracion: 2.6 }, | |
| { nombre: "Santa Fe", usuarios: 85000, beneficiarios: 59500, penetracion: 2.5 }, | |
| { nombre: "Mendoza", usuarios: 75000, beneficiarios: 52500, penetracion: 3.8 }, | |
| { nombre: "Tucumán", usuarios: 65000, beneficiarios: 45500, penetracion: 4.1 } | |
| ], | |
| productos_destacados: [ | |
| { titulo: "Smartphone Samsung A54", precio: 299000, precio_ml: 386700, vendedor: "TechCoopBA", rating: 4.8, ahorro: 87700, categoria: "tecnologia", id: 1 }, | |
| { titulo: "Heladera Gafa 365L", precio: 485000, precio_ml: 627000, vendedor: "ElectroCoopSF", rating: 4.9, ahorro: 142000, categoria: "hogar", id: 2 }, | |
| { titulo: "Notebook Lenovo IdeaPad", precio: 520000, precio_ml: 728000, vendedor: "CompuCoopCor", rating: 4.7, ahorro: 208000, categoria: "tecnologia", id: 3 }, | |
| { titulo: "Smart TV 55 pulgadas", precio: 680000, precio_ml: 884000, vendedor: "TechCoopBA", rating: 4.6, ahorro: 204000, categoria: "tecnologia", id: 4 }, | |
| { titulo: "Lavarropas Automático 8kg", precio: 420000, precio_ml: 546000, vendedor: "ElectroCoopSF", rating: 4.8, ahorro: 126000, categoria: "hogar", id: 5 }, | |
| { titulo: "Zapatillas Running Nike", precio: 89000, precio_ml: 115700, vendedor: "SportCoopMdz", rating: 4.5, ahorro: 26700, categoria: "deportes", id: 6 }, | |
| { titulo: "Microondas LG 28L", precio: 180000, precio_ml: 234000, vendedor: "ElectroCoopSF", rating: 4.7, ahorro: 54000, categoria: "hogar", id: 7 }, | |
| { titulo: "Tablet Samsung 10.1", precio: 220000, precio_ml: 286000, vendedor: "TechCoopBA", rating: 4.6, ahorro: 66000, categoria: "tecnologia", id: 8 } | |
| ], | |
| ganadores_recientes: [ | |
| { fecha: "2025-06-01", ganador: "María G.", categoria: "Jubilada", premio: 2500000, provincia: "Buenos Aires" }, | |
| { fecha: "2025-05-25", ganador: "Carlos R.", categoria: "Jubilado", premio: 1800000, provincia: "Córdoba" }, | |
| { fecha: "2025-05-18", ganador: "Ana M.", categoria: "Vulnerable", premio: 1200000, provincia: "Santa Fe" }, | |
| { fecha: "2025-05-11", ganador: "Luis M.", categoria: "General", premio: 800000, provincia: "CABA" }, | |
| { fecha: "2025-05-04", ganador: "Elena S.", categoria: "Jubilada", premio: 2100000, provincia: "Buenos Aires" }, | |
| { fecha: "2025-04-27", ganador: "Roberto P.", categoria: "Jubilado", premio: 1900000, provincia: "Mendoza" } | |
| ], | |
| testimonios: [ | |
| { nombre: "María González", edad: 68, ubicacion: "San Isidro, Buenos Aires", testimonio: "Gracias a SoberanIA puedo comprar medicamentos sin preocuparme. He ganado dos sorteos que me cambiaron la vida.", monto_ganado: 2500000 }, | |
| { nombre: "Carlos Rodríguez", edad: 72, ubicacion: "Córdoba Capital", testimonio: "Después de 40 años de trabajo, mi jubilación no alcanzaba. Con SoberanIA recuperé la dignidad.", monto_ganado: 1800000 }, | |
| { nombre: "Ana Fernández", edad: 65, ubicacion: "Rosario, Santa Fe", testimonio: "Mis nietos ahora pueden estudiar gracias a los sorteos de SoberanIA. Es una bendición.", monto_ganado: 1200000 }, | |
| { nombre: "Elena Silva", edad: 70, ubicacion: "La Plata, Buenos Aires", testimonio: "Por primera vez en años puedo ayudar a mis hijos. SoberanIA cambió mi vida completamente.", monto_ganado: 2100000 } | |
| ], | |
| noticias: [ | |
| { fecha: "Junio 8, 2025", titulo: "SoberanIA alcanza 2500 comercios adheridos en Tucumán", descripcion: "La economía local registra 18% de aumento en transacciones sin intermediarios." }, | |
| { fecha: "Junio 5, 2025", titulo: "BCRA reconoce al PESO-D como medio de pago oficial", descripcion: "Fortalece la soberanía monetaria digital argentina." }, | |
| { fecha: "Junio 3, 2025", titulo: "Jubilados de Mendoza reciben $45M en redistribución", descripcion: "El sistema cooperativo supera todas las expectativas de impacto social." }, | |
| { fecha: "Junio 1, 2025", titulo: "Nueva alianza con cooperativas agropecuarias", descripcion: "Productos del campo directo al consumidor con 0% comisión." } | |
| ] | |
| }; | |
| this.state = { | |
| currentSection: 'dashboard', | |
| isLoggedIn: false, | |
| user: null, | |
| cart: [], | |
| balance: 25450.00, | |
| sorteoTarget: this.getNextSundayAt8PM(), | |
| countdownInterval: null | |
| }; | |
| this.charts = {}; | |
| this.productsLoaded = false; | |
| this.init(); | |
| } | |
| init() { | |
| // Wait for DOM to be fully loaded | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', () => { | |
| this.setupApp(); | |
| }); | |
| } else { | |
| this.setupApp(); | |
| } | |
| } | |
| setupApp() { | |
| this.setupEventListeners(); | |
| this.startCounters(); | |
| this.initializeCharts(); | |
| this.loadDynamicContent(); | |
| this.startCountdown(); | |
| this.updateCountersInterval(); | |
| this.updateBalance(); | |
| this.updateCartDisplay(); | |
| console.log('SoberanIA App initialized successfully'); | |
| } | |
| setupEventListeners() { | |
| // Navigation links | |
| const navLinks = document.querySelectorAll('.nav__link'); | |
| navLinks.forEach(link => { | |
| link.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| const section = link.getAttribute('data-section'); | |
| if (section) { | |
| this.navigateToSection(section); | |
| } | |
| }); | |
| }); | |
| // Mobile menu toggle | |
| const navToggle = document.getElementById('nav-toggle'); | |
| const navMenu = document.getElementById('nav-menu'); | |
| if (navToggle) { | |
| navToggle.addEventListener('click', () => { | |
| if (navMenu) { | |
| navMenu.classList.toggle('active'); | |
| } | |
| }); | |
| } | |
| // Close mobile menu when clicking nav links | |
| navLinks.forEach(link => { | |
| link.addEventListener('click', () => { | |
| if (navMenu) { | |
| navMenu.classList.remove('active'); | |
| } | |
| }); | |
| }); | |
| // Modal controls | |
| this.setupModalControls(); | |
| // Form handlers | |
| this.setupFormHandlers(); | |
| // Marketplace functionality | |
| this.setupMarketplaceControls(); | |
| // Fintech functionality | |
| this.setupFintechControls(); | |
| // Cart functionality | |
| this.setupCartControls(); | |
| console.log('Event listeners setup complete'); | |
| } | |
| setupModalControls() { | |
| // Register modal triggers | |
| const registerBtn = document.getElementById('register-btn'); | |
| const heroRegister = document.getElementById('hero-register'); | |
| if (registerBtn) { | |
| registerBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| if (this.state.isLoggedIn) { | |
| this.logout(); | |
| } else { | |
| this.openModal('register-modal'); | |
| } | |
| }); | |
| } | |
| if (heroRegister) { | |
| heroRegister.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| this.openModal('register-modal'); | |
| }); | |
| } | |
| // Login modal | |
| const loginBtn = document.getElementById('login-btn'); | |
| if (loginBtn) { | |
| loginBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| if (!this.state.isLoggedIn) { | |
| this.openModal('login-modal'); | |
| } | |
| }); | |
| } | |
| // Send money modal | |
| const sendMoneyBtn = document.getElementById('send-money-btn'); | |
| if (sendMoneyBtn) { | |
| sendMoneyBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| if (this.state.isLoggedIn) { | |
| this.openModal('send-money-modal'); | |
| } else { | |
| this.showToast('Por favor inicia sesión primero', 'error'); | |
| } | |
| }); | |
| } | |
| // Sell product modal | |
| const sellProductBtn = document.getElementById('sell-product-btn'); | |
| if (sellProductBtn) { | |
| sellProductBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| if (this.state.isLoggedIn) { | |
| this.openModal('sell-product-modal'); | |
| } else { | |
| this.showToast('Por favor inicia sesión primero', 'error'); | |
| } | |
| }); | |
| } | |
| // Modal close buttons | |
| document.querySelectorAll('.modal-close').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| const modalId = btn.getAttribute('data-modal'); | |
| if (modalId) { | |
| this.closeModal(modalId); | |
| } else { | |
| // Find the closest modal and close it | |
| const modal = btn.closest('.modal'); | |
| if (modal) { | |
| this.closeModal(modal.id); | |
| } | |
| } | |
| }); | |
| }); | |
| // Close modal on backdrop click | |
| document.querySelectorAll('.modal').forEach(modal => { | |
| modal.addEventListener('click', (e) => { | |
| if (e.target === modal) { | |
| this.closeModal(modal.id); | |
| } | |
| }); | |
| }); | |
| } | |
| setupFormHandlers() { | |
| // Register form | |
| const registerForm = document.getElementById('register-form'); | |
| if (registerForm) { | |
| registerForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| this.handleRegister(e.target); | |
| }); | |
| } | |
| // Login form | |
| const loginForm = document.getElementById('login-form'); | |
| if (loginForm) { | |
| loginForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| this.handleLogin(e.target); | |
| }); | |
| } | |
| // Send money form | |
| const sendMoneyForm = document.getElementById('send-money-form'); | |
| if (sendMoneyForm) { | |
| sendMoneyForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| this.handleSendMoney(e.target); | |
| }); | |
| } | |
| // Sell product form | |
| const sellProductForm = document.getElementById('sell-product-form'); | |
| if (sellProductForm) { | |
| sellProductForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| this.handleSellProduct(e.target); | |
| }); | |
| } | |
| } | |
| setupMarketplaceControls() { | |
| // Product search | |
| const productSearch = document.getElementById('product-search'); | |
| if (productSearch) { | |
| productSearch.addEventListener('input', (e) => { | |
| this.filterProducts(e.target.value); | |
| }); | |
| } | |
| // Category filters | |
| document.querySelectorAll('.filter-btn').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| const category = btn.getAttribute('data-category'); | |
| this.filterProductsByCategory(category); | |
| }); | |
| }); | |
| } | |
| setupFintechControls() { | |
| // Investment calculators | |
| document.querySelectorAll('.investment-input').forEach(input => { | |
| input.addEventListener('input', (e) => { | |
| this.calculateInvestment(e.target); | |
| }); | |
| }); | |
| // Credit calculators | |
| document.querySelectorAll('.credit-input, .credit-term').forEach(input => { | |
| input.addEventListener('input', (e) => { | |
| this.calculateCredit(e.target); | |
| }); | |
| input.addEventListener('change', (e) => { | |
| this.calculateCredit(e.target); | |
| }); | |
| }); | |
| // QR and history buttons | |
| const receiveBtn = document.getElementById('receive-money-btn'); | |
| const historyBtn = document.getElementById('history-btn'); | |
| if (receiveBtn) { | |
| receiveBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| this.showToast('QR generado para recibir pagos PESO-D', 'success', 'QR Generado'); | |
| }); | |
| } | |
| if (historyBtn) { | |
| historyBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| this.showTransactionHistory(); | |
| }); | |
| } | |
| } | |
| setupCartControls() { | |
| // Cart close | |
| const cartClose = document.getElementById('cart-close'); | |
| if (cartClose) { | |
| cartClose.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| this.closeCart(); | |
| }); | |
| } | |
| // Checkout | |
| const checkoutBtn = document.getElementById('checkout-btn'); | |
| if (checkoutBtn) { | |
| checkoutBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| this.handleCheckout(); | |
| }); | |
| } | |
| } | |
| navigateToSection(sectionId) { | |
| console.log('Navigating to section:', sectionId); | |
| // Update active nav link | |
| document.querySelectorAll('.nav__link').forEach(link => { | |
| link.classList.remove('active'); | |
| }); | |
| const activeLink = document.querySelector(`[data-section="${sectionId}"]`); | |
| if (activeLink) { | |
| activeLink.classList.add('active'); | |
| } | |
| // Update active section | |
| document.querySelectorAll('.section').forEach(section => { | |
| section.classList.remove('active'); | |
| }); | |
| const activeSection = document.getElementById(sectionId); | |
| if (activeSection) { | |
| activeSection.classList.add('active'); | |
| console.log('Section activated:', sectionId); | |
| } else { | |
| console.error('Section not found:', sectionId); | |
| } | |
| this.state.currentSection = sectionId; | |
| // Load section-specific content if needed | |
| if (sectionId === 'marketplace' && !this.productsLoaded) { | |
| setTimeout(() => { | |
| this.loadProducts(); | |
| this.productsLoaded = true; | |
| }, 100); | |
| } | |
| // Re-initialize charts if needed for fintech section | |
| if (sectionId === 'fintech') { | |
| setTimeout(() => { | |
| this.initializeFintechCharts(); | |
| }, 200); | |
| } | |
| // Scroll to top | |
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| } | |
| openModal(modalId) { | |
| const modal = document.getElementById(modalId); | |
| if (modal) { | |
| modal.classList.add('active'); | |
| document.body.style.overflow = 'hidden'; | |
| console.log('Modal opened:', modalId); | |
| } else { | |
| console.error('Modal not found:', modalId); | |
| } | |
| } | |
| closeModal(modalId) { | |
| const modal = document.getElementById(modalId); | |
| if (modal) { | |
| modal.classList.remove('active'); | |
| document.body.style.overflow = 'auto'; | |
| console.log('Modal closed:', modalId); | |
| } | |
| } | |
| showToast(message, type = 'info', title = '') { | |
| const toastContainer = document.getElementById('toast-container'); | |
| if (!toastContainer) { | |
| console.error('Toast container not found'); | |
| return; | |
| } | |
| const toast = document.createElement('div'); | |
| toast.className = `toast toast--${type}`; | |
| const icons = { | |
| success: 'fas fa-check-circle', | |
| error: 'fas fa-exclamation-circle', | |
| info: 'fas fa-info-circle' | |
| }; | |
| toast.innerHTML = ` | |
| <div class="toast-content"> | |
| <i class="toast-icon ${icons[type]}"></i> | |
| <div class="toast-message"> | |
| ${title ? `<div class="toast-title">${title}</div>` : ''} | |
| <div class="toast-description">${message}</div> | |
| </div> | |
| </div> | |
| `; | |
| toastContainer.appendChild(toast); | |
| setTimeout(() => { | |
| if (toast.parentNode) { | |
| toast.remove(); | |
| } | |
| }, 5000); | |
| } | |
| handleRegister(form) { | |
| const formData = new FormData(form); | |
| const userData = { | |
| username: formData.get('username'), | |
| email: formData.get('email'), | |
| dni: formData.get('dni'), | |
| password: formData.get('password'), | |
| confirmPassword: formData.get('confirm-password'), | |
| category: formData.get('category') | |
| }; | |
| // Validate DNI | |
| if (!/^\d{8}$/.test(userData.dni)) { | |
| this.showToast('DNI debe tener exactamente 8 dígitos', 'error', 'Error de Validación'); | |
| return; | |
| } | |
| // Validate passwords match | |
| if (userData.password !== userData.confirmPassword) { | |
| this.showToast('Las contraseñas no coinciden', 'error', 'Error de Validación'); | |
| return; | |
| } | |
| // Validate required fields | |
| if (!userData.username || !userData.email || !userData.category) { | |
| this.showToast('Todos los campos son obligatorios', 'error', 'Error de Validación'); | |
| return; | |
| } | |
| // Simulate registration | |
| this.state.user = { | |
| username: userData.username, | |
| email: userData.email, | |
| dni: userData.dni, | |
| category: userData.category | |
| }; | |
| this.state.isLoggedIn = true; | |
| this.closeModal('register-modal'); | |
| this.showToast('¡Registro exitoso! Bienvenido a la revolución fintech cooperativa', 'success', '¡Bienvenido!'); | |
| this.updateUIForLoggedInUser(); | |
| // Add welcome bonus | |
| this.state.balance += 1000; | |
| this.showToast('Has recibido $1,000 PESO-D de bienvenida', 'success', 'Bono de Bienvenida'); | |
| this.updateBalance(); | |
| // Reset form | |
| form.reset(); | |
| } | |
| handleLogin(form) { | |
| const formData = new FormData(form); | |
| // Simulate login | |
| this.state.user = { | |
| username: formData.get('username'), | |
| email: formData.get('username'), | |
| category: 'general' | |
| }; | |
| this.state.isLoggedIn = true; | |
| this.closeModal('login-modal'); | |
| this.showToast('Sesión iniciada correctamente', 'success', 'Bienvenido de vuelta'); | |
| this.updateUIForLoggedInUser(); | |
| // Reset form | |
| form.reset(); | |
| } | |
| handleSendMoney(form) { | |
| const formData = new FormData(form); | |
| const amount = parseFloat(formData.get('amount')); | |
| const recipient = formData.get('recipient'); | |
| if (!recipient) { | |
| this.showToast('Ingresa el destinatario', 'error'); | |
| return; | |
| } | |
| if (amount <= 0) { | |
| this.showToast('El monto debe ser mayor a 0', 'error'); | |
| return; | |
| } | |
| if (amount > this.state.balance) { | |
| this.showToast('Saldo insuficiente para realizar la transferencia', 'error', 'Error de Saldo'); | |
| return; | |
| } | |
| this.state.balance -= amount; | |
| this.updateBalance(); | |
| this.closeModal('send-money-modal'); | |
| this.showToast(`Enviaste $${amount.toLocaleString()} PESO-D a ${recipient} exitosamente`, 'success', 'Transferencia Exitosa'); | |
| // Reset form | |
| form.reset(); | |
| } | |
| handleSellProduct(form) { | |
| const formData = new FormData(form); | |
| const newProduct = { | |
| titulo: formData.get('title'), | |
| precio: parseInt(formData.get('price')), | |
| precio_ml: parseInt(formData.get('price')) * 1.3, // 30% más en MercadoLibre | |
| vendedor: this.state.user.username, | |
| rating: 5.0, | |
| categoria: formData.get('category'), | |
| id: Date.now(), | |
| ahorro: parseInt(formData.get('price')) * 0.3 | |
| }; | |
| this.data.productos_destacados.push(newProduct); | |
| this.loadProducts(); | |
| this.closeModal('sell-product-modal'); | |
| this.showToast('Producto publicado exitosamente con 0% de comisión', 'success', 'Publicación Exitosa'); | |
| // Reset form | |
| form.reset(); | |
| } | |
| updateUIForLoggedInUser() { | |
| const loginBtn = document.getElementById('login-btn'); | |
| const registerBtn = document.getElementById('register-btn'); | |
| if (loginBtn && registerBtn && this.state.user) { | |
| loginBtn.innerHTML = `<i class="fas fa-user"></i> ${this.state.user.username}`; | |
| registerBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i> Salir'; | |
| } | |
| } | |
| logout() { | |
| this.state.isLoggedIn = false; | |
| this.state.user = null; | |
| this.state.cart = []; | |
| this.state.balance = 25450.00; | |
| const loginBtn = document.getElementById('login-btn'); | |
| const registerBtn = document.getElementById('register-btn'); | |
| if (loginBtn && registerBtn) { | |
| loginBtn.innerHTML = '<i class="fas fa-sign-in-alt"></i> Ingresar'; | |
| registerBtn.innerHTML = '<i class="fas fa-user-plus"></i> Registro GRATIS'; | |
| } | |
| this.updateBalance(); | |
| this.updateCartDisplay(); | |
| this.showToast('Sesión cerrada correctamente', 'info', 'Hasta luego'); | |
| } | |
| startCounters() { | |
| const counters = document.querySelectorAll('.counter'); | |
| counters.forEach(counter => { | |
| const target = parseInt(counter.getAttribute('data-target')); | |
| if (isNaN(target)) return; | |
| const duration = 2000; | |
| const step = target / (duration / 16); | |
| let current = 0; | |
| const updateCounter = () => { | |
| current += step; | |
| if (current < target) { | |
| counter.textContent = Math.floor(current).toLocaleString(); | |
| requestAnimationFrame(updateCounter); | |
| } else { | |
| counter.textContent = target.toLocaleString(); | |
| } | |
| }; | |
| // Start animation when element is visible | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| updateCounter(); | |
| observer.unobserve(entry.target); | |
| } | |
| }); | |
| }); | |
| observer.observe(counter); | |
| }); | |
| } | |
| updateCountersInterval() { | |
| setInterval(() => { | |
| // Simulate real-time growth | |
| this.data.usuarios_actuales += Math.floor(Math.random() * 15) + 5; | |
| this.data.redistribuido_total += Math.floor(Math.random() * 2000000) + 500000; | |
| this.data.peso_d_circulacion += Math.floor(Math.random() * 800000) + 250000; | |
| this.data.pool_sorteos += Math.floor(Math.random() * 50000) + 10000; | |
| // Update displays without animation | |
| const userCounter = document.querySelector('[data-target="1247500"]'); | |
| if (userCounter) { | |
| userCounter.textContent = this.data.usuarios_actuales.toLocaleString(); | |
| } | |
| // Update sorteo pool | |
| const poolElements = document.querySelectorAll('.sorteo-pool strong'); | |
| poolElements.forEach(el => { | |
| el.textContent = `$${this.data.pool_sorteos.toLocaleString()}`; | |
| }); | |
| }, 5000); | |
| } | |
| initializeCharts() { | |
| // Wait for charts to be visible before initializing | |
| setTimeout(() => { | |
| this.createGrowthChart(); | |
| this.createRedistributionChart(); | |
| this.createSorteoDistributionChart(); | |
| }, 1000); | |
| } | |
| initializeFintechCharts() { | |
| // Additional charts for fintech section if needed | |
| console.log('Fintech charts initialized'); | |
| } | |
| createGrowthChart() { | |
| const ctx = document.getElementById('growthChart'); | |
| if (!ctx) { | |
| console.log('Growth chart canvas not found'); | |
| return; | |
| } | |
| const months = []; | |
| const users = []; | |
| const baseDate = new Date(2024, 0, 1); // January 2024 | |
| for (let i = 0; i <= 18; i++) { | |
| const date = new Date(baseDate); | |
| date.setMonth(baseDate.getMonth() + i); | |
| months.push(date.toLocaleDateString('es-AR', { month: 'short', year: '2-digit' })); | |
| // Exponential growth curve | |
| const growth = 250000 + (1200000 * (1 - Math.exp(-i/8))); | |
| users.push(Math.floor(growth)); | |
| } | |
| this.charts.growth = new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels: months, | |
| datasets: [{ | |
| label: 'Usuarios Registrados', | |
| data: users, | |
| borderColor: '#0033A0', | |
| backgroundColor: 'rgba(0, 51, 160, 0.1)', | |
| fill: true, | |
| tension: 0.4, | |
| pointBackgroundColor: '#0033A0', | |
| pointBorderColor: '#FFFFFF', | |
| pointBorderWidth: 2, | |
| pointRadius: 4 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| display: false | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| beginAtZero: false, | |
| ticks: { | |
| callback: function(value) { | |
| return (value / 1000000).toFixed(1) + 'M'; | |
| } | |
| }, | |
| grid: { | |
| color: 'rgba(0, 0, 0, 0.1)' | |
| } | |
| }, | |
| x: { | |
| grid: { | |
| color: 'rgba(0, 0, 0, 0.1)' | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| console.log('Growth chart created'); | |
| } | |
| createRedistributionChart() { | |
| const ctx = document.getElementById('redistributionChart'); | |
| if (!ctx) { | |
| console.log('Redistribution chart canvas not found'); | |
| return; | |
| } | |
| this.charts.redistribution = new Chart(ctx, { | |
| type: 'doughnut', | |
| data: { | |
| labels: ['Jubilados (70%)', 'Sectores Vulnerables (20%)', 'Pool General (10%)'], | |
| datasets: [{ | |
| data: [70, 20, 10], | |
| backgroundColor: ['#0033A0', '#75AADB', '#FFFFFF'], | |
| borderColor: ['#0033A0', '#75AADB', '#0033A0'], | |
| borderWidth: 2 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| position: 'bottom', | |
| labels: { | |
| padding: 20, | |
| usePointStyle: true, | |
| font: { | |
| size: 12 | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| console.log('Redistribution chart created'); | |
| } | |
| createSorteoDistributionChart() { | |
| const ctx = document.getElementById('sorteoDistributionChart'); | |
| if (!ctx) { | |
| console.log('Sorteo distribution chart canvas not found'); | |
| return; | |
| } | |
| this.charts.sorteoDistribution = new Chart(ctx, { | |
| type: 'pie', | |
| data: { | |
| labels: ['Jubilados', 'Vulnerables', 'General'], | |
| datasets: [{ | |
| data: [70, 20, 10], | |
| backgroundColor: ['#0033A0', '#75AADB', '#E3F2FD'], | |
| borderColor: '#FFFFFF', | |
| borderWidth: 3 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| display: false | |
| } | |
| } | |
| } | |
| }); | |
| console.log('Sorteo distribution chart created'); | |
| } | |
| loadDynamicContent() { | |
| this.loadNews(); | |
| this.loadProducts(); | |
| this.loadTestimonials(); | |
| this.loadProvincialStats(); | |
| this.loadWinners(); | |
| console.log('Dynamic content loaded'); | |
| } | |
| loadNews() { | |
| const container = document.getElementById('news-container'); | |
| if (!container) return; | |
| container.innerHTML = this.data.noticias.map(noticia => ` | |
| <div class="news-card"> | |
| <div class="news-card__date">${noticia.fecha}</div> | |
| <h3 class="news-card__title">${noticia.titulo}</h3> | |
| <p class="news-card__description">${noticia.descripcion}</p> | |
| </div> | |
| `).join(''); | |
| } | |
| loadProducts() { | |
| const container = document.getElementById('products-container'); | |
| if (!container) return; | |
| container.innerHTML = this.data.productos_destacados.map(producto => ` | |
| <div class="product-card" data-category="${producto.categoria}"> | |
| <div class="product-card__image"> | |
| <i class="fas fa-${this.getProductIcon(producto.categoria)}"></i> | |
| </div> | |
| <div class="product-card__content"> | |
| <h3 class="product-card__title">${producto.titulo}</h3> | |
| <div class="product-card__price">$${producto.precio.toLocaleString()}</div> | |
| <div class="product-card__comparison"> | |
| <span class="ml-price">ML: $${producto.precio_ml.toLocaleString()}</span> | |
| <span class="savings">Ahorras: $${producto.ahorro.toLocaleString()}</span> | |
| </div> | |
| <div class="product-card__seller">Por ${producto.vendedor}</div> | |
| <div class="product-card__rating"> | |
| <span class="stars">${'★'.repeat(Math.floor(producto.rating))}${'☆'.repeat(5-Math.floor(producto.rating))}</span> | |
| <span>${producto.rating}</span> | |
| </div> | |
| <div class="product-card__actions"> | |
| <button class="btn btn--primary btn--sm" onclick="window.app.addToCart(${producto.id})"> | |
| <i class="fas fa-cart-plus"></i> Agregar | |
| </button> | |
| <button class="btn btn--secondary btn--sm" onclick="window.app.viewProduct(${producto.id})"> | |
| <i class="fas fa-eye"></i> Ver | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| console.log('Products loaded:', this.data.productos_destacados.length); | |
| } | |
| getProductIcon(categoria) { | |
| const icons = { | |
| 'tecnologia': 'laptop', | |
| 'hogar': 'home', | |
| 'ropa': 'tshirt', | |
| 'deportes': 'futbol' | |
| }; | |
| return icons[categoria] || 'box'; | |
| } | |
| filterProducts(searchTerm) { | |
| const products = document.querySelectorAll('.product-card'); | |
| products.forEach(product => { | |
| const title = product.querySelector('.product-card__title'); | |
| if (title) { | |
| const matches = title.textContent.toLowerCase().includes(searchTerm.toLowerCase()); | |
| product.style.display = matches ? 'block' : 'none'; | |
| } | |
| }); | |
| } | |
| filterProductsByCategory(category) { | |
| const products = document.querySelectorAll('.product-card'); | |
| products.forEach(product => { | |
| const productCategory = product.getAttribute('data-category'); | |
| const show = category === 'all' || productCategory === category; | |
| product.style.display = show ? 'block' : 'none'; | |
| }); | |
| } | |
| addToCart(productId) { | |
| if (!this.state.isLoggedIn) { | |
| this.showToast('Por favor inicia sesión primero', 'error'); | |
| return; | |
| } | |
| const product = this.data.productos_destacados.find(p => p.id === productId); | |
| if (!product) { | |
| console.error('Product not found:', productId); | |
| return; | |
| } | |
| const existingItem = this.state.cart.find(item => item.id === productId); | |
| if (existingItem) { | |
| existingItem.quantity += 1; | |
| } else { | |
| this.state.cart.push({ ...product, quantity: 1 }); | |
| } | |
| this.updateCartDisplay(); | |
| this.showCart(); | |
| this.showToast(`${product.titulo} agregado al carrito`, 'success'); | |
| console.log('Product added to cart:', product.titulo); | |
| } | |
| viewProduct(productId) { | |
| const product = this.data.productos_destacados.find(p => p.id === productId); | |
| if (product) { | |
| this.showToast(`Viendo: ${product.titulo} - Ahorro vs ML: $${product.ahorro.toLocaleString()}`, 'info', 'Detalles del Producto'); | |
| } | |
| } | |
| showCart() { | |
| const cartSidebar = document.getElementById('cart-sidebar'); | |
| if (cartSidebar) { | |
| cartSidebar.classList.add('active'); | |
| } | |
| } | |
| updateCartDisplay() { | |
| const cartItems = document.getElementById('cart-items'); | |
| const cartTotal = document.getElementById('cart-total'); | |
| if (!cartItems || !cartTotal) return; | |
| if (this.state.cart.length === 0) { | |
| cartItems.innerHTML = '<p style="text-align: center; color: var(--color-text-secondary); padding: 20px;">Tu carrito está vacío</p>'; | |
| cartTotal.innerHTML = '0'; | |
| return; | |
| } | |
| cartItems.innerHTML = this.state.cart.map(item => ` | |
| <div class="cart-item"> | |
| <div class="cart-item__info"> | |
| <div class="cart-item__title">${item.titulo}</div> | |
| <div class="cart-item__price">$${item.precio.toLocaleString()} × ${item.quantity}</div> | |
| <div class="cart-item__savings">Ahorras: $${(item.ahorro * item.quantity).toLocaleString()}</div> | |
| </div> | |
| <button onclick="window.app.removeFromCart(${item.id})" class="btn btn--outline btn--sm"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| `).join(''); | |
| const total = this.state.cart.reduce((sum, item) => sum + (item.precio * item.quantity), 0); | |
| const totalSavings = this.state.cart.reduce((sum, item) => sum + (item.ahorro * item.quantity), 0); | |
| cartTotal.innerHTML = `${total.toLocaleString()}<br><small style="color: var(--color-success);">Ahorras: $${totalSavings.toLocaleString()}</small>`; | |
| } | |
| removeFromCart(productId) { | |
| this.state.cart = this.state.cart.filter(item => item.id !== productId); | |
| this.updateCartDisplay(); | |
| this.showToast('Producto eliminado del carrito', 'info'); | |
| } | |
| closeCart() { | |
| const cartSidebar = document.getElementById('cart-sidebar'); | |
| if (cartSidebar) { | |
| cartSidebar.classList.remove('active'); | |
| } | |
| } | |
| handleCheckout() { | |
| if (this.state.cart.length === 0) { | |
| this.showToast('Tu carrito está vacío', 'error'); | |
| return; | |
| } | |
| const total = this.state.cart.reduce((sum, item) => sum + (item.precio * item.quantity), 0); | |
| const totalSavings = this.state.cart.reduce((sum, item) => sum + (item.ahorro * item.quantity), 0); | |
| if (total > this.state.balance) { | |
| this.showToast('Saldo insuficiente. Recarga tu wallet PESO-D', 'error', 'Error de Pago'); | |
| return; | |
| } | |
| this.state.balance -= total; | |
| const itemCount = this.state.cart.length; | |
| this.state.cart = []; | |
| this.updateBalance(); | |
| this.updateCartDisplay(); | |
| this.closeCart(); | |
| this.showToast(`Compra realizada por $${total.toLocaleString()}. Ahorraste $${totalSavings.toLocaleString()} vs MercadoLibre. ${itemCount} producto${itemCount > 1 ? 's' : ''} comprado${itemCount > 1 ? 's' : ''}.`, 'success', '¡Compra Exitosa!'); | |
| } | |
| loadTestimonials() { | |
| const container = document.getElementById('testimonials-carousel'); | |
| if (!container) return; | |
| container.innerHTML = this.data.testimonios.map(testimonio => ` | |
| <div class="testimonial-card"> | |
| <div class="testimonial-content"> | |
| "${testimonio.testimonio}" | |
| </div> | |
| <div class="testimonial-author"> | |
| <div class="author-info"> | |
| <h4>${testimonio.nombre}, ${testimonio.edad} años</h4> | |
| <p>${testimonio.ubicacion}</p> | |
| </div> | |
| <div class="testimonial-prize"> | |
| +$${testimonio.monto_ganado.toLocaleString()} | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| loadProvincialStats() { | |
| const container = document.getElementById('provincial-stats'); | |
| if (!container) return; | |
| container.innerHTML = this.data.provincias.map(provincia => ` | |
| <div class="provincial-card"> | |
| <h4>${provincia.nombre}</h4> | |
| <div class="provincial-stats-grid"> | |
| <div class="provincial-stat"> | |
| <span class="label">Usuarios:</span> | |
| <span class="value">${provincia.usuarios.toLocaleString()}</span> | |
| </div> | |
| <div class="provincial-stat"> | |
| <span class="label">Beneficiarios:</span> | |
| <span class="value">${provincia.beneficiarios.toLocaleString()}</span> | |
| </div> | |
| <div class="provincial-stat"> | |
| <span class="label">Penetración:</span> | |
| <span class="value">${provincia.penetracion}%</span> | |
| </div> | |
| <div class="provincial-stat"> | |
| <span class="label">Cobertura:</span> | |
| <span class="value">${Math.round((provincia.beneficiarios/provincia.usuarios)*100)}%</span> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| loadWinners() { | |
| const tbody = document.getElementById('winners-tbody'); | |
| if (!tbody) return; | |
| tbody.innerHTML = this.data.ganadores_recientes.map(winner => ` | |
| <tr> | |
| <td>${winner.fecha}</td> | |
| <td>${winner.ganador}</td> | |
| <td><span class="status status--${winner.categoria.toLowerCase()}">${winner.categoria}</span></td> | |
| <td>$${winner.premio.toLocaleString()}</td> | |
| </tr> | |
| `).join(''); | |
| } | |
| calculateInvestment(input) { | |
| const amount = parseFloat(input.value) || 0; | |
| const rate = parseFloat(input.getAttribute('data-rate')); | |
| const result = amount * rate - amount; // Annual return | |
| const resultElement = input.parentElement.querySelector('.investment-result span'); | |
| if (resultElement) { | |
| resultElement.textContent = result.toLocaleString(); | |
| } | |
| } | |
| calculateCredit(input) { | |
| const container = input.closest('.credit-option'); | |
| if (!container) return; | |
| const amountInput = container.querySelector('.credit-input'); | |
| const termSelect = container.querySelector('.credit-term'); | |
| const resultElement = container.querySelector('.credit-result span'); | |
| if (!amountInput || !termSelect || !resultElement) return; | |
| const amount = parseFloat(amountInput.value) || 0; | |
| const monthlyRate = parseFloat(amountInput.getAttribute('data-rate')); | |
| const term = parseInt(termSelect.value); | |
| if (amount && monthlyRate && term) { | |
| // Simple interest calculation | |
| const totalInterest = amount * monthlyRate * term; | |
| const totalAmount = amount + totalInterest; | |
| const monthlyPayment = totalAmount / term; | |
| resultElement.textContent = monthlyPayment.toLocaleString(); | |
| } | |
| } | |
| updateBalance() { | |
| const balanceElements = document.querySelectorAll('.balance-amount'); | |
| balanceElements.forEach(el => { | |
| el.textContent = `$${this.state.balance.toLocaleString()}`; | |
| }); | |
| } | |
| showTransactionHistory() { | |
| const mockHistory = [ | |
| { fecha: '2025-06-08', descripcion: 'Compra en TechCoopBA', monto: -299000, tipo: 'compra' }, | |
| { fecha: '2025-06-07', descripcion: 'Transferencia recibida de Carlos R.', monto: 50000, tipo: 'recibido' }, | |
| { fecha: '2025-06-06', descripcion: 'Inversión en Bonos Argentinos', monto: -100000, tipo: 'inversion' }, | |
| { fecha: '2025-06-05', descripcion: 'Premio sorteo semanal', monto: 1200000, tipo: 'premio' }, | |
| { fecha: '2025-06-04', descripcion: 'Staking PESO-D completado', monto: 25000, tipo: 'interes' }, | |
| { fecha: '2025-06-03', descripcion: 'Transferencia a Ana F.', monto: -75000, tipo: 'envio' } | |
| ]; | |
| const historyHTML = ` | |
| <div style="max-width: 400px;"> | |
| <h3 style="margin-bottom: 16px; color: var(--color-text);">Historial de Transacciones</h3> | |
| <div class="transaction-history"> | |
| ${mockHistory.map(tx => ` | |
| <div class="transaction-item" style="display: flex; justify-content: space-between; align-items: center; padding: 12px 0; border-bottom: 1px solid var(--color-border);"> | |
| <div class="transaction-info"> | |
| <div class="transaction-description" style="font-weight: 500; color: var(--color-text); margin-bottom: 4px;">${tx.descripcion}</div> | |
| <div class="transaction-date" style="font-size: 12px; color: var(--color-text-secondary);">${tx.fecha}</div> | |
| </div> | |
| <div class="transaction-amount" style="font-weight: bold; color: ${tx.monto > 0 ? 'var(--color-success)' : 'var(--color-error)'};"> | |
| ${tx.monto > 0 ? '+' : ''}$${Math.abs(tx.monto).toLocaleString()} | |
| </div> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| `; | |
| // Create a custom toast for transaction history | |
| const toastContainer = document.getElementById('toast-container'); | |
| if (!toastContainer) return; | |
| const toast = document.createElement('div'); | |
| toast.className = 'toast toast--info'; | |
| toast.style.maxWidth = '450px'; | |
| toast.innerHTML = ` | |
| <div class="toast-content"> | |
| <i class="toast-icon fas fa-history"></i> | |
| <div class="toast-message"> | |
| ${historyHTML} | |
| </div> | |
| </div> | |
| `; | |
| toastContainer.appendChild(toast); | |
| setTimeout(() => { | |
| if (toast.parentNode) { | |
| toast.remove(); | |
| } | |
| }, 10000); // Show longer for history | |
| } | |
| getNextSundayAt8PM() { | |
| const now = new Date(); | |
| const nextSunday = new Date(); | |
| const daysUntilSunday = (7 - now.getDay()) % 7; | |
| if (daysUntilSunday === 0 && now.getHours() >= 20) { | |
| // If it's Sunday after 8 PM, go to next Sunday | |
| nextSunday.setDate(now.getDate() + 7); | |
| } else { | |
| nextSunday.setDate(now.getDate() + daysUntilSunday); | |
| } | |
| nextSunday.setHours(20, 0, 0, 0); | |
| return nextSunday; | |
| } | |
| startCountdown() { | |
| const updateCountdown = () => { | |
| const now = new Date().getTime(); | |
| const distance = this.state.sorteoTarget.getTime() - now; | |
| if (distance < 0) { | |
| // Reset to next week | |
| this.state.sorteoTarget = this.getNextSundayAt8PM(); | |
| this.showToast('¡Sorteo realizado! Próximo sorteo programado', 'success', 'Sorteo Completado'); | |
| return; | |
| } | |
| const days = Math.floor(distance / (1000 * 60 * 60 * 24)); | |
| const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); | |
| const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); | |
| const seconds = Math.floor((distance % (1000 * 60)) / 1000); | |
| // Update countdown displays | |
| const daysEl = document.getElementById('days'); | |
| const hoursEl = document.getElementById('hours'); | |
| const minutesEl = document.getElementById('minutes'); | |
| const secondsEl = document.getElementById('seconds'); | |
| const sorteoCountdown = document.getElementById('sorteo-countdown'); | |
| if (daysEl) daysEl.textContent = days; | |
| if (hoursEl) hoursEl.textContent = hours; | |
| if (minutesEl) minutesEl.textContent = minutes; | |
| if (secondsEl) secondsEl.textContent = seconds; | |
| if (sorteoCountdown) { | |
| sorteoCountdown.textContent = `${days}d ${hours}h ${minutes}m`; | |
| } | |
| }; | |
| // Update immediately | |
| updateCountdown(); | |
| // Update every second | |
| if (this.state.countdownInterval) { | |
| clearInterval(this.state.countdownInterval); | |
| } | |
| this.state.countdownInterval = setInterval(updateCountdown, 1000); | |
| } | |
| // Cleanup method | |
| destroy() { | |
| if (this.state.countdownInterval) { | |
| clearInterval(this.state.countdownInterval); | |
| } | |
| // Destroy charts | |
| Object.values(this.charts).forEach(chart => { | |
| if (chart && typeof chart.destroy === 'function') { | |
| chart.destroy(); | |
| } | |
| }); | |
| } | |
| } | |
| // Initialize the application | |
| document.addEventListener('DOMContentLoaded', () => { | |
| window.app = new SoberanIAApp(); | |
| console.log('SoberanIA Argentina app loaded successfully'); | |
| }); | |
| // Handle page unload | |
| window.addEventListener('beforeunload', () => { | |
| if (window.app) { | |
| window.app.destroy(); | |
| } | |
| }); |