HackODS UNAM
  • Equipo linuxitOS
La Estructura de la Pobreza: La Brecha Hídrica en México
  • LA BRECHA ESTRUCTURAL
  • LA VOZ Y LA ACCIÓN
  • AUDITORÍA DE DATOS
municipios_scatter = FileAttachment("data/municipios_scatter.json").json()
{
  document.addEventListener('click', e => {
    if (!e.target.closest('.ref-sup')) return;
    const target = document.getElementById('fuentes-testimonios');
    if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
  });
}
La Estructura de la Pobreza: La Brecha Hídrica en México

Cruzar la pobreza extrema con la falta de agua limpia no arroja una simple estadística; expone una condena estructural. La pobreza va más allá de la falta de ingresos; es la ausencia de los servicios básicos que permiten vivir con dignidad. Hoy, el promedio nacional invisibiliza esta carencia y encubre la trampa del México rural.

Cobertura Nacional (SIODS)

64%

Municipios Rurales

55%

Desigualdad Hídrica Rural

4.6x

Carencia de Agua Entubada en los 2,469 Municipios de México - 2020
Distribución de Municipios en México - 2020
{
  await new Promise(r => setTimeout(r, 500));

  const modalEl = html`<div class="modal fade" id="dist-metodologia-modal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-dialog-scrollable">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title dm-titulo">Nota Metodológica</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
        </div>
        <div class="modal-body dm-cuerpo">
          <p>En este análisis, la clasificación de municipios como rurales no se realiza de manera individual, sino a partir de un criterio agregado a nivel estatal. Se considera que un estado es predominantemente rural cuando más del 50% de sus municipios tienen menos de 2,500 habitantes; en ese caso, todos los municipios del estado se clasifican como rurales, independientemente de su tamaño poblacional individual.</p>
          <p>Este enfoque busca <strong>visibilizar la predominancia territorial de lo rural</strong>, una realidad que a menudo queda oculta cuando el análisis se centra únicamente en las zonas de alta densidad geográfica. Si bien los grandes centros urbanos tienen un gran volumen de habitantes, la estructura municipal del país demuestra que la vida rural abarca la mayor parte del territorio nacional. Esta inmensa presencia territorial exige replantear los desafíos en la infraestructura y acceso a servicios desde la equidad, no desde la densidad.</p>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-sm dm-btn-cerrar" data-bs-dismiss="modal">Cerrar</button>
        </div>
      </div>
    </div>
  </div>`;
  document.body.appendChild(modalEl);

  const infoBtn = html`<button
    class="btn-nota-metodologica-dist"
    data-bs-toggle="modal"
    data-bs-target="#dist-metodologia-modal"
    title="Ver nota metodológica">ℹ</button>`;

  const headers = document.querySelectorAll('.card-header');
  for (const h of headers) {
    if (h.textContent.trim().includes('Distribución de Municipios')) {
      const card = h.closest('.card');
      card.style.position = 'relative';
      card.appendChild(infoBtn);
      break;
    }
  }
}
La Ilusión Nacional

México reporta un 64% de acceso a agua segura a nivel nacional. Sin embargo, las grandes ciudades “promedian hacia arriba”, borrando a las comunidades rurales y periféricas. Al analizar los microdatos, la estadística se derrumba. Invisibilizar la carencia es violar un derecho humano.

La Realidad Demográfica

Solo el 17% de los mexicanos vive en zonas rurales, pero esto representa más del 55% de los municipios del país. Si medimos por equidad territorial y no por densidad, la vida rural abarca la mayor parte de México.

El Veredicto Matemático

4.6× Mayor carencia de agua en el campo

2× Mayor pobreza extrema rural

Cruzar el Censo con la infraestructura desmiente la “Ilusión Nacional”. La carencia de agua entubada es 4.6 veces mayor en el campo, y la pobreza extrema es el doble. La pobreza tiene una dirección postal olvidada por el presupuesto.

