PageSourceSearch

https://lunarapp-urania-production.up.railway.app/static/js/app.js?v=20260403

js lunarapp-urania-production.up.railway.app collected 2026-10-03 12:12:57 UTC 24,962 bytes, 651 lines download raw bytes

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 = '&#128302; 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.