{"id":804,"date":"2025-10-15T12:17:49","date_gmt":"2025-10-15T12:17:49","guid":{"rendered":"https:\/\/nb-consulting.pro\/?page_id=804"},"modified":"2025-11-04T10:41:16","modified_gmt":"2025-11-04T10:41:16","slug":"dashboard","status":"publish","type":"page","link":"https:\/\/nb-consulting.pro\/en\/dashboard\/","title":{"rendered":"Dashboard TMA BAIRES"},"content":{"rendered":"<!DOCTYPE html>\r\n<html lang=\"es\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\r\n    <title>KPI Dashboard - Vuelos<\/title>\r\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.6\/dist\/chart.umd.min.js\"><\/script>\r\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chartjs-plugin-zoom@2.0.1\/dist\/chartjs-plugin-zoom.min.js\"><\/script>\r\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/jquery@3.7.1\/dist\/jquery.min.js\"><\/script>\r\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/moment@2.30.1\/moment.min.js\"><\/script>\r\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/moment@2.30.1\/locale\/es.js\"><\/script>\r\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/daterangepicker\/daterangepicker.min.js\"><\/script>\r\n    <link rel=\"stylesheet\" href=\"https:\/\/cdn.jsdelivr.net\/npm\/daterangepicker\/daterangepicker.css\" \/>\r\n    <link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\"><link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin><link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;700&display=swap\" rel=\"stylesheet\">\r\n    \r\n    <style>\r\n      :root {\r\n        --bg-color-dark: #020617; --bg-color-light: #0f172a; --card-bg-color: #1e293b;\r\n        --border-color: #334155; --text-color: #e2e8f0; --text-muted-color: #94a3b8;\r\n        --primary-accent: #0ea5e9; --secondary-accent: #f43f5e;\r\n      }\r\n      html.light-mode {\r\n        --bg-color-dark: #f1f5f9; --bg-color-light: #f8fafc; --card-bg-color: #ffffff;\r\n        --border-color: #e2e8f0; --text-color: #0f172a; --text-muted-color: #64748b;\r\n      }\r\n      body { \r\n        background-color: var(--bg-color-light); \r\n        background-image: linear-gradient(to bottom right, var(--bg-color-light), var(--bg-color-dark)); \r\n        color: var(--text-color); font-family: 'Inter', sans-serif; margin: 0; min-height: 100vh;\r\n        transition: background-color 0.3s, color 0.3s;\r\n      }\r\n      .kpi-dashboard-wrapper { padding: 25px; max-width: 1600px; margin: 20px auto; }\r\n      .kpi-card { background-color: var(--card-bg-color); padding: 20px; border-radius: 12px; border: 1px solid var(--border-color); display: flex; flex-direction: column; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05), 0 2px 4px -2px rgba(0,0,0,0.05); transition: transform 0.2s, box-shadow 0.2s, background-color 0.3s, border-color 0.3s; position: relative; }\r\n      .kpi-card:hover { transform: translateY(-5px); box-shadow: 0 10px 15px -3px rgba(0,0,0,0.07), 0 4px 6px -4px rgba(0,0,0,0.07); }\r\n      .kpi-card h4 { margin-top: 0; border-bottom: 1px solid var(--border-color); padding-bottom: 10px; color: var(--text-color); font-weight: 500; display: flex; align-items: center; justify-content: space-between; gap: 8px; position: relative; z-index: 2; transition: color 0.3s, border-color 0.3s; }\r\n      .kpi-card h4 .title-content { display: flex; align-items: center; gap: 8px; }\r\n      .kpi-card h4 .icon { color: var(--primary-accent); }\r\n      .dashboard-controls { background-color: var(--card-bg-color); border: 1px solid var(--border-color); border-radius: 12px; padding: 15px 20px; display: flex; justify-content: center; align-items: center; gap: 20px; margin-bottom: 40px; flex-wrap: wrap; transition: background-color 0.3s, border-color 0.3s; }\r\n      .dashboard-controls label { color: var(--text-muted-color); font-size: 0.9em; transition: color 0.3s; }\r\n      .dashboard-controls input, .dashboard-controls select { background-color: var(--bg-color-light); color: var(--text-color); border: 1px solid var(--border-color); padding: 10px; border-radius: 8px; min-width: 200px; transition: background-color 0.3s, color 0.3s, border-color 0.3s; }\r\n      .update-button { background-color: var(--primary-accent); color: #fff; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 700; cursor: pointer; transition: background-color 0.2s; }\r\n      .update-button:hover { background-color: #0c87bf; }\r\n      .daterangepicker {font-size: 0.85rem !important; \/* <-- A\u00f1ade esta l\u00ednea *\/ background-color: var(--card-bg-color) !important;\u00a0border-color: var(--border color) !important;}\r\n      .daterangepicker .drp-calendar, .daterangepicker .calendar-table { background-color: var(--card-bg-color) !important; }\r\n      .daterangepicker .calendar-table th, .daterangepicker .calendar-table td {color: var(--text-color) !important;padding: 6px 8px !important; \/* <-- white-space: nowrap; \/* <-- A\u00d1ADE ESTA L\u00cdNEA (evita saltos de l\u00ednea) *\/}\r\n      html.light-mode .daterangepicker td.off { color: #aaa !important; }\r\n      .daterangepicker td.off { color: #444 !important; }\r\n      .daterangepicker .month, .daterangepicker .year { color: var(--primary-accent) !important; }\r\n      .daterangepicker td.active, .daterangepicker td.active:hover { background-color: var(--primary-accent) !important; }\r\n      .daterangepicker .drp-buttons .btn { background-color: var(--primary-accent) !important; border-color: var(--primary-accent) !important; }\r\n      .daterangepicker .drp-buttons .cancelBtn { background-color: var(--bg-color-light) !important; border-color: var(--border-color) !important; }\r\n\t  .daterangepicker .drp-calendar {width: 300px !important; }\r\n      .kpi-box-container { display: flex; gap: 15px; text-align: center; }\r\n      .kpi-box { flex: 1; }\r\n      .kpi-value { font-size: 2.2em; font-weight: 700; color: var(--primary-accent); }\r\n      .kpi-label { font-size: 0.9em; color: var(--text-muted-color); margin-top: 5px; transition: color 0.3s; }\r\n      .chart-container { position: relative; height: 350px; flex-grow: 1; }\r\n      .details-table { width: 100%; margin-top: 15px; border-collapse: collapse; }\r\n      .details-table th, .details-table td { padding: 8px 10px; border-bottom: 1px solid var(--border-color); text-align: left; font-size: 0.9em; transition: border-color 0.3s; }\r\n      .details-table th { color: var(--primary-accent); }\r\n      .card-state-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; justify-content: center; align-items: center; background-color: rgba(30, 41, 59, 0.8); backdrop-filter: blur(4px); z-index: 10; border-radius: 11px; opacity: 0; visibility: hidden; transition: opacity 0.3s; }\r\n      html.light-mode .card-state-overlay { background-color: rgba(248, 250, 252, 0.8); }\r\n      .card-state-overlay.visible { opacity: 1; visibility: visible; }\r\n      .state-content p { color: var(--text-muted-color); }\r\n      .spinner { border: 4px solid rgba(100, 116, 139, 0.2); border-left-color: var(--primary-accent); border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto; }\r\n      @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\r\n      .kpi-card .card-content { flex-grow: 1; display: flex; flex-direction: column; }\r\n      .toggle-details-btn { background-color: var(--primary-accent); color: white; border: none; padding: 8px 15px; border-radius: 5px; cursor: pointer; margin-top: auto; }\r\n\t  .large-chart { grid-column: 1 \/ -1; }\r\n      .dashboard-grid { display: grid; gap: 20px; align-items: stretch; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }\r\n      @media (min-width: 1280px) { .large-chart { height: 400px; } }\r\n      @media (max-width: 600px) {\u00a0\r\n\u00a0 \u00a0 \u00a0 \u00a0 .kpi-dashboard-wrapper { padding: 15px; }\u00a0\r\n\u00a0 \u00a0 \u00a0 \u00a0 .kpi-card { padding: 15px; }\u00a0\r\n\u00a0 \u00a0 \u00a0 \u00a0 .kpi-value { font-size: 1.6em; } \/* Reducido de 1.8em *\/\r\n\u00a0 \u00a0 \u00a0 \u00a0 .kpi-label { font-size: 0.8em; } \/* A\u00f1adido para reducir la etiqueta *\/}\r\n      .info-btn { background-color: var(--border-color); color: var(--text-muted-color); border: none; border-radius: 50%; width: 22px; height: 22px; font-size: 14px; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; line-height: 1; transition: background-color 0.2s, color 0.2s; }\r\n      .info-btn:hover { background-color: var(--primary-accent); color: white; }\r\n      .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); backdrop-filter: blur(5px); z-index: 1000; display: none; justify-content: center; align-items: center; opacity: 0; transition: opacity 0.3s; }\r\n      .modal-overlay.visible { display: flex; opacity: 1; }\r\n      .modal-content { background-color: var(--card-bg-color); padding: 25px 30px; border-radius: 12px; border: 1px solid var(--border-color); max-width: 500px; width: 90%; position: relative; transform: scale(0.9); transition: transform 0.3s, background-color 0.3s, border-color 0.3s; }\r\n      .modal-overlay.visible .modal-content { transform: scale(1); }\r\n      .modal-close { position: absolute; top: 10px; right: 15px; font-size: 24px; color: var(--text-muted-color); cursor: pointer; transition: color 0.2s; }\r\n      .modal-close:hover { color: var(--text-color); }\r\n      .modal-content h3 { margin-top: 0; color: var(--primary-accent); }\r\n      .modal-content p { color: var(--text-muted-color); line-height: 1.6; transition: color 0.3s; }\r\n      .onboarding-nav { margin-top: 20px; display: flex; justify-content: space-between; align-items: center; }\r\n      .onboarding-btn { background-color: var(--primary-accent); color: white; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 700; cursor: pointer; }\r\n      .onboarding-btn:disabled { background-color: var(--border-color); cursor: not-allowed; }\r\n      .onboarding-dots { display: flex; gap: 8px; }\r\n      .onboarding-dots span { width: 10px; height: 10px; background-color: var(--border-color); border-radius: 50%; transition: background-color 0.3s; }\r\n      .onboarding-dots span.active { background-color: var(--primary-accent); }\r\n      #theme-toggle { background: none; border: 1px solid var(--border-color); border-radius: 8px; width: 40px; height: 40px; padding: 8px; cursor: pointer; color: var(--text-muted-color); transition: color 0.2s, border-color 0.3s; }\r\n      #theme-toggle:hover { color: var(--primary-accent); }\r\n      #theme-toggle svg { width: 100%; height: 100%; stroke: currentColor; }\r\n      .theme-icon-sun { display: none; } html.light-mode .theme-icon-sun { display: block; }\r\n      .theme-icon-moon { display: block; } html.light-mode .theme-icon-moon { display: none; }\r\n\t  @media (max-width: 768px) {\r\n  .dashboard-controls {\r\n    flex-direction: column; \/* Apila los elementos verticalmente *\/\r\n    align-items: stretch; \/* Hace que ocupen todo el ancho *\/\r\n    gap: 15px;\r\n  }\r\n\r\n  .dashboard-controls div {\r\n    display: flex;\r\n    flex-direction: column; \/* Pone la etiqueta encima del input *\/\r\n    gap: 5px; \/* Un peque\u00f1o espacio entre etiqueta e input *\/\r\n  }\r\n\r\n  .dashboard-controls input,\r\n  .dashboard-controls select {\r\n    min-width: 0; \/* Resetea el ancho m\u00ednimo *\/\r\n    width: 100%; \/* Ocupa el 100% del contenedor *\/\r\n  }\r\n}\r\n    <\/style>\r\n<script>\r\n  \/\/ Este script se ejecuta instant\u00e1neamente para evitar el parpadeo del tema\r\n  (function() {\r\n    const savedTheme = localStorage.getItem('dashboardTheme');\r\n    const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;\r\n    let theme = 'dark-mode'; \/\/ Default\r\n    if (savedTheme) {\r\n      theme = savedTheme;\r\n    } else if (!prefersDark) {\r\n      theme = 'light-mode';\r\n    }\r\n    document.documentElement.className = theme; \/\/ Aplicar al <html>\r\n  })();\r\n<\/script>\r\n<\/head>\r\n<body>\r\n    <div class=\"kpi-dashboard-wrapper\">\r\n        <div class=\"dashboard-controls\">\r\n            <button id=\"theme-toggle\" title=\"Cambiar tema\">\r\n                <svg class=\"theme-icon-sun\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z\" \/><\/svg>\r\n                <svg class=\"theme-icon-moon\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z\" \/><\/svg>\r\n            <\/button>\r\n            <div><label for=\"daterange\" data-translate-key=\"date_range_label\">Rango de fechas:<\/label><input type=\"text\" id=\"daterange\" name=\"daterange\"><\/div>\r\n            <div><label for=\"intervalSelector\" data-translate-key=\"congestion_interval_label\">Intervalo de congesti\u00f3n:<\/label><select id=\"intervalSelector\"><option value=\"5\">5 min<\/option><option value=\"10\">10 min<\/option><option value=\"15\" selected>15 min<\/option><option value=\"30\">30 min<\/option><option value=\"60\">60 min<\/option><\/select><\/div>\r\n            <button id=\"updateDashboardBtn\" class=\"update-button\" data-translate-key=\"update_button\">Actualizar Dashboard<\/button>\r\n        <\/div>\r\n        \r\n        <div class=\"dashboard-grid\">\r\n            <div class=\"kpi-card\" id=\"summary-card\">\r\n                <h4><span class=\"title-content\"><span class=\"icon\">\ud83d\udcca<\/span> <span data-translate-key=\"summary_title\">Resumen<\/span><\/span><button class=\"info-btn\">?<\/button><\/h4>\r\n                <div class=\"card-content\" style=\"justify-content: center;\">\r\n                    <div class=\"kpi-box-container\" style=\"width:100%;\">\r\n                        <div class=\"kpi-box\"><div id=\"kpi-total-records\" class=\"kpi-value\">--<\/div><div class=\"kpi-label\" data-translate-key=\"total_records_label\">Total Registros<\/div><\/div>\r\n                        <div class=\"kpi-box\"><div id=\"kpi-unique-aircraft\" class=\"kpi-value\">--<\/div><div class=\"kpi-label\" data-translate-key=\"unique_aircraft_label\">Aeronaves \u00danicas<\/div><\/div>\r\n                    <\/div>\r\n                <\/div>\r\n                <div class=\"card-state-overlay\"><div class=\"state-content\"><\/div><\/div>\r\n            <\/div>\r\n            <div class=\"kpi-card\" id=\"hfe-card\">\r\n                <h4><span class=\"title-content\"><span class=\"icon\">\ud83d\udccf<\/span> <span data-translate-key=\"hfe_title\">Eficiencia Horizontal<\/span><\/span><button class=\"info-btn\">?<\/button><\/h4>\r\n                <div class=\"card-content\">\r\n                    <div class=\"kpi-box-container\" style=\"width:100%;\">\r\n                        <div class=\"kpi-box\"><div id=\"kpi-hfe-ratio\" class=\"kpi-value\">--<\/div><div class=\"kpi-label\" data-translate-key=\"avg_ratio_label\">Ratio Promedio<\/div><\/div>\r\n                        <div class=\"kpi-box\"><div id=\"kpi-hfe-extra\" class=\"kpi-value\">--<\/div><div class=\"kpi-label\" data-translate-key=\"inefficiency_label\">% Ineficiencia<\/div><\/div>\r\n                    <\/div>\r\n                    <button id=\"toggle-hfe-details\" class=\"toggle-details-btn\" data-translate-key=\"view_worst_cases_btn\">Ver Peores Casos<\/button>\r\n                    <div id=\"hfe-details-container\" style=\"display:none; max-height: 200px; overflow-y: auto; margin-top:15px;\"><\/div>\r\n                <\/div>\r\n                <div class=\"card-state-overlay\"><div class=\"state-content\"><\/div><\/div>\r\n            <\/div>\r\n            <div class=\"kpi-card\" id=\"leveloff-climb-card\">\r\n                <h4><span class=\"title-content\"><span class=\"icon\">\ud83d\udeeb<\/span> <span data-translate-key=\"climb_leveloff_title\">Level-off Ascenso<\/span><\/span><button class=\"info-btn\">?<\/button><\/h4>\r\n                <div class=\"card-content\">\r\n                    <div class=\"kpi-box-container\">\r\n                        <div class=\"kpi-box\"><div id=\"kpi-total-level-off-climb-seconds\" class=\"kpi-value\">--<\/div><div class=\"kpi-label\" data-translate-key=\"total_minutes_label\">Minutos Totales<\/div><\/div>\r\n                        <div class=\"kpi-box\"><div id=\"kpi-avg-level-off-climb-seconds\" class=\"kpi-value\">--<\/div><div class=\"kpi-label\" data-translate-key=\"avg_duration_label\">Duraci\u00f3n Promedio<\/div><\/div>\r\n                    <\/div>\r\n                    <p style=\"text-align:center; font-size:0.9em; color:var(--text-muted-color); margin:15px 0 10px 0;\" data-translate-key=\"altitude_distribution_label\">Distribuci\u00f3n por Altitud<\/p>\r\n                    <div class=\"chart-container\" style=\"height: 180px;\"><canvas id=\"levelOffClimbDistributionChart\"><\/canvas><\/div>\r\n                    <button id=\"toggle-level-off-climb-details\" class=\"toggle-details-btn\" data-translate-key=\"view_last_cases_btn\">Ver \u00daltimos Casos<\/button>\r\n                    <div id=\"level-off-climb-details-container\" style=\"display:none; max-height: 200px; overflow-y: auto; margin-top:15px;\"><\/div>\r\n                <\/div>\r\n                <div class=\"card-state-overlay\"><div class=\"state-content\"><\/div><\/div>\r\n            <\/div>\r\n            <div class=\"kpi-card\" id=\"leveloff-descent-card\">\r\n                <h4><span class=\"title-content\"><span class=\"icon\">\ud83d\udeec<\/span> <span data-translate-key=\"descent_leveloff_title\">Level-off Descenso<\/span><\/span><button class=\"info-btn\">?<\/button><\/h4>\r\n                <div class=\"card-content\">\r\n                    <div class=\"kpi-box-container\">\r\n                        <div class=\"kpi-box\"><div id=\"kpi-total-level-off-descent-seconds\" class=\"kpi-value\">--<\/div><div class=\"kpi-label\" data-translate-key=\"total_minutes_label\">Minutos Totales<\/div><\/div>\r\n                        <div class=\"kpi-box\"><div id=\"kpi-avg-level-off-descent-seconds\" class=\"kpi-value\">--<\/div><div class=\"kpi-label\" data-translate-key=\"avg_duration_label\">Duraci\u00f3n Promedio<\/div><\/div>\r\n                    <\/div>\r\n                    <p style=\"text-align:center; font-size:0.9em; color:var(--text-muted-color); margin:15px 0 10px 0;\" data-translate-key=\"altitude_distribution_label\">Distribuci\u00f3n por Altitud<\/p>\r\n                    <div class=\"chart-container\" style=\"height: 180px;\"><canvas id=\"levelOffDescentDistributionChart\"><\/canvas><\/div>\r\n                    <button id=\"toggle-level-off-descent-details\" class=\"toggle-details-btn\" data-translate-key=\"view_last_cases_btn\">Ver \u00daltimos Casos<\/button>\r\n                    <div id=\"level-off-descent-details-container\" style=\"display:none; max-height: 200px; overflow-y: auto; margin-top:15px;\"><\/div>\r\n                <\/div>\r\n                <div class=\"card-state-overlay\"><div class=\"state-content\"><\/div><\/div>\r\n            <\/div>\r\n            <div class=\"kpi-card\" id=\"go-around-card\">\r\n                <h4><span class=\"title-content\"><span class=\"icon\">\ud83d\udd04<\/span> <span data-translate-key=\"go_around_title\">Go-Arounds<\/span><\/span><button class=\"info-btn\">?<\/button><\/h4>\r\n                <div class=\"card-content\">\r\n                    <div class=\"kpi-box-container\"><div class=\"kpi-box\"><div id=\"kpi-go-around-total\" class=\"kpi-value\">--<\/div><div class=\"kpi-label\" data-translate-key=\"total_in_period_label\">Total en Periodo<\/div><\/div><\/div>\r\n                    <button id=\"toggle-go-around-details\" class=\"toggle-details-btn\" data-translate-key=\"view_last_cases_btn\">Ver \u00daltimos Casos<\/button>\r\n                    <div id=\"go-around-details-container\" style=\"display:none; max-height: 200px; overflow-y: auto; margin-top:15px;\"><\/div>\r\n                <\/div>\r\n                <div class=\"card-state-overlay\"><div class=\"state-content\"><\/div><\/div>\r\n            <\/div>\r\n            \r\n            <div class=\"kpi-card large-chart\" id=\"tma-chart-card\">\r\n                <h4><span class=\"title-content\"><span class=\"icon\">\ud83d\udcc8<\/span> <span data-translate-key=\"flights_in_tma_title\">Vuelos en TMA<\/span><\/span><button class=\"info-btn\">?<\/button><\/h4>\r\n                <div class=\"card-content\"><div class=\"chart-container\"><canvas id=\"tmaChart\"><\/canvas><\/div><\/div>\r\n                <div class=\"card-state-overlay\"><div class=\"state-content\"><\/div><\/div>\r\n            <\/div>\r\n\t\t\t<div class=\"kpi-card large-chart\" id=\"speed-chart-card\">\r\n                <h4><span class=\"title-content\"><span class=\"icon\">\ud83d\ude80<\/span> <span data-translate-key=\"speed_by_level_title\">Velocidad por Nivel<\/span><\/span><button class=\"info-btn\">?<\/button><\/h4>\r\n                <div class=\"card-content\"><div class=\"chart-container\"><canvas id=\"speedByLevelChart\"><\/canvas><\/div><\/div>\r\n                <div class=\"card-state-overlay\"><div class=\"state-content\"><\/div><\/div>\r\n            <\/div>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <!-- Info Modal -->\r\n    <div id=\"info-modal\" class=\"modal-overlay\" role=\"dialog\" aria-hidden=\"true\">\r\n        <div class=\"modal-content\">\r\n            <span id=\"info-modal-close\" class=\"modal-close\" aria-label=\"Cerrar\">&times;<\/span>\r\n            <h3 id=\"info-modal-title\"><\/h3>\r\n            <div id=\"info-modal-text\"><\/div>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <!-- Onboarding Modal -->\r\n    <div id=\"onboarding-modal\" class=\"modal-overlay\" role=\"dialog\" aria-hidden=\"true\">\r\n        <div class=\"modal-content\">\r\n            <span id=\"onboarding-modal-close\" class=\"modal-close\" aria-label=\"Cerrar\">&times;<\/span>\r\n            <h3 id=\"onboarding-title\"><\/h3>\r\n            <p id=\"onboarding-text\"><\/p>\r\n            <div class=\"onboarding-nav\">\r\n                <button id=\"onboarding-prev\" class=\"onboarding-btn\"><\/button>\r\n                <div id=\"onboarding-dots\" class=\"onboarding-dots\"><\/div>\r\n                <button id=\"onboarding-next\" class=\"onboarding-btn\"><\/button>\r\n            <\/div>\r\n        <\/div>\r\n    <\/div>\r\n\r\n<script>\r\ndocument.addEventListener(\"DOMContentLoaded\", () => {\r\n  const apiUrl = '\/api.php'; \/\/ ajusta si es necesario\r\n  let speedChart, tmaChart, levelOffClimbDistChart, levelOffDescentDistChart;\r\n  const datePicker = $('input[name=\"daterange\"]');\r\n  const intervalSelector = document.getElementById('intervalSelector');\r\n  const updateBtn = document.getElementById('updateDashboardBtn');\r\n  const themeToggle = document.getElementById('theme-toggle');\r\n\r\nconst setTheme = (theme) => {\r\n    document.documentElement.className = theme; \/\/ Asegurarse de que el <html> tiene la clase\r\n    localStorage.setItem('dashboardTheme', theme);\r\n    \r\n    \/\/ Funci\u00f3n para actualizar los colores de los gr\u00e1ficos\r\n    const updateChartColors = () => {\r\n        const newTextColor = getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim();\r\n        const newGridColor = getComputedStyle(document.documentElement).getPropertyValue('--border-color').trim();\r\n        const allCharts = [speedChart, tmaChart, levelOffClimbDistChart, levelOffDescentDistChart];\r\n        \r\n        allCharts.forEach(chart => {\r\n            if (chart && chart.options) {\r\n                if(chart.options.plugins.legend) chart.options.plugins.legend.labels.color = newTextColor;\r\n                Object.values(chart.options.scales).forEach(scale => {\r\n                    if (scale.ticks) scale.ticks.color = newTextColor;\r\n                    if (scale.grid) scale.grid.color = newGridColor;\r\n                    if (scale.title) scale.title.color = newTextColor;\r\n                });\r\n                chart.update('none');\r\n            }\r\n        });\r\n    };\r\n    \r\n    \/\/ Esperar un instante para que las variables CSS se actualicen antes de redibujar los gr\u00e1ficos\r\n    setTimeout(updateChartColors, 50); \r\n};\r\n\r\nthemeToggle.addEventListener('click', () => {\r\n    const newTheme = document.documentElement.classList.contains('light-mode') ? 'dark-mode' : 'light-mode';\r\n    setTheme(newTheme);\r\n});\r\n\r\n\/\/ Sincronizar el estado inicial (el script del head ya hizo el trabajo visual)\r\nif (!localStorage.getItem('dashboardTheme')) {\r\n    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {\r\n        localStorage.setItem('dashboardTheme', 'dark-mode');\r\n    } else {\r\n        localStorage.setItem('dashboardTheme', 'light-mode');\r\n    }\r\n}\r\n\r\nconst savedTheme = localStorage.getItem('dashboardTheme');\r\nconst prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;\r\nif (savedTheme) {\r\n    setTheme(savedTheme);\r\n} else if (prefersDark) {\r\n    setTheme('dark-mode');\r\n} else {\r\n    setTheme('light-mode');\r\n}\r\n  \/\/ Detectar idioma seg\u00fan <html lang=\"...\">\r\n  const lang = document.documentElement.lang && String(document.documentElement.lang).toLowerCase().startsWith('en') ? 'en' : 'es';\r\n  document.documentElement.lang = lang; \/\/ forzar formato limpio\r\n  moment.locale(lang);\r\n\r\n  \/\/ Traducciones (tus textos)\r\n  const translations = {\r\n    date_range_label: { es: 'Rango de fechas:', en: 'Date range:' },\r\n    congestion_interval_label: { es: 'Intervalo de congesti\u00f3n:', en: 'Congestion interval:' },\r\n    update_button: { es: 'Actualizar Dashboard', en: 'Update Dashboard' },\r\n    loading_message: { es: 'Cargando datos...', en: 'Loading data...' },\r\n    error_message: { es: 'Error', en: 'Error' },\r\n    error_load_message: { es: 'Error al cargar.', en: 'Error while loading.' },\r\n    empty_message: { es: 'No hay datos.', en: 'No data available.' },\r\n    no_cases_message: { es: 'Sin casos.', en: 'No cases.' },\r\n    summary_title: { es: 'Resumen', en: 'Summary' },\r\n    hfe_title: { es: 'Eficiencia Horizontal', en: 'Horizontal Efficiency' },\r\n    climb_leveloff_title: { es: 'Level-off Ascenso', en: 'Climb Level-off' },\r\n    descent_leveloff_title: { es: 'Level-off Descenso', en: 'Descent Level-off' },\r\n    go_around_title: { es: 'Go-Arounds', en: 'Go-Arounds' },\r\n    speed_by_level_title: { es: 'Velocidad por Nivel', en: 'Speed by Level' },\r\n    flights_in_tma_title: { es: 'Vuelos en TMA', en: 'Flights in TMA' },\r\n    total_records_label: { es: 'Total Registros', en: 'Total Records' },\r\n    unique_aircraft_label: { es: 'Aeronaves \u00danicas', en: 'Unique Aircraft' },\r\n    avg_ratio_label: { es: 'Ratio Promedio', en: 'Average Ratio' },\r\n    inefficiency_label: { es: '% Ineficiencia', en: '% Inefficiency' },\r\n    total_minutes_label: { es: 'Minutos Totales', en: 'Total Minutes' },\r\n    avg_duration_label: { es: 'Duraci\u00f3n Promedio', en: 'Average Duration' },\r\n    altitude_distribution_label: { es: 'Distribuci\u00f3n por Altitud', en: 'Distribution by Altitude' },\r\n    total_in_period_label: { es: 'Total en Periodo', en: 'Total in Period' },\r\n    view_worst_cases_btn: { es: 'Ver Peores Casos', en: 'View Worst Cases' },\r\n    view_last_cases_btn: { es: 'Ver \u00daltimos Casos', en: 'View Last Cases' },\r\n    hide_cases_btn: { es: 'Ocultar Casos', en: 'Hide Cases' },\r\n    avg_speed_chart_label: { es: 'Velocidad Promedio (kts)', en: 'Average Speed (kts)' },\r\n    avg_simultaneous_ac_label: { es: 'Promedio de aeronaves simult\u00e1neas', en: 'Average simultaneous aircraft' },\r\n    time_of_day_axis_label: { es: 'Hora del D\u00eda (UTC)', en: 'Time of Day (UTC)' },\r\n    avg_aircraft_axis_label: { es: 'Promedio de Aeronaves', en: 'Average Aircraft' },\r\n    events_label: { es: 'N\u00ba de Eventos', en: 'N\u00ba of Events' },\r\n    callsign: { es: 'Callsign', en: 'Callsign' },\r\n    time_utc: { es: 'Hora (UTC)', en: 'Time (UTC)' },\r\n    duration: { es: 'Duraci\u00f3n', en: 'Duration' },\r\n    altitude: { es: 'Altitud', en: 'Altitude' },\r\n    ratio: { es: 'Ratio', en: 'Ratio' },\r\n    actual_dist: { es: 'Dist. Real', en: 'Actual Dist.' },\r\n    optimal_dist: { es: 'Dist. \u00d3ptima', en: 'Optimal Dist.' },\r\n    airport: { es: 'Aeropuerto', en: 'Airport' },\r\n    min_altitude: { es: 'Alt. M\u00ednima', en: 'Min. Altitude' },\r\n    apply_button: { es: 'Aplicar', en: 'Apply' },\r\n    cancel_button: { es: 'Cancelar', en: 'Cancel' },\r\n    onboarding_steps: [\r\n        { es: { title: \"\u00a1Bienvenido al Dashboard de KPIs!\", text: \"Este es un r\u00e1pido tour para mostrarte c\u00f3mo funciona. Usa los botones de navegaci\u00f3n para continuar.\" }, en: { title: \"Welcome to the KPI Dashboard!\", text: \"This is a quick tour to show you how it works. Use the navigation buttons to continue.\" } },\r\n        { es: { title: \"1. Selecciona las Fechas\", text: \"Usa este calendario para elegir el rango de fechas que quieres analizar. Puedes seleccionar d\u00edas, semanas o meses.\" }, en: { title: \"1. Select the Dates\", text: \"Use this calendar to choose the date range you want to analyze. You can select days, weeks, or months.\" } },\r\n        { es: { title: \"2. Actualiza los Datos\", text: \"Una vez que hayas elegido las fechas, haz clic en 'Actualizar Dashboard'. Todas las m\u00e9tricas y gr\u00e1ficos se recargar\u00e1n con la nueva informaci\u00f3n.\" }, en: { title: \"2. Update the Data\", text: \"Once you have chosen the dates, click 'Update Dashboard'. All metrics and charts will reload with the new information.\" } },\r\n        { es: { title: \"3. Explora cada KPI\", text: \"Cada tarjeta tiene un bot\u00f3n de ayuda. Haz clic en \u00e9l para entender qu\u00e9 significa cada m\u00e9trica y por qu\u00e9 es importante.\" }, en: { title: \"3. Explore each KPI\", text: \"Each card has a help button. Click on it to understand what each metric means and why it's important.\" } }\r\n    ],\r\n    onboarding_prev_btn: { es: 'Anterior', en: 'Previous' },\r\n    onboarding_next_btn: { es: 'Siguiente', en: 'Next' },\r\n    onboarding_finish_btn: { es: 'Finalizar', en: 'Finish' },\r\n    kpi_explanations: {\r\n        'summary-card': { es: \"Muestra el volumen total de datos procesados (registros) y el n\u00famero de aeronaves \u00fanicas detectadas en el per\u00edodo de tiempo seleccionado.\", en: \"Shows the total volume of processed data (records) and the number of unique aircraft detected in the selected time period.\" },\r\n        'hfe-card': { es: \"La Eficiencia Horizontal de Vuelo (HFE) compara la distancia real volada contra la distancia \u00f3ptima (directa). Un ratio de 1.0 es perfecto. El '% de Ineficiencia' muestra cu\u00e1nto m\u00e1s se vol\u00f3. Valores altos pueden indicar vectores ineficientes.\", en: \"Horizontal Flight Efficiency (HFE) compares the actual distance flown against the optimal (direct) distance. A ratio of 1.0 is perfect. The '% Inefficiency' shows how much extra distance was flown. High values may indicate inefficient vectors.\" },\r\n        'leveloff-climb-card': { es: \"Detecta y cuantifica cuando una aeronave deja de ascender y vuela nivelada por un tiempo antes de continuar el ascenso. Esto puede indicar restricciones del control de tr\u00e1fico y generar un consumo extra de combustible.\", en: \"Detects and quantifies when an aircraft stops climbing and flies level for a period before resuming the climb. This can indicate air traffic control restrictions and lead to extra fuel consumption.\" },\r\n        'leveloff-descent-card': { es: \"Similar al de ascenso, este KPI mide las interrupciones en un descenso continuo. Los 'level-offs' en descenso son comunes en aproximaciones escalonadas y pueden reducir la eficiencia del perfil de llegada.\", en: \"Similar to the climb KPI, this metric measures interruptions in a continuous descent. Descent level-offs are common in stepped approaches and can reduce the efficiency of the arrival profile.\" },\r\n        'go-around-card': { es: \"Contabiliza el n\u00famero de maniobras de 'Go-Around' (aproximaci\u00f3n frustrada) en los aeropuertos de SABE y SAEZ. Es un indicador clave de seguridad y eficiencia en la fase final de la aproximaci\u00f3n.\", en: \"Counts the number of 'Go-Around' maneuvers (missed approaches) at SABE and SAEZ airports. It is a key indicator of safety and efficiency in the final approach phase.\" },\r\n        'speed-chart-card': { es: \"Visualiza la velocidad promedio (en nudos) de todas las aeronaves a diferentes niveles de vuelo (altitudes). Permite identificar si las velocidades se ajustan a los procedimientos est\u00e1ndar en cada fase del vuelo.\", en: \"Displays the average speed (in knots) of all aircraft at different flight levels (altitudes). It helps identify whether speeds comply with standard procedures in each phase of flight.\" },\r\n        'tma-chart-card': { es: \"Muestra el n\u00famero promedio de aeronaves que se encuentran simult\u00e1neamente dentro del TMA (\u00c1rea de Maniobras Terminal) de Baires, agrupadas por hora del d\u00eda. Ayuda a identificar picos de congesti\u00f3n y carga de trabajo para el control a\u00e9reo.\", en: \"Shows the average number of aircraft simultaneously within the TMA (Terminal Maneuvering Area) of Baires, grouped by time of day. It helps identify peaks in congestion and workload for air traffic control.\" }\r\n    }\r\n  };\r\n\r\n  function T(key) { return translations[key] ? (translations[key][lang] || translations[key]['es']) : key; }\r\n\r\n  function applyStaticTranslations() {\r\n    document.querySelectorAll('[data-translate-key]').forEach(el => {\r\n        const key = el.dataset.translateKey;\r\n        if(T(key)) el.innerText = T(key);\r\n    });\r\n  }\r\n\r\n  \/\/ Helpers para mostrar estados\r\n  const showState = (cardId, content) => {\r\n    const card = document.getElementById(cardId); if (!card) return;\r\n    const overlay = card.querySelector('.card-state-overlay'); if (!overlay) return;\r\n    const stateContent = overlay.querySelector('.state-content'); if (!stateContent) return;\r\n    stateContent.innerHTML = content; overlay.classList.add('visible');\r\n  };\r\n  const hideState = (cardId) => {\r\n    const card = document.getElementById(cardId); if (!card) return;\r\n    const overlay = card.querySelector('.card-state-overlay'); if (!overlay) return;\r\n    overlay.classList.remove('visible');\r\n  };\r\n  const showLoading = (cardId) => showState(cardId, `<div class=\"spinner\"><\/div><p>${T('loading_message')}<\/p>`);\r\n  const showError = (cardId, message) => showState(cardId, `<p style=\"color:var(--error-color);\">\u274c ${T('error_message')}<\/p><p>${message}<\/p>`);\r\n  const showEmpty = (cardId, message) => showState(cardId, `<p>\ud83e\udd37\u200d\u2640\ufe0f<\/p><p>${message}<\/p>`);\r\n\r\n  \/\/ Inicializar date range picker\r\n  datePicker.daterangepicker({\r\n      opens: 'left',\r\n      startDate: moment().utc(),\r\n      endDate: moment().utc(),\r\n      locale: { applyLabel: T('apply_button'), cancelLabel: T('cancel_button'), format: \"YYYY-MM-DD\" }\r\n  });\r\n\r\n  const chartTextColor = getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim();\r\n  const chartGridColor = getComputedStyle(document.documentElement).getPropertyValue('--border-color').trim();\r\n  const primaryAccent = getComputedStyle(document.documentElement).getPropertyValue('--primary-accent').trim();\r\n  const secondaryAccent = getComputedStyle(document.documentElement).getPropertyValue('--secondary-accent').trim();\r\n\r\n  function hexToRGBA(hex, alpha = 1) {\r\n      let r = 0, g = 0, b = 0;\r\n      if (hex && hex.match(\/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$\/)) {\r\n          hex = hex.substring(1); if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];\r\n          r = parseInt(hex.substring(0, 2), 16); g = parseInt(hex.substring(2, 4), 16); b = parseInt(hex.substring(4, 6), 16);\r\n      }\r\n      return `rgba(${r}, ${g}, ${b}, ${alpha})`;\r\n  }\r\n\r\n  const commonChartOptions = {\r\n    responsive: true, maintainAspectRatio: false,\r\n    plugins: { legend: { labels: { color: chartTextColor } } },\r\n    scales: {\r\n      y: { beginAtZero: true, grid: { color: chartGridColor }, ticks: { color: chartTextColor } },\r\n      x: { grid: { color: chartGridColor }, ticks: { color: chartTextColor } }\r\n    }\r\n  };\r\n\r\n  async function fetchApiData(kpi, params = {}) {\r\n    const urlParams = new URLSearchParams({ kpi, ...params });\r\n    const url = `${apiUrl}?${urlParams.toString()}`;\r\n    try {\r\n        const response = await fetch(url);\r\n        if (!response.ok) throw new Error(`Error de red: ${response.statusText}`);\r\n        const data = await response.json();\r\n        if (!data.success) throw new Error(data.error || 'La API devolvi\u00f3 un error');\r\n        return data.data;\r\n    } catch (error) {\r\n        console.error(`Error fetching ${kpi}:`, error);\r\n        throw error;\r\n    }\r\n  }\r\n\r\n  \/\/ --- Modal \/ Info handling (event delegation) ---\r\n  const infoModal = document.getElementById('info-modal');\r\n  const infoModalTitle = document.getElementById('info-modal-title');\r\n  const infoModalText = document.getElementById('info-modal-text');\r\n  const infoModalClose = document.getElementById('info-modal-close');\r\n\r\n  \/\/ Fallbacks para modal (por si falta traducci\u00f3n)\r\n  const modalHelpFallback = {\r\n    es: {\r\n      'summary-card': \"Muestra el volumen total de datos procesados (registros) y el n\u00famero de aeronaves \u00fanicas detectadas en el per\u00edodo seleccionado.\",\r\n      'hfe-card': \"La Eficiencia Horizontal (HFE) compara la distancia real volada con la \u00f3ptima. Un ratio de 1.0 es perfecto.\",\r\n      'leveloff-climb-card': \"Detecta cuando una aeronave deja de ascender y vuela nivelada por un tiempo antes de continuar.\",\r\n      'leveloff-descent-card': \"Mide interrupciones en un descenso continuo; comunes en aproximaciones escalonadas.\",\r\n      'go-around-card': \"Contabiliza maniobras de 'Go-Around' (aproximaciones frustradas) en aeropuertos seleccionados.\",\r\n      'speed-chart-card': \"Velocidad promedio en distintos niveles de vuelo (en nudos).\",\r\n      'tma-chart-card': \"Promedio de aeronaves simult\u00e1neas dentro del TMA por hora del d\u00eda.\",\r\n      'default': \"Informaci\u00f3n del KPI.\"\r\n    },\r\n    en: {\r\n      'summary-card': \"Shows total processed records and number of unique aircraft detected in the selected period.\",\r\n      'hfe-card': \"Horizontal Flight Efficiency (HFE) compares actual flown distance to the optimal distance. 1.0 is perfect.\",\r\n      'leveloff-climb-card': \"Detects when an aircraft levels off during climb before resuming ascent.\",\r\n      'leveloff-descent-card': \"Measures interruptions in continuous descent; common in stepped approaches.\",\r\n      'go-around-card': \"Counts 'Go-Around' maneuvers (missed approaches) at selected airports.\",\r\n      'speed-chart-card': \"Average speed at different flight levels (in knots).\",\r\n      'tma-chart-card': \"Average simultaneous aircraft within the TMA grouped by time of day.\",\r\n      'default': \"KPI information.\"\r\n    }\r\n  };\r\n\r\n  \/\/ Delegaci\u00f3n: detectar clicks en botones ? dentro del grid\r\n  document.querySelector('.dashboard-grid').addEventListener('click', (e) => {\r\n    const infoBtn = e.target.closest('.info-btn');\r\n    if (!infoBtn) return;\r\n    const card = infoBtn.closest('.kpi-card');\r\n    if (!card) return;\r\n    const cardId = card.id;\r\n    const titleEl = card.querySelector('h4 .title-content');\r\n    const title = titleEl ? titleEl.innerText : '';\r\n    infoModalTitle.innerText = title;\r\n\r\n    \/\/ intento leer la explicaci\u00f3n oficial desde translations.kpi_explanations\r\n    let explanation = \"\";\r\n    if (translations.kpi_explanations && translations.kpi_explanations[cardId]) {\r\n      explanation = translations.kpi_explanations[cardId][lang] || translations.kpi_explanations[cardId]['es'] || \"\";\r\n    }\r\n    \/\/ fallback\r\n    if (!explanation) {\r\n      explanation = (modalHelpFallback[lang] && modalHelpFallback[lang][cardId]) ? modalHelpFallback[lang][cardId] : (modalHelpFallback[lang] ? modalHelpFallback[lang]['default'] : modalHelpFallback['es']['default']);\r\n    }\r\n\r\n    infoModalText.innerText = explanation;\r\n    infoModal.classList.add('visible');\r\n  });\r\n\r\n  const closeInfoModal = () => infoModal.classList.remove('visible');\r\n  if (infoModalClose) infoModalClose.addEventListener('click', closeInfoModal);\r\n  if (infoModal) infoModal.addEventListener('click', (e) => { if (e.target === infoModal) closeInfoModal(); });\r\n\r\n  \/\/ Onboarding modal logic\r\n  const onboardingModal = document.getElementById('onboarding-modal');\r\n  const onboardingClose = document.getElementById('onboarding-modal-close');\r\n  const onboardingTitle = document.getElementById('onboarding-title');\r\n  const onboardingText = document.getElementById('onboarding-text');\r\n  const onboardingPrevBtn = document.getElementById('onboarding-prev');\r\n  const onboardingNextBtn = document.getElementById('onboarding-next');\r\n  const onboardingDotsContainer = document.getElementById('onboarding-dots');\r\n  let currentStep = 0;\r\n\r\n  function showTutorialStep(stepIndex) {\r\n      const step = translations.onboarding_steps[stepIndex][lang];\r\n      onboardingTitle.innerText = step.title;\r\n      onboardingText.innerText = step.text;\r\n      onboardingPrevBtn.innerText = T('onboarding_prev_btn');\r\n      onboardingPrevBtn.disabled = stepIndex === 0;\r\n      onboardingNextBtn.innerText = stepIndex === translations.onboarding_steps.length - 1 ? T('onboarding_finish_btn') : T('onboarding_next_btn');\r\n      onboardingDotsContainer.innerHTML = '';\r\n      translations.onboarding_steps.forEach((_, index) => {\r\n          const dot = document.createElement('span');\r\n          if (index === stepIndex) dot.classList.add('active');\r\n          onboardingDotsContainer.appendChild(dot);\r\n      });\r\n  }\r\n\r\n  function closeOnboarding() {\r\n      onboardingModal.classList.remove('visible');\r\n      localStorage.setItem('dashboardOnboardingComplete', 'true');\r\n  }\r\n\r\n  onboardingNextBtn.addEventListener('click', () => {\r\n    if (currentStep < translations.onboarding_steps.length - 1) {\r\n        currentStep++;\r\n        showTutorialStep(currentStep);\r\n    } else { closeOnboarding(); }\r\n  });\r\n  onboardingPrevBtn.addEventListener('click', () => {\r\n    if (currentStep > 0) {\r\n        currentStep--;\r\n        showTutorialStep(currentStep);\r\n    }\r\n  });\r\n  onboardingClose.addEventListener('click', closeOnboarding);\r\n\r\n  if (localStorage.getItem('dashboardOnboardingComplete') !== 'true') {\r\n    onboardingModal.classList.add('visible');\r\n    showTutorialStep(0);\r\n  }\r\n\r\n  \/\/ Charts & KPI creation functions (tu l\u00f3gica original, ligeramente reorganizada)\r\n  async function createOrUpdateSummaryAndSpeedChart(start, end) {\r\n    const speedCardId = 'speed-chart-card'; const summaryCardId = 'summary-card';\r\n    showLoading(speedCardId); showLoading(summaryCardId);\r\n    try {\r\n      const data = await fetchApiData('speed_by_level', { start, end });\r\n      document.getElementById('kpi-total-records').textContent = (data.summary_data.total_records || 0).toLocaleString();\r\n      document.getElementById('kpi-unique-aircraft').textContent = (data.summary_data.unique_aircraft || 0).toLocaleString();\r\n      hideState(summaryCardId);\r\n      if (!data.chart_data || !data.chart_data.data || data.chart_data.data.length === 0) {\r\n        showEmpty(speedCardId, T('empty_message')); if(speedChart) speedChart.destroy(); return;\r\n      }\r\n      const ctx = document.getElementById('speedByLevelChart').getContext('2d');\r\n      const gradient = ctx.createLinearGradient(0, 0, 0, ctx.canvas.height);\r\n      gradient.addColorStop(0, hexToRGBA(primaryAccent, 0.5)); gradient.addColorStop(1, hexToRGBA(primaryAccent, 0.05));\r\n      if (speedChart) speedChart.destroy();\r\n      speedChart = new Chart(ctx, {\r\n        type: 'line', data: { labels: data.chart_data.labels, datasets: [{ label: T('avg_speed_chart_label'), data: data.chart_data.data, borderColor: primaryAccent, backgroundColor: gradient, fill: true, pointRadius: 0, tension: 0.3 }] },\r\n        options: { ...commonChartOptions, scales: { ...commonChartOptions.scales, y: { ...commonChartOptions.scales.y, beginAtZero: false }}}\r\n      });\r\n      hideState(speedCardId);\r\n    } catch (error) { showError(speedCardId, error.message); showError(summaryCardId, T('error_load_message')); }\r\n  }\r\n\r\n  async function createOrUpdateTmaChart(start, end) {\r\n    const cardId = 'tma-chart-card'; showLoading(cardId);\r\n    try {\r\n      const interval = intervalSelector.value;\r\n      const tmaData = await fetchApiData('tma_simultaneous_traffic', { start, end, interval });\r\n      if (!tmaData.data || tmaData.data.length === 0) {\r\n        showEmpty(cardId, T('empty_message')); if (tmaChart) tmaChart.destroy(); return;\r\n      }\r\n      const aggregation = {};\r\n      tmaData.labels.forEach((label, index) => {\r\n        const timeMatch = String(label).match(\/(\\d{2}:\\d{2})\/); if (!timeMatch) return;\r\n        const timeOfDay = timeMatch[0];\r\n        if (!aggregation[timeOfDay]) aggregation[timeOfDay] = { sum: 0, count: 0 };\r\n        aggregation[timeOfDay].sum += tmaData.data[index] || 0; aggregation[timeOfDay].count += 1;\r\n      });\r\n      const aggregatedLabels = Object.keys(aggregation).sort();\r\n      const aggregatedData = aggregatedLabels.map(label => aggregation[label].count > 0 ? aggregation[label].sum \/ aggregation[label].count : 0);\r\n      const ctx = document.getElementById('tmaChart').getContext('2d');\r\n      if (tmaChart) tmaChart.destroy();\r\n      tmaChart = new Chart(ctx, {\r\n        type: 'bar', data: { labels: aggregatedLabels, datasets: [{ label: T('avg_simultaneous_ac_label'), data: aggregatedData, backgroundColor: hexToRGBA(secondaryAccent, 0.6), borderColor: secondaryAccent, borderWidth: 1 }] },\r\n        options: { ...commonChartOptions,\r\n          plugins: { ...commonChartOptions.plugins, zoom: { zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'x' }, pan: { enabled: true, mode: 'x' } } },\r\n          scales: { x: { ...commonChartOptions.scales.x, title: { display: true, text: T('time_of_day_axis_label'), color: chartTextColor }, grid: { display: false } }, y: { ...commonChartOptions.scales.y, title: { display: true, text: T('avg_aircraft_axis_label'), color: chartTextColor } } }\r\n        }\r\n      });\r\n      hideState(cardId);\r\n    } catch (error) { showError(cardId, error.message); }\r\n  }\r\n\r\n  async function createOrUpdateClimbLevelOffData(start, end) {\r\n      const cardId = 'leveloff-climb-card'; showLoading(cardId);\r\n      try {\r\n        const [aggregates, details] = await Promise.all([ fetchApiData('level_off_aggregates_climb', { start, end }), fetchApiData('level_off_summary_climb', { start, end }) ]);\r\n        if (!aggregates.summary || aggregates.summary.total_seconds === 0) {\r\n          showEmpty(cardId, T('empty_message')); document.getElementById('kpi-total-level-off-climb-seconds').textContent = '0'; document.getElementById('kpi-avg-level-off-climb-seconds').textContent = '0s';\r\n          if (levelOffClimbDistChart) levelOffClimbDistChart.destroy(); document.getElementById('level-off-climb-details-container').innerHTML = ''; return;\r\n        }\r\n        document.getElementById('kpi-total-level-off-climb-seconds').textContent = (aggregates.summary.total_seconds \/ 60).toFixed(1);\r\n        document.getElementById('kpi-avg-level-off-climb-seconds').textContent = aggregates.summary.average_seconds.toFixed(1) + 's';\r\n        const ctx = document.getElementById('levelOffClimbDistributionChart').getContext('2d');\r\n        if (levelOffClimbDistChart) levelOffClimbDistChart.destroy();\r\n        levelOffClimbDistChart = new Chart(ctx, {\r\n          type: 'bar', data: { labels: aggregates.distribution.labels, datasets: [{ label: T('events_label'), data: aggregates.distribution.data, backgroundColor: hexToRGBA(primaryAccent, 0.6), borderColor: primaryAccent, borderWidth: 1 }] },\r\n          options: { ...commonChartOptions, plugins: { legend: { display: false } }, scales: { ...commonChartOptions.scales, x: { ...commonChartOptions.scales.x, grid: { display: false }, ticks: { ...commonChartOptions.scales.x.ticks, font: { size: 10 } } }, y: { ...commonChartOptions.scales.y, ticks: { stepSize: 1 } } } }\r\n        });\r\n        const container = document.getElementById('level-off-climb-details-container');\r\n        if (!details || details.length === 0) container.innerHTML = `<p>${T('no_cases_message')}<\/p>`;\r\n        else {\r\n          let html = `<table class=\"details-table\"><thead><tr><th>${T('callsign')}<\/th><th>${T('time_utc')}<\/th><th>${T('duration')}<\/th><th>${T('altitude')}<\/th><\/tr><\/thead><tbody>`;\r\n          details.forEach(f => html += `<tr><td>${f.callsign || 'N\/A'}<\/td><td>${moment(f.start_time).format('MM-DD HH:mm')}<\/td><td>${f.duration_seconds} seg<\/td><td>${f.altitude_ft} ft<\/td><\/tr>`);\r\n          container.innerHTML = html + '<\/tbody><\/table>';\r\n        }\r\n        hideState(cardId);\r\n      } catch (error) { showError(cardId, error.message); }\r\n    }\r\n\r\n  async function createOrUpdateDescentLevelOffData(start, end) {\r\n    const cardId = 'leveloff-descent-card'; showLoading(cardId);\r\n    try {\r\n      const [aggregates, details] = await Promise.all([ fetchApiData('level_off_aggregates_descent', { start, end }), fetchApiData('level_off_summary_descent', { start, end }) ]);\r\n      if (!aggregates.summary || aggregates.summary.total_seconds === 0) {\r\n        showEmpty(cardId, T('empty_message')); document.getElementById('kpi-total-level-off-descent-seconds').textContent = '0'; document.getElementById('kpi-avg-level-off-descent-seconds').textContent = '0s';\r\n        if (levelOffDescentDistChart) levelOffDescentDistChart.destroy(); document.getElementById('level-off-descent-details-container').innerHTML = ''; return;\r\n      }\r\n      document.getElementById('kpi-total-level-off-descent-seconds').textContent = (aggregates.summary.total_seconds \/ 60).toFixed(1);\r\n      document.getElementById('kpi-avg-level-off-descent-seconds').textContent = aggregates.summary.average_seconds.toFixed(1) + 's';\r\n      const ctx = document.getElementById('levelOffDescentDistributionChart').getContext('2d');\r\n      if (levelOffDescentDistChart) levelOffDescentDistChart.destroy();\r\n      levelOffDescentDistChart = new Chart(ctx, {\r\n        type: 'bar', data: { labels: aggregates.distribution.labels, datasets: [{ label: T('events_label'), data: aggregates.distribution.data, backgroundColor: hexToRGBA(secondaryAccent, 0.6), borderColor: secondaryAccent, borderWidth: 1 }] },\r\n        options: { ...commonChartOptions, plugins: { legend: { display: false } }, scales: { ...commonChartOptions.scales, x: { ...commonChartOptions.scales.x, grid: { display: false }, ticks: { ...commonChartOptions.scales.x.ticks, font: { size: 10 } } }, y: { ...commonChartOptions.scales.y, ticks: { stepSize: 1 } } } }\r\n      });\r\n      const container = document.getElementById('level-off-descent-details-container');\r\n      if (!details || details.length === 0) container.innerHTML = `<p>${T('no_cases_message')}<\/p>`;\r\n      else {\r\n        let html = `<table class=\"details-table\"><thead><tr><th>${T('callsign')}<\/th><th>${T('time_utc')}<\/th><th>${T('duration')}<\/th><th>${T('altitude')}<\/th><\/tr><\/thead><tbody>`;\r\n        details.forEach(f => html += `<tr><td>${f.callsign || 'N\/A'}<\/td><td>${moment(f.start_time).format('MM-DD HH:mm')}<\/td><td>${f.duration_seconds} seg<\/td><td>${f.altitude_ft} ft<\/td><\/tr>`);\r\n        container.innerHTML = html + '<\/tbody><\/table>';\r\n      }\r\n      hideState(cardId);\r\n    } catch (error) { showError(cardId, error.message); }\r\n  }\r\n\r\n  async function createOrUpdateGoAroundData(start, end) {\r\n    const cardId = 'go-around-card'; showLoading(cardId);\r\n    try {\r\n        const data = await fetchApiData('go_around_summary', { start, end });\r\n        if (!data || data.total_count === 0) {\r\n            showEmpty(cardId, T('empty_message')); document.getElementById('kpi-go-around-total').textContent = '0';\r\n            document.getElementById('go-around-details-container').innerHTML = ''; return;\r\n        }\r\n        document.getElementById('kpi-go-around-total').textContent = data.total_count;\r\n        const container = document.getElementById('go-around-details-container');\r\n        if (!data.recent_cases || data.recent_cases.length === 0) {\r\n            container.innerHTML = `<p>${T('no_cases_message')}<\/p>`;\r\n        } else {\r\n            let html = `<table class=\"details-table\"><thead><tr><th>${T('callsign')}<\/th><th>${T('airport')}<\/th><th>${T('time_utc')}<\/th><th>${T('min_altitude')}<\/th><\/tr><\/thead><tbody>`;\r\n            data.recent_cases.forEach(f => {\r\n                html += `<tr><td>${f.callsign || 'N\/A'}<\/td><td>${f.airport_icao}<\/td><td>${moment(f.timestamp_event).utc().format('MM-DD HH:mm')}<\/td><td>${f.min_altitude_ft} ft<\/td><\/tr>`;\r\n            });\r\n            container.innerHTML = html + '<\/tbody><\/table>';\r\n        }\r\n        hideState(cardId);\r\n    } catch (error) { showError(cardId, error.message); }\r\n  }\r\n\r\n  async function createOrUpdateHfeSummary(start, end) {\r\n    const cardId = 'hfe-card';\r\n    showLoading(cardId);\r\n    try {\r\n        const [summaryData, detailsData] = await Promise.all([\r\n            fetchApiData('hfe_summary', { start, end }),\r\n            fetchApiData('hfe_details', { start, end })\r\n        ]);\r\n        if (!summaryData || summaryData.total_flights === 0) {\r\n            showEmpty(cardId, T('empty_message')); document.getElementById('kpi-hfe-ratio').textContent = 'N\/A';\r\n            document.getElementById('kpi-hfe-extra').textContent = 'N\/A';\r\n            document.getElementById('hfe-details-container').innerHTML = ''; return;\r\n        }\r\n        const ratio = summaryData.avg_hfe_ratio;\r\n        const inefficiency = (ratio - 1) * 100;\r\n        document.getElementById('kpi-hfe-ratio').textContent = ratio.toFixed(3);\r\n        document.getElementById('kpi-hfe-extra').textContent = inefficiency.toFixed(1) + '%';\r\n        \r\n        const container = document.getElementById('hfe-details-container');\r\n        if (!detailsData || detailsData.length === 0) {\r\n            container.innerHTML = `<p>${T('no_cases_message')}<\/p>`;\r\n        } else {\r\n            let html = `<table class=\"details-table\"><thead><tr><th>${T('callsign')}<\/th><th>${T('time_utc')}<\/th><th>${T('ratio')}<\/th><th>${T('actual_dist')}<\/th><th>${T('optimal_dist')}<\/th><\/tr><\/thead><tbody>`;\r\n            detailsData.forEach(f => {\r\n                html += `<tr><td>${f.callsign || 'N\/A'}<\/td><td>${moment(f.entry_time).utc().format('MM-DD HH:mm')}<\/td><td>${Number(f.hfe_ratio).toFixed(3)}<\/td><td>${Number(f.distancia_real_nm).toFixed(1)} NM<\/td><td>${Number(f.distancia_ideal_nm).toFixed(1)} NM<\/td><\/tr>`;\r\n            });\r\n            container.innerHTML = html + '<\/tbody><\/table>';\r\n        }\r\n        hideState(cardId);\r\n    } catch (error) { showError(cardId, error.message); }\r\n  }\r\n\r\n  function toggleDetails(containerId, button) {\r\n    const c = document.getElementById(containerId);\r\n    if (!c) return;\r\n    const isVisible = c.style.display === 'block';\r\n    c.style.display = isVisible ? 'none' : 'block';\r\n    button.textContent = isVisible ? button.dataset.hideText : button.dataset.showText;\r\n  }\r\n\r\n  document.querySelectorAll('.toggle-details-btn').forEach(button => {\r\n    button.addEventListener('click', (e) => {\r\n      toggleDetails(e.target.id.replace('toggle-', '') + '-container', e.target);\r\n    });\r\n  });\r\n\r\n  async function updateDashboard() {\r\n      document.querySelectorAll('.toggle-details-btn').forEach(btn => {\r\n        btn.dataset.showText = T(btn.dataset.translateKey);\r\n        btn.dataset.hideText = T('hide_cases_btn');\r\n        btn.textContent = btn.dataset.showText;\r\n        const containerId = btn.id.replace('toggle-', '') + '-container';\r\n        const container = document.getElementById(containerId);\r\n        if (container) container.style.display = 'none';\r\n      });\r\n      const start = datePicker.data('daterangepicker').startDate.format('YYYY-MM-DD');\r\n      const end = datePicker.data('daterangepicker').endDate.format('YYYY-MM-DD');\r\n      await Promise.all([\r\n          createOrUpdateSummaryAndSpeedChart(start, end), createOrUpdateTmaChart(start, end),\r\n          createOrUpdateClimbLevelOffData(start, end), createOrUpdateDescentLevelOffData(start, end),\r\n          createOrUpdateGoAroundData(start, end), createOrUpdateHfeSummary(start, end)\r\n      ]);\r\n  }\r\n\r\n  \/\/ Aplicar textos est\u00e1ticos\r\n  applyStaticTranslations();\r\n\r\n  \/\/ event listeners\r\n  if (updateBtn) updateBtn.addEventListener('click', updateDashboard);\r\n\r\n  \/\/ arrancar\r\n  updateDashboard().catch(err => {\r\n    console.error('Error inicializando dashboard:', err);\r\n  });\r\n\r\n});\r\ndocument.addEventListener('DOMContentLoaded', () => {\r\n\r\n  \/\/ Esperar hasta que el modal est\u00e9 presente en el DOM\r\n  const waitForModal = setInterval(() => {\r\n    const infoModalText = document.getElementById('info-modal-text');\r\n    if (infoModalText) {\r\n      clearInterval(waitForModal);\r\n\r\n      const modalTranslations = {\r\n        es: `\r\n          <h2>Ayuda del panel<\/h2>\r\n          <p>Este panel muestra los principales indicadores de rendimiento (KPI) relacionados con la eficiencia de vuelo.<\/p>\r\n          <ul>\r\n            <li><strong>Eficiencia Horizontal (HFE):<\/strong> compara la distancia volada real con la \u00f3ptima.<\/li>\r\n            <li><strong>Level-offs:<\/strong> detecta segmentos donde una aeronave deja de ascender o descender.<\/li>\r\n            <li><strong>Go-Arounds:<\/strong> contabiliza las aproximaciones frustradas en los aeropuertos.<\/li>\r\n            <li><strong>Velocidades:<\/strong> muestra la media por nivel de vuelo.<\/li>\r\n            <li><strong>TMA:<\/strong> promedio de aeronaves dentro del espacio TMA por hora.<\/li>\r\n          <\/ul>\r\n        `,\r\n        en: `\r\n          <h2>Dashboard Help<\/h2>\r\n          <p>This dashboard displays the main performance indicators (KPIs) related to flight efficiency.<\/p>\r\n          <ul>\r\n            <li><strong>Horizontal Flight Efficiency (HFE):<\/strong> compares actual flown distance to the optimal route.<\/li>\r\n            <li><strong>Level-offs:<\/strong> detects when an aircraft stops climbing or descending temporarily.<\/li>\r\n            <li><strong>Go-Arounds:<\/strong> counts missed approaches at monitored airports.<\/li>\r\n            <li><strong>Speeds:<\/strong> shows the average speed per flight level.<\/li>\r\n            <li><strong>TMA:<\/strong> average number of aircraft within the TMA per hour.<\/li>\r\n          <\/ul>\r\n        `\r\n      };\r\n\r\n      const currentLang = document.documentElement.lang.startsWith('en') ? 'en' : 'es';\r\n      infoModalText.innerHTML = modalTranslations[currentLang];\r\n    }\r\n  }, 200); \/\/ Revisa cada 200 ms hasta que el modal aparezca\r\n});\r\n\r\n\r\n<\/script>\r\n<\/body>\r\n<\/html>\r\n\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-dashboard.php","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-804","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>Dashboard TMA BAIRES - NB Consulting<\/title>\n<meta name=\"description\" content=\"Dashboard de TMA BAIRES con distintos KPI tales como eficiencia horizontal, vertical, analisis de go-arounds, actualizado diariamente.\" \/>\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\/dashboard\/\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Dashboard TMA BAIRES - NB Consulting\" \/>\n<meta property=\"og:description\" content=\"Dashboard de TMA BAIRES con distintos KPI tales como eficiencia horizontal, vertical, analisis de go-arounds, actualizado diariamente.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/nb-consulting.pro\/en\/dashboard\/\" \/>\n<meta property=\"og:site_name\" content=\"NB Consulting\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-04T10:41:16+00:00\" \/>\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\/dashboard\/\",\"url\":\"https:\/\/nb-consulting.pro\/dashboard\/\",\"name\":\"Dashboard TMA BAIRES - NB Consulting\",\"isPartOf\":{\"@id\":\"https:\/\/nb-consulting.pro\/#website\"},\"datePublished\":\"2025-10-15T12:17:49+00:00\",\"dateModified\":\"2025-11-04T10:41:16+00:00\",\"description\":\"Dashboard de TMA BAIRES con distintos KPI tales como eficiencia horizontal, vertical, analisis de go-arounds, actualizado diariamente.\",\"breadcrumb\":{\"@id\":\"https:\/\/nb-consulting.pro\/dashboard\/#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/nb-consulting.pro\/dashboard\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/nb-consulting.pro\/dashboard\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Portada\",\"item\":\"https:\/\/nb-consulting.pro\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Dashboard TMA BAIRES\"}]},{\"@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":"Dashboard TMA BAIRES - NB Consulting","description":"Dashboard de TMA BAIRES con distintos KPI tales como eficiencia horizontal, vertical, analisis de go-arounds, actualizado diariamente.","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\/dashboard\/","og_locale":"en_GB","og_type":"article","og_title":"Dashboard TMA BAIRES - NB Consulting","og_description":"Dashboard de TMA BAIRES con distintos KPI tales como eficiencia horizontal, vertical, analisis de go-arounds, actualizado diariamente.","og_url":"https:\/\/nb-consulting.pro\/en\/dashboard\/","og_site_name":"NB Consulting","article_modified_time":"2025-11-04T10:41:16+00:00","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\/dashboard\/","url":"https:\/\/nb-consulting.pro\/dashboard\/","name":"Dashboard TMA BAIRES - NB Consulting","isPartOf":{"@id":"https:\/\/nb-consulting.pro\/#website"},"datePublished":"2025-10-15T12:17:49+00:00","dateModified":"2025-11-04T10:41:16+00:00","description":"Dashboard de TMA BAIRES con distintos KPI tales como eficiencia horizontal, vertical, analisis de go-arounds, actualizado diariamente.","breadcrumb":{"@id":"https:\/\/nb-consulting.pro\/dashboard\/#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/nb-consulting.pro\/dashboard\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/nb-consulting.pro\/dashboard\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Portada","item":"https:\/\/nb-consulting.pro\/"},{"@type":"ListItem","position":2,"name":"Dashboard TMA BAIRES"}]},{"@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\/804","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=804"}],"version-history":[{"count":7,"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/pages\/804\/revisions"}],"predecessor-version":[{"id":879,"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/pages\/804\/revisions\/879"}],"wp:attachment":[{"href":"https:\/\/nb-consulting.pro\/en\/wp-json\/wp\/v2\/media?parent=804"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}