Déficit de Agua vs. Pobreza Extrema en México (2,469 Municipios) - 2020
{
  const searchInput = html`<input type="text" placeholder="Ej. Oaxaca, Guerrero..."
    style="padding: 0.5rem 0.65rem; border: 1px solid #d1d5db; border-radius: 6px;
           font-family: Inter, sans-serif; font-size: 0.95rem;
           width: min(400px, 100%); outline: none;
           transition: border-color 0.2s, box-shadow 0.2s;">`;
           
  searchInput.addEventListener("focus", () => {
    searchInput.style.borderColor = "#78350F";
    searchInput.style.boxShadow = "0 0 0 3px rgba(120, 53, 15, 0.12)";
  });
  
  searchInput.addEventListener("blur", () => {
    searchInput.style.borderColor = "#d1d5db";
    searchInput.style.boxShadow = "none";
  });

  const searchRow = html`<label style="display:flex; gap:0.75rem; align-items:center;
    font-family:Inter, sans-serif; font-weight:600; color:#1a1a1a;
    font-size:0.95rem; margin:0; padding:0; flex-wrap:wrap;">
    <span>Busca tu municipio o entidad federativa:</span>${searchInput}</label>`;

  const chartWrapper = html`<div style="flex:1; position:relative; min-height:420px; width:100%; margin-top:0.5rem;"></div>`;
  const chartEl = html`<div style="position:absolute; top:0; left:0; right:0; bottom:0; overflow:hidden;"></div>`;
  chartWrapper.append(chartEl);

  const container = html`<div style="display:flex; flex-direction:column;
    height:100%; width:100%; padding:0.25rem 0;"></div>`;

  container.append(searchRow, chartWrapper);

  // Tooltip HTML personalizado — Plot.tip NO soporta fill dinámico por canal
  // (doc oficial: "tip mark does not support the standard style channels such as varying fill").
  // Implementamos tooltip como <div> con hit-testing por búsqueda lineal O(n).
  const tooltip = html`<div style="
    position:absolute; pointer-events:none; opacity:0; left:0; top:0;
    padding:0.55rem 0.8rem; border-radius:8px;
    font-family:Inter,sans-serif; font-size:11.5px; line-height:1.55;
    box-shadow:0 6px 20px rgba(0,0,0,0.22);
    z-index:30; max-width:280px; white-space:nowrap;
    transition:opacity 0.08s ease-out;
    border:1.5px solid rgba(255,255,255,0.35);"></div>`;
  chartWrapper.append(tooltip);

  // Interpolador de color idéntico a la escala de Plot [0,35,65,100] → verde→naranja→rojo→rojo oscuro
  function severityColor(v) {
    const stops = [
      [0,   [39, 174, 96]],
      [35,  [243, 156, 18]],
      [65,  [231, 76, 60]],
      [100, [127, 29, 29]]
    ];
    v = Math.max(0, Math.min(100, v || 0));
    for (let i = 1; i < stops.length; i++) {
      const [s0, c0] = stops[i - 1];
      const [s1, c1] = stops[i];
      if (v <= s1) {
        const t = (v - s0) / (s1 - s0);
        const rr = Math.round(c0[0] + (c1[0] - c0[0]) * t);
        const gg = Math.round(c0[1] + (c1[1] - c0[1])  * t);
        const bb = Math.round(c0[2] + (c1[2] - c0[2])  * t);
        return `rgb(${rr},${gg},${bb})`;
      }
    }
    return 'rgb(127,29,29)';
  }

  // Contraste automático vía luminancia YIQ — el naranja necesita texto oscuro, el resto blanco
  function textColorFor(bg) {
    const [r, g, b] = bg.match(/\d+/g).map(Number);
    const y = 0.299 * r + 0.587 * g + 0.114 * b;
    return y > 150 ? '#0f172a' : '#ffffff';
  }

  // Estado compartido entre render() y handler de mousemove
  // pointsWithPx: Array<{d, px, py}> — posiciones en píxeles precomputadas
  let pointsWithPx = [];
  let svgCache = null;

  function render() {
    const q = searchInput.value.trim().toLowerCase();
    const hasSearch = q !== "";
    const ok = d =>
      (d.Municipio || "").toLowerCase().includes(q) ||
      (d.Estado || "").toLowerCase().includes(q);

    const activos = hasSearch ? municipios_scatter.filter(ok) : municipios_scatter;
    const inactivos = hasSearch ? municipios_scatter.filter(d => !ok(d)) : [];

    const top5_peores = municipios_scatter
      .slice()
      .sort((a, b) => (b.severidad || 0) - (a.severidad || 0))
      .slice(0, 5);

    const w = chartEl.clientWidth || 800;
    const h = (chartEl.clientHeight || 420) - 60;

    const plotFig = Plot.plot({
      width: w,
      height: h,
      marginLeft: 60,
      marginBottom: 50,
      x: { label: "% Sin Agua Entubada (CONAGUA)", grid: true },
      y: { label: "% en Pobreza Extrema (CONEVAL)", grid: true },
      color: {
        type: "linear",
        domain: [0, 35, 65, 100],
        range: ["#27ae60", "#f39c12", "#e74c3c", "#7f1d1d"],
        label: "Índice de Severidad",
        legend: true
      },
      marks: [
        Plot.dot(inactivos, {
          x: "carencia_agua_conagua_pct",
          y: "Pobreza_extrema_pct",
          fill: "#cccccc",
          opacity: 0.15,
          r: 4
        }),
        Plot.dot(activos, {
          x: "carencia_agua_conagua_pct",
          y: "Pobreza_extrema_pct",
          fill: "severidad",
          stroke: "rgba(255,255,255,0.4)",
          strokeWidth: 1,
          r: 4,
          opacity: 0.85
        }),
        Plot.arrow(top5_peores, {
          x1: (d, i) => d.carencia_agua_conagua_pct - (27 - (i * 2)),
          y1: (d, i) => d.Pobreza_extrema_pct + (18 - (i * 1)),
          x2: "carencia_agua_conagua_pct",
          y2: "Pobreza_extrema_pct",
          bend: true,
          stroke: "#4b5563",
          strokeWidth: 1.5,
          headLength: 5
        }),
        Plot.text(top5_peores, {
          x: (d, i) => d.carencia_agua_conagua_pct - (27 - (i * 2)),
          y: (d, i) => d.Pobreza_extrema_pct + (18 - (i * 1)),
          text: d => `${d.Municipio},\n${d.Estado}`,
          textAnchor: "end",
          dx: -4,
          lineHeight: 1.1,
          fill: "#1f2937",
          fontSize: 10,
          fontFamily: "Inter, sans-serif",
          fontWeight: 600,
          stroke: "white",
          strokeWidth: 4
        }),
        // Halo al hover — Plot.dot SÍ soporta fill por canal
        Plot.dot(activos, Plot.pointer({
          x: "carencia_agua_conagua_pct",
          y: "Pobreza_extrema_pct",
          fill: "severidad",
          r: 8,
          stroke: "white",
          strokeWidth: 2
        }))
      ]
    });

    chartEl.replaceChildren(plotFig);

    // Plot con legend:true retorna <figure> con MÚLTIPLES <svg> (legend ramp + plot).
    // Elegimos el SVG más grande (el plot principal) — querySelector('svg') agarraba el legend.
    let svgEl = null;
    if (plotFig.tagName && plotFig.tagName.toLowerCase() === 'svg') {
      svgEl = plotFig;
    } else {
      const svgs = plotFig.querySelectorAll('svg');
      let maxArea = -1;
      for (const s of svgs) {
        const r = s.getBoundingClientRect();
        const area = (r.width || 0) * (r.height || 0);
        if (area > maxArea) { maxArea = area; svgEl = s; }
      }
      // Fallback si todos tienen dimensiones 0 (no-rendered yet): el último
      if (!svgEl && svgs.length > 0) svgEl = svgs[svgs.length - 1];
    }

    if (!svgEl) {
      console.warn('[scatter-tip] no SVG en plotFig');
      pointsWithPx = [];
      svgCache = null;
      return;
    }

    // Extraer escalas — API oficial Plot 0.6.11: plot.scale(name).apply(value) → pixel
    let xApply = null, yApply = null;
    try {
      const xs = plotFig.scale("x");
      const ys = plotFig.scale("y");
      if (xs && typeof xs.apply === "function") xApply = v => xs.apply(v);
      if (ys && typeof ys.apply === "function") yApply = v => ys.apply(v);
      if (xApply && yApply) {
        const tx = xApply(50), ty = yApply(50);
        if (!Number.isFinite(tx) || !Number.isFinite(ty)) { xApply = null; yApply = null; }
      }
    } catch (err) { /* fallback below */ }

    // Fallback: reconstruir escala lineal manualmente si plot.scale falla
    if (!xApply || !yApply) {
      const xVals = municipios_scatter.map(d => d.carencia_agua_conagua_pct || 0);
      const yVals = municipios_scatter.map(d => d.Pobreza_extrema_pct || 0);
      const xMax = Math.max(...xVals);
      const yMax = Math.max(...yVals);
      const actualW = svgEl.clientWidth || w;
      const actualH = svgEl.clientHeight || h;
      const marginL = 60, marginR = 20, marginT = 20, marginB = 50;
      xApply = v => marginL + (v / xMax) * (actualW - marginL - marginR);
      yApply = v => (actualH - marginB) - (v / yMax) * (actualH - marginB - marginT);
    }

    // Precomputar posiciones en píxeles + filtrar NaN (si domain no matchea datos)
    pointsWithPx = activos
      .map(d => ({
        d,
        px: xApply(d.carencia_agua_conagua_pct),
        py: yApply(d.Pobreza_extrema_pct)
      }))
      .filter(p => Number.isFinite(p.px) && Number.isFinite(p.py));

    svgCache = svgEl;
    svgEl.style.cursor = "crosshair";
    // Listener backup directo en SVG por si chartWrapper pierde eventos
    svgEl.addEventListener("mousemove", handleMouseMove);
  }

  function handleMouseMove(event) {
    if (!svgCache || pointsWithPx.length === 0) {
      tooltip.style.opacity = 0;
      return;
    }

    const svgRect = svgCache.getBoundingClientRect();
    const mx = event.clientX - svgRect.left;
    const my = event.clientY - svgRect.top;

    if (mx < 0 || my < 0 || mx > svgRect.width || my > svgRect.height) {
      tooltip.style.opacity = 0;
      return;
    }

    // Búsqueda lineal O(n) — 2469 puntos × 60fps = 148k ops/s, trivial
    let bestIdx = -1;
    let bestDist = Infinity;
    for (let i = 0; i < pointsWithPx.length; i++) {
      const p = pointsWithPx[i];
      const dx = p.px - mx;
      const dy = p.py - my;
      const dist = dx * dx + dy * dy;
      if (dist < bestDist) {
        bestDist = dist;
        bestIdx = i;
      }
    }

    // Umbral 40px (1600 = 40²)
    if (bestIdx < 0 || bestDist > 1600) {
      tooltip.style.opacity = 0;
      return;
    }

    const pt = pointsWithPx[bestIdx].d;

    const bg = severityColor(pt.severidad);
    const fg = textColorFor(bg);
    tooltip.style.background = bg;
    tooltip.style.color = fg;
    tooltip.innerHTML =
      `<div style="font-weight:700;margin-bottom:3px;">${pt.Municipio}, ${pt.Estado}</div>` +
      `<div style="opacity:0.92;">RHA: ${pt.RHA}</div>` +
      `<div style="opacity:0.92;">Población: ${pt.poblacion_total.toLocaleString("es-MX")}</div>` +
      `<div style="opacity:0.92;">Clasificación: ${pt.clasificacion_rural}</div>` +
      `<div style="margin-top:3px;font-weight:600;">Severidad: ${pt.severidad}</div>` +
      `<div>% Sin Agua: ${pt.carencia_agua_conagua_pct}%</div>` +
      `<div>% Pobreza Extrema: ${pt.Pobreza_extrema_pct}%</div>`;

    // Medir después de set innerHTML (offsetWidth/Height funcionan con opacity:0)
    const tipW = tooltip.offsetWidth;
    const tipH = tooltip.offsetHeight;

    // Posicionar relativo a chartWrapper (donde vive el tooltip)
    const wrapperRect = chartWrapper.getBoundingClientRect();
    const localX = event.clientX - wrapperRect.left;
    const localY = event.clientY - wrapperRect.top;

    let tx = localX + 14;
    let ty = localY + 14;
    if (tx + tipW > wrapperRect.width) tx = localX - tipW - 14;
    if (ty + tipH > wrapperRect.height) ty = localY - tipH - 14;
    if (tx < 0) tx = 4;
    if (ty < 0) ty = 4;

    tooltip.style.left = `${tx}px`;
    tooltip.style.top = `${ty}px`;
    tooltip.style.opacity = 1;
  }

  chartWrapper.addEventListener("mousemove", handleMouseMove);
  chartWrapper.addEventListener("mouseleave", () => { tooltip.style.opacity = 0; });

  searchInput.addEventListener("input", render);
  requestAnimationFrame(() => requestAnimationFrame(render));

  const ro = new ResizeObserver(() => render());
  ro.observe(chartWrapper);

  invalidation.then(() => {
    ro.disconnect();
    chartWrapper.removeEventListener("mousemove", handleMouseMove);
  });

  return container;
}
Cobertura de Agua(%) en México por Estado - 2020
Municipios con Mayor Rezago de Infraestructura Hidríca en México - 2020
El Costo de la Lejanía

