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
{
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;
}
}
}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.
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.
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.
{
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;
}¿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.
Conoce a las personas que viven esta realidad y a las organizaciones que están cambiando el mapa.
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

“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

“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

“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

“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
Donde el presupuesto no llega, la innovación social da un paso al frente. No se espera el futuro, se construye gota a gota.




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?) |