const DateTime = luxon.DateTime; const mxZone = "America/Mexico_City"; // Lista de las sesiones de trading más importantes a nivel global const markets = [ { id: "nyse", name: "Nueva York (NYSE / NASDAQ)", lat: 40.7069, lng: -74.0113, tz: "America/New_York", open: "09:30", close: "16:00", country: "EE.UU." }, { id: "lse", name: "Londres (LSE)", lat: 51.5153, lng: -0.0997, tz: "Europe/London", open: "08:00", close: "16:30", country: "Reino Unido" }, { id: "fsx", name: "Frankfurt (FSX) - Sesión Europea", lat: 50.1154, lng: 8.6778, tz: "Europe/Berlin", open: "09:00", close: "17:30", country: "Alemania" }, { id: "tse", name: "Tokio (TSE) - Sesión Asiática", lat: 35.6816, lng: 139.7781, tz: "Asia/Tokyo", open: "09:00", close: "15:00", country: "Japón" }, { id: "hkex", name: "Hong Kong (HKEX)", lat: 22.2843, lng: 114.1583, tz: "Asia/Hong_Kong", open: "09:30", close: "16:00", country: "Hong Kong" }, { id: "asx", name: "Sídney (ASX) - Sesión Oceánica", lat: -33.8647, lng: 151.2081, tz: "Australia/Sydney", open: "10:00", close: "16:00", country: "Australia" } ]; // Inicializar el mapa const map = L.map('map', { zoomControl: false }).setView([20, 0], 3); L.control.zoom({ position: 'bottomright' }).addTo(map); // Capa de mapa de estilo oscuro con estética premium (CartoDB Dark Matter) L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { attribution: '© OpenStreetMap contributors © CARTO', subdomains: 'abcd', maxZoom: 20 }).addTo(map); const markers = {}; function updateTimeLogic() { // 1. Mostrar la hora exacta en vivo de la Ciudad de México const nowMx = DateTime.now().setZone(mxZone); document.querySelector('#live-clock .time').textContent = nowMx.toFormat('HH:mm:ss'); // 2. Iterar sobre todos los mercados y calcular su estado markets.forEach(market => { // Hora actual en la zona horaria del mercado const nowLocal = DateTime.now().setZone(market.tz); // Convertir horas de apertura y cierre const [openH, openM] = market.open.split(':').map(Number); const [closeH, closeM] = market.close.split(':').map(Number); // Verificar si es fin de semana (sábado = 6, domingo = 7) const isWeekend = nowLocal.weekday === 6 || nowLocal.weekday === 7; const localMinutes = nowLocal.hour * 60 + nowLocal.minute; const openMinutes = openH * 60 + openM; const closeMinutes = closeH * 60 + closeM; // Evaluar estado abierto/cerrado const isOpen = !isWeekend && (localMinutes >= openMinutes && localMinutes < closeMinutes); // Actualizar la tarjeta (badge de status) const card = document.getElementById(`market-${market.id}`); if(card) { const badge = card.querySelector('.status-badge'); if (isOpen) { badge.textContent = 'Abierto'; badge.className = 'status-badge open pulse-icon'; // Agregamos animacion pulse } else { badge.textContent = 'Cerrado'; badge.className = 'status-badge closed'; } } // Actualizar el color del marcador en el mapa if(markers[market.id]) { markers[market.id].setStyle({ fillColor: isOpen ? "#3fb950" : "#ff7b72", color: isOpen ? "#eafeee" : "#ffdcd7" }); // Si el popup de este mercado está abierto, también podríamos actualizar su estado, // pero el badge en la tarjeta ya muestra la info dinámica. } }); } function initDashboard() { const listContainer = document.getElementById('markets-list'); markets.forEach(market => { // Calcular los horarios exactos de apertura y cierre sincronizados a hora México (CDMX) const currentLocal = DateTime.now().setZone(market.tz); const [openH, openM] = market.open.split(':'); const [closeH, closeM] = market.close.split(':'); const localOpenDT = currentLocal.set({hour: openH, minute: openM, second: 0, millisecond: 0}); const localCloseDT = currentLocal.set({hour: closeH, minute: closeM, second: 0, millisecond: 0}); // Convertidos a hora México const mxOpenStr = localOpenDT.setZone(mxZone).toFormat('HH:mm'); const mxCloseStr = localCloseDT.setZone(mxZone).toFormat('HH:mm'); // ==== Crear Marcador en el Mapa ==== const marker = L.circleMarker([market.lat, market.lng], { radius: 8, fillColor: "#58a6ff", // Color inicial, será actualizado en updateTimeLogic color: "#fff", weight: 2, opacity: 1, fillOpacity: 0.9 }).addTo(map); const popupHTML = ` `; marker.bindPopup(popupHTML); markers[market.id] = marker; // ==== Crear Tarjeta del Sidebar ==== const card = document.createElement('div'); card.className = 'market-card'; card.id = `market-${market.id}`; card.innerHTML = `
${market.name} Calculando...
Apertura (CDMX): ${mxOpenStr} hrs
Cierre (CDMX): ${mxCloseStr} hrs
Horario Local (${market.country}): ${market.open} - ${market.close}
`; // Vuelo animado al hacer click card.addEventListener('click', () => { // Resaltar tarjeta seleccionada document.querySelectorAll('.market-card').forEach(c => c.classList.remove('active')); card.classList.add('active'); map.flyTo([market.lat, market.lng], 6, { duration: 1.5 }); marker.openPopup(); }); listContainer.appendChild(card); }); // Llamada inicial updateTimeLogic(); // Iniciar loop cada 1 segundo (se actualiza el reloj y todos los estados) setInterval(updateTimeLogic, 1000); } document.addEventListener('DOMContentLoaded', initDashboard);