¿Rentabilidad Política o Equidad Social? Entubar agua en una ciudad densa es rápido y rentable. Llevarla a comunidades dispersas exige voluntad. Las políticas públicas priorizan el volumen, convirtiendo la distancia física en distancia social.

Detrás de cada estadística hay una historia, y detrás de cada carencia, una oportunidad de acción.

Conoce a las personas que viven esta realidad y a las organizaciones que están cambiando el mapa.

Conoce las historias y las acciones ➔
El costo de la búsqueda del agua

La falta de infraestructura es una factura social. El tiempo que mujeres y niños dedican a acarrear agua es tiempo robado a la educación y al trabajo remunerado.

Personas sin acceso a agua entubada

4.92M

Sin agua y en pobreza extrema

1.08M

Acarreo de agua en burro, México rural
“Me gustaría tener una regadera… nada más de ir a abrir la llave y ya sale el agua… y no preocuparte de que tienes que ir a traer agua”
Angel Arroyo, Xicotlán — Azteca Noticias, 2024 1

Acarreo de agua en burro, México rural
“Antes caían aguacerazos y nunca juntábamos el agua, ni siquiera para el baño. No la valoramos hasta llegar aquí, donde vine a sufrir de verdad, a sentir lo que es no tener agua”.
Sonia Hernández, Tetacalanco Xochimilco — Isla Urbana, Historias de agua 3

