{"id":740,"date":"2025-07-11T12:31:02","date_gmt":"2025-07-11T12:31:02","guid":{"rendered":"https:\/\/nb-consulting.pro\/?page_id=740"},"modified":"2025-07-11T12:31:02","modified_gmt":"2025-07-11T12:31:02","slug":"visualizador-2","status":"publish","type":"page","link":"https:\/\/nb-consulting.pro\/en\/visualizador-2\/","title":{"rendered":"Visualizador 2"},"content":{"rendered":"<p><!-- === INICIO DEL C\u00d3DIGO FINAL PARA ELEMENTOR === --><\/p>\n<p><!-- 1. Carga de Librer\u00edas (Tu plugin PHP se encargar\u00e1 de esto) --><br \/>\n<!-- No necesitas poner los <link> y <script> para Leaflet\/Turf aqu\u00ed si tu PHP los encola. -->\n<!-- Los dejo comentados como referencia de lo que el PHP debe hacer: -->\n<!-- \n<link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.css\"\/>\n<script src=\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.js\"><\/script>\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/leaflet-rotatedmarker@0.2.0\/leaflet.rotatedMarker.min.js\"><\/script>\n<script src=\"https:\/\/unpkg.com\/@turf\/turf@6\/turf.min.js\"><\/script> \n--><\/p>\n<p><!-- 2. Estilos CSS para el mapa y los paneles --><\/p>\n<style>\n    #nb-map-wrapper {\n        position: relative;\n        width: 100%;\n        min-height: 650px; \n    }\n    #map-div {\n        height: 650px; \n        width: 100%; \n        border: 1px solid #333; \n        background-color: #101015;\n    }\n    .info-panel {\n        position: absolute; right: 10px; background: rgba(10,10,20,0.92); \n        color: #ddd; padding: 10px; border: 1px solid #2a2a4a; \n        border-radius: 5px; z-index: 1000; \n        font-family: Consolas, Monaco, monospace; width: 290px;\n        box-shadow: 0 2px 8px rgba(0,0,0,0.6);\n    }\n    #info-box { top: 10px; font-size: 12px; }\n    #info-box p { margin: 5px 0; }\n    #info-box strong { color: #20e040; }\n    #sector-traffic-table-container { top: 95px; font-size: 11px; max-height: 180px; overflow-y: auto; }\n    #sector-traffic-table-container h4, #metar-container h4 { \n        margin-top: 0; margin-bottom: 6px; color: #20e040; \n        font-size: 11px; border-bottom: 1px solid #333; padding-bottom: 3px; text-transform: uppercase;\n    }\n    #sector-traffic-table { width: 100%; border-collapse: collapse; }\n    #sector-traffic-table th, #sector-traffic-table td {\n        padding: 2px 4px; border-bottom: 1px dotted #444; text-align: left;\n    }\n    #sector-traffic-table th { color: #20e040; font-size:9px; }\n    #sector-traffic-table td:last-child { text-align: center; font-weight: bold; }\n    #metar-container { top: 300px; font-size: 9px; max-height: 180px; overflow-y: auto; }\n    #metar-list div { margin-bottom: 6px; line-height: 1.35; border-bottom: 1px dotted #444; padding-bottom: 5px;}\n    #metar-list div:last-child { border-bottom: none; }\n    #metar-list strong { color: #90ee90; }<\/p>\n<p>    .leaflet-popup-content-wrapper { background: #2c2c3a; color: #fff; border-radius: 5px; border: 1px solid #555;}\n    .leaflet-popup-tip-container .leaflet-popup-tip { background: #2c2c3a; border-bottom-color: #555;}\n    .flight-info { margin-bottom: 5px; font-family: Consolas, monospace; font-size: 11px; }\n    .flight-info strong { display: inline-block; width: 70px; color: #9f9; }\n    .atc-sector-label { background: rgba(60,60,70,0.75) !important; border-color: rgba(120,120,140,0.75) !important; color: #f0f0f0 !important; text-shadow: 1px 1px #000 !important; font-weight: bold; font-size: 10px !important; padding: 2px 5px !important; border-radius: 3px !important; }\n    .leaflet-control-layers { background: rgba(20,20,30,0.85); padding: 6px 8px; border: 1px solid #004488; color: #0f0; border-radius: 4px; }\n    .leaflet-control-layers-selector { margin-right: 4px; }\n    .leaflet-control-layers label { color: #0f0; }\n    .leaflet-control-layers-separator { border-top: 1px solid #444; margin: 3px 0; }\n<\/style>\n<p><!-- 3. HTML para el mapa y los paneles --><\/p>\n<div id=\"nb-map-wrapper\">\n<div id=\"map-div\">Cargando mapa&#8230;<\/div>\n<div id=\"info-box\" class=\"info-panel\" style=\"display: none;\">\n<p><strong>Aviones Totales:<\/strong> <span id=\"aircraft-count\">0<\/span><\/p>\n<p><strong>Actualizado (UTC):<\/strong> <span id=\"last-updated\">&#8211;<\/span><\/p>\n<\/p><\/div>\n<div id=\"sector-traffic-table-container\" class=\"info-panel\" style=\"display: none;\">\n<h4>Tr\u00e1fico Baires APP (FL055-FL244)<\/h4>\n<table id=\"sector-traffic-table\">\n<thead>\n<tr>\n<th>Sector<\/th>\n<th>Aviones<\/th>\n<\/tr>\n<\/thead>\n<tbody><\/tbody>\n<\/table><\/div>\n<div id=\"metar-container\" class=\"info-panel\" style=\"display: none;\">\n<h4>METARs<\/h4>\n<div id=\"metar-list\">Cargando METARs&#8230;<\/div>\n<\/p><\/div>\n<\/div>\n<p><!-- 4. El JavaScript completo --><br \/>\n<script>\n(function() { \n    'use strict';\n    console.log(\"NB_FLIGHT_MAP: Script v2.2 (Final con METARs y UTC de API) - INICIO\");<\/p>\n<p>    const EZEIZA_LAT = -34.8222; const EZEIZA_LON = -58.5358;\n    const UPDATE_INTERVAL_FLIGHTS_MS = 3000; \n    const UPDATE_INTERVAL_METAR_MS = 600000;\n    const EARTH_RADIUS_KM = 6371; const NM_TO_KM = 1.852;\n    const TARGET_SECTOR_ID = \"Baires Control\"; \n    const TARGET_SECTOR_MIN_FL_VALUE = 55;   \n    const TARGET_SECTOR_MAX_FL_VALUE = 244;  <\/p>\n<p>    var map, aircraftIcon, aircraftData = { layerGroup: null, markers: {} }, baseMaps = {}, overlayMaps = {}, layerControl;\n    var infoBoxElement, sectorTableContainerElement, metarContainerElement;\n    var allSectorLayers = []; <\/p>\n<p>   function runMapLogic() {\n    console.log(\"NB_FLIGHT_MAP: runMapLogic - Iniciando...\");\n    const mapDivId = 'map-div';<\/p>\n<p>    if (!document.getElementById(mapDivId) || typeof L === 'undefined' || typeof L.map !== 'function') {\n        console.error(`NB_FLIGHT_MAP: #${mapDivId} o Leaflet (L) no disponibles.`); return;\n    }<\/p>\n<p>    infoBoxElement = document.getElementById('info-box');\n    sectorTableContainerElement = document.getElementById('sector-traffic-table-container');\n    metarContainerElement = document.getElementById('metar-container');<\/p>\n<p>    if (infoBoxElement) infoBoxElement.style.display = 'block';\n    if (sectorTableContainerElement) sectorTableContainerElement.style.display = 'block';\n    if (metarContainerElement) metarContainerElement.style.display = 'block';<\/p>\n<p>    \/\/ Destruir instancia previa del mapa si existe\n    if (map && typeof map.remove === 'function') {\n        console.log(\"NB_FLIGHT_MAP: Eliminando instancia de mapa previa.\");\n        map.remove();\n        map = null;\n    }<\/p>\n<p>    map = L.map(mapDivId).setView([EZEIZA_LAT, EZEIZA_LON], 7);\n    console.log(\"NB_FLIGHT_MAP: L.map inicializado en #\" + mapDivId);<\/p>\n<p>    var darkMapLayer = L.tileLayer('https:\/\/{s}.basemaps.cartocdn.com\/dark_all\/{z}\/{x}\/{y}{r}.png', {\n        attribution: '\u00a9 OSM \u00a9 CARTO', subdomains: 'abcd', maxZoom: 20\n    }).on('tileerror', function(e){ console.warn('NB_FLIGHT_MAP: Error tile CartoDB:', e.tile.src);}).addTo(map);\n    baseMaps = {\"Dark Mode\": darkMapLayer}; <\/p>\n<p>    aircraftIcon = L.divIcon({\n        html: '<\/p>\n<div style=\"width: 8px; height: 8px; background-color: #00cc00; border: 1px solid #008000;\"><\/div>\n<p>',\n        className: 'aircraft-icon-wrapper', iconSize: [10, 10], iconAnchor: [5, 5]\n    });<\/p>\n<p>    aircraftData.layerGroup = L.layerGroup().addTo(map);\n    overlayMaps = { \"Tr\u00e1fico A\u00e9reo\": aircraftData.layerGroup };<\/p>\n<p>    \/\/ LA L\u00cdNEA PROBLEM\u00c1TICA HA SIDO ELIMINADA. NO USAR .innerHTML = \"\" EN EL DIV DEL MAPA DESPU\u00c9S DE INICIALIZAR L.map().\n    \/\/ const mapDivContent = document.getElementById(mapDivId); \n    \/\/ if (mapDivContent) mapDivContent.innerHTML = \"\"; \/\/ <-- L\u00cdNEA ELIMINADA\n\n    console.log(\"NB_FLIGHT_MAP: runMapLogic - Mapa y variables base inicializadas.\");\n    \n    loadAndDisplaySectors(); \n    fetchAndDisplayFlights(); \n    fetchAndDisplayMetars();\n    \n    setInterval(fetchAndDisplayFlights, UPDATE_INTERVAL_FLIGHTS_MS);\n    setInterval(fetchAndDisplayMetars, UPDATE_INTERVAL_METAR_MS);\n    \n    console.log(\"NB_FLIGHT_MAP: runMapLogic - Carga inicial y intervalos configurados.\");\n}\n    \n    function toRadians(d){return d*Math.PI\/180}function toDegrees(r){return r*180\/Math.PI}function calculateFuturePosition(lat,lon,bearing,distanceKm){const R=EARTH_RADIUS_KM;const lr=toRadians(lat);const lonRad=toRadians(lon);const br=toRadians(bearing);const ad=distanceKm\/R;let \u03c62=Math.asin(Math.sin(lr)*Math.cos(ad)+Math.cos(lr)*Math.sin(ad)*Math.cos(br));let \u03bb2=lonRad+Math.atan2(Math.sin(br)*Math.sin(ad)*Math.cos(lr),Math.cos(ad)-Math.sin(lr)*Math.sin(\u03c62));return{lat:toDegrees(\u03c62),lon:toDegrees(\u03bb2)}}\n    function parseVatsimCoord(c){if(typeof c!=='string'){return null}const n=c.startsWith('-');const a=n?c.substring(1):c;let d,m,s;const l=a.length;if(l>=4&&l<=8){const sl=2,ml=2,dl=l-sl-ml;if(dl<1){return null}d=parseInt(a.substring(0,dl));m=parseInt(a.substring(dl,dl+ml));s=parseInt(a.substring(dl+ml,dl+ml+sl));if(isNaN(d)||isNaN(m)||isNaN(s)){return null}}else{return null}let e=d+(m\/60)+(s\/3600);return n?-e:e}\n    function flToFeet(f){if(f===undefined||f===null)return -Infinity;const s=String(f).toUpperCase();if(s===\"GND\")return 0;if(s===\"UNL\")return Infinity;if(s.startsWith('FL')){const v=parseInt(s.substring(2));return!isNaN(v)?v*100:-Infinity}const u=parseInt(s);if(!isNaN(u))return u*100;return -Infinity}\n\n    async function loadAndDisplaySectors(){\n        allSectorLayers=[];\n        try{\n            const surl='https:\/\/nb-consulting.pro\/wp-content\/uploads\/2025\/05\/sectores_argentina.json';\n            const rsp=await fetch(surl);if(!rsp.ok){throw new Error(`HTTP ${rsp.status} para ${surl}`)}\n            const sData=await rsp.json();\n            if(!sData||!sData.airspace||!sData.groups){console.error(\"NB_FLIGHT_MAP: JSON sectores sin estructura.\");return}\n            const sGroups={};\n            sData.airspace.forEach(ab=>{\n                if(!ab||!ab.group||!ab.sectors){return}const gn=sData.groups[ab.group]?.name||ab.group;\n                if(!sGroups[gn]){sGroups[gn]=L.layerGroup();if(overlayMaps)overlayMaps[gn]=sGroups[gn];}\n                ab.sectors.forEach((s,si)=>{\n                    if(!s||!s.points||!Array.isArray(s.points)){return}\n                    const gf={type:\"Feature\",properties:{},geometry:{type:\"Polygon\",coordinates:[[]]}};\n                    gf.properties={name:`${ab.id||'UnkID'}_S${si+1}`,airspace_id:ab.id||'UnkID',group:gn,owners:Array.isArray(ab.owner)?ab.owner.join(', '):'N\/A',min_fl_str:s.min!==undefined?`FL${String(s.min).padStart(3,'0')}`:\"GND\",max_fl_str:s.max!==undefined?`FL${String(s.max).padStart(3,'0')}`:\"UNL\",min_val:s.min,max_val:s.max};\n                    if(s.min===undefined&&s.max===0){gf.properties.min_val=0;gf.properties.max_val=0;gf.properties.min_fl_str=\"GND\";gf.properties.max_fl_str=\"SFC\"}\n                    else if(s.min===undefined){gf.properties.min_val=0}if(s.max===undefined){gf.properties.max_val=Infinity}\n                    s.points.forEach(pp=>{if(!Array.isArray(pp)||pp.length<2){return}const lat=parseVatsimCoord(pp[0]);const lon=parseVatsimCoord(pp[1]);if(lat!==null&#038;&#038;lon!==null){gf.geometry.coordinates[0].push([lon,lat])}});\n                    if(gf.geometry.coordinates[0].length>1){const f=gf.geometry.coordinates[0][0];const l=gf.geometry.coordinates[0][gf.geometry.coordinates[0].length-1];if(f[0]!==l[0]||f[1]!==l[1]){gf.geometry.coordinates[0].push(f)}}\n                    if(gf.geometry.coordinates[0].length>=4){\n                        const slo=L.geoJSON(gf,{\n                            style:ft=>{let c=\"#88f\";if(gn.includes(\"Center\")||gn.includes(\"SACF\")||gn.includes(\"SAMF\")||gn.includes(\"SARR\")||gn.includes(\"SAVF\"))c=\"#28a745\";else if(gn.includes(\"Approach\")||gn.includes(\"APP\"))c=\"#ffc107\";else if(gn.includes(\"Tower\")||gn.includes(\"TWR\"))c=\"#dc3545\";else if(gn.includes(\"Information\")||gn.includes(\"INF\"))c=\"#17a2b8\";return{fillColor:c,weight:1,opacity:0.8,color:'#ccc',fillOpacity:0.15}},\n                            onEachFeature:(ft,lyr)=>{if(ft.properties){let tt=`<b>${ft.properties.airspace_id}<\/b> (${ft.properties.group})<br \/>Niveles: ${ft.properties.min_fl_str} - ${ft.properties.max_fl_str}`;lyr.bindTooltip(tt,{permanent:false,direction:'center',className:'atc-sector-label'})}}});\n                        allSectorLayers.push({name:gf.properties.name,displayName:gf.properties.airspace_id,group:gf.properties.group,layer:slo,feature:gf,min_fl_str:gf.properties.min_fl_str,max_fl_str:gf.properties.max_fl_str});\n                        if(sGroups[gn]){sGroups[gn].addLayer(slo)}}});});\n        }catch(e){console.error(\"NB_FLIGHT_MAP: Error sectores ATC:\",e);if(infoBoxElement)infoBoxElement.innerHTML+=`<\/p>\n<p style='color:red;'>Error sectores: ${e.message}<\/p>\n<p>`}\n        finally{if(map){if(layerControl&&map.hasControl(layerControl)){map.removeControl(layerControl)}layerControl=L.control.layers(baseMaps,overlayMaps,{collapsed:false,position:'topleft'}).addTo(map);console.log(\"NB_FLIGHT_MAP: Control de capas (re)creado.\")}}\n    }<\/p>\n<p>    function isPointInGeoJSONPolygon(p,gf){if(typeof turf==='undefined'||!turf||!gf||!gf.geometry||gf.geometry.type!=='Polygon'){return false}var t=turf.point([p.lng,p.lat]);try{if(gf.geometry.coordinates&&Array.isArray(gf.geometry.coordinates)&&gf.geometry.coordinates.length>0&&Array.isArray(gf.geometry.coordinates[0])&&gf.geometry.coordinates[0].length>=4){return turf.booleanPointInPolygon(t,gf.geometry)}else{return false}}catch(e){return false}}\n    function updateSectorTrafficTable(fls){\n        if(!allSectorLayers||allSectorLayers.length===0){return}let tsc=0;\n        const tskd=`${TARGET_SECTOR_ID} (FL${String(TARGET_SECTOR_MIN_FL_VALUE).padStart(3,'0')}-FL${String(TARGET_SECTOR_MAX_FL_VALUE).padStart(3,'0')})`;\n        fls.forEach(f=>{\n            if(f.latitude===undefined||f.longitude===undefined||f.altitude_ft===undefined||f.altitude_ft===\"N\/A\"){return}\n            const fll=L.latLng(f.latitude,f.longitude);const flf=parseFloat(f.altitude_ft);if(isNaN(flf))return;\n            for(const si of allSectorLayers){\n                if(si.displayName===TARGET_SECTOR_ID&&si.feature.properties.min_val===TARGET_SECTOR_MIN_FL_VALUE&&si.feature.properties.max_val===TARGET_SECTOR_MAX_FL_VALUE){\n                    if(si.feature&&isPointInGeoJSONPolygon(fll,si.feature)){\n                        const minF=TARGET_SECTOR_MIN_FL_VALUE*100;const maxF=TARGET_SECTOR_MAX_FL_VALUE*100;\n                        if(flf>=minF&&flf<=maxF){tsc++;break}}}}});\n        const tb=document.getElementById('sector-traffic-table')?.getElementsByTagName('tbody')[0];\n        if(!tb){console.error(\"NB_FLIGHT_MAP: No se encontr\u00f3 tbody de tabla.\");return}\n        tb.innerHTML='';let r=tb.insertRow();let cs=r.insertCell();let ct=r.insertCell();cs.textContent=tskd;ct.textContent=tsc;\n    }\n    async function fetchAndDisplayFlights(){\n        const au=(window.ajaxurl||'\/wp-admin\/admin-ajax.php');\n        const p=new URLSearchParams();p.append('action','get_adsb_lol_data');\n        try{\n            const rs=await fetch(au,{method:'POST',body:p});\n            if(!rs.ok){const et=await rs.text();throw new Error(`HTTP ${rs.status}. Resp: ${et.substring(0,100)}`);}\n            const responsePayload=await rs.json();\n            if(responsePayload.error){throw new Error(`PHP Error: ${responsePayload.error}`);}\n            const fls=Array.isArray(responsePayload.flights)?responsePayload.flights:[];\n            const apiTimestampMs=responsePayload.api_timestamp_ms;\n            updateAircraftOnMap(fls);updateSectorTrafficTable(fls);\n            if(document.getElementById('aircraft-count'))document.getElementById('aircraft-count').textContent=fls.length;\n            if(document.getElementById('last-updated')){\n                if(apiTimestampMs&#038;&#038;!isNaN(apiTimestampMs)){const d=new Date(apiTimestampMs);const h=d.getUTCHours().toString().padStart(2,'0');const m=d.getUTCMinutes().toString().padStart(2,'0');const s=d.getUTCSeconds().toString().padStart(2,'0');document.getElementById('last-updated').textContent=`${h}:${m}:${s} Z`;}\n                else{const n=new Date();const h=n.getUTCHours().toString().padStart(2,'0');const m=n.getUTCMinutes().toString().padStart(2,'0');const s=n.getUTCSeconds().toString().padStart(2,'0');document.getElementById('last-updated').textContent=`${h}:${m}:${s} Z (Cliente)`;}\n            }\n        }catch(e){console.error(\"NB_FLIGHT_MAP: Error fetchAndDisplayFlights:\",e);if(infoBoxElement)infoBoxElement.innerHTML=`\n\n<p><strong>Aviones:<\/strong>Error<\/p>\n<p><strong>Actualizado:<\/strong>Error<\/p>\n<p style='color:red;'>${e.message}<\/p>\n<p>`;updateAircraftOnMap([]);updateSectorTrafficTable([]);if(document.getElementById('aircraft-count'))document.getElementById('aircraft-count').textContent=\"0\"}\n    }<\/p>\n<p>    async function fetchAndDisplayMetars() {\n        if(!metarContainerElement){ metarContainerElement = document.getElementById('metar-container'); if(!metarContainerElement) return; }\n        const metarListDiv = document.getElementById('metar-list'); if (!metarListDiv) return;\n        metarListDiv.innerHTML = 'Actualizando METARs...';\n        const ajax_url_metar = (window.ajaxurl || '\/wp-admin\/admin-ajax.php');\n        const params = new URLSearchParams(); params.append('action', 'get_metar_data');\n        try {\n            const response = await fetch(ajax_url_metar, { method: 'POST', body: params });\n            if (!response.ok) { const errorText = await response.text(); throw new Error(`Error HTTP ${response.status} para METARs.`);}\n            const metars = await response.json(); \n            metarListDiv.innerHTML = ''; \n            if (typeof metars === 'object' && metars !== null) {\n                for (const icao in metars) { if (metars.hasOwnProperty(icao)) { const metarDiv = document.createElement('div'); metarDiv.innerHTML = `<strong>${icao}:<\/strong> ${metars[icao]}`; metarListDiv.appendChild(metarDiv);}}\n            } else { metarListDiv.innerHTML = `<\/p>\n<p style='color:orange;'>Formato de METARs inesperado.<\/p>\n<p>`; }\n        } catch (error) { console.error(\"NB_FLIGHT_MAP: Error fetchAndDisplayMetars:\", error); if (metarListDiv) metarListDiv.innerHTML = `<\/p>\n<p style='color:red;'>Error al cargar METARs.<\/p>\n<p>`;}\n    }<\/p>\n<p>   function updateAircraftOnMap(flights) {\n    if (!aircraftData || !aircraftData.layerGroup || !map) {\n        console.error(\"NB_FLIGHT_MAP: Dependencias no listas para updateAircraftOnMap.\");\n        return;\n    }\n    aircraftData.layerGroup.clearLayers();<\/p>\n<p>    if (!Array.isArray(flights)) {\n        console.warn(\"NB_FLIGHT_MAP: 'flights' no es un array en updateAircraftOnMap.\");\n        return;\n    }<\/p>\n<p>    flights.forEach(function(flight) {\n        if (!flight || flight.latitude === undefined || flight.longitude === undefined || flight.latitude === null || flight.longitude === null) {\n            return;\n        }<\/p>\n<p>        const position = [flight.latitude, flight.longitude];\n        const track = parseFloat(flight.track_deg) || 0;\n        const groundSpeedKts = parseFloat(flight.ground_speed_kts);<\/p>\n<p>        \/\/ Crear el marcador del avi\u00f3n\n        let popupContent = \n            `<\/p>\n<div class=\"flight-info\"><strong>ICAO:<\/strong> ${flight.icao || 'N\/A'}<\/div>\n<p>` +\n            `<\/p>\n<div class=\"flight-info\"><strong>Callsign:<\/strong> ${flight.callsign || 'N\/A'}<\/div>\n<p>` +\n            `<\/p>\n<div class=\"flight-info\"><strong>Alt (ft):<\/strong> ${flight.altitude_ft || 'N\/A'}<\/div>\n<p>` +\n            `<\/p>\n<div class=\"flight-info\"><strong>GS (kts):<\/strong> ${flight.ground_speed_kts || 'N\/A'}<\/div>\n<p>` +\n            `<\/p>\n<div class=\"flight-info\"><strong>Track:<\/strong> ${track.toFixed(0)}\u00b0<\/div>\n<p>`;<\/p>\n<p>        if (!aircraftIcon) {\n             console.error(\"NB_FLIGHT_MAP: aircraftIcon no est\u00e1 definido!\");\n             aircraftIcon = L.divIcon({html: '\u2708\ufe0f', className: 'dummy-icon', iconSize:[12,12]});\n        }<\/p>\n<p>        const marker = L.marker(position, { icon: aircraftIcon }).bindPopup(popupContent);\n        aircraftData.layerGroup.addLayer(marker);<\/p>\n<p>        \/\/ --- L\u00d3GICA DE VECTOR DE PREDICCI\u00d3N CON VALIDACI\u00d3N ADICIONAL ---\n        if (!isNaN(groundSpeedKts) && groundSpeedKts > 10) {\n            const speedKmh = groundSpeedKts * NM_TO_KM;\n            const distanceKmIn1Min = speedKmh * (1 \/ 60);<\/p>\n<p>            \/\/ La funci\u00f3n calculateFuturePosition puede devolver NaN si hay errores de c\u00e1lculo\n            const futurePosition = calculateFuturePosition(flight.latitude, flight.longitude, track, distanceKmIn1Min);<\/p>\n<p>            \/\/ **VALIDACI\u00d3N CRUCIAL AQU\u00cd:**\n            \/\/ Asegurarse de que futurePosition es un objeto v\u00e1lido y que sus lat y lon son n\u00fameros finitos.\n            if (futurePosition && \n                typeof futurePosition.lat === 'number' && isFinite(futurePosition.lat) &&\n                typeof futurePosition.lon === 'number' && isFinite(futurePosition.lon)) {<\/p>\n<p>                const vectorLinePoints = [\n                    position, \/\/ [lat, lon]\n                    [futurePosition.lat, futurePosition.lon]\n                ];<\/p>\n<p>                const vectorLineOptions = { \n                    color: '#00cc00', \n                    weight: 1, \n                    opacity: 0.7 \n                };<\/p>\n<p>                try {\n                    const vectorLine = L.polyline(vectorLinePoints, vectorLineOptions);\n                    aircraftData.layerGroup.addLayer(vectorLine);\n                } catch (e) {\n                    console.error(\"NB_FLIGHT_MAP: Error al crear o a\u00f1adir L.polyline para\", flight.icao, \". Puntos:\", vectorLinePoints, \"Error:\", e);\n                }\n            } else {\n                \/\/ Loguear si la posici\u00f3n futura es inv\u00e1lida, para depuraci\u00f3n\n                \/\/ console.warn(\"NB_FLIGHT_MAP: Posici\u00f3n futura inv\u00e1lida calculada para\", flight.icao, futurePosition);\n            }\n        }\n    }); \/\/ Cierre del forEach\n}<\/p>\n<p>    let initializeAttempts=0;const maxInitializeAttempts=20;\n    function attemptRunMapLogic(){\n        console.log(`NB_FLIGHT_MAP: attemptRunMapLogic - Intento #${initializeAttempts + 1}`);\n        if(document.getElementById('map-div')&&typeof L!=='undefined'&&typeof L.map==='function'&&typeof turf!=='undefined'){\n            console.log(\"NB_FLIGHT_MAP: \u00a1Dependencias listas! Inicializando...\");runMapLogic();\n        }else{\n            initializeAttempts++;\n            if(initializeAttempts<maxInitializeAttempts){\n                console.warn(\"NB_FLIGHT_MAP: Dependencias no listas. Reintentando...\");\n                if(typeof L==='undefined')console.warn(\"Leaflet (L) no definido.\");\n                if(typeof turf==='undefined')console.warn(\"Turf.js (turf) no definido.\");\n                setTimeout(attemptRunMapLogic,500);\n            }else{\n                console.error(\"NB_FLIGHT_MAP: No se pudo inicializar (dependencias no disponibles).\");\n                const mapDivError=document.getElementById('map-div');\n                if(mapDivError)mapDivError.innerHTML=\"\n\n<p style='color:red; padding:20px;'>Error: No se pudo cargar el mapa. Verifique que Leaflet y Turf.js est\u00e9n cargados.<\/p>\n<p>\";\n            }\n        }\n    }\n    if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',attemptRunMapLogic);}else{attemptRunMapLogic();}\n    console.log(\"NB_FLIGHT_MAP: Script del mapa - FIN de la configuraci\u00f3n de listeners.\");\n})(); \n<\/script><br \/>\n<!-- === FIN DEL C\u00d3DIGO PARA PEGAR EN WORDPRESS === --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Cargando mapa&#8230; Aviones Totales: 0 Actualizado (UTC): &#8211; Tr\u00e1fico Baires APP (FL055-FL244) Sector Aviones METARs Cargando METARs&#8230;<\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"off","neve_meta_content_width":100,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"class_list":["post-740","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Visualizador 2 - NB Consulting<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/nb-consulting.pro\/en\/visualizador-2\/\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Visualizador 2 - NB Consulting\" \/>\n<meta property=\"og:description\" content=\"Cargando mapa&#8230; Aviones Totales: 0 Actualizado (UTC): &#8211; Tr\u00e1fico Baires APP (FL055-FL244) Sector Aviones METARs Cargando METARs&#8230;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/nb-consulting.pro\/en\/visualizador-2\/\" \/>\n<meta property=\"og:site_name\" content=\"NB Consulting\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Estimated reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/nb-consulting.pro\/visualizador-2\/\",\"url\":\"https:\/\/nb-consulting.pro\/visualizador-2\/\",\"name\":\"Visualizador 2 - NB Consulting\",\"isPartOf\":{\"@id\":\"https:\/\/nb-consulting.pro\/#website\"},\"datePublished\":\"2025-07-11T12:31:02+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/nb-consulting.pro\/visualizador-2\/#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/nb-consulting.pro\/visualizador-2\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/nb-consulting.pro\/visualizador-2\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Portada\",\"item\":\"https:\/\/nb-consulting.pro\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Visualizador 2\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/nb-consulting.pro\/#website\",\"url\":\"https:\/\/nb-consulting.pro\/\",\"name\":\"NB Consulting\",\"description\":\"Consultor&iacute;a especializada en navegaci&oacute;n a&eacute;rea y formaci&oacute;n profesional en gesti&oacute;n del tr&aacute;fico a&eacute;reo (ATM). Ofrecemos soluciones personalizadas para mejorar la eficiencia, seguridad y sostenibilidad en ANSPs, aeropuertos y aerol&iacute;neas.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/nb-consulting.pro\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-GB\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Visualizador 2 - NB Consulting","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/nb-consulting.pro\/en\/visualizador-2\/","og_locale":"en_GB","og_type":"article","og_title":"Visualizador 2 - NB Consulting","og_description":"Cargando mapa&#8230; Aviones Totales: 0 Actualizado (UTC): &#8211; Tr\u00e1fico Baires APP (FL055-FL244) Sector Aviones METARs Cargando METARs&#8230;","og_url":"https:\/\/nb-consulting.pro\/en\/visualizador-2\/","og_site_name":"NB Consulting","twitter_card":"summary_large_image","twitter_misc":{"Estimated reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/nb-consulting.pro\/visualizador-2\/","url":"https:\/\/nb-consulting.pro\/visualizador-2\/","name":"Visualizador 2 - NB Consulting","isPartOf":{"@id":"https:\/\/nb-consulting.pro\/#website"},"datePublished":"2025-07-11T12:31:02+00:00","breadcrumb":{"@id":"https:\/\/nb-consulting.pro\/visualizador-2\/#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/nb-consulting.pro\/visualizador-2\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/nb-consulting.pro\/visualizador-2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Portada","item":"https:\/\/nb-consulting.pro\/"},{"@type":"ListItem","position":2,"name":"Visualizador 2"}]},{"@type":"WebSite","@id":"https:\/\/nb-consulting.pro\/#website","url":"https:\/\/nb-consulting.pro\/","name":"NB Consulting","description":"Consultor&iacute;a especializada en navegaci&oacute;n a&eacute;rea y formaci&oacute;n profesional en gesti&oacute;n del tr&aacute;fico a&eacute;reo (ATM). Ofrecemos soluciones personalizadas para mejorar la eficiencia, seguridad y sostenibilidad en ANSPs, aeropuertos y aerol&iacute;neas.","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/nb-consulting.pro\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-GB"}]}},"_links":{"self":[{"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/pages\/740","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/comments?post=740"}],"version-history":[{"count":3,"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/pages\/740\/revisions"}],"predecessor-version":[{"id":773,"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/pages\/740\/revisions\/773"}],"wp:attachment":[{"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/media?parent=740"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}