DataHub España — Arquitectura
Repo
- GitHub:
Ntizar/DataHubEspana (PÚBLICO)
- Pages:
https://ntizar.github.io/DataHubEspana/
- Workflow:
.github/workflows/pages.yml (actions/deploy-pages@v4)
- Branch:
main
Estructura
DataHubEspana/
├── index.html # Dashboard principal (SPA)
├── data/
│ └── codigos-postales-centroids.json # 251 centroides postales
├── scrapers/ # 19 scrapers Python
│ ├── esios/ # ESIOS/REE — energía
│ ├── aemet/ # AEMET — meteorología
│ ├── boe/ # BOE — boletín oficial
│ ├── borme/ # BORME — mercantil
│ ├── catastro/ # Catastro — catastro
│ ├── contratacion/ # Contratación pública
│ ├── dgt/ # DGT — tráfico
│ ├── embalses/ # Embalses — agua
│ ├── ine/ # INE — demografía
│ ├── ign/ # IGN — geográfico
│ ├── idee/ # IDEE — espacial
│ ├── oapn/ # OAPN — parques nacionales
│ ├── puertos/ # Puertos del Estado
│ ├── subvenciones/ # Subvenciones
│ ├── madrid/ # Madrid CKAN
│ ├── ckan_multi_portal/ # CKAN multi-portal
│ ├── nap_transporte/ # NAP Transportes
│ ├── datos_gob/ # datos.gob.es
│ └── orchestrator_v2/ # Orquestador maestro
└── tools/
├── dashboard-economico/ # Dashboard económico
├── mapa-trafico/ # Mapa de tráfico
└── monitor-energia/ # Monitor de energía
Stack
- CSS: Inline CSS (sin framework externo). Diseño limpio profesional.
- Mapa: Leaflet 1.9.4 + Layer Control (carreteras, ferrocarriles, choropleth toggleable, parques markers)
- Charts: Chart.js 4.4.4
- Base: HTML/CSS/JS puro (sin framework)
- Deploy: GitHub Pages via GitHub Actions
Layout v2.6 (2026-06-30) — Tabs horizontales + capas toggleables
- Pestañas en barra horizontal
#tab-navbar arriba del mapa (scrollable horizontal) — David: "las pestañas deberían salir arriba y se viesen más datos"
- Sidebar solo muestra contenido de la pestaña activa (no botones de tabs)
- Botón ☰ para colapsar/expandir sidebar
- Pronóstico 7 días fusionado en Clima (ya no pestaña separada)
- Transporte ELIMINADO (API DGT rota, datos estáticos inútiles — David: "no aporta información")
- Tipografía reducida: KPIs 14-17px, títulos 12px, body 10-11px
- Total: 35+ pestañas (Panel, Energía, Clima, Agua, Economía, Ambiente, Catastro, Población, Economía Det., Calidad Aire, Demografía, Puertos, Polen, Inundaciones, Suelo + 20 de cron: GBFS, Nieve, Mar, UV, Visibilidad, Ráfagas, Lluvia, Nubosidad, Presión, Fuego, ET0, CAPE, Horas Sol, Rocío, Temp Suelo, Radiación, Sensación, Aire Ext, Mareas, Eólica)
Capas de datos (13+ pestañas)
Cron Job Orchestration (20+ tabs)
Cuando se necesitan crear many tabs independientes, usar cron jobs one-shot espaciados 10 min:
- Cada cron:
git pull → modificar index.html → verificar DOM → commit → push
- IMPORTANTE: cada cron debe hacer
git pull ANTES de modificar para evitar conflictos
- Verificación pre-commit OBLIGATORIA: DOM balance debe ser -1
- Patrón de fix crons: después de crear tabs básicas, segunda tanda que añade selectores, alertas, corrige datos absurdos
Open-Meteo API — Parámetros current vs daily
sunrise y sunset NO son válidos en current=. Solo en daily=. Falla silenciosa.
snow_depth viene en METROS (×100 para cm)
- Parámetros current válidos: temperature_2m, relative_humidity_2m, apparent_temperature, precipitation, rain, snowfall, snow_depth, weather_code, cloud_cover, pressure_msl, surface_pressure, wind_speed_10m, wind_direction_10m, wind_gusts_10m
1. 📊 Panel (vista general)
- KPIs: Población total, Provincias, PVPC, Demanda, Renovables, Temperatura Madrid
- KPIs nuevos v2.3: AQI Europeo, Índice UV (Open-Meteo Air Quality)
- Último terremoto (USGS)
- Hidroelectricidad (embalses)
2. ⚡ Energía (ESIOS/REE)
- PVPC: Precio medio spot (€/MWh) — indicator 1001
- Demanda: Demanda eléctrica total (MW) — indicator 1293
- Renovables: % generación renovable — indicator 1294 (⚠️ fallback 45.2% si 403)
- CO₂: Intensidad de emisiones (g/kWh)
- Gráficos: PVPC últimas 24h (line), Generación por fuente (doughnut)
3. 🌤️ Clima (Open-Meteo) — v2.5: incluye Pronóstico 7 días
- Temperatura, viento, humedad, condición meteorológica
- Pronóstico 7 días integrado (cards por día + 3 gráficos: temp, precipitación, viento)
- Click provincia → clima de su capital + pronóstico actualizado
- Error handling: showToast + fallback values + setTxt() helper
- API:
https://api.open-meteo.com/v1/forecast
4. 💧 Agua (Embalses)
- Nivel medio por cuenca (%)
- Gráfico de barras por cuenca
- Lista con progress bars por embalse
5. 💼 Economía (BOE/BORME)
- Constituciones vs disoluciones (doughnut)
- Datos hardcodeados (mejorable con API real)
6. 🌿 Medio Ambiente (OAPN) — v2.6: parques reales con coords
- 16 parques nacionales con coordenadas reales, superficie (km²), año declaración
- Click en parque → flyTo en mapa (zoom 11, 1.5s animación)
- Markers verdes toggleables desde layer control del mapa
- Overlay layer:
parksOverlay (L.layerGroup con circle markers verdes)
- Coordenadas: Picos de Europa (43.17,-4.85), Ordesa (42.67,-0.02), Aigüestortes (42.58,1.02), Sierra de Guadarrama (40.85,-3.85), Monfragüe (39.83,-6.05), Cabañeros (39.38,-4.32), Doñana (36.95,-6.35), Sierra Nevada (37.05,-3.37), Tablas de Daimiel (39.15,-3.08), Timanfaya (29.00,-13.83), Caldera de Taburiente (28.75,-17.87), Garajonay (28.10,-17.23), Teide (28.27,-16.64), Archipiélago de Cabrera (39.15,2.95), Islas Atlánticas (42.50,-9.00), Sierra de las Nieves (36.68,-5.02)
7. 🏗️ Catastro — v2.6: datos reales por provincia
- Provincia seleccionada: nombre, código INE, CCAA, capital
- Superficie (km²), Densidad (hab/km², calculada), Municipios (conteo)
- Enlace directo a Sede Electrónica del Catastro para la provincia
- Se actualiza al seleccionar provincia en el mapa
9. 👥 Población (INE) — NUEVO v2.1
- Población total: 47.615.034
- Densidad, extranjeros, crecimiento
- Gráfico: Top 10 CCAA (barras horizontales)
- Gráfico: Distribución por edad (doughnut)
10. 📈 Economía detallada — NUEVO v2.1
- PIB: 1.418.352M€, Paro: 11,2%, IPC: 2,8%
- Gráfico: PIB por sector (barras horizontales)
- Gráfico: Paro por provincias Top 10 (barras coloreadas)
11. 🚢 Puertos — v2.6: flyTo en mapa + datos en vivo
- Contenedores TEU, Mercancía total, Pasajeros, Cruceros
- Gráfico: Top 10 puertos por tonelaje (barras horizontales, clickable)
- Lista interactiva: 10 puertos costeros con click → panel detalle
- Panel detalle puerto (al click): oleaje, viento, temperatura, humedad, amanecer/atardecer
- ** flyTo en mapa:** Al hacer click en puerto →
map.flyTo([port.lat, port.lon], 12, {duration: 1.5})
- APIs: Open-Meteo Marine (oleaje) + Open-Meteo Weather (clima)
- Puertos con coordenadas: Algeciras, Valencia, Barcelona, Bilbao, Las Palmas, Tenerife, Cartagena, Huelva, A Coruña, Gijón
12. 🌬️ Calidad del Aire (Open-Meteo Air Quality) — NUEVO v2.3
- API:
https://air-quality-api.open-meteo.com/v1/air-quality (CAMS European forecasts, sin auth)
- KPIs en tiempo real: AQI Europeo, PM2.5, PM10, Ozono O₃, NO₂, CO, Índice UV, Polvo, SO₂
- Gráficos:
- Evolución contaminantes 24h (líneas, hourly data)
- Distribución contaminantes (donut)
- Se actualiza al seleccionar provincia → fetch con centroides
- Fórmula:
?current=european_aqi,us_aqi,pm10,pm2_5,nitrogen_dioxide,ozone,carbon_monoxide,sulphur_dioxide,dust,uv_index&hourly=pm10,pm2_5,nitrogen_dioxide,ozone,carbon_monoxide,sulphur_dioxide&past_days=1&forecast_days=0
- Verificado: Madrid AQI=38, PM2.5=8.8, O3=96, NO2=10.4, CO=126, UV=7.3
13. 📊 Demografía (INE) — NUEVO v2.3
- API:
https://servicios.ine.es/wstempus/js/ES/DATOS_TABLA/9681?tip=AM&nult=1 (población por CCAA y sexo)
- KPIs: Población total, Mujeres, Hombres, Tasa dependencia, Índice envejecimiento
- Gráficos:
- Pirámide poblacional (barras horizontales divergentes, mujeres=rosa/hombres=azul)
- Población por CCAA (top 10, barras horizontales)
- Distribución por sexo (pie chart)
- Verificado: total=47,615,034, women=24,304,407, men=23,310,627, aging=112.7, dependency=49.2%
- Tabla INE 2852: población por provincia y sexo (159 entries, funciona)
14. 🌾 Polen (Open-Meteo Air Quality) — NUEVO v2.4
- API:
https://air-quality-api.open-meteo.com/v1/air-quality?...¤t=alder_pollen,birch_pollen,grass_pollen,mugwort_pollen,olive_pollen,ragweed_pollen
- KPIs: Gramen, Olivo, Abedul, Aliso, Artemisa, Ambrosía (gr/m³)
- Gráficos: Distribución por tipo (barras) + evolución 24h (líneas)
- Se actualiza al seleccionar provincia
15. 🌊 Inundaciones (Open-Meteo Flood) — v2.6: ríos por provincia
- API:
https://flood-api.open-meteo.com/v1/flood?...&daily=river_discharge&past_days=7&forecast_days=7
- KPIs: Caudal actual, Caudal máx 7d, Caudal mín 7d, Caudal medio
- Nombre del río: Mapeo de 52 provincias → río principal (Madrid→Tajo, Barcelona→Llobregat, Sevilla→Guadalquivir, Bilbao→Nervión, Valencia→Turia, Zaragoza→Ebro)
- Gráfico: Línea de caudal 14 días (7 pasados + 7 previsión) con nombre del río en título
- Se actualiza al seleccionar provincia → muestra río correspondiente
16. 🌡️ Suelo (Open-Meteo Forecast) — NUEVO v2.4
- API:
https://api.open-meteo.com/v1/forecast?...&hourly=soil_temperature_6cm,soil_temperature_18cm,soil_temperature_54cm,soil_moisture_0_to_1cm,soil_moisture_1_to_3cm,soil_moisture_3_to_9cm,soil_moisture_9_to_27cm
- KPIs: Temp suelo 6/18/54cm, Humedad suelo 0-1/3-9/9-27cm
- Gráficos: Temperatura por profundidad (3 líneas) + Humedad por profundidad (3 líneas)
Mapa — Capas interactivas (v2.6)
Base tiles
- CARTO light_all (por defecto):
https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png
- OSM estándar:
https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
Overlay layers (toggleables desde Leaflet layer control)
- Carreteras (OpenStreetMap HOT):
https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png
- Ferrocarriles (OpenRailwayMap):
https://tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png
- Provincias (coropleta):
provincesOverlay (L.layerGroup) — choropleth de 52 provincias
- Parques Nacionales:
parksOverlay (L.layerGroup) — 16 markers verdes con circle markers
Layer control
const baseMaps = { 'CARTO': baseOSM, 'OSM': baseOSMStd };
const overlayMaps = { 'Carreteras': roadsLayer, 'Ferrocarriles': railwaysLayer, 'Provincias': provincesOverlay, 'Parques': parksOverlay };
L.control.layers(baseMaps, overlayMaps).addTo(map);
Choropleth (toggleable)
- 52 provincias con GeoJSON simplificado (487KB)
- Canvas renderer para rendimiento
- Click provincia → zoom + panel detalle derecho
- Toggle:
provincesOverlay permite activar/desactivar la capa desde el layer control
Diseño actual (v2.5) — Limpio profesional, compacto
Estilo de cards (CRÍTICO: sin border-left)
- NO usar
border-left de colores → David rechazó explícitamente ("las cards se notan que son ia")
- Usar gradientes sutiles en background:
linear-gradient(135deg, #f0fdf4 0%, #ffffff 100%) para green
- Hover elevación:
box-shadow: 0 4px 12px rgba(0,0,0,0.08); transform: translateY(-1px)
- Colores: Azul #2563eb, Naranja #f97316, Verde #16a34a, Rojo #dc2626
- Fondo KPIs:
#f8fafc base con gradientes por tipo (green/orange/red/blue)
- Border radius: 10px para KPIs, 12px para cards
- Sin glass/blur/saturate → rechazado en v1
Paleta
- Background:
#f8fafc
- Cards:
#ffffff + border: 1px solid #e2e8f0
- Texto primario:
#0f172a
- Texto secundario:
#64748b
- Labels KPI:
#94a3b8 (más sutiles)
Sidebar
- Width: 380px, colapsable con botón ☰
- NO contiene tabs (v2.5: tabs en #tab-navbar horizontal arriba)
- Solo muestra contenido de la pestaña activa
Mapa
- Tile: CARTO light_all
- Choropleth: 52 provincias con GeoJSON simplificado (487KB)
- Canvas renderer para rendimiento
- Click provincia → zoom + panel detalle derecho
Cron Jobs existentes
mastermind-autoconfig — diario 09:00 UTC
chromadb-reindex-semanal — domingo 04:00 UTC
- DataHub 20 tabs creation — 20 crons espaciados 10 min (12:15-15:25 UTC) — crear pestañas nuevas
- DataHub 20 tabs fixes — 20 crons espaciados 10 min (16:00-19:10 UTC) — mejorar cada pestaña
Referencia completa: references/cron-development-pattern.md
Patrón de desarrollo incremental con crons (Lección 2026-06-30)
Flujo de 2 oleadas
- Oleada 1 — Creación: 20 cron jobs, cada uno crea una pestaña nueva con API básica
- Oleada 2 — Mejora: 20 cron jobs, cada uno arregla y mejora la pestaña correspondiente
Por qué funciona
- Cada cron corre en sesión fresca — no hay dependencias entre ellos
- Commits incrementales — cada cron hace git commit + push, así siempre hay un estado funcional
- 10 min de separación — suficiente tiempo para que GitHub Pages despliegue antes del siguiente
- Fix crons mejoran lo creado — la oleada 2 puede leer el estado actual y mejorarlo
Estructura de cada cron creation
REPO: cd /root/workspace/temp-datahub
1. git pull origin main
2. Leer index.html actual
3. Añadir botón de pestaña en tab-navbar
4. Añadir panel HTML en .tab-content (ANTES del cierre de tab-content)
5. Añadir función JavaScript fetch + render
6. Añadir llamada en init()
7. Verificar DOM balance: python3 -c "..."
8. git commit + push
Estructura de cada cron fix
REPO: cd /root/workspace/temp-datahub
1. git pull origin main
2. Leer index.html, encontrar la pestaña específica
3. AÑADIR selector de ciudad/región con 8-10 ciudades
4. MEJORAR datos (clasificaciones, semáforos, alertas)
5. AÑADIR gráficos o mejorar existentes
6. Verificar DOM balance
7. git commit + push
APIs usadas en la creación masiva (20 tabs)
| Tab |
API |
Endpoint |
| GBFS |
GBFS feeds |
bike*.gbfs.com |
| Nieve |
Open-Meteo |
snowfall, snow_depth |
| Mar |
Marine API |
waves, swell, sea_temp |
| UV |
Open-Meteo |
uv_index |
| Visibilidad |
Open-Meteo |
visibility |
| Ráfagas |
Open-Meteo |
wind_gusts_10m |
| Lluvia |
Open-Meteo |
precipitation_probability |
| Nubosidad |
Open-Meteo |
cloud_cover |
| Presión |
Open-Meteo |
pressure_msl |
| Fuego |
Open-Meteo |
cape + wind + humidity |
| ET0 |
Open-Meteo |
et0_fao_evapotranspiration |
| CAPE |
Open-Meteo |
cape |
| Horas Sol |
Open-Meteo |
sunshine_duration |
| Rocío |
Open-Meteo |
dew_point_2m |
| Temp Suelo |
Open-Meteo |
soil_temperature_* |
| Radiación |
Open-Meteo |
shortwave_radiation |
| Sensación |
Open-Meteo |
apparent_temperature |
| Aire Ext |
Air Quality API |
co, so2, nh3 |
| Mareas |
Marine API |
ocean_current, wave_height |
| Eólica |
Open-Meteo |
wind_speed + wind_gusts |
Open-Meteo — Parámetros completos válidos (v3.0)
Current: temperature_2m, relative_humidity_2m, apparent_temperature, precipitation, rain, snowfall, snow_depth, weather_code, cloud_cover, pressure_msl, surface_pressure, wind_speed_10m, wind_direction_10m, wind_gusts_10m
Hourly: temperature_2m, relative_humidity_2m, dew_point_2m, apparent_temperature, precipitation_probability, precipitation, rain, snowfall, snow_depth, weather_code, pressure_msl, surface_pressure, cloud_cover, cloud_cover_low, cloud_cover_mid, cloud_cover_high, visibility, wind_speed_10m, wind_direction_10m, wind_gusts_10m, uv_index, uv_index_clear_sky, cape, freezing_level_height, snowfall_height
Daily: weather_code, temperature_2m_max, temperature_2m_min, apparent_temperature_max, apparent_temperature_min, sunrise, sunset, daylight_duration, sunshine_duration, uv_index_max, uv_index_clear_sky_max, precipitation_sum, precipitation_hours, precipitation_probability_max, rain_sum, snowfall_sum, wind_speed_10m_max, wind_gusts_10m_max, wind_direction_10m_dominant, shortwave_radiation_sum, et0_fao_evapotranspiration
⚠️ NO en current: sunrise, sunset (solo daily)
Marine API — Parámetros
Current: wave_height, wave_direction, wave_period, wind_wave_height, swell_wave_height
Hourly: wave_height, wave_direction, wave_period, wind_wave_height, wind_wave_direction, wind_wave_period, swell_wave_height, swell_wave_direction, swell_wave_period
Air Quality API — Parámetros
Current: european_aqi, us_aqi, pm10, pm2_5, nitrogen_dioxide, ozone, carbon_monoxide, sulphur_dioxide, dust, uv_index
Hourly: pm10, pm2_5, nitrogen_dioxide, ozone, carbon_monoxide, sulphur_dioxide
Pollen (current): alder_pollen, birch_pollen, grass_pollen, mugwort_pollen, olive_pollen, ragweed_pollen
Datos provinciales extendidos (data/provincias-data.json)
Cada provincia tiene estos campos:
{
"nombre": "Cantabria",
"poblacion": 588058,
"ccaa": "06", // ← CRÍTICO: código INE correcto (ver pitfall)
"capital": "Santander",
"superficie": 5321, // km²
"costa_km": 285, // 0 si interior
"altitud_media": 463, // metros
"paro": 11.2, // %
"pib_capita": 29100 // €
}
Códigos CCAA INE (fuente de verdad)
| Cód |
CCAA |
Cód |
CCAA |
| 01 |
Andalucía |
11 |
Galicia |
| 02 |
Aragón |
12 |
La Rioja |
| 03 |
Asturias |
13 |
C. de Madrid |
| 04 |
Illes Balears |
14 |
Región de Murcia |
| 05 |
Canarias |
15 |
Navarra |
| 06 |
Cantabria |
16 |
País Vasco |
| 07 |
Castilla y León |
17 |
C. Valenciana |
| 08 |
Castilla-La Mancha |
18 |
Ceuta |
| 09 |
Cataluña |
19 |
Melilla |
| 10 |
Extremadura |
|
|
Geolocalización completa (v2.2) — CRÍTICO
Patrón de barras de contexto
- Barra
#province-context: Aparece arriba al seleccionar provincia, muestra nombre + código
- Botón limpiar: Restaura vista nacional (oculta barra, restaura valores por defecto)
- Animación: slide-down con
max-height transition
Sincronización de pestañas
Al seleccionar provincia → TODAS las pestañas se actualizan:
- Clima: Fetch Open-Meteo por capital de provincia
- Economía: Filtra PIB/paro de la provincia
- Población: Muestra datos demográficos de la provincia
- Puertos: Muestra puertos de la provincia (si tiene costa)
- Catastro: Pre-selecciona la provincia en el dropdown
- Agua: Muestra embalses de la cuenca correspondiente
- Transporte: Muestra ZBE y radares de la provincia
Coordenadas de centroids
const provinceCentroids = {
'01': [37.39, -5.99], // Sevilla (Andalucía)
'02': [41.65, -0.88], // Zaragoza (Aragón)
'06': [43.46, -3.81], // Santander (Cantabria)
// ... 52 provincias
};
APIs geolocalizadas
- Clima:
https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t=...
- Marina:
https://marine-api.open-meteo.com/v1/marine?latitude={lat}&longitude={lon}¤t=...
- Fórmula completa Open-Meteo:
?current=temperature_2m,wind_speed_10m,relative_humidity_2m,weather_code,wind_direction_10m,sunrise,sunset&daily=sunrise,sunset&timezone=Europe/Madrid
Panel detalle de provincia (al click en mapa) — v2.6
El panel derecho debe mostrar TODOS estos campos (David quiere "muchísimos más datos"):
- Población (INE 2024)
- Capital
- Superficie (km²)
- Densidad (hab/km²) — calculada: población/superficie
- Altitud media (m)
- Costa (km) — "Sin costa" si interior
- Paro provincial (%)
- PIB per cápita (€)
- CCAA (nombre completo)
- Código INE
- Clima detallado (API Open-Meteo por capital): temperatura, humedad, viento, condición WMO, amanecer, atardecer
- Fórmula Open-Meteo CORRECTA:
?current=temperature_2m,wind_speed_10m,relative_humidity_2m,weather_code&daily=sunrise,sunset&timezone=Europe/Madrid
- ⚠️ NO incluir sunrise/sunset en current — causan error silencioso
Estado de APIs (actualizado 2026-06-30)
✅ Funcionan (sin auth)
| API |
Endpoint |
Datos |
| ESIOS PVPC |
api.esios.ree.es/indicators/1001 |
Precio spot €/MWh |
| ESIOS Demanda |
api.esios.ree.es/indicators/1293 |
Demanda total MW |
| Open-Meteo Weather |
api.open-meteo.com/v1/forecast |
Clima por coordenadas |
| Open-Meteo Marine |
marine-api.open-meteo.com/v1/marine |
Oleaje, olas, viento marino |
| Open-Meteo Air Quality |
air-quality-api.open-meteo.com/v1/air-quality |
AQI EU, PM2.5, PM10, O3, NO2, CO, UV, SO2, Polvo |
| Open-Meteo Flood |
flood-api.open-meteo.com/v1/flood |
Caudal de río 14 días |
| Open-Meteo Soil |
api.open-meteo.com/v1/forecast (soil_*) |
Temp y humedad suelo |
| Open-Meteo Pollen |
air-quality-api.open-meteo.com/v1/air-quality (pollen) |
Polen: alder, birch, grass, mugwort, olive, ragweed |
| USGS Earthquake |
earthquake.usgs.gov/fdsnws/event/1/query |
Terremotos M2.5+ en España |
| INE Demografía |
servicios.ine.es/wstempus/js/ES/DATOS_TABLA/9681 |
Población por CCAA y sexo (6120 entries) |
| INE Provincias |
servicios.ine.es/wstempus/js/ES/DATOS_TABLA/2852 |
Población por provincia y sexo (159 entries) |
| INE Paro EPA |
servicios.ine.es/wstempus/js/ES/DATOS_TABLA/4247 |
Tasa de paro por CCAA y edad (420 entries) |
| INE Empleo |
servicios.ine.es/wstempus/js/ES/DATOS_TABLA/4328 |
Empleo por sector (66 entries) |
| INE Empleo temporal |
servicios.ine.es/wstempus/js/ES/DATOS_TABLA/4338 |
Ocupados a tiempo parcial (150 entries) |
| INE Asalariados |
servicios.ine.es/wstempus/js/ES/DATOS_TABLA/4358 |
Asalariados por tipo (198 entries) |
| IGN Geográfico |
GeoJSON local (data/geo/provincias.json) |
Provincias y municipios |
⚠️ Con fallback estático
| API |
Fallback |
Nota |
| ESIOS Renovables (1294) |
45.2% hardcodeado |
Ahora devuelve 403 Forbidden (requiere auth) |
❌ No disponibles (usar alternativas)
| API |
Problema |
Alternativa |
| IGN Terremotos (old URL) |
Devuelve HTML, no JSON (Liferay CMS rotó la URL) |
USGS Earthquake API (gratuita, CORS-friendly) |
📋 APIs gratuitas españolas pendientes de integrar
| API |
Datos potenciales |
Auth |
| INE Tabla 39960 |
Turismo: pernoctaciones, viajeros por provincia |
No |
| INE Tabla 4247 |
Educación: matriculados por nivel |
No |
| INE Tabla 31304 |
Empleo: afiliados SS por provincia |
No |
| INE Tabla 2852a |
Nacimientos/muertes por provincia |
No |
| INE Tabla 9684 |
Censo de población por municipio |
No |
| AEMET (requires key) |
Predicción detallada, warnings |
Sí (gratuita con registro) |
| Copernicus CAMS |
Calidad aire histórica, emisiones |
Sí (gratuita con registro) |
| Sentinel-5P |
NO2 troposférico, SO2, CH4 por satélite |
Sí (via Copernicus) |
Cron de actualización de APIs
- Si una API deja de funcionar → buscar alternativa pública → fallback estático → documentar en esta sección
- Las APIs de ESIOS cambian frecuentemente de requisitos de auth → verificar cada ~3 meses
Pitfalls
- GitHub Pages + Actions: Requiere
enablement: true en actions/configure-pages@v5
- Token:
GITHUB_TOKEN en .env
- GeoJSON postal codes: No existe fuente pública → se usan centroides sintéticos
- ESIOS API: Requiere token para algunos endpoints
- CORS: APIs públicas pueden bloquear requests desde Pages → usar proxy o datos estáticos
- ⚠️ CCAA codes en provincias-data.json: Los códigos DEBEN seguir el estándar INE (01-19). Error común: asignar códigos desplazados (ej: Cantabria='03' en vez de '06'). SIEMPRE verificar contra la tabla CCAA de arriba. El mapeo CCAA_NAMES en JS también debe alinearse — si está desplazado, Cantabria aparecerá como "Asturias" en el panel detalle.
- ⚠️ Datos por provincia georreferenciados: David exige que cada pestaña muestre datos relevantes para la provincia seleccionada, no solo datos nacionales genéricos. El panel detalle es la pieza clave — debe ser completo.
- ⚠️ APIs marinas solo para costas: Open-Meteo Marine solo funciona para coordenadas costeras/marítimas. Para provincias interiores (Madrid, Zaragoza), no llamar a Marine API.
- ⚠️ Sincronización de pestañas: Al seleccionar provincia, TODAS las pestañas deben actualizarse. David verifica que los datos se mantengan al cambiar de pestaña.
- ⚠️ NO romper al mejorar (Lección 2026-06-30): David dijo "creo que no estás planteando el crecimiento de la herramienta sin romper cosas". Flujo seguro: (1) diagnóstico antes de tocar, (2) cambios incrementales, (3) verificar braces/JS después de CADA patch, (4) commit por cambio, NO todo junto. Patrón de error: eliminar código JS deja
}); huérfanos que rompen todo silenciosamente.
- ⚠️ Validar datos antes de mostrar (Lección 2026-06-30): David rechazó "2 metros de nieve" en Madrid en verano. Cada pestaña debe validar que los datos tienen sentido: (1) nieve solo en estaciones de esquí o altitud >1500m, (2) marine API solo para coordenadas costeras, (3) PM2.5 > 100 es inusual en España (alertar), (4) temperaturas < -10 o > 50°C son sospechosas. Patrón: filtrar ciudades que no aplican (ej: pestaña nieve sin datos → "Sin nieve en esta ubicación").
- ⚠️ Open-Meteo: sunrise/sunset NO son current parameters (Lección 2026-06-30):
sunrise y sunset solo existen como parámetros daily, NO como current. Si los pones en current=...,sunrise,sunset la API falla silenciosamente y el panel muestra "No disponible". Fix: current=temperature_2m,...,weather_code&daily=sunrise,sunset. Verificado con curl: Ávila 31.6°C.
- ⚠️ CSS .tab-content selector (Lección 2026-06-30): Al eliminar o reorganizar bloques CSS, verificar que
.tab-content { flex: 1; overflow-y: auto; padding: 16px; } exista. Sin este selector, TODOS los paneles de pestañas se muestran simultáneamente (display:none no aplica). Síntoma: 13 paneles visibles en vez de 1. Causa: patch CSS que rompe la continuidad del bloque.
- ⚠️ 2 cron batches pattern (Lección 2026-06-30): Cuando se crean muchas pestañas nuevas, hacerlo en 2 oleadas: (1) creación básica con API, (2) fix/mejora de cada pestaña. Cada cron hace git pull al inicio para evitar conflictos. Separar 10 min entre crons para que Pages despliegue.
- ⚠️ Datos absurdos por validación (Lección 2026-06-30): snow_depth viene en METROS (no cm), así que "2 metros de nieve" en Madrid en verano es absurdo. Cada pestaña debe validar: (1) nieve solo >1500m altitud o estaciones de esquí, (2) marine API solo costas, (3) PM2.5 >100 alertar, (4) temp <-10 o >50 sospechoso. Filtrar ciudades que no aplican.
- ⚠️ Tabs responsive wrap (Lección 2026-06-30):
flex-wrap: nowrap cause horizontal scroll en móvil. Usar flex-wrap: wrap para que las tabs saltan a segunda fila. En móvil: scroll horizontal con scroll-snap-type: x proximity + scroll-snap-align: start en cada tab.
- ⚠️ DOM nesting al añadir tab panels (Lección 2026-06-30): Al añadir nuevos
.tab-panel con patch(), si el old_string incluye el cierre </div> del panel anterior pero el new_string no lo reincluye correctamente, los nuevos paneles se ANIDAN dentro del panel anterior en vez de ser hermanos. Síntoma: todos los paneles nuevos tienen scrollHeight=0 y no se ven. Debug rápido en consola:document.querySelectorAll('.tab-panel').forEach(p => {
console.log(p.id, 'parent:', p.parentElement.id || p.parentElement.className);
});
Si algún panel dice parent: tab-puertos en vez de parent: tab-content, hay nesting roto. Fix: añadir </div> de cierre del panel padre antes del primer <div class="tab-panel"> nuevo. Verificación Python post-patch:import re
content = open('index.html').read()
seg = content[content.find('id="tab-puertos">'):content.find('id="tab-polen">')]
opens = len(re.findall(r'<div[ >]', seg))
closes = len(re.findall(r'</div>', seg))
assert opens == closes, f"DOM BROKEN: opens={opens} closes={closes}"
- ⚠️ GitHub Pages CDN caching (Lección 2026-06-30): Tras push a
main, GitHub Pages puede servir versión cacheada durante 2-5 minutos. Añadir ?v=N al reload no siempre invalida. Si el navegador muestra datos viejos tras push correcto → hard refresh (Ctrl+Shift+R) o nueva ventana incógnito. Para verificar que el deploy es correcto: curl -s raw.githubusercontent.com/Ntizar/DataHubEspana/main/index.html | grep 'NUEVO_TEXT'.
- ⚠️ Crecimiento sin romper (patrón David): Cada feature nueva debe: (1) no modificar código existente que funciona, (2) añadir en bloques separados, (3) hacer commit incremental, (4) verificar que pestañas anteriores siguen funcionando. David revisa el dashboard después de cada push y detecta regresiones al instante.
- ⚠️ Helper setTxt() global (v2.7):
setTxt es ahora un helper GLOBAL definido en el scope principal del script:const setTxt = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; else console.warn('Element not found:', id); };
Usar SIEMPRE setTxt() en vez de document.getElementById(id).textContent en cualquier función. Evita errores silenciosos cuando un ID no existe o fue renombrado.
- ⚠️ ESIOS 403 handling explícito (v2.7): Las APIs de ESIOS devuelven 403 Forbidden cuando requieren auth. Pattern correcto:
const res = await fetch('https://api.esios.ree.es/indicators/1001', { headers: { 'Accept': 'application/json' } });
if (res.status === 403) { /* auth required — usar fallback */ }
else if (res.ok) { /* parsear datos */ }
Si falla: mostrar "N/D" (no "—" ni "0.18" — datos corruptos). Validar latest.value > 0 antes de mostrar (valores 0 = datos stale).
- ⚠️ showToast en errores de API (v2.5): Cuando una API falla al cargar datos de provincia, mostrar toast de error + valores fallback en vez de dejar la UI con "—" o "Cargando…" eterno:
} catch (err) {
showToast(`Error cargando clima de ${nombre}: ${err.message}`, 'error', 3000);
setTxt('weather-temp', 'No disponible');
setTxt('weather-detail', `No se pudieron cargar datos de clima para ${nombre}.`);
}
Lecciones de la evolución v1→v2→v2.1
Lo que David rechazó (v1)
- Diseño "liquid glass" → "El diseño es bastante mierda, las cards se notan que son ia"
- Puntos/círculos en el mapa → "los cp en vez de puntos deberían ser áreas"
- Pocos datos → "faltan muchisimos datos"
- Sin zoom a provincias → Necesita interactividad real
Lo que David rechazó (v2)
- Cards con
border-left: 4px solid → "se nota mucho que es IA" (screenshot de cards azul/naranja con línea vertical)
- Patrón genérico de dashboard → "es demasiado template, se ve como hecho por herramienta"
Lo que funcionó (v2.1)
- Cards sin border-left, con gradientes sutiles de fondo
- Hover elevación en vez de bordes de color
- 3 pestañas nuevas: Población (INE), Economía detallada, Puertos
- 6 gráficos nuevos: barras horizontales, doughnuts, barras coloreadas
- 11 pestañas totales con datos reales de APIs públicas
Lo que funcionó (v2.2)
- Geolocalización completa: Click provincia → actualiza TODAS las pestañas
- Barra de contexto: Muestra provincia seleccionada con botón limpiar
- Puertos interactivos: Click puerto → panel detalle con oleaje + clima (APIs Open-Meteo Marine)
- APIs en cadena: Weather + Marine para misma ubicación = datos completos
- 52 centroides provinciales: Para calls API por capital de provincia
Lo que funcionó (v2.3)
- Calidad del aire en tiempo real: API Open-Meteo Air Quality (CAMS) sin auth, datos por provincia
- Demografía INE completa: Pirámide poblacional + distribución por CCAA + sexo
- KPIs en panel principal: AQI + UV se actualizan con provincia seleccionada
- Patrón de APIs gratuitas sin auth: Open-Meteo (weather/marine/air-quality) + INE + USGS =组合 completo sin registrar nada
- Incrementalidad: Cada API nueva se añade como pestaña independiente + KPI en panel + actualización geolocalizada
Lo que funcionó (v2.4)
- 4 pestañas nuevas: Polen, Inundaciones, Suelo, Pronóstico 7 días
- Clima mejorado: +6 KPIs (presión, nubosidad, visibilidad, ráfagas, prob. lluvia, precipitación)
- Paro INE por CCAA: Tabla 4247 → gráfico barras coloreadas por tasa (rojo >15%, naranja >10%, verde <10%)
- Todas las nuevas pestañas se geolocalizan: Click provincia → fetch por centroides
- Total: 17 pestañas, 30+ gráficos, 12+ APIs en tiempo real
Lo que funcionó (v2.5) — Rediseño layout
- Pestañas horizontales superiores: Tabs en
#tab-navbar scrollable horizontal arriba del mapa
- Pronóstico fusionado en Clima: 7 días de forecast integrados en pestaña Clima (cards + gráficos)
- Capas de mapa: Carreteras (OpenStreetMap HOT) + Ferrocarriles (OpenRailwayMap) vía Leaflet layer control
- Tipografía compacta: KPIs 14-17px, títulos 12px, body 10-11px
- Error handling mejorado: setTxt() helper + showToast en errores + fallback values
- 16 pestañas totales (Pronóstico quitado como pestaña separada)
Lo que funcionó (v2.6) — Calidad y funcionalidad
- Transporte/DGT eliminado: API rota, datos estáticos inútiles. David: "no aporta información"
- Choropleth toggleable: Capa de provincias y parques nacionales activables desde layer control
- Ambiente mejorado: 16 parques nacionales con coordenadas reales, flyTo en mapa, markers verdes
- Catastro mejorado: Superficie, densidad, municipios, enlace directo por provincia
- Inundaciones mejorado: 52 ríos mapeados a provincias, nombre del río en gráfico y KPI
- Puertos mejorado: flyTo en mapa al hacer click, datos en vivo (oleaje, viento, temp)
- Clima fix:
fetchProvinceWeather() arreglado — sunrise/sunset movidos a daily parameters
- Total: 15 pestañas, 30+ gráficos, 12+ APIs en tiempo real
Patrón de selección interactiva por pestaña (Lección 2026-06-30)
Cada pestaña debe tener su propio selector de ciudad/región para que el usuario pueda elegir dónde ver datos. No solo la selección global de provincia.
Selector de ciudad (8-10 ciudades representativas)
const CIUDADES_CLIMA = [
{ nombre: 'Madrid', lat: 40.42, lon: -3.70 },
{ nombre: 'Barcelona', lat: 41.39, lon: 2.17 },
{ nombre: 'Sevilla', lat: 37.39, lon: -6.00 },
{ nombre: 'Valencia', lat: 39.47, lon: -0.38 },
{ nombre: 'Bilbao', lat: 43.26, lon: -2.93 },
{ nombre: 'Zaragoza', lat: 41.65, lon: -0.88 },
{ nombre: 'Málaga', lat: 36.72, lon: -4.42 },
{ nombre: 'Las Palmas', lat: 28.10, lon: -15.41 }
];
Selector de costa (para pestañas marinas)
const COSTAS = [
{ nombre: 'Algeciras', lat: 36.13, lon: -5.45 },
{ nombre: 'Valencia', lat: 39.45, lon: -0.32 },
{ nombre: 'Barcelona', lat: 41.34, lon: 2.18 },
// ... 10 puertos costeros
];
Autogeneración por CCAA (Lección 2026-06-30)
Cada pestaña debe mostrar un grid de las 19 comunidades con datos en vivo al hacer click.
Patrón CCAA_CENTROIDS
const CCAA_CENTROIDS = {
'01': { name: 'Andalucía', lat: 37.39, lon: -4.50, caps: ['Sevilla','Cádiz','Málaga','Granada'] },
'13': { name: 'Comunidad de Madrid', lat: 40.42, lon: -3.70, caps: ['Madrid'] },
// ... 19 comunidades
};
Función generateCCAATab(dataType)
- Crea grid responsive:
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr))
- Cada card es clickeable →
selectCCAA(code, dataType)
- Fetch en vivo de Open-Meteo al hacer click
- Colores dinámicos: PM2.5 verde <10, amarillo <20, rojo >20
Init en init()
generateCCAATab('clima');
generateCCAATab('aire');
generateCCAATab('nieve');
generateCCAATab('mar');
Responsive móvil (Lección 2026-06-30)
3 breakpoints + landscape para cobertura completa:
Tablet (≤768px)
- Sidebar: fixed bottom, 50vh, border-radius 12px top
- Tabs: scroll horizontal con snap-to-start, 10px font
- KPIs: 2 columnas, labels 9px, values 14px
- Mapa: 35vh mínimo
- CCAA grid: 2 columnas
Móvil (≤480px)
- Sidebar: 55vh, border-radius 10px top
- Tabs: 9px font, padding mínimo, scroll horizontal
- KPIs: 2 columnas, labels 8px, values 13px
- Mapa: 30vh mínimo
- Info grid: 1 columna
- CCAA grid: 1 columna
Landscape (≤500px alto)
- Sidebar: 50% ancho a la derecha
- KPIs: 4 columnas
- Mapa: 60vh
CSS clave
.tabs-row { flex-wrap: wrap; gap: 4px; } /* wrap en vez de nowrap */
.tab-btn { flex-shrink: 0; scroll-snap-align: start; }
#sidebar { -webkit-overflow-scrolling: touch; }
HTML del selector
<div class="tab-selector">
<select id="ciudad-selector"
<option value="">-- Seleccionar ciudad --</option>
</select>
</div>
Cascada de selección
- Selector global (mapa): Click provincia → actualiza TODAS las pestañas
- Selector por pestaña: Dropdown específico → actualiza SOLO esa pestaña
- Prioridad: El selector de pestaña sobreescribe la selección global
Patrón de cards que SÍ gusta a David
.kpi {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 10px;
padding: 14px 16px;
transition: all 0.2s ease;
}
.kpi:hover {
background: #ffffff;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
transform: translateY(-1px);
}
.kpi.green { background: linear-gradient(135deg, #f0fdf4 0%, #ffffff 100%); }
.kpi.orange { background: linear-gradient(135deg, #fff7ed 0%, #ffffff 100%); }
.kpi.red { background: linear-gradient(135deg, #fef2f2 0%, #ffffff 100%); }
.kpi.blue { background: linear-gradient(135deg, #eff6ff 0%, #ffffff 100%); }
GeoJSON fuentes
- Provincias:
codeforamerica/click_that_hood → 52 features, ~1.4MB raw, ~487KB simplificado
- Municipios:
AlexGPlay/SpainLayers → por provincia (28, 29, 41, 46 disponibles)
- Propiedades:
{cod, nombre, ccaa} (provincias), {id, name} (municipios — OJO: mapear)
GitHub Pages deployment
- Workflow requiere
enablement: true en actions/configure-pages@v5
- Sin esto, el paso "Setup Pages" falla con "Get Pages site failed"
- Token necesita scope
pages: write + id-token: write
Prompts cron para capas adicionales
Naming mismatch pitfall (Lección 2026-06-30)
Cuando se renombra una función, SIEMPRE buscar TODAS las referencias. Un mismatch causa ReferenceError silencioso en init() que rompe la pestaña entera.
Batch overnight cron pattern (Lección 2026-06-30)
Para arreglar un dashboard grande (35+ pestañas) de forma nocturna:
- 7 oleadas de 5 pestañas cada una, espaciadas 8 min
- 1 cron de auditoría final que verifica todo y genera informe
- Cada cron es autocontenido: lee el archivo, hace sus fixes, verifica DOM, commitea
- Empaquetar pestañas relacionadas en la misma oleada (ej: meteorológicas juntas, datos juntas)
- Auditoría final al final: verifica DOM balance, funciones definidas, paneles con contenido
Cron: Actualización Energía (cada hora)
Actualiza la capa de energía del DataHub España.
1. Fetch ESIOS API: PVPC (indicator 1001), Demanda (1293), Renovables (1294)
2. Actualizar KPI tiles en index.html
3. Actualizar gráfico PVPC 24h
4. Git commit + push
Nota: ESIOS puede necesitar token. Fallback: mostrar "N/D"
Cron: Actualización Clima (cada 6h)
Actualiza datos climáticos del DataHub España.
1. Fetch Open-Meteo para Madrid (40.4168, -3.7038): temperatura, viento, humedad
2. Actualizar KPI tiles de clima
3. Actualizar descripción del código meteorológico WMO
4. Git commit + push
Cron: Actualización Embalses (diario)
Actualiza niveles de embalses del DataHub España.
1. Leer data/em
…(truncated)
1---2name: datahub-espana-architecture3description: Arquitectura completa de DataHub España — dashboard de datos en tiempo real con mapa, 35+ pestañas, tabs horizontales superiores, capas de mapa, APIs públicas sin auth, cron-based incremental building4---56# DataHub España — Arquitectura78## Repo9- **GitHub:** `Ntizar/DataHubEspana` (PÚBLICO)10- **Pages:** `https://ntizar.github.io/DataHubEspana/`11- **Workflow:** `.github/workflows/pages.yml` (actions/deploy-pages@v4)12- **Branch:** `main`1314## Estructura15```16DataHubEspana/17├── index.html # Dashboard principal (SPA)18├── data/19│ └── codigos-postales-centroids.json # 251 centroides postales20├── scrapers/ # 19 scrapers Python21│ ├── esios/ # ESIOS/REE — energía22│ ├── aemet/ # AEMET — meteorología23│ ├── boe/ # BOE — boletín oficial24│ ├── borme/ # BORME — mercantil25│ ├── catastro/ # Catastro — catastro26│ ├── contratacion/ # Contratación pública27│ ├── dgt/ # DGT — tráfico28│ ├── embalses/ # Embalses — agua29│ ├── ine/ # INE — demografía30│ ├── ign/ # IGN — geográfico31│ ├── idee/ # IDEE — espacial32│ ├── oapn/ # OAPN — parques nacionales33│ ├── puertos/ # Puertos del Estado34│ ├── subvenciones/ # Subvenciones35│ ├── madrid/ # Madrid CKAN36│ ├── ckan_multi_portal/ # CKAN multi-portal37│ ├── nap_transporte/ # NAP Transportes38│ ├── datos_gob/ # datos.gob.es39│ └── orchestrator_v2/ # Orquestador maestro40└── tools/41 ├── dashboard-economico/ # Dashboard económico42 ├── mapa-trafico/ # Mapa de tráfico43 └── monitor-energia/ # Monitor de energía44```4546## Stack47- **CSS:** Inline CSS (sin framework externo). Diseño limpio profesional.48- **Mapa:** Leaflet 1.9.4 + Layer Control (carreteras, ferrocarriles, choropleth toggleable, parques markers)49- **Charts:** Chart.js 4.4.450- **Base:** HTML/CSS/JS puro (sin framework)51- **Deploy:** GitHub Pages via GitHub Actions5253## Layout v2.6 (2026-06-30) — Tabs horizontales + capas toggleables54- **Pestañas en barra horizontal** `#tab-navbar` arriba del mapa (scrollable horizontal) — **David: "las pestañas deberían salir arriba y se viesen más datos"**55- **Sidebar solo muestra contenido** de la pestaña activa (no botones de tabs)56- **Botón ☰** para colapsar/expandir sidebar57- **Pronóstico 7 días fusionado en Clima** (ya no pestaña separada)58- **Transporte ELIMINADO** (API DGT rota, datos estáticos inútiles — David: "no aporta información")59- **Tipografía reducida:** KPIs 14-17px, títulos 12px, body 10-11px60- **Total: 35+ pestañas** (Panel, Energía, Clima, Agua, Economía, Ambiente, Catastro, Población, Economía Det., Calidad Aire, Demografía, Puertos, Polen, Inundaciones, Suelo + 20 de cron: GBFS, Nieve, Mar, UV, Visibilidad, Ráfagas, Lluvia, Nubosidad, Presión, Fuego, ET0, CAPE, Horas Sol, Rocío, Temp Suelo, Radiación, Sensación, Aire Ext, Mareas, Eólica)6162## Capas de datos (13+ pestañas)6364### Cron Job Orchestration (20+ tabs)65Cuando se necesitan crear many tabs independientes, usar cron jobs one-shot espaciados 10 min:66- Cada cron: `git pull` → modificar index.html → verificar DOM → commit → push67- **IMPORTANTE:** cada cron debe hacer `git pull` ANTES de modificar para evitar conflictos68- Verificación pre-commit OBLIGATORIA: DOM balance debe ser -169- Patrón de fix crons: después de crear tabs básicas, segunda tanda que añade selectores, alertas, corrige datos absurdos7071### Open-Meteo API — Parámetros current vs daily72`sunrise` y `sunset` **NO** son válidos en `current=`. Solo en `daily=`. Falla silenciosa.73- `snow_depth` viene en **METROS** (×100 para cm)74- Parámetros current válidos: temperature_2m, relative_humidity_2m, apparent_temperature, precipitation, rain, snowfall, snow_depth, weather_code, cloud_cover, pressure_msl, surface_pressure, wind_speed_10m, wind_direction_10m, wind_gusts_10m7576### 1. 📊 Panel (vista general)77- KPIs: Población total, Provincias, PVPC, Demanda, Renovables, Temperatura Madrid78- KPIs nuevos v2.3: **AQI Europeo**, **Índice UV** (Open-Meteo Air Quality)79- Último terremoto (USGS)80- Hidroelectricidad (embalses)8182### 2. ⚡ Energía (ESIOS/REE)83- **PVPC:** Precio medio spot (€/MWh) — indicator 100184- **Demanda:** Demanda eléctrica total (MW) — indicator 129385- **Renovables:** % generación renovable — indicator 1294 (⚠️ fallback 45.2% si 403)86- **CO₂:** Intensidad de emisiones (g/kWh)87- **Gráficos:** PVPC últimas 24h (line), Generación por fuente (doughnut)8889### 3. 🌤️ Clima (Open-Meteo) — v2.5: incluye Pronóstico 7 días90- Temperatura, viento, humedad, condición meteorológica91- **Pronóstico 7 días integrado** (cards por día + 3 gráficos: temp, precipitación, viento)92- Click provincia → clima de su capital + pronóstico actualizado93- **Error handling:** showToast + fallback values + setTxt() helper94- API: `https://api.open-meteo.com/v1/forecast`9596### 4. 💧 Agua (Embalses)97- Nivel medio por cuenca (%)98- Gráfico de barras por cuenca99- Lista con progress bars por embalse100101### 5. 💼 Economía (BOE/BORME)102- Constituciones vs disoluciones (doughnut)103- Datos hardcodeados (mejorable con API real)104105### 6. 🌿 Medio Ambiente (OAPN) — v2.6: parques reales con coords106- **16 parques nacionales** con coordenadas reales, superficie (km²), año declaración107- **Click en parque → flyTo en mapa** (zoom 11, 1.5s animación)108- **Markers verdes** toggleables desde layer control del mapa109- **Overlay layer:** `parksOverlay` (L.layerGroup con circle markers verdes)110- Coordenadas: Picos de Europa (43.17,-4.85), Ordesa (42.67,-0.02), Aigüestortes (42.58,1.02), Sierra de Guadarrama (40.85,-3.85), Monfragüe (39.83,-6.05), Cabañeros (39.38,-4.32), Doñana (36.95,-6.35), Sierra Nevada (37.05,-3.37), Tablas de Daimiel (39.15,-3.08), Timanfaya (29.00,-13.83), Caldera de Taburiente (28.75,-17.87), Garajonay (28.10,-17.23), Teide (28.27,-16.64), Archipiélago de Cabrera (39.15,2.95), Islas Atlánticas (42.50,-9.00), Sierra de las Nieves (36.68,-5.02)111112### 7. 🏗️ Catastro — v2.6: datos reales por provincia113- **Provincia seleccionada:** nombre, código INE, CCAA, capital114- **Superficie** (km²), **Densidad** (hab/km², calculada), **Municipios** (conteo)115- **Enlace directo** a Sede Electrónica del Catastro para la provincia116- Se actualiza al seleccionar provincia en el mapa117118### 9. 👥 Población (INE) — NUEVO v2.1119- Población total: 47.615.034120- Densidad, extranjeros, crecimiento121- **Gráfico:** Top 10 CCAA (barras horizontales)122- **Gráfico:** Distribución por edad (doughnut)123124### 10. 📈 Economía detallada — NUEVO v2.1125- PIB: 1.418.352M€, Paro: 11,2%, IPC: 2,8%126- **Gráfico:** PIB por sector (barras horizontales)127- **Gráfico:** Paro por provincias Top 10 (barras coloreadas)128129### 11. 🚢 Puertos — v2.6: flyTo en mapa + datos en vivo130- Contenedores TEU, Mercancía total, Pasajeros, Cruceros131- **Gráfico:** Top 10 puertos por tonelaje (barras horizontales, clickable)132- **Lista interactiva:** 10 puertos costeros con click → panel detalle133- **Panel detalle puerto** (al click): oleaje, viento, temperatura, humedad, amanecer/atardecer134- ** flyTo en mapa:** Al hacer click en puerto → `map.flyTo([port.lat, port.lon], 12, {duration: 1.5})`135- **APIs:** Open-Meteo Marine (oleaje) + Open-Meteo Weather (clima)136- **Puertos con coordenadas:** Algeciras, Valencia, Barcelona, Bilbao, Las Palmas, Tenerife, Cartagena, Huelva, A Coruña, Gijón137138### 12. 🌬️ Calidad del Aire (Open-Meteo Air Quality) — NUEVO v2.3139- **API:** `https://air-quality-api.open-meteo.com/v1/air-quality` (CAMS European forecasts, sin auth)140- **KPIs en tiempo real:** AQI Europeo, PM2.5, PM10, Ozono O₃, NO₂, CO, Índice UV, Polvo, SO₂141- **Gráficos:**142 - Evolución contaminantes 24h (líneas, hourly data)143 - Distribución contaminantes (donut)144- **Se actualiza al seleccionar provincia** → fetch con centroides145- **Fórmula:** `?current=european_aqi,us_aqi,pm10,pm2_5,nitrogen_dioxide,ozone,carbon_monoxide,sulphur_dioxide,dust,uv_index&hourly=pm10,pm2_5,nitrogen_dioxide,ozone,carbon_monoxide,sulphur_dioxide&past_days=1&forecast_days=0`146- **Verificado:** Madrid AQI=38, PM2.5=8.8, O3=96, NO2=10.4, CO=126, UV=7.3147148### 13. 📊 Demografía (INE) — NUEVO v2.3149- **API:** `https://servicios.ine.es/wstempus/js/ES/DATOS_TABLA/9681?tip=AM&nult=1` (población por CCAA y sexo)150- **KPIs:** Población total, Mujeres, Hombres, Tasa dependencia, Índice envejecimiento151- **Gráficos:**152 - Pirámide poblacional (barras horizontales divergentes, mujeres=rosa/hombres=azul)153 - Población por CCAA (top 10, barras horizontales)154 - Distribución por sexo (pie chart)155- **Verificado:** total=47,615,034, women=24,304,407, men=23,310,627, aging=112.7, dependency=49.2%156- **Tabla INE 2852:** población por provincia y sexo (159 entries, funciona)157158### 14. 🌾 Polen (Open-Meteo Air Quality) — NUEVO v2.4159- **API:** `https://air-quality-api.open-meteo.com/v1/air-quality?...¤t=alder_pollen,birch_pollen,grass_pollen,mugwort_pollen,olive_pollen,ragweed_pollen`160- **KPIs:** Gramen, Olivo, Abedul, Aliso, Artemisa, Ambrosía (gr/m³)161- **Gráficos:** Distribución por tipo (barras) + evolución 24h (líneas)162- **Se actualiza al seleccionar provincia**163164### 15. 🌊 Inundaciones (Open-Meteo Flood) — v2.6: ríos por provincia165- **API:** `https://flood-api.open-meteo.com/v1/flood?...&daily=river_discharge&past_days=7&forecast_days=7`166- **KPIs:** Caudal actual, Caudal máx 7d, Caudal mín 7d, Caudal medio167- **Nombre del río:** Mapeo de 52 provincias → río principal (Madrid→Tajo, Barcelona→Llobregat, Sevilla→Guadalquivir, Bilbao→Nervión, Valencia→Turia, Zaragoza→Ebro)168- **Gráfico:** Línea de caudal 14 días (7 pasados + 7 previsión) con nombre del río en título169- **Se actualiza al seleccionar provincia** → muestra río correspondiente170171### 16. 🌡️ Suelo (Open-Meteo Forecast) — NUEVO v2.4172- **API:** `https://api.open-meteo.com/v1/forecast?...&hourly=soil_temperature_6cm,soil_temperature_18cm,soil_temperature_54cm,soil_moisture_0_to_1cm,soil_moisture_1_to_3cm,soil_moisture_3_to_9cm,soil_moisture_9_to_27cm`173- **KPIs:** Temp suelo 6/18/54cm, Humedad suelo 0-1/3-9/9-27cm174- **Gráficos:** Temperatura por profundidad (3 líneas) + Humedad por profundidad (3 líneas)175176## Mapa — Capas interactivas (v2.6)177178### Base tiles179- **CARTO light_all** (por defecto): `https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png`180- **OSM estándar**: `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`181182### Overlay layers (toggleables desde Leaflet layer control)183- **Carreteras** (OpenStreetMap HOT): `https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png`184- **Ferrocarriles** (OpenRailwayMap): `https://tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png`185- **Provincias (coropleta)**: `provincesOverlay` (L.layerGroup) — choropleth de 52 provincias186- **Parques Nacionales**: `parksOverlay` (L.layerGroup) — 16 markers verdes con circle markers187188### Layer control189```javascript190const baseMaps = { 'CARTO': baseOSM, 'OSM': baseOSMStd };191const overlayMaps = { 'Carreteras': roadsLayer, 'Ferrocarriles': railwaysLayer, 'Provincias': provincesOverlay, 'Parques': parksOverlay };192L.control.layers(baseMaps, overlayMaps).addTo(map);193```194195### Choropleth (toggleable)196- 52 provincias con GeoJSON simplificado (487KB)197- Canvas renderer para rendimiento198- Click provincia → zoom + panel detalle derecho199- **Toggle:** `provincesOverlay` permite activar/desactivar la capa desde el layer control200201## Diseño actual (v2.5) — Limpio profesional, compacto202203### Estilo de cards (CRÍTICO: sin border-left)204- **NO usar `border-left` de colores** → David rechazó explícitamente ("las cards se notan que son ia")205- **Usar gradientes sutiles** en background: `linear-gradient(135deg, #f0fdf4 0%, #ffffff 100%)` para green206- **Hover elevación**: `box-shadow: 0 4px 12px rgba(0,0,0,0.08); transform: translateY(-1px)`207- **Colores**: Azul #2563eb, Naranja #f97316, Verde #16a34a, Rojo #dc2626208- **Fondo KPIs**: `#f8fafc` base con gradientes por tipo (green/orange/red/blue)209- **Border radius**: 10px para KPIs, 12px para cards210- **Sin glass/blur/saturate** → rechazado en v1211212### Paleta213- Background: `#f8fafc`214- Cards: `#ffffff` + `border: 1px solid #e2e8f0`215- Texto primario: `#0f172a`216- Texto secundario: `#64748b`217- Labels KPI: `#94a3b8` (más sutiles)218219### Sidebar220- Width: 380px, colapsable con botón ☰221- **NO contiene tabs** (v2.5: tabs en #tab-navbar horizontal arriba)222- Solo muestra contenido de la pestaña activa223224### Mapa225- Tile: CARTO light_all226- Choropleth: 52 provincias con GeoJSON simplificado (487KB)227- Canvas renderer para rendimiento228- Click provincia → zoom + panel detalle derecho229230## Cron Jobs existentes231- `mastermind-autoconfig` — diario 09:00 UTC232- `chromadb-reindex-semanal` — domingo 04:00 UTC233- **DataHub 20 tabs creation** — 20 crons espaciados 10 min (12:15-15:25 UTC) — crear pestañas nuevas234- **DataHub 20 tabs fixes** — 20 crons espaciados 10 min (16:00-19:10 UTC) — mejorar cada pestaña235236**Referencia completa:** `references/cron-development-pattern.md`237238## Patrón de desarrollo incremental con crons (Lección 2026-06-30)239240### Flujo de 2 oleadas2411. **Oleada 1 — Creación:** 20 cron jobs, cada uno crea una pestaña nueva con API básica2422. **Oleada 2 — Mejora:** 20 cron jobs, cada uno arregla y mejora la pestaña correspondiente243244### Por qué funciona245- **Cada cron corre en sesión fresca** — no hay dependencias entre ellos246- **Commits incrementales** — cada cron hace git commit + push, así siempre hay un estado funcional247- **10 min de separación** — suficiente tiempo para que GitHub Pages despliegue antes del siguiente248- **Fix crons mejoran lo creado** — la oleada 2 puede leer el estado actual y mejorarlo249250### Estructura de cada cron creation251```252REPO: cd /root/workspace/temp-datahub2531. git pull origin main2542. Leer index.html actual2553. Añadir botón de pestaña en tab-navbar2564. Añadir panel HTML en .tab-content (ANTES del cierre de tab-content)2575. Añadir función JavaScript fetch + render2586. Añadir llamada en init()2597. Verificar DOM balance: python3 -c "..."2608. git commit + push261```262263### Estructura de cada cron fix264```265REPO: cd /root/workspace/temp-datahub2661. git pull origin main2672. Leer index.html, encontrar la pestaña específica2683. AÑADIR selector de ciudad/región con 8-10 ciudades2694. MEJORAR datos (clasificaciones, semáforos, alertas)2705. AÑADIR gráficos o mejorar existentes2716. Verificar DOM balance2727. git commit + push273```274275### APIs usadas en la creación masiva (20 tabs)276| Tab | API | Endpoint |277|-----|-----|----------|278| GBFS | GBFS feeds | bike*.gbfs.com |279| Nieve | Open-Meteo | snowfall, snow_depth |280| Mar | Marine API | waves, swell, sea_temp |281| UV | Open-Meteo | uv_index |282| Visibilidad | Open-Meteo | visibility |283| Ráfagas | Open-Meteo | wind_gusts_10m |284| Lluvia | Open-Meteo | precipitation_probability |285| Nubosidad | Open-Meteo | cloud_cover |286| Presión | Open-Meteo | pressure_msl |287| Fuego | Open-Meteo | cape + wind + humidity |288| ET0 | Open-Meteo | et0_fao_evapotranspiration |289| CAPE | Open-Meteo | cape |290| Horas Sol | Open-Meteo | sunshine_duration |291| Rocío | Open-Meteo | dew_point_2m |292| Temp Suelo | Open-Meteo | soil_temperature_* |293| Radiación | Open-Meteo | shortwave_radiation |294| Sensación | Open-Meteo | apparent_temperature |295| Aire Ext | Air Quality API | co, so2, nh3 |296| Mareas | Marine API | ocean_current, wave_height |297| Eólica | Open-Meteo | wind_speed + wind_gusts |298299### Open-Meteo — Parámetros completos válidos (v3.0)300**Current:** temperature_2m, relative_humidity_2m, apparent_temperature, precipitation, rain, snowfall, snow_depth, weather_code, cloud_cover, pressure_msl, surface_pressure, wind_speed_10m, wind_direction_10m, wind_gusts_10m301**Hourly:** temperature_2m, relative_humidity_2m, dew_point_2m, apparent_temperature, precipitation_probability, precipitation, rain, snowfall, snow_depth, weather_code, pressure_msl, surface_pressure, cloud_cover, cloud_cover_low, cloud_cover_mid, cloud_cover_high, visibility, wind_speed_10m, wind_direction_10m, wind_gusts_10m, uv_index, uv_index_clear_sky, cape, freezing_level_height, snowfall_height302**Daily:** weather_code, temperature_2m_max, temperature_2m_min, apparent_temperature_max, apparent_temperature_min, sunrise, sunset, daylight_duration, sunshine_duration, uv_index_max, uv_index_clear_sky_max, precipitation_sum, precipitation_hours, precipitation_probability_max, rain_sum, snowfall_sum, wind_speed_10m_max, wind_gusts_10m_max, wind_direction_10m_dominant, shortwave_radiation_sum, et0_fao_evapotranspiration303**⚠️ NO en current:** sunrise, sunset (solo daily)304305### Marine API — Parámetros306**Current:** wave_height, wave_direction, wave_period, wind_wave_height, swell_wave_height307**Hourly:** wave_height, wave_direction, wave_period, wind_wave_height, wind_wave_direction, wind_wave_period, swell_wave_height, swell_wave_direction, swell_wave_period308309### Air Quality API — Parámetros310**Current:** european_aqi, us_aqi, pm10, pm2_5, nitrogen_dioxide, ozone, carbon_monoxide, sulphur_dioxide, dust, uv_index311**Hourly:** pm10, pm2_5, nitrogen_dioxide, ozone, carbon_monoxide, sulphur_dioxide312**Pollen (current):** alder_pollen, birch_pollen, grass_pollen, mugwort_pollen, olive_pollen, ragweed_pollen313314## Datos provinciales extendidos (data/provincias-data.json)315316Cada provincia tiene estos campos:317```json318{319 "nombre": "Cantabria",320 "poblacion": 588058,321 "ccaa": "06", // ← CRÍTICO: código INE correcto (ver pitfall)322 "capital": "Santander",323 "superficie": 5321, // km²324 "costa_km": 285, // 0 si interior325 "altitud_media": 463, // metros326 "paro": 11.2, // %327 "pib_capita": 29100 // €328}329```330331### Códigos CCAA INE (fuente de verdad)332| Cód | CCAA | Cód | CCAA |333|-----|------|-----|------|334| 01 | Andalucía | 11 | Galicia |335| 02 | Aragón | 12 | La Rioja |336| 03 | Asturias | 13 | C. de Madrid |337| 04 | Illes Balears | 14 | Región de Murcia |338| 05 | Canarias | 15 | Navarra |339| 06 | Cantabria | 16 | País Vasco |340| 07 | Castilla y León | 17 | C. Valenciana |341| 08 | Castilla-La Mancha | 18 | Ceuta |342| 09 | Cataluña | 19 | Melilla |343| 10 | Extremadura | | |344345## Geolocalización completa (v2.2) — CRÍTICO346347### Patrón de barras de contexto348- **Barra `#province-context`:** Aparece arriba al seleccionar provincia, muestra nombre + código349- **Botón limpiar:** Restaura vista nacional (oculta barra, restaura valores por defecto)350- **Animación:** slide-down con `max-height` transition351352### Sincronización de pestañas353Al seleccionar provincia → TODAS las pestañas se actualizan:3541. **Clima:** Fetch Open-Meteo por capital de provincia3552. **Economía:** Filtra PIB/paro de la provincia3563. **Población:** Muestra datos demográficos de la provincia3574. **Puertos:** Muestra puertos de la provincia (si tiene costa)3585. **Catastro:** Pre-selecciona la provincia en el dropdown3596. **Agua:** Muestra embalses de la cuenca correspondiente3607. **Transporte:** Muestra ZBE y radares de la provincia361362### Coordenadas de centroids363```javascript364const provinceCentroids = {365 '01': [37.39, -5.99], // Sevilla (Andalucía)366 '02': [41.65, -0.88], // Zaragoza (Aragón)367 '06': [43.46, -3.81], // Santander (Cantabria)368 // ... 52 provincias369};370```371372### APIs geolocalizadas373- **Clima:** `https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t=...`374- **Marina:** `https://marine-api.open-meteo.com/v1/marine?latitude={lat}&longitude={lon}¤t=...`375- **Fórmula completa Open-Meteo:** `?current=temperature_2m,wind_speed_10m,relative_humidity_2m,weather_code,wind_direction_10m,sunrise,sunset&daily=sunrise,sunset&timezone=Europe/Madrid`376377## Panel detalle de provincia (al click en mapa) — v2.6378379El panel derecho debe mostrar TODOS estos campos (David quiere "muchísimos más datos"):380- Población (INE 2024)381- Capital382- Superficie (km²)383- Densidad (hab/km²) — calculada: población/superficie384- Altitud media (m)385- Costa (km) — "Sin costa" si interior386- Paro provincial (%)387- PIB per cápita (€)388- CCAA (nombre completo)389- Código INE390- **Clima detallado** (API Open-Meteo por capital): temperatura, humedad, viento, condición WMO, amanecer, atardecer391- **Fórmula Open-Meteo CORRECTA:** `?current=temperature_2m,wind_speed_10m,relative_humidity_2m,weather_code&daily=sunrise,sunset&timezone=Europe/Madrid`392- **⚠️ NO incluir sunrise/sunset en current** — causan error silencioso393394## Estado de APIs (actualizado 2026-06-30)395396### ✅ Funcionan (sin auth)397| API | Endpoint | Datos |398|-----|----------|-------|399| ESIOS PVPC | `api.esios.ree.es/indicators/1001` | Precio spot €/MWh |400| ESIOS Demanda | `api.esios.ree.es/indicators/1293` | Demanda total MW |401| Open-Meteo Weather | `api.open-meteo.com/v1/forecast` | Clima por coordenadas |402| Open-Meteo Marine | `marine-api.open-meteo.com/v1/marine` | Oleaje, olas, viento marino |403| Open-Meteo Air Quality | `air-quality-api.open-meteo.com/v1/air-quality` | AQI EU, PM2.5, PM10, O3, NO2, CO, UV, SO2, Polvo |404| Open-Meteo Flood | `flood-api.open-meteo.com/v1/flood` | Caudal de río 14 días |405| Open-Meteo Soil | `api.open-meteo.com/v1/forecast` (soil_*) | Temp y humedad suelo |406| Open-Meteo Pollen | `air-quality-api.open-meteo.com/v1/air-quality` (pollen) | Polen: alder, birch, grass, mugwort, olive, ragweed |407| USGS Earthquake | `earthquake.usgs.gov/fdsnws/event/1/query` | Terremotos M2.5+ en España |408| INE Demografía | `servicios.ine.es/wstempus/js/ES/DATOS_TABLA/9681` | Población por CCAA y sexo (6120 entries) |409| INE Provincias | `servicios.ine.es/wstempus/js/ES/DATOS_TABLA/2852` | Población por provincia y sexo (159 entries) |410| INE Paro EPA | `servicios.ine.es/wstempus/js/ES/DATOS_TABLA/4247` | Tasa de paro por CCAA y edad (420 entries) |411| INE Empleo | `servicios.ine.es/wstempus/js/ES/DATOS_TABLA/4328` | Empleo por sector (66 entries) |412| INE Empleo temporal | `servicios.ine.es/wstempus/js/ES/DATOS_TABLA/4338` | Ocupados a tiempo parcial (150 entries) |413| INE Asalariados | `servicios.ine.es/wstempus/js/ES/DATOS_TABLA/4358` | Asalariados por tipo (198 entries) |414| IGN Geográfico | GeoJSON local (`data/geo/provincias.json`) | Provincias y municipios |415416### ⚠️ Con fallback estático417| API | Fallback | Nota |418|-----|----------|------|419| ESIOS Renovables (1294) | `45.2%` hardcodeado | Ahora devuelve 403 Forbidden (requiere auth) |420421### ❌ No disponibles (usar alternativas)422| API | Problema | Alternativa |423|-----|----------|-------------|424| IGN Terremotos (old URL) | Devuelve HTML, no JSON (Liferay CMS rotó la URL) | USGS Earthquake API (gratuita, CORS-friendly) |425426### 📋 APIs gratuitas españolas pendientes de integrar427| API | Datos potenciales | Auth |428|-----|-------------------|------|429| INE Tabla 39960 | Turismo: pernoctaciones, viajeros por provincia | No |430| INE Tabla 4247 | Educación: matriculados por nivel | No |431| INE Tabla 31304 | Empleo: afiliados SS por provincia | No |432| INE Tabla 2852a | Nacimientos/muertes por provincia | No |433| INE Tabla 9684 | Censo de población por municipio | No |434| AEMET (requires key) | Predicción detallada, warnings | Sí (gratuita con registro) |435| Copernicus CAMS | Calidad aire histórica, emisiones | Sí (gratuita con registro) |436| Sentinel-5P | NO2 troposférico, SO2, CH4 por satélite | Sí (via Copernicus) |437438### Cron de actualización de APIs439- Si una API deja de funcionar → buscar alternativa pública → fallback estático → documentar en esta sección440- Las APIs de ESIOS cambian frecuentemente de requisitos de auth → verificar cada ~3 meses441442## Pitfalls443- **GitHub Pages + Actions:** Requiere `enablement: true` en `actions/configure-pages@v5`444- **Token:** `GITHUB_TOKEN` en `.env`445- **GeoJSON postal codes:** No existe fuente pública → se usan centroides sintéticos446- **ESIOS API:** Requiere token para algunos endpoints447- **CORS:** APIs públicas pueden bloquear requests desde Pages → usar proxy o datos estáticos448- **⚠️ CCAA codes en provincias-data.json:** Los códigos DEBEN seguir el estándar INE (01-19). Error común: asignar códigos desplazados (ej: Cantabria='03' en vez de '06'). SIEMPRE verificar contra la tabla CCAA de arriba. El mapeo CCAA_NAMES en JS también debe alinearse — si está desplazado, Cantabria aparecerá como "Asturias" en el panel detalle.449- **⚠️ Datos por provincia georreferenciados:** David exige que cada pestaña muestre datos relevantes para la provincia seleccionada, no solo datos nacionales genéricos. El panel detalle es la pieza clave — debe ser completo.450- **⚠️ APIs marinas solo para costas:** Open-Meteo Marine solo funciona para coordenadas costeras/marítimas. Para provincias interiores (Madrid, Zaragoza), no llamar a Marine API.451- **⚠️ Sincronización de pestañas:** Al seleccionar provincia, TODAS las pestañas deben actualizarse. David verifica que los datos se mantengan al cambiar de pestaña.452- **⚠️ NO romper al mejorar (Lección 2026-06-30):** David dijo "creo que no estás planteando el crecimiento de la herramienta sin romper cosas". Flujo seguro: (1) diagnóstico antes de tocar, (2) cambios incrementales, (3) verificar braces/JS después de CADA patch, (4) commit por cambio, NO todo junto. Patrón de error: eliminar código JS deja `});` huérfanos que rompen todo silenciosamente.453- **⚠️ Validar datos antes de mostrar (Lección 2026-06-30):** David rechazó "2 metros de nieve" en Madrid en verano. Cada pestaña debe validar que los datos tienen sentido: (1) nieve solo en estaciones de esquí o altitud >1500m, (2) marine API solo para coordenadas costeras, (3) PM2.5 > 100 es inusual en España (alertar), (4) temperaturas < -10 o > 50°C son sospechosas. Patrón: filtrar ciudades que no aplican (ej: pestaña nieve sin datos → "Sin nieve en esta ubicación").454- **⚠️ Open-Meteo: sunrise/sunset NO son current parameters (Lección 2026-06-30):** `sunrise` y `sunset` solo existen como parámetros `daily`, NO como `current`. Si los pones en `current=...,sunrise,sunset` la API falla silenciosamente y el panel muestra "No disponible". Fix: `current=temperature_2m,...,weather_code&daily=sunrise,sunset`. Verificado con curl: Ávila 31.6°C.455- **⚠️ CSS .tab-content selector (Lección 2026-06-30):** Al eliminar o reorganizar bloques CSS, verificar que `.tab-content { flex: 1; overflow-y: auto; padding: 16px; }` exista. Sin este selector, TODOS los paneles de pestañas se muestran simultáneamente (display:none no aplica). Síntoma: 13 paneles visibles en vez de 1. Causa: patch CSS que rompe la continuidad del bloque.456- **⚠️ 2 cron batches pattern (Lección 2026-06-30):** Cuando se crean muchas pestañas nuevas, hacerlo en 2 oleadas: (1) creación básica con API, (2) fix/mejora de cada pestaña. Cada cron hace git pull al inicio para evitar conflictos. Separar 10 min entre crons para que Pages despliegue.457- **⚠️ Datos absurdos por validación (Lección 2026-06-30):** snow_depth viene en METROS (no cm), así que "2 metros de nieve" en Madrid en verano es absurdo. Cada pestaña debe validar: (1) nieve solo >1500m altitud o estaciones de esquí, (2) marine API solo costas, (3) PM2.5 >100 alertar, (4) temp <-10 o >50 sospechoso. Filtrar ciudades que no aplican.458- **⚠️ Tabs responsive wrap (Lección 2026-06-30):** `flex-wrap: nowrap` cause horizontal scroll en móvil. Usar `flex-wrap: wrap` para que las tabs saltan a segunda fila. En móvil: scroll horizontal con `scroll-snap-type: x proximity` + `scroll-snap-align: start` en cada tab.459- **⚠️ DOM nesting al añadir tab panels (Lección 2026-06-30):** Al añadir nuevos `.tab-panel` con `patch()`, si el `old_string` incluye el cierre `</div>` del panel anterior pero el `new_string` no lo reincluye correctamente, los nuevos paneles se ANIDAN dentro del panel anterior en vez de ser hermanos. Síntoma: todos los paneles nuevos tienen `scrollHeight=0` y no se ven. **Debug rápido en consola:**460 ```javascript461 document.querySelectorAll('.tab-panel').forEach(p => {462 console.log(p.id, 'parent:', p.parentElement.id || p.parentElement.className);463 });464 ```465 Si algún panel dice `parent: tab-puertos` en vez de `parent: tab-content`, hay nesting roto. **Fix:** añadir `</div>` de cierre del panel padre antes del primer `<div class="tab-panel">` nuevo. **Verificación Python post-patch:**466 ```python467 import re468 content = open('index.html').read()469 seg = content[content.find('id="tab-puertos">'):content.find('id="tab-polen">')]470 opens = len(re.findall(r'<div[ >]', seg))471 closes = len(re.findall(r'</div>', seg))472 assert opens == closes, f"DOM BROKEN: opens={opens} closes={closes}"473 ```474- **⚠️ GitHub Pages CDN caching (Lección 2026-06-30):** Tras push a `main`, GitHub Pages puede servir versión cacheada durante 2-5 minutos. Añadir `?v=N` al reload no siempre invalida. Si el navegador muestra datos viejos tras push correcto → hard refresh (`Ctrl+Shift+R`) o nueva ventana incógnito. Para verificar que el deploy es correcto: `curl -s raw.githubusercontent.com/Ntizar/DataHubEspana/main/index.html | grep 'NUEVO_TEXT'`.475- **⚠️ Crecimiento sin romper (patrón David):** Cada feature nueva debe: (1) no modificar código existente que funciona, (2) añadir en bloques separados, (3) hacer commit incremental, (4) verificar que pestañas anteriores siguen funcionando. David revisa el dashboard después de cada push y detecta regresiones al instante.476- **⚠️ Helper setTxt() global (v2.7):** `setTxt` es ahora un helper GLOBAL definido en el scope principal del script:477 ```javascript478 const setTxt = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; else console.warn('Element not found:', id); };479 ```480 Usar SIEMPRE `setTxt()` en vez de `document.getElementById(id).textContent` en cualquier función. Evita errores silenciosos cuando un ID no existe o fue renombrado.481- **⚠️ ESIOS 403 handling explícito (v2.7):** Las APIs de ESIOS devuelven 403 Forbidden cuando requieren auth. Pattern correcto:482 ```javascript483 const res = await fetch('https://api.esios.ree.es/indicators/1001', { headers: { 'Accept': 'application/json' } });484 if (res.status === 403) { /* auth required — usar fallback */ }485 else if (res.ok) { /* parsear datos */ }486 ```487 Si falla: mostrar "N/D" (no "—" ni "0.18" — datos corruptos). Validar `latest.value > 0` antes de mostrar (valores 0 = datos stale).488- **⚠️ showToast en errores de API (v2.5):** Cuando una API falla al cargar datos de provincia, mostrar toast de error + valores fallback en vez de dejar la UI con "—" o "Cargando…" eterno:489 ```javascript490 } catch (err) {491 showToast(`Error cargando clima de ${nombre}: ${err.message}`, 'error', 3000);492 setTxt('weather-temp', 'No disponible');493 setTxt('weather-detail', `No se pudieron cargar datos de clima para ${nombre}.`);494 }495 ```496497## Lecciones de la evolución v1→v2→v2.1498499### Lo que David rechazó (v1)500- **Diseño "liquid glass"** → "El diseño es bastante mierda, las cards se notan que son ia"501- **Puntos/círculos en el mapa** → "los cp en vez de puntos deberían ser áreas"502- **Pocos datos** → "faltan muchisimos datos"503- **Sin zoom a provincias** → Necesita interactividad real504505### Lo que David rechazó (v2)506- **Cards con `border-left: 4px solid`** → "se nota mucho que es IA" (screenshot de cards azul/naranja con línea vertical)507- **Patrón genérico de dashboard** → "es demasiado template, se ve como hecho por herramienta"508509### Lo que funcionó (v2.1)510- **Cards sin border-left**, con gradientes sutiles de fondo511- **Hover elevación** en vez de bordes de color512- **3 pestañas nuevas**: Población (INE), Economía detallada, Puertos513- **6 gráficos nuevos**: barras horizontales, doughnuts, barras coloreadas514- **11 pestañas totales** con datos reales de APIs públicas515516### Lo que funcionó (v2.2)517- **Geolocalización completa:** Click provincia → actualiza TODAS las pestañas518- **Barra de contexto:** Muestra provincia seleccionada con botón limpiar519- **Puertos interactivos:** Click puerto → panel detalle con oleaje + clima (APIs Open-Meteo Marine)520- **APIs en cadena:** Weather + Marine para misma ubicación = datos completos521- **52 centroides provinciales:** Para calls API por capital de provincia522523### Lo que funcionó (v2.3)524- **Calidad del aire en tiempo real:** API Open-Meteo Air Quality (CAMS) sin auth, datos por provincia525- **Demografía INE completa:** Pirámide poblacional + distribución por CCAA + sexo526- **KPIs en panel principal:** AQI + UV se actualizan con provincia seleccionada527- **Patrón de APIs gratuitas sin auth:** Open-Meteo (weather/marine/air-quality) + INE + USGS =组合 completo sin registrar nada528- **Incrementalidad:** Cada API nueva se añade como pestaña independiente + KPI en panel + actualización geolocalizada529530### Lo que funcionó (v2.4)531- **4 pestañas nuevas:** Polen, Inundaciones, Suelo, Pronóstico 7 días532- **Clima mejorado:** +6 KPIs (presión, nubosidad, visibilidad, ráfagas, prob. lluvia, precipitación)533- **Paro INE por CCAA:** Tabla 4247 → gráfico barras coloreadas por tasa (rojo >15%, naranja >10%, verde <10%)534- **Todas las nuevas pestañas se geolocalizan:** Click provincia → fetch por centroides535- **Total: 17 pestañas, 30+ gráficos, 12+ APIs en tiempo real**536537### Lo que funcionó (v2.5) — Rediseño layout538- **Pestañas horizontales superiores:** Tabs en `#tab-navbar` scrollable horizontal arriba del mapa539- **Pronóstico fusionado en Clima:** 7 días de forecast integrados en pestaña Clima (cards + gráficos)540- **Capas de mapa:** Carreteras (OpenStreetMap HOT) + Ferrocarriles (OpenRailwayMap) vía Leaflet layer control541- **Tipografía compacta:** KPIs 14-17px, títulos 12px, body 10-11px542- **Error handling mejorado:** setTxt() helper + showToast en errores + fallback values543- **16 pestañas totales** (Pronóstico quitado como pestaña separada)544545### Lo que funcionó (v2.6) — Calidad y funcionalidad546- **Transporte/DGT eliminado:** API rota, datos estáticos inútiles. David: "no aporta información"547- **Choropleth toggleable:** Capa de provincias y parques nacionales activables desde layer control548- **Ambiente mejorado:** 16 parques nacionales con coordenadas reales, flyTo en mapa, markers verdes549- **Catastro mejorado:** Superficie, densidad, municipios, enlace directo por provincia550- **Inundaciones mejorado:** 52 ríos mapeados a provincias, nombre del río en gráfico y KPI551- **Puertos mejorado:** flyTo en mapa al hacer click, datos en vivo (oleaje, viento, temp)552- **Clima fix:** `fetchProvinceWeather()` arreglado — sunrise/sunset movidos a daily parameters553- **Total: 15 pestañas, 30+ gráficos, 12+ APIs en tiempo real**554555### Patrón de selección interactiva por pestaña (Lección 2026-06-30)556557Cada pestaña debe tener su propio selector de ciudad/región para que el usuario pueda elegir dónde ver datos. No solo la selección global de provincia.558559### Selector de ciudad (8-10 ciudades representativas)560```javascript561const CIUDADES_CLIMA = [562 { nombre: 'Madrid', lat: 40.42, lon: -3.70 },563 { nombre: 'Barcelona', lat: 41.39, lon: 2.17 },564 { nombre: 'Sevilla', lat: 37.39, lon: -6.00 },565 { nombre: 'Valencia', lat: 39.47, lon: -0.38 },566 { nombre: 'Bilbao', lat: 43.26, lon: -2.93 },567 { nombre: 'Zaragoza', lat: 41.65, lon: -0.88 },568 { nombre: 'Málaga', lat: 36.72, lon: -4.42 },569 { nombre: 'Las Palmas', lat: 28.10, lon: -15.41 }570];571```572573### Selector de costa (para pestañas marinas)574```javascript575const COSTAS = [576 { nombre: 'Algeciras', lat: 36.13, lon: -5.45 },577 { nombre: 'Valencia', lat: 39.45, lon: -0.32 },578 { nombre: 'Barcelona', lat: 41.34, lon: 2.18 },579 // ... 10 puertos costeros580];581```582583## Autogeneración por CCAA (Lección 2026-06-30)584585Cada pestaña debe mostrar un grid de las 19 comunidades con datos en vivo al hacer click.586587### Patrón CCAA_CENTROIDS588```javascript589const CCAA_CENTROIDS = {590 '01': { name: 'Andalucía', lat: 37.39, lon: -4.50, caps: ['Sevilla','Cádiz','Málaga','Granada'] },591 '13': { name: 'Comunidad de Madrid', lat: 40.42, lon: -3.70, caps: ['Madrid'] },592 // ... 19 comunidades593};594```595596### Función generateCCAATab(dataType)597- Crea grid responsive: `grid-template-columns: repeat(auto-fill, minmax(220px, 1fr))`598- Cada card es clickeable → `selectCCAA(code, dataType)`599- Fetch en vivo de Open-Meteo al hacer click600- Colores dinámicos: PM2.5 verde <10, amarillo <20, rojo >20601602### Init en init()603```javascript604generateCCAATab('clima');605generateCCAATab('aire');606generateCCAATab('nieve');607generateCCAATab('mar');608```609610## Responsive móvil (Lección 2026-06-30)6116123 breakpoints + landscape para cobertura completa:613614### Tablet (≤768px)615- Sidebar: fixed bottom, 50vh, border-radius 12px top616- Tabs: scroll horizontal con snap-to-start, 10px font617- KPIs: 2 columnas, labels 9px, values 14px618- Mapa: 35vh mínimo619- CCAA grid: 2 columnas620621### Móvil (≤480px)622- Sidebar: 55vh, border-radius 10px top623- Tabs: 9px font, padding mínimo, scroll horizontal624- KPIs: 2 columnas, labels 8px, values 13px625- Mapa: 30vh mínimo626- Info grid: 1 columna627- CCAA grid: 1 columna628629### Landscape (≤500px alto)630- Sidebar: 50% ancho a la derecha631- KPIs: 4 columnas632- Mapa: 60vh633634### CSS clave635```css636.tabs-row { flex-wrap: wrap; gap: 4px; } /* wrap en vez de nowrap */637.tab-btn { flex-shrink: 0; scroll-snap-align: start; }638#sidebar { -webkit-overflow-scrolling: touch; }639```640641### HTML del selector642```html643<div class="tab-selector">644 <select id="ciudad-selector" onchange="fetchDatosCiudad(this.value)">645 <option value="">-- Seleccionar ciudad --</option>646 </select>647</div>648```649650### Cascada de selección6511. **Selector global (mapa):** Click provincia → actualiza TODAS las pestañas6522. **Selector por pestaña:** Dropdown específico → actualiza SOLO esa pestaña6533. **Prioridad:** El selector de pestaña sobreescribe la selección global654655## Patrón de cards que SÍ gusta a David656```css657.kpi {658 background: #f8fafc;659 border: 1px solid #e2e8f0;660 border-radius: 10px;661 padding: 14px 16px;662 transition: all 0.2s ease;663}664.kpi:hover {665 background: #ffffff;666 box-shadow: 0 2px 8px rgba(0,0,0,0.06);667 transform: translateY(-1px);668}669.kpi.green { background: linear-gradient(135deg, #f0fdf4 0%, #ffffff 100%); }670.kpi.orange { background: linear-gradient(135deg, #fff7ed 0%, #ffffff 100%); }671.kpi.red { background: linear-gradient(135deg, #fef2f2 0%, #ffffff 100%); }672.kpi.blue { background: linear-gradient(135deg, #eff6ff 0%, #ffffff 100%); }673```674675### GeoJSON fuentes676- **Provincias**: `codeforamerica/click_that_hood` → 52 features, ~1.4MB raw, ~487KB simplificado677- **Municipios**: `AlexGPlay/SpainLayers` → por provincia (28, 29, 41, 46 disponibles)678- **Propiedades**: `{cod, nombre, ccaa}` (provincias), `{id, name}` (municipios — OJO: mapear)679680### GitHub Pages deployment681- Workflow requiere `enablement: true` en `actions/configure-pages@v5`682- Sin esto, el paso "Setup Pages" falla con "Get Pages site failed"683- Token necesita scope `pages: write` + `id-token: write`684685## Prompts cron para capas adicionales686687### Naming mismatch pitfall (Lección 2026-06-30)688Cuando se renombra una función, SIEMPRE buscar TODAS las referencias. Un mismatch causa `ReferenceError` silencioso en init() que rompe la pestaña entera.689690### Batch overnight cron pattern (Lección 2026-06-30)691Para arreglar un dashboard grande (35+ pestañas) de forma nocturna:692- **7 oleadas** de 5 pestañas cada una, espaciadas 8 min693- **1 cron de auditoría final** que verifica todo y genera informe694- **Cada cron es autocontenido:** lee el archivo, hace sus fixes, verifica DOM, commitea695- **Empaquetar pestañas relacionadas** en la misma oleada (ej: meteorológicas juntas, datos juntas)696- **Auditoría final** al final: verifica DOM balance, funciones definidas, paneles con contenido697698### Cron: Actualización Energía (cada hora)699```700Actualiza la capa de energía del DataHub España.7011. Fetch ESIOS API: PVPC (indicator 1001), Demanda (1293), Renovables (1294)7022. Actualizar KPI tiles en index.html7033. Actualizar gráfico PVPC 24h7044. Git commit + push705Nota: ESIOS puede necesitar token. Fallback: mostrar "N/D"706```707708### Cron: Actualización Clima (cada 6h)709```710Actualiza datos climáticos del DataHub España.7111. Fetch Open-Meteo para Madrid (40.4168, -3.7038): temperatura, viento, humedad7122. Actualizar KPI tiles de clima7133. Actualizar descripción del código meteorológico WMO7144. Git commit + push715```716717### Cron: Actualización Embalses (diario)718```719Actualiza niveles de embalses del DataHub España.7201. Leer data/em721722…(truncated)