María de Los Ángeles Martínez, Las Higueras de Los Natoches
“No se trata de la pipa. Se trata de que nosotros queremos agua potable, agua bien tratada, no el cochinero de agua que nos mandan”.
María de Los Ángeles Martínez, Las Higueras de Los Natoches — Espejo: Las cosas como son 2

Comité del agua de mujeres, Luquilhó, San Andrés Larráinzar, Chiapas
“Las mujeres somos las más interesadas en resolver la falta de agua; caminamos entre cerros para llegar al manantial, cargando en la espalda 40 litros y al niño”.
Comité del agua de mujeres, Luquilhó, San Andrés Larráinzar, Chiapas — PNUD México, enero 2023 4
ONGs con impacto en infraestructura hídrica en México - 2020
La Estadística no es Destino

Donde el presupuesto no llega, la innovación social da un paso al frente. No se espera el futuro, se construye gota a gota.

Fundación Gonzalo Río Arronte
Fundación Río Arronte — El músculo financiero en zonas de estrés hídrico.

CEMDA
CEMDA — Defensa legal frente a la explotación del recurso.

Cántaro Azul
Cántaro Azul — Ecotecnias de purificación.

PNUD
PNUD — Resiliencia e infraestructura comunitaria.
Fuentes de los Testimonios
  • Testimonio 1 — (Video de YouTube – Azteca Noticas) YouTube. (14 mar 2023). [Video]. YouTube. https://youtu.be/mrqfSbxZgnU»*. Fotografía: José Jiménez y Luis Gress / Azteca Noticias, 2024. Fuente
  • Testimonio 2 — (Artículo periodístico) Hernández, C. E. (2025, marzo 24). ¿Cómo es vivir sin agua? La realidad de los pueblos del norte de Sinaloa. Revista Espejo. https://revistaespejo.com/2025/03/24/como-es-vivir-sin-agua-la-realidad-de-los-pueblos-del-norte-de-sinaloa/»*. Espejo: Las cosas como son. Fotografía: Isla Urbana. Fuente
  • Testimonio 3 — (Página web / organización) Isla Urbana. (s.f.). Historias de agua. https://islaurbana.org/historias-de-agua/»*. Isla Urbana, Historias de agua. Fotografía: Espejo: Las cosas como son. Fuente
  • Testimonio 4 — (Organismo internacional – nota web) Programa de las Naciones Unidas para el Desarrollo (PNUD). (5 ene 2023). Más de 6 mil familias de 44 localidades rurales e indígenas en el sureste de México mejoran su acceso a agua y saneamiento. https://www.undp.org/es/mexico/noticias/mas-de-6-mil-familias-de-44-localidades-rurales-e-indigenas-en-el-sureste-de-mexico-mejoran-su-acceso-agua-y-saneamiento»*. PNUD México, enero 2023. Fotografía: PNUD México. Fuente
