1/** 2 * LUNAR APP - JavaScript Principal 3 * Maneja la comunicación con el backend y actualización de la UI 4 */ 5 6// ============================================================================ 7// CONFIGURACIÃN 8// ============================================================================ 9 10const API_BASE = window.location.origin; 11let ultimaActualizacion = null; 12let datosLunarActuales = null; // Guardar datos para reutilizar en el modal 13 14// Detectar zona horaria del usuario automáticamente 15const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; 16console.log(`ð Zona horaria detectada: ${userTimezone}`); 17 18// Función para obtener sÃmbolo zodiacal 19function getSignoSimbolo(signo) { 20 const simbolos = { 21 'Aries': 'â', 'Tauro': 'â', 'Géminis': 'â', 'Cáncer': 'â', 22 'Leo': 'â', 'Virgo': 'â', 'Libra': 'â', 'Escorpio': 'â', 23 'Sagitario': 'â', 'Capricornio': 'â', 'Acuario': 'â', 'Piscis': 'â' 24 }; 25 return simbolos[signo] || signo; 26} 27 28// Calcular offset UTC 29function obtenerOffsetUTC() { 30 const offset = -new Date().getTimezoneOffset(); // en minutos 31 const horas = Math.floor(Math.abs(offset) / 60); 32 const minutos = Math.abs(offset) % 60; 33 const signo = offset >= 0 ? '+' : '-'; 34 35 if (minutos === 0) { 36 return `UTC${signo}${horas}`; 37 } else { 38 return `UTC${signo}${horas}:${minutos.toString().padStart(2, '0')}`; 39 } 40} 41 42const userUTCOffset = obtenerOffsetUTC(); 43console.log(`ð UTC Offset: ${userUTCOffset}`); 44 45// ============================================================================ 46// INICIALIZACIÃN 47// ============================================================================ 48 49document.addEventListener('DOMContentLoaded', function() { 50 console.log('ð Lunar App inicializada'); 51 52 // ========== MENà HAMBURGUESA (MÃVIL) ========== 53 const menuToggle = document.getElementById('menu-toggle'); 54 const mainNav = document.getElementById('main-nav'); 55 const menuOverlay = document.getElementById('menu-overlay'); 56 57 if (menuToggle && mainNav && menuOverlay) { 58 // Abrir/cerrar menú 59 menuToggle.addEventListener('click', function() { 60 menuToggle.classList.toggle('active'); 61 mainNav.classList.toggle('active'); 62 menuOverlay.classList.toggle('active'); 63 64 // Prevenir scroll del body cuando el menú está abierto 65 if (mainNav.classList.contains('active')) { 66 document.body.style.overflow = 'hidden'; 67 } else { 68 document.body.style.overflow = ''; 69 } 70 71 // Tracking de evento 72 if (window.trackEvent) { 73 window.trackEvent('menu_toggle', { 74 accion: mainNav.classList.contains('active') ? 'abrir' : 'cerrar' 75 }); 76 } 77 }); 78 79 // Cerrar menú al hacer click en el overlay 80 menuOverlay.addEventListener('click', function() { 81 menuToggle.classList.remove('active'); 82 mainNav.classList.remove('active'); 83 menuOverlay.classList.remove('active'); 84 document.body.style.overflow = ''; 85 }); 86 87 // Cerrar menú al hacer click en un link 88 const navLinks = mainNav.querySelectorAll('.nav-link'); 89 navLinks.forEach(link => { 90 link.addEventListener('click', function() { 91 menuToggle.classList.remove('active'); 92 mainNav.classList.remove('active'); 93 menuOverlay.classList.remove('active'); 94 document.body.style.overflow = ''; 95 }); 96 }); 97 } 98 99 // ========== ACTUALIZAR CONTENIDO ========== 100 actualizarTodo(); 101 102 // Actualizar cada 60 segundos 103 setInterval(actualizarTodo, 60000); 104 105 // ========== EVENT LISTENERS DEL MODAL ========== 106 107 // Botón de ver interpretación 108 const btnInterpretacion = document.getElementById('btn-ver-interpretacion'); 109 if (btnInterpretacion) { 110 btnInterpretacion.addEventListener('click', mostrarInterpretacion); 111 console.log('â Event listener del botón interpretación registrado'); 112 } else { 113 console.warn('â ï¸ No se encontró el botón btn-ver-interpretacion'); 114 } 115 116 // Botón de cerrar modal 117 const btnCerrar = document.getElementById('modal-cerrar'); 118 if (btnCerrar) { 119 btnCerrar.addEventListener('click', cerrarModal); 120 } 121 122 // Cerrar al hacer clic fuera del modal 123 const modal = document.getElementById('modal-interpretacion'); 124 if (modal) { 125 modal.addEventListener('click', (e) => { 126 if (e.target === modal) { 127 cerrarModal(); 128 } 129 }); 130 } 131 132 // Cerrar con tecla ESC 133 document.addEventListener('keydown', (e) => { 134 if (e.key === 'Escape') { 135 cerrarModal(); 136 } 137 }); 138 139 // Toggle de descripción de signo (colapsable) 140 const signoToggle = document.getElementById('signo-toggle'); 141 if (signoToggle) { 142 signoToggle.addEventListener('click', function() { 143 const contenido = document.getElementById('signo-descripcion-contenido'); 144 const estaAbierto = contenido.classList.contains('active'); 145 146 this.classList.toggle('active'); 147 contenido.classList.toggle('active'); 148 149 // Registrar evento en Firebase 150 if (window.trackEvent) { 151 window.trackEvent('toggle_signo', { 152 accion: estaAbierto ? 'cerrar' : 'abrir' 153 }); 154 } 155 }); 156 } 157}); 158 159// ============================================================================ 160// FUNCIÃN PRINCIPAL DE ACTUALIZACIÃN 161// ============================================================================ 162 163async function actualizarTodo() { 164 console.log('ð Actualizando datos...'); 165 166 try { 167 await Promise.all([ 168 actualizarLunaAhora(), 169 actualizarDiaLunar(), 170 actualizarFasesPrincipales() 171 ]); 172 173 actualizarTimestamp(); 174 console.log('â Datos actualizados correctamente'); 175 176 } catch (error) { 177 console.error('â Error al actualizar:', error); 178 mostrarError('Error al actualizar los datos'); 179 } 180} 181 182// ============================================================================ 183// ACTUALIZAR INFORMACIÃN LUNAR ACTUAL 184// ============================================================================ 185 186async function actualizarLunaAhora() { 187 try { 188 const response = await fetch(`${API_BASE}/api/luna/ahora?timezone=${encodeURIComponent(userTimezone)}`); 189 const data = await response.json(); 190 191 if (data.exito) { 192 // Guardar datos para reutilizar en el modal 193 datosLunarActuales = data; 194 195 // Renderizar Luna con foto real según dÃa del ciclo 196 const lunaContainer = document.getElementById('luna-container'); 197 if (window.lunaRealista && lunaContainer) { 198 window.lunaRealista.renderizar( 199 lunaContainer, 200 data.fase.dia_ciclo, 201 300 202 ); 203 } 204 205 // Actualizar información de fase 206 document.getElementById('fase-nombre').textContent = data.fase.nombre; 207 document.getElementById('iluminacion').textContent = `${data.fase.iluminacion}%`; 208 document.getElementById('angulo').textContent = `${data.fase.angulo}°`; 209 210 // Actualizar barra de progreso 211 const progreso = (data.fase.angulo / 360) * 100; 212 document.getElementById('progreso-fill').style.width = `${progreso}%`; 213 214 // Actualizar signo con sÃmbolo 215 document.getElementById('signo-nombre').textContent = `Luna en ${getSignoSimbolo(data.posicion.signo
215)} ${data.posicion.signo}`; 216 document.getElementById('signo-grado').textContent = `${data.posicion.grado_sexagesimal}`; 217 218 // Actualizar próximo cambio de signo 219 await actualizarProximoCambioSigno(); 220 221 // Llenar consejos prácticos en la página principal 222 if (data.fase.consejos) { 223 const consejos = data.fase.consejos; 224 225 const listaQueHacer = document.getElementById('consejos-que-hacer'); 226 if (listaQueHacer) { 227 listaQueHacer.innerHTML = ''; 228 (consejos.que_hacer || []).forEach(c => { 229 const li = document.createElement('li'); 230 li.textContent = c; 231 listaQueHacer.appendChild(li); 232 }); 233 } 234 235 const listaQueEvitar = document.getElementById('consejos-que-evitar'); 236 if (listaQueEvitar) { 237 listaQueEvitar.innerHTML = ''; 238 (consejos.que_evitar || []).forEach(c => { 239 const li = document.createElement('li'); 240 li.textContent = c; 241 listaQueEvitar.appendChild(li); 242 }); 243 } 244 245 const listaMomento = document.getElementById('consejos-momento-ideal'); 246 if (listaMomento) { 247 listaMomento.innerHTML = ''; 248 (consejos.momento_ideal || []).forEach(c => { 249 const li = document.createElement('li'); 250 li.textContent = c; 251 listaMomento.appendChild(li); 252 }); 253 } 254 } 255 } 256 } catch (error) { 257 console.error('Error al actualizar luna ahora:', error); 258 } 259} 260 261// ============================================================================ 262// ACTUALIZAR PRÃXIMO CAMBIO DE SIGNO 263// ============================================================================ 264 265async function actualizarProximoCambioSigno() { 266 try { 267 const response = await fetch(`${API_BASE}/api/luna/signo-actual?timezone=${encodeURIComponent(userTimezone)}`); 268 const data = await response.json(); 269 270 if (data.exito && data.proximo_cambio) { 271 const proximo = data.proximo_cambio.proximo_signo; 272 document.getElementById('proximo-signo').textContent = 273 `${getSignoSimbolo(proximo)} ${proximo}`; 274 document.getElementById('cambio-fecha').textContent = 275 formatearFechaCompleta(data.proximo_cambio.fecha_estimada); 276 } 277 } catch (error) { 278 console.error('Error al actualizar próximo cambio:', error); 279 } 280} 281 282// ============================================================================ 283// ACTUALIZAR DÃA LUNAR (TITHI) 284// ============================================================================ 285 286async function actualizarDiaLunar() { 287 try { 288 const response = await fetch(`${API_BASE}/api/luna/dia-lunar?timezone=${encodeURIComponent(userTimezone)}`); 289 const data = await response.json(); 290 291 if (data.exito) { 292 document.getElementById('tithi-numero').textContent = data.dia_lunar; 293 document.getElementById('tithi-nombre').textContent = data.nombre; 294 295 // Agregar interpretación si existe 296 if (data.interpretacion) { 297 const interpretacionEl = document.getElementById('tithi-interpretacion'); 298 if (interpretacionEl) { 299 interpretacionEl.textContent = data.interpretacion; 300 interpretacionEl.style.display = 'block'; 301 } 302 } 303 304 document.getElementById('tithi-progreso').textContent = 305 `${data.progreso_mes}% del mes lunar`; 306 307 // Actualizar texto de progreso (sin /30) 308 document.getElementById('progreso-texto').textContent = 309 `DÃa lunar: ${data.dia_lunar}`; 310 } 311 } catch (error) { 312 console.error('Error al actualizar dÃa lunar:', error); 313 } 314} 315 316// ============================================================================ 317// ACTUALIZAR FASES PRINCIPALES DEL MES 318// ============================================================================ 319 320async function actualizarFasesPrincipales() { 321 try { 322 const ahora = new Date(); 323 const year = ahora.getFullYear();
324 const month = ahora.getMonth() + 1; 325 326 // Titulo dinamico 327 const mesesNombres = ['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre']; 328 const tituloEl = document.getElementById('titulo-fases-principales'); 329 if (tituloEl) { tituloEl.textContent = 'Fases Principales - ' + mesesNombres[month - 1] + ' ' + year; } 330 331 const response = await fetch(`${API_BASE}/api/luna/fases-principales?year=${year}&month=${month}&timezone=${encodeURIComponent(userTimezone)}`); 332 const data = await response.json(); 333 334 if (data.exito && data.fases) { 335 const fasesGrid = document.getElementById('fases-grid'); 336 fasesGrid.innerHTML = ''; 337 338 data.fases.forEach(fase => { 339 const faseItem = crearElementoFase(fase); 340 fasesGrid.appendChild(faseItem); 341 }); 342 } 343 } catch (error) { 344 console.error('Error al actualizar fases principales:', error); 345 document.getElementById('fases-grid').innerHTML = 346 '<div class="loading">Error al cargar fases</div>'; 347 } 348} 349 350// ============================================================================ 351// CREAR ELEMENTO DE FASE 352// ============================================================================ 353 354function crearElementoFase(fase) { 355 const div = document.createElement('div'); 356 div.className = 'fase-item'; 357 358 // Crear contenedor para la luna 359 const lunaDiv = document.createElement('div'); 360 lunaDiv.className = 'fase-item-luna'; 361 lunaDiv.id = `fase-principal-${fase.fecha}`; 362 363 div.innerHTML = ` 364 <div class="fase-item-nombre">${fase.fase}</div> 365 <div class="fase-item-fecha">${fase.dia} de ${fase.mes_nombre}</div> 366 <div class="fase-item-hora">${fase.hora}</div> 367 `; 368 369 // Insertar luna al principio 370 div.insertBefore(lunaDiv, div.firstChild); 371 372 // Boton VER CARTA NATAL 373 if (fase.jd && typeof cfAbrirCarta === 'function') { 374 const btnCarta = document.createElement('button'); 375 btnCarta.innerHTML = '🔮 VER CARTA NATAL'; 376 btnCarta.style.cssText = 'background:linear-gradient(135deg,#d4af37,#b8962e);color:#0f0f1e;border:none;padding:0.45rem 0.9rem;border-radius:6px;font-weight:700;font-size:0.78rem;cursor:pointer;font-family:Cinzel,serif;margin-top:0.5rem;display:block;width:100%;'; 377 btnCarta.addEventListener('click', function(e) { 378 e.stopPropagation(); 379 cfAbrirCarta(fase.jd, fase.fase + ' - ' + fase.dia + ' de ' + fase.mes_nombre); 380 }); 381 div.appendChild(btnCarta); 382 } 383 384 // Renderizar luna con foto real 385 if (window.lunaRealista && fase.dia_ciclo) { 386 setTimeout(() => { 387 window.lunaRealista.renderizar( 388 lunaDiv, 389 fase.dia_ciclo, 390 80 // Tamaño mediano 391 ); 392 }, 10); 393 } 394 395 return div; 396} 397 398// ============================================================================ 399// FUNCIONES AUXILIARES DE FORMATO 400// ============================================================================ 401 402function formatearFecha(fechaStr) { 403 const fecha = new Date(fechaStr); 404 const meses = [ 405 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 406 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre' 407 ]; 408 409 return `${fecha.getDate()} de ${meses[fecha.getMonth()]}`; 410} 411 412function formatearFechaCompleta(fechaStr) { 413 const fecha = new Date(fechaStr); 414 const meses = [ 415 'Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 416 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic' 417 ]; 418 419 const dia = fecha.getDate(); 420 const mes = meses[fecha.getMonth()]; 421 const hora = fecha.getHours().toString().padStart(2, '0'); 422 const minutos = fecha.getMinutes().toString().padStart(2, '0'); 423 424 return `${dia} ${mes} ${hora}:${minutos}`; 425} 426 427function actualizarTimestamp() { 428 const ahora = new Date();
429 const timestamp = ahora.toLocaleString('es-AR', { 430 day: '2-digit', 431 month: '2-digit', 432 year: 'numeric', 433 hour: '2-digit', 434 minute: '2-digit', 435 second: '2-digit' 436 }); 437 438 document.getElementById('ultima-actualizacion').textContent = 439 `Ãltima actualización: ${timestamp}`; 440} 441 442function mostrarError(mensaje) { 443 // PodrÃas implementar un sistema de notificaciones aquà 444 console.error(mensaje); 445} 446 447// ============================================================================ 448// FUNCIONES PARA PWA 449// ============================================================================ 450 451// Detectar si está instalada como PWA 452function esPWA() { 453 return window.matchMedia('(display-mode: standalone)').matches || 454 window.navigator.standalone === true; 455} 456 457// Mostrar botón de instalación (si es necesario) 458let deferredPrompt; 459 460window.addEventListener('beforeinstallprompt', (e) => { 461 e.preventDefault(); 462 deferredPrompt = e; 463 console.log('ð¾ App puede ser instalada'); 464 // Aquà podrÃas mostrar un botón de instalación personalizado 465}); 466 467window.addEventListener('appinstalled', () => { 468 console.log('â App instalada correctamente'); 469 deferredPrompt = null; 470}); 471 472// ============================================================================ 473// MODAL DE INTERPRETACIÃN 474// ============================================================================ 475 476async function mostrarInterpretacion() { 477 try { 478 // Registrar evento en Firebase 479 if (window.trackEvent) { 480 window.trackEvent('ver_interpretacion', { 481 fase: datosLunarActuales?.fase?.nombre || 'desconocida' 482 }); 483 } 484 485 // Usar datos ya calculados (evita diferencia de grados) 486 if (!datosLunarActuales) { 487 mostrarError('Por favor espera a que se carguen los datos'); 488 return; 489 } 490 491 const data = datosLunarActuales; 492 493 // Renderizar foto real de la luna 494 const lunaContainer = document.getElementById('interpretacion-luna-container'); 495 if (window.lunaRealista && lunaContainer && data.fase.dia_ciclo) { 496 await window.lunaRealista.renderizar( 497 lunaContainer, 498 data.fase.dia_ciclo, 499 120 // Tamaño 120px 500 ); 501 } 502 503 // Nombre de la fase 504 document.getElementById('interpretacion-nombre').textContent = data.fase.fase_completa || data.fase.nombre; 505 506 // Datos técnicos (usar dÃa lunar REAL con decimales) 507 document.getElementById('dato-dia-ciclo').textContent = `${data.fase.dia_lunar_real}`; 508 document.getElementById('dato-iluminacion').textContent = `${data.fase.iluminacion}%`; 509 document.getElementById('dato-angulo').textContent = `${data.fase.angulo}°`; 510 511 // Posición lunar (usar grado sexagesimal) 512 document.getElementById('interpretacion-signo-nombre').textContent = data.posicion.signo; 513 document.getElementById('interpretacion-grado').textContent = `(${data.posicion.grado_sexagesimal})`; 514 515 // Descripción del signo (sección colapsable) 516 if (data.posicion.descripcion) { 517 const desc = data.posicion.descripcion; 518 519 // Nombre del signo en el botón toggle 520 document.getElementById('signo-toggle-nombre').textContent = data.posicion.signo; 521 522 // Descripción general 523 document.getElementById('signo-descripcion-texto').textContent = desc.descripcion; 524 525 // Palabras clave (como badges) 526 const palabrasClaveContainer = document.getElementById('palabras-clave-lista'); 527 palabrasClaveContainer.innerHTML = ''; 528 desc.palabras_clave.forEach(palabra => { 529 const badge = document.createElement('span'); 530 badge.className = 'palabra-clave'; 531 badge.textContent = palabra; 532 palabrasClaveContainer.appendChild(badge); 533 }); 534 535 // Necesita 536 const listaNecesita = document.getElementById('signo-necesita-lista'); 537 listaNecesita.innerHTML = ''; 538 desc.necesita.forEach(item => { 539 const li = document.createElement('li'); 540 li.textContent = item; 541 listaNecesita.appendChild(li); 542 }); 543 544 // DesafÃos 545 const listaDesafios = document.getElementById('signo-desafios-lista'); 546 listaDesafios.innerHTML = ''; 547 desc.desafios.forEach(item => { 548 const li = document.createElement('li'); 549 li.textContent = item; 550 listaDesafios.appendChild(li); 551 }); 552 } 553 554 // Interpretación completa (texto combinado) 555 document.getElementById('interpretacion-texto-completo').textContent = data.fase.texto_completo; 556 557 // Consejos prácticos 558 if (data.fase.consejos) { 559 const consejos = data.fase.consejos; 560 561 // Qué hacer 562 const listaQueHacer = document.getElementById('consejos-que-hacer'); 563 listaQueHacer.innerHTML = ''; 564 consejos.que_hacer.forEach(consejo => { 565 const li = document.createElement('li'); 566 li.textContent = consejo; 567 listaQueHacer.appendChild(li); 568 }); 569 570 // Qué evitar 571 const listaQueEvitar = document.getElementById('consejos-que-evitar'); 572 listaQueEvitar.innerHTML = ''; 573 consejos.que_evitar.forEach(consejo => { 574 const li = document.createElement('li'); 575 li.textContent = consejo; 576 listaQueEvitar.appendChild(li); 577 }); 578 579 // Momento ideal 580 const listaMomentoIdeal = document.getElementById('consejos-momento-ideal'); 581 listaMomentoIdeal.innerHTML = ''; 582 consejos.momento_ideal.forEach(consejo => { 583 const li = document.createElement('li'); 584 li.textContent = consejo; 585 listaMomentoIdeal.appendChild(li); 586 }); 587 } 588 589 // Mostrar el modal 590 const modal = document.getElementById('modal-interpretacion'); 591 modal.classList.add('active'); 592 593 // Prevenir scroll del body 594 document.body.style.overflow = 'hidden'; 595 596 } catch (error) { 597 console.error('Error al mostrar interpretación:', error); 598 mostrarError('Error al mostrar la interpretación'); 599 } 600} 601 602function cerrarModal() { 603 const modal = document.getElementById('modal-interpretacion'); 604 modal.classList.remove('active'); 605 606 // Restaurar scroll del body 607 document.body.style.overflow = ''; 608} 609 610// ============================================================================ 611// MANEJO DE CONEXIÃN (ONLINE/OFFLINE) 612// ============================================================================ 613 614window.addEventListener('online', () => { 615 console.log('ð Conexión restaurada'); 616 actualizarTodo(); 617}); 618 619window.addEventListener('offline', () => { 620 console.log('ðµ Sin conexión - Modo offline'); 621 mostrarError('Sin conexión a Internet'); 622}); 623 624// ============================================================================ 625// NAVEGACIÃN ENTRE PÃGINAS (PARA FUTURAS EXPANSIONES) 626// ============================================================================ 627 628function navegarA(pagina) { 629 window.location.href = pagina; 630} 631 632// Marcar enlace activo en navegación 633document.addEventListener('DOMContentLoaded', () => { 634 const rutaActual = window.location.pathname; 635 const enlaces = document.querySelectorAll('.nav-link'); 636
637 enlaces.forEach(enlace => { 638 if (enlace.getAttribute('href') === rutaActual) { 639 enlace.classList.add('active'); 640 } else { 641 enlace.classList.remove('active'); 642 } 643 }); 644}); 645 646// ============================================================================ 647// EXPORTAR FUNCIONES GLOBALES 648// ============================================================================ 649 650window.actualizarTodo = actualizarTodo; 651window.navegarA = navegarA;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.