(function () { "use strict"; const DATA_BASE = ( window.MAPA_DATA_BASE || window.MAPA_POBLACION_DATA_BASE || "data/" ).replace(/\/?$/, "/"); const ID_PREFIX = window.MAPA_ID_PREFIX || ""; const root = (window.MAPA_ROOT_ID && document.getElementById(window.MAPA_ROOT_ID)) || document.querySelector("[data-mapa-poblacion]") || document.getElementById("mapa-poblacion"); function $(id) { const el = document.getElementById(ID_PREFIX + id); if (!el) throw new Error(`Elemento #${ID_PREFIX}${id} no encontrado`); return el; } if (!root) { console.error("Mapa población: falta contenedor (#mapa-poblacion o data-mapa-poblacion)"); return; } const svg = d3.select($("map")); const mapWrap = $("map-wrap"); const tooltip = $("tooltip"); const subtitle = $("subtitle"); const btnBack = $("btn-back"); const loadingEl = $("loading"); const loadingText = $("loading-text"); const legendEl = $("legend"); const legendLabel = $("legend-label"); const legendBar = $("legend-bar"); const legendTicks = $("legend-ticks"); const scaleToggle = $("scale-toggle"); const neighborPanel = $("neighbor-panel"); const neighborList = $("neighbor-list"); const titleEl = root.querySelector(".mapa-poblacion__title"); const projection = d3.geoMercator(); const path = d3.geoPath().projection(projection); const POP_COLOR_MIN = "#f5f0eb"; const POP_COLOR_MAX = "#7b1800"; const popColor = d3 .scaleSequential() .interpolator((t) => d3.interpolateRgb(POP_COLOR_MIN, POP_COLOR_MAX)(t)) .unknown("#e8e4dc"); let width = 960; let height = 560; let estadosFc = null; let nationalPop = null; let slugByName = new Map(); let vecinosMap = new Map(); let overlayStates = new Set(); let overlayMunicipiosCache = new Map(); let view = "nacional"; let activeState = null; let activeStateFeature = null; let scaleMode = "estatal"; let currentMunicipiosFc = null; let isTransitioning = false; let zoomTransform = d3.zoomIdentity; let municipalBaseTransform = d3.zoomIdentity; const prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)" ).matches; const ZOOM_MS = prefersReducedMotion ? 0 : 850; const FADE_MS = prefersReducedMotion ? 0 : 550; const STATE_FADE_MS = prefersReducedMotion ? 0 : 280; const NEIGHBOR_FILL = "#e8e4dc"; const zoomLayer = svg.append("g").attr("class", "zoom-layer"); const gEstados = zoomLayer.append("g").attr("class", "layer-estados"); const gMunicipios = zoomLayer.append("g").attr("class", "layer-municipios"); const gMunicipiosOverlay = zoomLayer.append("g").attr("class", "layer-municipios-overlay"); const gOutline = zoomLayer.append("g").attr("class", "layer-outline"); const zoom = d3 .zoom() .scaleExtent([1, 1]) .filter((event) => { if (isTransitioning || view !== "estatal") return false; if ( (event.type === "mousedown" || event.type === "touchstart") && event.target instanceof Element && event.target.classList.contains("is-neighbor") ) { return false; } return (!event.ctrlKey || event.type === "wheel") && !event.button; }) .on("start", () => { if (view === "estatal") { svg.classed("is-panning", true); hideTooltip(); } }) .on("end", () => svg.classed("is-panning", false)) .on("zoom", (event) => { zoomTransform = event.transform; zoomLayer.attr("transform", event.transform); }); svg.call(zoom).on("dblclick.zoom", null); svg.on("dblclick", (event) => { if (view !== "estatal" || isTransitioning) return; event.preventDefault(); svg .transition() .duration(250) .call(zoom.transform, municipalBaseTransform); }); function setMunicipalZoomBounds(base) { municipalBaseTransform = base; zoom.scaleExtent([base.k * 0.65, base.k * 8]); } function interruptTransitions() { svg.interrupt(); gEstados.selectAll("*").interrupt(); gMunicipios.selectAll("*").interrupt(); gMunicipiosOverlay.selectAll("*").interrupt(); gOutline.selectAll("*").interrupt(); } function ensureNationalProjection() { projection.fitSize([width, height], estadosFc); path.projection(projection); } function applyZoomTransform(transform) { zoomTransform = transform; zoomLayer.attr("transform", transform); svg.call(zoom.transform, transform); } function transformForFeature(feature) { return transformForBounds(path.bounds(feature)); } function transformForGeoJSON(geojson) { return transformForBounds(path.bounds(geojson)); } function transformForBounds([[x0, y0], [x1, y1]]) { const dx = x1 - x0; const dy = y1 - y0; const x = (x0 + x1) / 2; const y = (y0 + y1) / 2; const scale = 0.92 / Math.max(dx / width, dy / height); return d3.zoomIdentity .translate(width / 2, height / 2) .scale(scale) .translate(-x, -y); } function animateZoomTo(target, duration) { if (duration <= 0) { applyZoomTransform(target); return Promise.resolve(); } return new Promise((resolve) => { svg .transition() .duration(duration) .ease(d3.easeCubicInOut) .call(zoom.transform, target) .on("end", resolve); }); } function transitionOpacity(selection, opacity, duration) { if (selection.empty()) return Promise.resolve(); if (duration <= 0) { selection.style("opacity", opacity); return Promise.resolve(); } return selection .transition() .duration(duration) .style("opacity", opacity) .end() .catch(() => {}); } function transitionFillOpacity(selection, fillOpacity, duration) { if (selection.empty()) return Promise.resolve(); if (duration <= 0) { selection.style("fill-opacity", fillOpacity); return Promise.resolve(); } return selection .transition() .duration(duration) .style("fill-opacity", fillOpacity) .end() .catch(() => {}); } function resize() { const rect = mapWrap.getBoundingClientRect(); width = Math.max(320, rect.width); height = Math.round(width * 0.58); svg.attr("viewBox", `0 0 ${width} ${height}`); if (estadosFc && !isTransitioning) redrawCurrentView(); } function regionName(d) { return d.properties.NAME_2 || d.properties.NAME_1 || ""; } function regionPop(d) { return +d.properties.pob_total || 0; } function formatPop(n) { return n.toLocaleString("es-MX"); } function regionLabel(d) { const name = regionName(d); const pop = regionPop(d); if (pop > 0) return `${name} — ${formatPop(pop)} hab.`; return name; } function showTooltip(event, d) { if (isTransitioning) return; const label = typeof d === "string" ? d : regionLabel(d); if (!label) return; tooltip.hidden = false; tooltip.textContent = label; const bounds = mapWrap.getBoundingClientRect(); tooltip.style.left = `${event.clientX - bounds.left}px`; tooltip.style.top = `${event.clientY - bounds.top}px`; } function hideTooltip() { tooltip.hidden = true; } function setLoading(on) { loadingEl.hidden = !on; } function popRange(features, level) { const pops = features.map(regionPop).filter((n) => n > 0); if (!pops.length) return null; if (scaleMode === "nacional" && nationalPop && nationalPop[level]) { return { lo: nationalPop[level].min, hi: nationalPop[level].max }; } return { lo: d3.min(pops), hi: d3.max(pops) }; } function popScaleForFeatures(features, level) { const range = popRange(features, level); if (!range) return () => popColor.unknown(); popColor.domain([Math.log10(range.lo), Math.log10(range.hi)]); return (d) => { const p = regionPop(d); return p > 0 ? popColor(Math.log10(p)) : popColor.unknown(); }; } function legendScaleLabel(level) { if (view === "nacional") return "Escala: México (estados)"; if (level === "municipios" && overlayStates.size > 0) { const extra = [...overlayStates].join(", "); return `Escala: México (${activeState} + ${extra})`; } return scaleMode === "nacional" ? "Escala: México (municipios)" : `Escala: ${activeState || "estado"} (municipios)`; } function hasOverlays() { return overlayStates.size > 0; } function allVisibleMunicipioFeatures() { const feats = currentMunicipiosFc ? [...currentMunicipiosFc.features] : []; overlayStates.forEach((name) => { const fc = overlayMunicipiosCache.get(name); if (fc) feats.push(...fc.features); }); return feats; } function municipioFeaturesForScale() { if (hasOverlays() || scaleMode === "nacional") { return allVisibleMunicipioFeatures(); } return currentMunicipiosFc ? currentMunicipiosFc.features : []; } function combinedMunicipiosFc() { return { type: "FeatureCollection", features: allVisibleMunicipioFeatures() }; } function municipioKey(d) { return `${d.properties.NAME_1}|${d.properties.NAME_2}`; } function setNeighborPanelVisible(visible) { neighborPanel.hidden = !visible; } function clearOverlaySelection(resetCache) { overlayStates.clear(); gMunicipiosOverlay.selectAll("*").remove(); if (resetCache) overlayMunicipiosCache.clear(); neighborList.querySelectorAll("input[type=checkbox]").forEach((cb) => { cb.checked = false; }); if (view === "estatal") { applyEstatalEstadoStyle(gEstados.selectAll("path.region--estado")); } } function populateNeighborPanel(stateName) { neighborList.innerHTML = ""; const neighbors = vecinosMap.get(stateName) || []; setNeighborPanelVisible(neighbors.length > 0); neighbors.forEach((neighborName) => { const label = document.createElement("label"); label.className = "neighbor-panel__item"; const cb = document.createElement("input"); cb.type = "checkbox"; cb.value = neighborName; cb.addEventListener("change", () => { onNeighborToggle(neighborName, cb.checked); }); label.appendChild(cb); label.appendChild(document.createTextNode(` ${neighborName}`)); neighborList.appendChild(label); }); } async function loadMunicipiosForState(stateName) { if (overlayMunicipiosCache.has(stateName)) { return overlayMunicipiosCache.get(stateName); } const entry = slugByName.get(stateName); if (!entry) throw new Error(`Sin índice para ${stateName}`); const resp = await fetch(DATA_BASE + entry.file); if (!resp.ok) throw new Error(resp.statusText); const fc = await resp.json(); overlayMunicipiosCache.set(stateName, fc); return fc; } function refreshMunicipioColors() { const scaleFeats = municipioFeaturesForScale(); const fillFn = popScaleForFeatures(scaleFeats, "municipios"); gMunicipios.selectAll("path.region--municipio--primary").attr("fill", fillFn); gMunicipiosOverlay.selectAll("path.region--municipio--overlay").attr("fill", fillFn); updateLegend(scaleFeats, "municipios"); } function drawOverlayMunicipios() { const features = []; overlayStates.forEach((name) => { const fc = overlayMunicipiosCache.get(name); if (fc) features.push(...fc.features); }); const fillFn = popScaleForFeatures(municipioFeaturesForScale(), "municipios"); const joined = gMunicipiosOverlay .selectAll("path.region--municipio--overlay") .data(features, municipioKey); joined.exit().remove(); const enter = joined .enter() .append("path") .attr("class", "region region--municipio region--municipio--overlay"); joined .merge(enter) .attr("fill", fillFn) .attr("d", path) .style("opacity", 1) .call((sel) => bindInteractions(sel, !isTransitioning && view === "estatal")); if (features.length) updateLegend(municipioFeaturesForScale(), "municipios"); } function updateEstadoSilhouetteForOverlays() { gEstados.selectAll("path.region--estado.is-neighbor").style("opacity", (d) => overlayStates.has(d.properties.NAME_1) ? 0 : 1 ); } async function onNeighborToggle(neighborName, checked) { if (isTransitioning) return; hideTooltip(); if (checked) { try { await loadMunicipiosForState(neighborName); overlayStates.add(neighborName); } catch (err) { console.error(err); const cb = [...neighborList.querySelectorAll("input")].find( (el) => el.value === neighborName ); if (cb) cb.checked = false; return; } } else { overlayStates.delete(neighborName); } updateEstadoSilhouetteForOverlays(); drawOverlayMunicipios(); refreshMunicipioColors(); if (checked && currentMunicipiosFc) { const base = transformForGeoJSON(combinedMunicipiosFc()); setMunicipalZoomBounds(base); await animateZoomTo(base, prefersReducedMotion ? 0 : 450); } else if (!hasOverlays() && currentMunicipiosFc) { const base = transformForGeoJSON(currentMunicipiosFc); setMunicipalZoomBounds(base); await animateZoomTo(base, prefersReducedMotion ? 0 : 450); } } function fitVisibleMunicipios() { if (!currentMunicipiosFc) return; const fc = hasOverlays() ? combinedMunicipiosFc() : currentMunicipiosFc; const base = transformForGeoJSON(fc); setMunicipalZoomBounds(base); applyZoomTransform(base); } function updateLegend(features, level) { const range = popRange(features, level); if (!range) { legendEl.hidden = true; return; } const { lo, hi } = range; popColor.domain([Math.log10(lo), Math.log10(hi)]); legendLabel.textContent = `Población (Censo 2020) · ${legendScaleLabel(level)}`; const steps = 12; const stops = d3.range(steps).map((i) => { const t = i / (steps - 1); const logVal = Math.log10(lo) + t * (Math.log10(hi) - Math.log10(lo)); const pct = (t * 100).toFixed(1); return `${popColor(logVal)} ${pct}%`; }); legendBar.style.background = `linear-gradient(to right, ${stops.join(", ")})`; legendTicks.innerHTML = ""; [lo, hi].forEach((val) => { const span = document.createElement("span"); span.textContent = formatPop(val); legendTicks.appendChild(span); }); legendEl.hidden = false; } function setScaleToggleVisible(visible) { scaleToggle.hidden = !visible; } function updateScaleToggleUi() { scaleToggle.querySelectorAll("[data-mode]").forEach((btn) => { btn.classList.toggle("is-active", btn.dataset.mode === scaleMode); }); } function setScaleMode(mode) { if (scaleMode === mode) return; scaleMode = mode; updateScaleToggleUi(); if (view === "estatal" && currentMunicipiosFc) { drawMunicipios(currentMunicipiosFc.features, { opacity: 1, animate: false }); drawOverlayMunicipios(); updateEstadoSilhouetteForOverlays(); refreshMunicipioColors(); } } function bindInteractions(selection, clickable) { selection .on("mouseenter", function (event, d) { if (isTransitioning) return; d3.select(this).classed("is-hovered", true); showTooltip(event, d); }) .on("mousemove", (event, d) => showTooltip(event, d)) .on("mouseleave", function () { d3.select(this).classed("is-hovered", false); hideTooltip(); }) .on("click", clickable ? function (event, d) { event.stopPropagation(); if (isTransitioning) return; if (view === "nacional") enterState(d); else if (view === "estatal" && !isActiveState(d)) enterState(d); } : null); } function bindEstadoLayerInteractions(selection) { selection.each(function (d) { const clickable = view === "nacional" || (view === "estatal" && !isActiveState(d)); bindInteractions(d3.select(this), clickable); }); } function finishEstatalView(municipiosFc, name) { clearOverlaySelection(false); populateNeighborPanel(name); fitVisibleMunicipios(); btnBack.hidden = false; subtitle.textContent = `${municipiosFc.features.length} municipios · colindantes = superponer · rueda = zoom`; if (titleEl) titleEl.textContent = name; svg.classed("view--estados", false); setScaleToggleVisible(true); gMunicipios .selectAll("path.region--municipio--primary") .style("opacity", 1) .call((sel) => bindInteractions(sel, true)); bindEstadoLayerInteractions(gEstados.selectAll("path.region--estado")); isTransitioning = false; svg.classed("is-transitioning", false); } function isActiveState(d) { return d.properties.NAME_1 === activeState; } function applyEstatalEstadoStyle(selection) { selection .classed("is-selected", isActiveState) .classed("is-neighbor", (d) => !isActiveState(d)) .attr("fill", (d) => (isActiveState(d) ? null : NEIGHBOR_FILL)) .attr("stroke", null) .style("opacity", (d) => (isActiveState(d) ? 0 : 1)) .style("fill-opacity", 1); } function drawEstados(features, opts = {}) { const fillFn = popScaleForFeatures(features, "estados"); const keyFn = (d) => d.properties.NAME_1; const estatalLayer = view === "estatal" && activeState; const joined = gEstados .selectAll("path.region--estado") .data(features, keyFn); joined.exit().remove(); const enter = joined .enter() .append("path") .attr("class", "region region--estado"); const merged = joined.merge(enter).attr("d", path); if (estatalLayer) { applyEstatalEstadoStyle(merged); } else { merged .attr("fill", fillFn) .attr("stroke", null) .classed("is-selected", false) .classed("is-neighbor", false); if (typeof opts.opacity === "function") { merged.style("opacity", opts.opacity); } else if (opts.opacity != null) { merged.style("opacity", opts.opacity); } else { merged.style("opacity", 1); } if (typeof opts.fillOpacity === "function") { merged.style("fill-opacity", opts.fillOpacity); } else if (opts.fillOpacity != null) { merged.style("fill-opacity", opts.fillOpacity); } else { merged.style("fill-opacity", 1); } } merged.call((sel) => bindEstadoLayerInteractions(sel)); if (view === "nacional") updateLegend(features, "estados"); } function drawMunicipios(features, opts = {}) { const fillFn = popScaleForFeatures(municipioFeaturesForScale(), "municipios"); const keyFn = (d) => d.properties.NAME_2; const startOpacity = opts.opacity != null ? opts.opacity : 1; const animate = opts.animate !== false && startOpacity === 0 && FADE_MS > 0; const joined = gMunicipios .selectAll("path.region--municipio--primary") .data(features, keyFn); joined.exit().remove(); const enter = joined .enter() .append("path") .attr("class", "region region--municipio region--municipio--primary"); const merged = joined .merge(enter) .attr("fill", fillFn) .attr("d", path) .style("opacity", startOpacity) .style("fill-opacity", 1); if (animate) { merged .transition() .duration(opts.fadeMs != null ? opts.fadeMs : FADE_MS) .ease(d3.easeCubicOut) .style("opacity", 1); } merged.call((sel) => bindInteractions(sel, !isTransitioning && view === "estatal") ); updateLegend(municipioFeaturesForScale(), "municipios"); } function drawStateOutline(feature) { gOutline.selectAll("*").remove(); if (!feature) return; gOutline .append("path") .datum(feature) .attr("class", "region region--outline") .attr("d", path); } function clearMunicipios() { gMunicipios.selectAll("*").remove(); gMunicipiosOverlay.selectAll("*").remove(); } function clearOutline() { gOutline.selectAll("*").remove(); } function redrawCurrentView() { ensureNationalProjection(); if (view === "nacional") { svg.classed("view--estados", true); setScaleToggleVisible(false); applyZoomTransform(d3.zoomIdentity); clearMunicipios(); clearOverlaySelection(true); setNeighborPanelVisible(false); activeState = null; activeStateFeature = null; drawEstados(estadosFc.features, { opacity: 1, fillOpacity: 1, clickable: true }); return; } svg.classed("view--estados", false); setScaleToggleVisible(true); if (activeStateFeature && currentMunicipiosFc) { fitVisibleMunicipios(); } else if (activeStateFeature) { applyZoomTransform(transformForFeature(activeStateFeature)); } drawEstados(estadosFc.features, { clickable: false }); if (currentMunicipiosFc) { drawMunicipios(currentMunicipiosFc.features, { opacity: 1, animate: false }); drawOverlayMunicipios(); } clearOutline(); } function applyNacionalInstant() { view = "nacional"; currentMunicipiosFc = null; clearOverlaySelection(true); setNeighborPanelVisible(false); btnBack.hidden = true; subtitle.textContent = "32 estados · color = población (Censo 2020) — haz clic en un estado"; if (titleEl) titleEl.textContent = "México"; redrawCurrentView(); } async function renderNacional() { if (isTransitioning) return; hideTooltip(); if (view !== "estatal" || ZOOM_MS <= 0) { applyNacionalInstant(); return; } const prevState = activeState; const stateFeature = activeStateFeature; if (!stateFeature) { applyNacionalInstant(); return; } isTransitioning = true; svg.classed("is-transitioning", true); btnBack.disabled = true; await transitionOpacity( gMunicipios.selectAll("path.region--municipio--primary, path.region--municipio--overlay"), 0, FADE_MS ); clearMunicipios(); clearOverlaySelection(true); setNeighborPanelVisible(false); const selectedPath = gEstados.selectAll("path.region--estado").filter( (d) => d.properties.NAME_1 === prevState ); await transitionOpacity(selectedPath, 1, FADE_MS); selectedPath.style("fill-opacity", 1); btnBack.hidden = true; subtitle.textContent = "32 estados · color = población (Censo 2020) — haz clic en un estado"; if (titleEl) titleEl.textContent = "México"; view = "nacional"; activeState = null; activeStateFeature = null; currentMunicipiosFc = null; setScaleToggleVisible(false); svg.classed("view--estados", true); drawEstados(estadosFc.features, { opacity: 1, fillOpacity: 1, clickable: false }); gEstados .selectAll("path.region--estado") .call((sel) => bindInteractions(sel, false)); gEstados .selectAll("path.region--estado") .transition() .duration(ZOOM_MS) .style("opacity", 1); await animateZoomTo(d3.zoomIdentity, ZOOM_MS); clearOutline(); drawEstados(estadosFc.features, { opacity: 1, fillOpacity: 1, clickable: true }); isTransitioning = false; btnBack.disabled = false; svg.classed("is-transitioning", false); } async function enterState(estadoFeature) { if (isTransitioning) return; hideTooltip(); const name = estadoFeature.properties.NAME_1; if (view === "estatal" && name === activeState) return; const entry = slugByName.get(name); if (!entry) { console.warn("Sin datos municipales para:", name); return; } const fromEstatel = view === "estatal"; isTransitioning = true; svg.classed("is-transitioning", true); interruptTransitions(); clearOverlaySelection(true); ensureNationalProjection(); activeState = name; activeStateFeature = estadoFeature; view = "estatal"; if (fromEstatel) { await transitionOpacity( gMunicipios.selectAll("path.region--municipio--primary, path.region--municipio--overlay"), 0, FADE_MS ); clearMunicipios(); } else { gEstados .selectAll("path.region--estado") .classed("is-selected", (d) => d.properties.NAME_1 === name) .classed("is-neighbor", (d) => d.properties.NAME_1 !== name); const fillFn = popScaleForFeatures(estadosFc.features, "estados"); gEstados .selectAll("path.region--estado") .transition() .duration(ZOOM_MS) .style("opacity", 1) .attr("fill", (d) => d.properties.NAME_1 === name ? fillFn(d) : NEIGHBOR_FILL ); } applyEstatalEstadoStyle(gEstados.selectAll("path.region--estado")); bindEstadoLayerInteractions(gEstados.selectAll("path.region--estado")); let municipiosFc; try { const resp = await fetch(DATA_BASE + entry.file); if (!resp.ok) throw new Error(resp.statusText); municipiosFc = await resp.json(); } catch (err) { console.error(err); clearMunicipios(); if (!fromEstatel) { activeState = null; activeStateFeature = null; view = "nacional"; await animateZoomTo(d3.zoomIdentity, ZOOM_MS); drawEstados(estadosFc.features, { opacity: 1, fillOpacity: 1, clickable: true }); } else { bindEstadoLayerInteractions(gEstados.selectAll("path.region--estado")); } subtitle.textContent = `Error al cargar municipios de ${name}`; isTransitioning = false; svg.classed("is-transitioning", false); return; } currentMunicipiosFc = municipiosFc; const zoomStarted = performance.now(); const zoomPromise = animateZoomTo( transformForGeoJSON(municipiosFc), ZOOM_MS ); if (!fromEstatel) { const selectedPath = gEstados.selectAll("path.region--estado.is-selected"); await transitionOpacity(selectedPath, 0, STATE_FADE_MS); applyEstatalEstadoStyle(gEstados.selectAll("path.region--estado")); bindEstadoLayerInteractions(gEstados.selectAll("path.region--estado")); } const elapsed = performance.now() - zoomStarted; const municipioFadeMs = Math.max(FADE_MS, ZOOM_MS - elapsed); drawMunicipios(municipiosFc.features, { opacity: 0, fadeMs: municipioFadeMs, animate: municipioFadeMs > 0, }); await zoomPromise; finishEstatalView(municipiosFc, name); } async function init() { loadingText.textContent = "Cargando mapa…"; setLoading(true); window.addEventListener("resize", resize); btnBack.addEventListener("click", renderNacional); scaleToggle.querySelectorAll("[data-mode]").forEach((btn) => { btn.addEventListener("click", () => setScaleMode(btn.dataset.mode)); }); updateScaleToggleUi(); const [estadosResp, indexResp, popResp, vecinosResp] = await Promise.all([ fetch(DATA_BASE + "estados.geojson"), fetch(DATA_BASE + "index.json"), fetch(DATA_BASE + "poblacion-nacional.json"), fetch(DATA_BASE + "vecinos.json"), ]); if (!estadosResp.ok) throw new Error("No se pudo cargar estados.geojson"); if (!popResp.ok) throw new Error("No se pudo cargar poblacion-nacional.json"); estadosFc = await estadosResp.json(); nationalPop = await popResp.json(); const index = await indexResp.json(); slugByName = new Map(index.map((e) => [e.name, e])); if (vecinosResp.ok) { const vecinos = await vecinosResp.json(); vecinosMap = new Map(Object.entries(vecinos)); } resize(); applyNacionalInstant(); setLoading(false); } init().catch((err) => { console.error(err); setLoading(false); subtitle.textContent = "Error al cargar el mapa. Revisa MAPA_DATA_BASE y que los datos estén en GitHub."; }); })();