Los datos cuentan la historia completa Cada municipio, cada cifra, cada realidad.

Explora los microdatos de los 2,469 municipios de México: pobreza, cobertura de agua y clasificación rural.

Explora los Datos ➔

Microdatos de los 2,469 municipios de la República Mexicana. Todas las fuentes corresponden al corte 2020 para garantizar coherencia temporal. Para más información consulta nuestro repositorio de datos.

Fuente Año Variable principal Cobertura Limitación declarada
INEGI — Censo de Población y Vivienda (ITER) 2020 Clasificación rural/urbana por municipio 2,469 municipios La clasificación rural/urbana depende del umbral de 2,500 hab. por localidad; municipios en transición quedan en categoría binaria
CONEVAL — Concentrado de Indicadores de Pobreza 2020 % Pobreza extrema, % Carencia servicios básicos 2,469 municipios Pobreza medida cada 2 años; no captura choques post-pandemia ocurridos en 2020
CONAGUA — Población con acceso al agua 2020 % Población sin agua entubada por municipio 2,469 municipios Mide disponibilidad de red entubada, no calidad ni continuidad del servicio; no distingue agua de pipa
SIODS — Indicadores ODS 6.1.1.a API Agenda 2030 Cobertura nacional de agua segura (64%) Nacional (no desagrega a municipal) Indicador agregado; se usa solo como referencia de comparación contra microdatos CONAGUA

La clasificación rural/urbana es propia: un municipio se clasifica como Rural si más del 50% de sus localidades tienen menos de 2,500 habitantes (INEGI ITER 2020). Esta definición prioriza la estructura territorial sobre la densidad poblacional, visibilizando que el 55% del territorio municipal es rural aunque solo el 17% de la población viva en él.

El cruce de variables entre fuentes se realiza mediante clave municipal CVEGEO (5 dígitos). Todos los conjuntos de datos fueron filtrados al ciclo 2020 para eliminar desfases temporales. Los municipios sin dato en alguna fuente conservan el resto de sus variables; no se imputan valores.

Loading ITables v2.7.3 from the internet... (need help?)