---
title: "ASHIRA"
lang: es
format:
html:
theme:
light: flatly
dark: darkly
toc: true
toc-title: "El recorrido"
toc-depth: 2
toc-location: left
toc-expand: true
embed-resources: false
page-layout: full
smooth-scroll: true
code-fold: true
code-tools: true
code-summary: "Ver código que generó esta visualización"
code-overflow: wrap
lightbox: true
anchor-sections: true
link-external-newwindow: true
fig-cap-location: bottom
css: ashira_custom.css
include-before-body: _dark-init.html
execute:
echo: false
warning: false
message: false
jupyter: python3
---
::: {.ashira-hero #inicio}
::: {.ashira-hero-inner}
::: {.ashira-hero-badge}
[]{.badge-dot} HackODS UNAM 2026 · Objetivos de Desarrollo Sostenible 1 y 10
:::
# En México no se puede hablar de pobreza sin hablar de *dónde naciste.* {.ashira-hero-title}
::: {.ashira-hero-tagline}
Cuatro bases oficiales. Ocho años de datos. Una sola pregunta.
:::
::: {.ashira-hero-hook}
::: {.hook-question}
¿Cuánto influye el estado donde naciste en tu probabilidad de vivir en pobreza?
:::
::: {.hook-answer-short}
La respuesta corta. **Muchísimo.**
:::
::: {.hook-answer-long}
La respuesta larga. **Esta historia.**
:::
:::
::: {.ashira-hero-meta}
::: {.ashira-team}
::: {.ashira-team-label}
Equipo ASHIRA
:::
::: {.ashira-team-names}
Melisa Arano · Roberto Alegre · Israel Martínez
:::
:::
::: {.ashira-sources}
[ENIGH](https://www.inegi.org.mx/programas/enigh/nc/2024/){.hero-source-link} · [CONEVAL](https://www.coneval.org.mx/coordinacion/entidades/Paginas/inicioent.aspx){.hero-source-link} · [Censo 2020](https://www.inegi.org.mx/programas/ccpv/2020/){.hero-source-link} · [IMCO](https://imco.org.mx/indice-de-competitividad-estatal-2020/){.hero-source-link} · [Migración OD](https://www.inegi.org.mx/programas/ccpv/2020/#Tabulados){.hero-source-link}
:::
:::
:::
::: {.ashira-hero-date}
Dashboard realizado en Abril de 2026
:::
:::
::: {#ashira-scroll-hint .ashira-scroll-hint}
[Desliza]{.scroll-hint-label}
[↓]{.scroll-hint-arrow}
:::
```{python}
#| echo: false
import warnings
warnings.filterwarnings("ignore")
import unicodedata, json, sys
from pathlib import Path
import pandas as pd
import numpy as np
import openpyxl
import geopandas as gpd
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
sys.path.insert(0, str(Path(".").resolve().parent / "scripts"))
from parse_censo import extraer_valores_estado, REGIONES as REGIONES_CENSO, ESTADOS_CORTOS
_HERE = Path(".").resolve()
REPO = _HERE.parent
DATOS = REPO / "datos"
# ── Paleta ────────────────────────────────────────
BG = "#0d1117"
CARD = "#161b22"
ACCENT = "#e94560"
GOLD = "#f5a623"
TEAL = "#2ecc71" # Norte — verde, consistente con mapa de pobreza y narrativa
PURPLE = "#e879f9" # CDMX — fucsia, claramente distinto del azul
GREEN = "#6bcf8a"
MUTED = "#8b949e"
REGION_COLORS = {"Norte": TEAL, "Centro": GOLD, "Sur": ACCENT, "CDMX": PURPLE}
# ── Plotly template ───────────────────────────────
ashira_tpl = go.layout.Template()
ashira_tpl.layout = go.Layout(
paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor=CARD,
font=dict(color="#c9d1d9", family="Inter, -apple-system, sans-serif", size=14),
title=dict(font=dict(family="Fraunces, Georgia, serif", color="white", size=22), x=0.5, xanchor="center"),
xaxis=dict(gridcolor="#21262d", zerolinecolor="#21262d", title_font_color="white"),
yaxis=dict(gridcolor="#21262d", zerolinecolor="#21262d", title_font_color="white"),
colorway=[ACCENT, GOLD, TEAL, PURPLE, GREEN],
hoverlabel=dict(bgcolor="#1a1a2e", font_color="white", bordercolor="#30363d", font_size=14),
legend=dict(font_color="#c9d1d9", bgcolor="rgba(0,0,0,0)", borderwidth=0),
margin=dict(l=40, r=40, t=60, b=40),
)
pio.templates["ashira"] = ashira_tpl
pio.templates.default = "ashira"
REGIONES = {
"Aguascalientes":"Centro","Baja California":"Norte","Baja California Sur":"Norte",
"Campeche":"Sur","Coahuila":"Norte","Colima":"Centro","Chiapas":"Sur",
"Chihuahua":"Norte","Ciudad de México":"CDMX","Durango":"Norte",
"Guanajuato":"Centro","Guerrero":"Sur","Hidalgo":"Centro","Jalisco":"Centro",
"México":"Centro","Michoacán":"Centro","Morelos":"Centro","Nayarit":"Norte",
"Nuevo León":"Norte","Oaxaca":"Sur","Puebla":"Centro","Querétaro":"Centro",
"Quintana Roo":"Sur","San Luis Potosí":"Norte","Sinaloa":"Norte","Sonora":"Norte",
"Tabasco":"Sur","Tamaulipas":"Norte","Tlaxcala":"Centro","Veracruz":"Sur",
"Yucatán":"Sur","Zacatecas":"Norte",
}
ESTADOS_ENIGH = {
1:"Aguascalientes",2:"Baja California",3:"Baja California Sur",4:"Campeche",
5:"Coahuila",6:"Colima",7:"Chiapas",8:"Chihuahua",9:"Ciudad de México",
10:"Durango",11:"Guanajuato",12:"Guerrero",13:"Hidalgo",14:"Jalisco",
15:"México",16:"Michoacán",17:"Morelos",18:"Nayarit",19:"Nuevo León",
20:"Oaxaca",21:"Puebla",22:"Querétaro",23:"Quintana Roo",24:"San Luis Potosí",
25:"Sinaloa",26:"Sonora",27:"Tabasco",28:"Tamaulipas",29:"Tlaxcala",
30:"Veracruz",31:"Yucatán",32:"Zacatecas",
}
def wmean(x, w):
w = np.asarray(w, dtype=float)
return np.average(x, weights=w) if w.sum() > 0 else np.nan
def wmedian(vals, weights):
# Ordenar por VALOR (no por peso) para obtener la mediana ponderada correcta
s = sorted(zip(vals, weights))
wt = sum(w for _, w in s)
cum = 0
for v, w in s:
cum += w
if cum >= wt / 2: return v
return float("nan")
if hasattr(go.Figure, '_orig_show'):
go.Figure.show = go.Figure._orig_show
go.Figure._orig_show = go.Figure.show
def _show_responsive(self, *args, **kwargs):
kwargs.setdefault("config", {"responsive": True})
return go.Figure._orig_show(self, *args, **kwargs)
go.Figure.show = _show_responsive
```
```{python}
#| echo: false
# ── 1. Pobreza Multidimensional 2024 ─────────────
PM_PATH = DATOS / "pobreza_multidimensional" / "pm_ef_2024.xlsx"
PM_NAME_MAP = {
"Ciudad de México":"Ciudad de México","México":"México",
"Veracruz de Ignacio de la Llave":"Veracruz","Coahuila de Zaragoza":"Coahuila",
"Michoacán de Ocampo":"Michoacán",
}
SKIP_PM = {"Índice","EUM"}
records_pm = []
wb = openpyxl.load_workbook(PM_PATH, read_only=True)
for sn in wb.sheetnames:
if sn in SKIP_PM: continue
ws = wb[sn]
rows = list(ws.iter_rows(values_only=True))
if len(rows) < 10: continue
nombre = str(rows[3][0]).strip() if rows[3][0] else sn
nombre = PM_NAME_MAP.get(nombre, nombre)
r10 = rows[9]
pct = r10[14] if len(r10) > 14 else None
if pct is not None and isinstance(pct, (int, float)):
records_pm.append({"estado": nombre, "pct_pobreza_2024": float(pct)})
wb.close()
pm = pd.DataFrame(records_pm)
pm["region"] = pm["estado"].map(REGIONES).fillna("Otro")
# ── 2. ENIGH 2016–2024 ───────────────────────────
ENIGH_PATHS = {
2016: DATOS/"enigh"/"enigh_2016.csv", 2018: DATOS/"enigh"/"enigh_2018.csv",
2020: DATOS/"enigh"/"enigh_2020.csv", 2022: DATOS/"enigh"/"enigh_2022.csv",
2024: DATOS/"enigh"/"enigh_2024.csv",
}
COLS_E = ["ubica_geo","factor","tot_integ","ing_cor","ingtrab","transfer","bene_gob","remesas","tam_loc"]
frames = []
for year, path in ENIGH_PATHS.items():
df = pd.read_csv(path, usecols=lambda c: c in COLS_E, encoding="utf-8-sig", low_memory=False)
df["anio"] = year
df["cve_ent"] = df["ubica_geo"].map(lambda x: x//10000000 if x>=10000000 else x//1000)
df["estado"] = df["cve_ent"].map(ESTADOS_ENIGH)
df["region"] = df["estado"].map(REGIONES).fillna("Otro")
df["ing_pc"] = df["ing_cor"] / df["tot_integ"].clip(lower=1) / 3
frames.append(df)
enigh = pd.concat(frames, ignore_index=True)
enigh_est = enigh.groupby(["anio","estado","region"]).apply(
lambda g: pd.Series({
"ing_pc_mediana": wmedian(g["ing_pc"].values, g["factor"].values),
"ing_pc_media": wmean(g["ing_pc"], g["factor"]),
"pct_bene_gob": wmean(g["bene_gob"]/g["ing_cor"].clip(lower=1), g["factor"])*100,
"pct_remesas": wmean(g["remesas"]/g["ing_cor"].clip(lower=1), g["factor"])*100,
"pct_ingtrab": wmean(g["ingtrab"]/g["ing_cor"].clip(lower=1), g["factor"])*100,
"pct_transfer": wmean(g["transfer"]/g["ing_cor"].clip(lower=1), g["factor"])*100,
"n_hogares": len(g),
}), include_groups=False
).reset_index()
enigh_reg = enigh.groupby(["anio","region"]).apply(
lambda g: pd.Series({
"ing_pc_mediana": wmedian(g["ing_pc"].values, g["factor"].values),
"pct_bene_gob": wmean(g["bene_gob"]/g["ing_cor"].clip(lower=1), g["factor"])*100,
"pct_remesas": wmean(g["remesas"]/g["ing_cor"].clip(lower=1), g["factor"])*100,
"pct_ingtrab": wmean(g["ingtrab"]/g["ing_cor"].clip(lower=1), g["factor"])*100,
"pct_transfer": wmean(g["transfer"]/g["ing_cor"].clip(lower=1), g["factor"])*100,
}), include_groups=False
).reset_index()
# ── 3. Censo 2020 ────────────────────────────────
DATA_CENSO = DATOS / "censo2020"
df_edu = extraer_valores_estado(DATA_CENSO/"cpv2020_a_educacion.xlsx","04",[3],["pob_educ_superior"])
df_edu = df_edu[df_edu["estado"] != "Estados Unidos Mexicanos"]
df_etnia = extraer_valores_estado(DATA_CENSO/"cpv2020_a_etnicidad.xlsx","02",
[4,5],["pob_3mas","pct_indigena"],sexo_col=1,estimador_col=3,
extra_filter_col=2,extra_filter_val="Total")
df_etnia = df_etnia[df_etnia["estado"] != "Estados Unidos Mexicanos"]
df_alim = extraer_valores_estado(DATA_CENSO/"cpv2020_a_alimentacion.xlsx","02",
[3,4],["hogares_total","pct_inseg_alim"],sexo_col=1,estimador_col=2)
df_alim = df_alim[df_alim["estado"] != "Estados Unidos Mexicanos"]
CENSO_TO_STD = {v: v for v in ESTADOS_CORTOS.values()}
CENSO_TO_STD.update({"CDMX":"Ciudad de México","Estado de México":"México",
"Michoacán":"Michoacán","Coahuila":"Coahuila","Veracruz":"Veracruz"})
for dfc in [df_edu, df_etnia, df_alim]:
dfc["estado_std"] = dfc["estado"].map(CENSO_TO_STD).fillna(dfc["estado"])
censo = df_edu[["estado_std","region","pob_educ_superior"]].copy()
censo = censo.merge(df_etnia[["estado_std","pct_indigena"]], on="estado_std", how="left")
censo = censo.merge(df_alim[["estado_std","pct_inseg_alim"]], on="estado_std", how="left")
censo.rename(columns={"estado_std":"estado"}, inplace=True)
# ── 4. ICE IMCO ──────────────────────────────────
ICE_PATH = DATOS / "imco" / "ICE_2020_Base_datos.xlsx"
wb = openpyxl.load_workbook(ICE_PATH, read_only=True)
ws = wb["Ind (18)"]
ice_rows = list(ws.iter_rows(min_row=9, max_row=42, values_only=True))
wb.close()
ice_records = []
for row in ice_rows:
if not row[1] or not isinstance(row[1], str): continue
e = row[1].strip()
if e in ("","Entidad"): continue
try: inf = float(row[59])
except: inf = None
try: mig = float(row[40])
except: mig = None
ice_records.append({"estado": e, "ice_informalidad": inf, "ice_migracion": mig})
ice = pd.DataFrame(ice_records)
# ── 5. Dataset maestro ────────────────────────────
maestro = pm.merge(censo.drop(columns=["region"], errors="ignore"), on="estado", how="left") \
.merge(ice, on="estado", how="left")
enigh_2024 = enigh_est[enigh_est["anio"]==2024][["estado","ing_pc_mediana","pct_bene_gob","pct_remesas","pct_ingtrab"]].copy()
maestro = maestro.merge(enigh_2024, on="estado", how="left")
maestro["color"] = maestro["region"].map(REGION_COLORS).fillna("gray")
# ── 6. GeoJSON: matching robusto ────────────────
def norm(s):
return unicodedata.normalize("NFD", str(s)).encode("ascii","ignore").decode().lower().strip()
GEO_PATH = REPO / "datos" / "shapefiles" / "mexico_estados.geojson"
# Mapa explícito: nombre normalizado del GeoJSON → nombre en maestro
GEO_TO_MAESTRO = {
"aguascalientes": "Aguascalientes",
"baja california": "Baja California",
"baja california sur": "Baja California Sur",
"campeche": "Campeche",
"chiapas": "Chiapas",
"chihuahua": "Chihuahua",
"ciudad de mexico": "Ciudad de México",
"coahuila": "Coahuila",
"coahuila de zaragoza": "Coahuila",
"colima": "Colima",
"durango": "Durango",
"guanajuato": "Guanajuato",
"guerrero": "Guerrero",
"hidalgo": "Hidalgo",
"jalisco": "Jalisco",
"mexico": "México",
"michoacan": "Michoacán",
"michoacan de ocampo": "Michoacán",
"morelos": "Morelos",
"nayarit": "Nayarit",
"nuevo leon": "Nuevo León",
"oaxaca": "Oaxaca",
"puebla": "Puebla",
"queretaro": "Querétaro",
"quintana roo": "Quintana Roo",
"san luis potosi": "San Luis Potosí",
"sinaloa": "Sinaloa",
"sonora": "Sonora",
"tabasco": "Tabasco",
"tamaulipas": "Tamaulipas",
"tlaxcala": "Tlaxcala",
"veracruz": "Veracruz",
"veracruz de ignacio de la llave": "Veracruz",
"yucatan": "Yucatán",
"zacatecas": "Zacatecas",
}
with open(str(GEO_PATH), encoding="utf-8") as f:
mx_geo = json.load(f)
# Asignar ID usando mapa explícito; usar NFC para evitar problemas de encoding
import unicodedata as _ud
for feat in mx_geo["features"]:
raw = feat["properties"]["name"]
key = norm(raw)
mapped = GEO_TO_MAESTRO.get(key, raw)
# Normalizar a NFC para que coincida con strings de pandas
feat["id"] = _ud.normalize("NFC", mapped)
# Normalizar nombres del maestro a NFC para que coincidan con los IDs del GeoJSON
import unicodedata as _ud2
maestro["estado"] = maestro["estado"].map(lambda s: _ud2.normalize("NFC", str(s)))
# Valores clave
chiapas_pob = maestro.loc[maestro["estado"]=="Chiapas","pct_pobreza_2024"].values[0]
bc_pob = maestro.loc[maestro["estado"]=="Baja California","pct_pobreza_2024"].values[0]
brecha_pts = chiapas_pob - bc_pob
```
---
## El mapa de la pobreza
::: {.ashira-insight .accent-blue}
En este mapa encontramos una verdad incómoda: México no es un solo país, sino una **fragmentación de tres realidades** que apenas se reconocen entre sí.
> *Atravesar el país de [Tijuana]{.color-verde} a [Tapachula]{.color-rojo} no es solo un viaje de kilómetros; es un retroceso en el tiempo y una degradación cromática de la dignidad humana.*
[**El Norte Verde: La Aspiración**]{.color-verde style="font-size:1.05em;"}
[**El Centro Ámbar: La Paradoja**]{.color-ambar style="font-size:1.05em;"}
[**El Sur Rojo: El Olvido**]{.color-rojo style="font-size:1.05em;"}
::: {style="text-align:center; margin-top:0.4em; letter-spacing:0.04em;"}
**Tres colores · tres realidades · un solo territorio.**
:::
:::
```{python}
#| label: fig-mapa
#| fig-cap: "Patrón territorial de pobreza multidimensional por estado en 2024; contraste Norte-Sur.<br><span class='fig-source'>Fuente: Consejo Nacional de Evaluación de la Política de Desarrollo Social (CONEVAL), Medición de la Pobreza 2024.</span>"
# Preparar datos para hover personalizado
map_data = maestro.dropna(subset=["pct_pobreza_2024"]).copy()
map_data["pobreza_fmt"] = map_data["pct_pobreza_2024"].map(lambda x: f"{x:.1f}%")
map_data["ingreso_fmt"] = map_data["ing_pc_mediana"].map(
lambda x: f"${x:,.0f}/mes" if pd.notna(x) else "N/D"
)
fig = px.choropleth(
map_data, geojson=mx_geo, locations="estado", featureidkey="id",
color="pct_pobreza_2024",
color_continuous_scale=[[0,"#2ecc71"],[0.30,"#f5a623"],[0.65,"#e94560"],[1,"#7a0020"]],
range_color=[5, 70],
custom_data=["region","pobreza_fmt","ingreso_fmt"],
labels={"pct_pobreza_2024":"Pobreza %"},
)
fig.update_traces(
hovertemplate=(
"<b style='font-size:15px;color:#e6edf3'>%{location}</b><br>"
"<span style='color:#a0aec0'>Región: </span><b style='color:#e6edf3'>%{customdata[0]}</b><br>"
"<span style='color:#a0aec0'>Pobreza 2024: </span><b style='color:#e6edf3'>%{customdata[1]}</b><br>"
"<span style='color:#a0aec0'>Ingreso pc: </span><b style='color:#e6edf3'>%{customdata[2]}</b>"
"<extra></extra>"
),
marker_line_color="rgba(13,17,23,0.4)",
marker_line_width=0.5,
)
fig.update_geos(
visible=False,
bgcolor="rgba(0,0,0,0)", projection_type="mercator",
showframe=False, showcoastlines=False,
lataxis_range=[13.5, 33.5],
lonaxis_range=[-119.0, -85.5],
)
fig.update_layout(
height=680, margin=dict(l=0,r=110,t=55,b=0),
coloraxis_colorbar=dict(
title=dict(text="", side="top"),
ticksuffix="%", len=0.65, thickness=16,
x=1.03, xanchor="left",
bgcolor="rgba(0,0,0,0)", tickfont_color="#c9d1d9",
title_font_color="#c9d1d9", outlinewidth=0,
tickvals=[10,20,30,40,50,60,70],
),
title=dict(
text="Porcentaje de pobreza multidimensional por estado · México 2024",
),
geo=dict(bgcolor="rgba(0,0,0,0)", lakecolor="rgba(0,0,0,0)", landcolor="rgba(0,0,0,0)"),
)
fig.show()
```
::: {.ashira-insight .accent-blue style="padding:28px 36px; margin:32px 0 16px 0;"}
Los dos mapas que siguen muestran el ingreso promedio por estado en **2016** y en **2024**. Si el crecimiento hubiera sido equitativo, los colores habrían cambiado.
[**No cambiaron.**]{style="color:#38bdf8;"} Quien nació en el [sur]{.color-rojo}, sigue en [rojo]{.color-rojo}.
:::
```{python}
#| label: fig-ingreso-sidebyside
#| fig-cap: "Comparación del ingreso mediano por persona entre 2016 y 2024 con la misma escala de color.<br><span class='fig-source'>Fuente: Encuesta Nacional de Ingresos y Gastos de los Hogares (ENIGH), Instituto Nacional de Estadística y Geografía (INEGI).</span>"
from plotly.subplots import make_subplots
e16_map = enigh_est[enigh_est["anio"]==2016][["estado","region","ing_pc_mediana"]].dropna().copy()
e24_map = enigh_est[enigh_est["anio"]==2024][["estado","region","ing_pc_mediana"]].dropna().copy()
vmin_2016 = e16_map["ing_pc_mediana"].min() * 0.92
vmax_2016 = e16_map["ing_pc_mediana"].max() * 1.05
vmin_2024 = e24_map["ing_pc_mediana"].min() * 0.92
vmax_2024 = e24_map["ing_pc_mediana"].max() * 1.05
geo_cfg = dict(
visible=False, bgcolor="rgba(0,0,0,0)",
projection_type="mercator",
showframe=False, showcoastlines=False,
lataxis_range=[13.5, 33.5],
lonaxis_range=[-119.0, -85.5],
)
fig = make_subplots(
rows=1, cols=2,
specs=[[{"type": "choropleth"}, {"type": "choropleth"}]],
horizontal_spacing=0.02,
)
for col_n, (df_y, year) in enumerate([(e16_map, 2016), (e24_map, 2024)], start=1):
fig.add_trace(
go.Choropleth(
geojson=mx_geo,
locations=df_y["estado"],
featureidkey="id",
z=df_y["ing_pc_mediana"],
coloraxis="coloraxis" if year == 2016 else "coloraxis2",
customdata=df_y[["region","ing_pc_mediana"]].values,
marker_line_color="rgba(13,17,23,0.4)",
marker_line_width=0.5,
hovertemplate=(
f"<b style='font-size:14px;color:#e6edf3'>%{{location}}</b><br>"
f"<span style='color:#a0aec0'>Ingreso mediano {year}: </span>"
f"<b style='color:#e6edf3'>$%{{customdata[1]:,.0f}}/mes</b>"
"<extra></extra>"
),
),
row=1, col=col_n,
)
# Etiquetas de año como anotaciones
for col_n, year in enumerate([2016, 2024], start=1):
fig.add_annotation(
text=f"<b>{year}</b>",
x=0.25 if col_n == 1 else 0.75,
y=1.04,
xref="paper", yref="paper",
showarrow=False,
font=dict(color="white", size=18, family="Fraunces, Georgia, serif"),
xanchor="center",
)
fig.update_layout(
height=440,
margin=dict(l=0, r=140, t=80, b=10),
coloraxis=dict(
colorscale=[
[0.00, "#e94560"],
[0.25, "#f5a623"],
[0.60, "#2ecc71"],
[1.00, "#38bdf8"],
],
cmin=vmin_2016, cmax=vmax_2016,
colorbar=dict(
title=dict(text="", side="top"),
tickprefix="$", separatethousands=True,
len=0.72, thickness=14,
x=0.47, xanchor="left",
bgcolor="rgba(0,0,0,0)",
tickfont_color="#c9d1d9",
title_font_color="#c9d1d9",
outlinewidth=0,
),
),
coloraxis2=dict(
colorscale=[
[0.00, "#e94560"],
[0.25, "#f5a623"],
[0.60, "#2ecc71"],
[1.00, "#38bdf8"],
],
cmin=vmin_2024, cmax=vmax_2024,
colorbar=dict(
title=dict(text="", side="top"),
tickprefix="$", separatethousands=True,
len=0.72, thickness=14,
x=1.02, xanchor="left",
bgcolor="rgba(0,0,0,0)",
tickfont_color="#c9d1d9",
title_font_color="#c9d1d9",
outlinewidth=0,
),
),
geo=dict(**geo_cfg, lakecolor="rgba(0,0,0,0)", landcolor="rgba(0,0,0,0)"),
geo2=dict(**geo_cfg, lakecolor="rgba(0,0,0,0)", landcolor="rgba(0,0,0,0)"),
title=dict(
text="Ingreso mediano por persona en moneda nacional",
y=0.98, yanchor="top",
),
)
fig.show()
```
---
## Los dos Méxicos
::: {.ashira-insight .accent-blue}
En [**Chiapas**]{.color-rojo}, [**66 de cada 100**]{.color-rojo} personas viven en pobreza.\
En [**Baja California**]{.color-verde}, apenas [**10**]{.color-verde}.
::: {style="text-align:center; margin-top:0.8em;"}
Comparten bandera y constitución. **No comparten el mismo México.**
:::
:::
```{python}
#| label: fig-barras
#| fig-cap: "Ranking estatal de pobreza multidimensional en 2024 con referencia al promedio nacional.<br><span class='fig-source'>Fuente: Consejo Nacional de Evaluación de la Política de Desarrollo Social (CONEVAL), Medición de la Pobreza 2024.</span>"
df_plot = maestro.dropna(subset=["pct_pobreza_2024"]).sort_values("pct_pobreza_2024", ascending=True)
nac = df_plot["pct_pobreza_2024"].mean()
fig = px.bar(
df_plot, x="pct_pobreza_2024", y="estado", color="region",
color_discrete_map=REGION_COLORS, orientation="h",
text=df_plot["pct_pobreza_2024"].apply(lambda x: f"{x:.1f}%"),
custom_data=["region", "pct_pobreza_2024"],
category_orders={"region": ["Norte", "Centro", "Sur", "CDMX"]},
labels={"pct_pobreza_2024":"Pobreza (%)", "estado":"", "region":"Región"},
)
fig.update_traces(
textposition="outside", textfont_size=12, marker_line_width=0,
hovertemplate=(
"<b style='color:#e6edf3'>%{y}</b><br>"
"<span style='color:#a0aec0'>Región: </span><b style='color:#e6edf3'>%{customdata[0]}</b><br>"
"<span style='color:#a0aec0'>Pobreza 2024: </span><b style='color:#e6edf3'>%{customdata[1]:.1f}%</b>"
"<extra></extra>"
),
)
fig.add_vline(x=nac, line_dash="dash", line_color="#f5a623", opacity=0.85,
line_width=2,
annotation_text=f" Promedio nacional: {nac:.1f}% ",
annotation_position="top",
annotation_font_color="#0d1117", annotation_font_size=13,
annotation_bgcolor="#f5a623", annotation_bordercolor="#f5a623",
annotation_borderwidth=1, annotation_borderpad=4)
# Anotaciones de extremos
_chiapas_v = df_plot[df_plot["estado"]=="Chiapas"]["pct_pobreza_2024"].values[0]
_bc_v = df_plot[df_plot["estado"]=="Baja California"]["pct_pobreza_2024"].values[0]
fig.add_annotation(
x=_chiapas_v + 6, y="Chiapas",
text=f"<b>+{_chiapas_v-nac:.0f} puntos</b> sobre el promedio",
showarrow=False, font=dict(color=ACCENT, size=10), xanchor="left",
)
fig.add_annotation(
x=_bc_v + 4.5, y="Baja California",
text=f"<b>−{nac-_bc_v:.0f} puntos</b> bajo el promedio",
showarrow=False, font=dict(color=TEAL, size=10), xanchor="left",
)
# Anotaciones para excepciones del Sur (capital externo)
_qroo_v = df_plot[df_plot["estado"]=="Quintana Roo"]["pct_pobreza_2024"].values[0]
_yucatan_v = df_plot[df_plot["estado"]=="Yucatán"]["pct_pobreza_2024"].values[0]
_tabasco_v = df_plot[df_plot["estado"]=="Tabasco"]["pct_pobreza_2024"].values[0]
_campeche_v = df_plot[df_plot["estado"]=="Campeche"]["pct_pobreza_2024"].values[0]
fig.add_annotation(
x=_qroo_v + 6, y="Quintana Roo",
text="<b>Turismo</b>", showarrow=False, font=dict(color="#0f9b8e", size=9), xanchor="left",
)
fig.add_annotation(
x=_yucatan_v + 6, y="Yucatán",
text="<b>Turismo</b>", showarrow=False, font=dict(color="#0f9b8e", size=9), xanchor="left",
)
fig.add_annotation(
x=_tabasco_v + 6, y="Tabasco",
text="<b>Petróleo</b>", showarrow=False, font=dict(color="#f5a623", size=9), xanchor="left",
)
fig.add_annotation(
x=_campeche_v + 6, y="Campeche",
text="<b>Petróleo</b>", showarrow=False, font=dict(color="#f5a623", size=9), xanchor="left",
)
# Anotación con información de excepciones en espacio vacío del gráfico
fig.add_annotation(
text="<b>El falso rescate del Sur</b><br><br>" +
"Tener petróleo o turismo no ha sido<br>" +
"suficiente para cerrar la brecha.<br>" +
"El desarrollo es profundamente desigual:<br>" +
"la riqueza de estos sectores es selectiva<br>" +
"y deja fuera a la mayoría.<br><br>" +
"<span style='color:#e94560;'>En el Sur, ser un estado <i>\u2018rico en recursos\u2019</i><br>" +
"no significa tener menos pobreza.</span>",
xref="paper", yref="paper",
x=0.99, y=0.42, xanchor="right", yanchor="middle",
showarrow=False,
bgcolor="rgba(13,17,23,0.88)",
bordercolor="rgba(255,255,255,0.12)",
borderwidth=1,
borderpad=24,
font=dict(size=15, color="#c9d1d9", family="Inter, -apple-system, sans-serif"),
align="left",
)
fig.update_layout(
height=860,
margin=dict(l=150, r=80, t=110, b=50),
xaxis=dict(title="Porcentaje de la población en pobreza multidimensional", ticksuffix="%", range=[0,92],
showgrid=True, gridwidth=0.5, gridcolor="rgba(100,120,150,0.15)"),
yaxis=dict(categoryorder="total ascending", tickfont=dict(size=13), title="", showgrid=False),
title=dict(
text="Pobreza multidimensional por estado · 2024",
y=0.98, yanchor="top",
),
legend=dict(
orientation="h", y=1.04, x=0.5, xanchor="center", yanchor="bottom",
title=dict(text="<b>Región</b>", side="left"),
font=dict(size=11),
bgcolor="rgba(0,0,0,0)", bordercolor="rgba(0,0,0,0)",
),
bargap=0.22,
)
fig.show()
```
::: {.callout-important}
## ~56 puntos porcentuales separan a Chiapas de Baja California
Hablar de un **"promedio nacional"** en México es un **error estadístico** que oculta realidades opuestas: la distancia entre [**Chiapas**]{.color-rojo} y [**Baja California**]{.color-verde} es mayor a la diferencia entre **México y Alemania**.
:::
---
---
## La brecha que no cierra
::: {.ashira-insight .accent-gold}
En ocho años, el ingreso en México subió para todos, pero de forma **desigual**.
> *El Sur no está estancado, pero su ritmo no alcanza para seguirle el paso al Norte.*
::: {style="text-align:center; margin-top:0.5em;"}
[**El código postal sigue pesando más que el esfuerzo.**]{style="color:#38bdf8;"}
:::
:::
```{python}
#| label: fig-brecha
#| fig-cap: "Evolución de la brecha de ingreso Norte-Sur y ratio relativo entre 2016 y 2024.<br><span class='fig-source'>Fuente: Encuesta Nacional de Ingresos y Gastos de los Hogares (ENIGH), Instituto Nacional de Estadística y Geografía (INEGI).</span>"
from plotly.subplots import make_subplots
años = [2016,2018,2020,2022,2024]
norte_v = enigh_reg[enigh_reg["region"]=="Norte"].sort_values("anio")["ing_pc_mediana"].values
sur_v = enigh_reg[enigh_reg["region"]=="Sur"].sort_values("anio")["ing_pc_mediana"].values
ratio_v = norte_v / sur_v # ronda 1.65–1.70x en todos los años
fig = make_subplots(rows=1, cols=2,
subplot_titles=[
"Ingreso mediano Norte vs Sur · 2016–2024",
"¿Cuánto gana el Norte por cada peso del Sur?",
],
horizontal_spacing=0.14)
# Panel izquierdo: Norte y Sur principales; Centro oculto por defecto (clic para activar)
for region, color, visible in [("Norte", TEAL, True), ("Sur", ACCENT, True), ("Centro", GOLD, "legendonly")]:
sub = enigh_reg[enigh_reg["region"]==region].sort_values("anio")
fig.add_trace(go.Scatter(
x=sub["anio"], y=sub["ing_pc_mediana"], name=region,
mode="lines+markers",
line=dict(color=color, width=3.5 if visible is True else 2.5,
dash="solid" if visible is True else "dot"),
marker=dict(size=10 if visible is True else 8, line=dict(width=1.5, color="white")),
visible=visible,
hovertemplate="<b style='color:#e6edf3'>%{x}</b><span style='color:#a0aec0'>: $%{y:,.0f} pesos mensuales</span><extra>" + region + "</extra>",
), row=1, col=1)
# Área sombreada entre las dos curvas para visualizar la brecha
fig.add_trace(go.Scatter(
x=list(años) + list(años[::-1]),
y=list(norte_v) + list(sur_v[::-1]),
fill="toself",
fillcolor="rgba(15,155,142,0.13)",
line=dict(color="rgba(0,0,0,0)"),
hoverinfo="skip", showlegend=False,
), row=1, col=1)
# Banda COVID 2020-2022
fig.add_vrect(
x0=2019.6, x1=2022.4,
fillcolor="rgba(233,69,96,0.10)",
line_width=0,
row=1, col=1,
)
fig.add_vline(x=2020, line_dash="dot", line_color="#e94560", opacity=0.35, row=1, col=1)
_y_top = float(norte_v.max())
fig.add_annotation(
x=2021, y=_y_top * 1.04, text=" COVID + transferencias ",
showarrow=False,
font=dict(color="white", size=10),
bgcolor="#e94560", bordercolor="#e94560",
borderwidth=1, borderpad=3,
xref="x1", yref="y1", xanchor="center", yanchor="middle",
)
# Panel derecho: ratio Norte/Sur — la línea irrebatible
fig.add_trace(go.Scatter(
x=años, y=ratio_v, name="Ratio Norte/Sur",
mode="lines+markers",
line=dict(color="#38bdf8", width=3.5),
marker=dict(size=12, symbol="diamond", line=dict(width=1.5, color="white")),
text=[f"{r:.2f}x" for r in ratio_v],
textposition="top center", textfont=dict(size=11, color="white"),
hovertemplate="<b style='color:#e6edf3'>%{x}</b><span style='color:#a0aec0'>: el Norte ganó </span><b style='color:#e6edf3'>%{y:.2f}x</b><span style='color:#a0aec0'> más que el Sur</span><extra></extra>",
showlegend=False,
), row=1, col=2)
# Línea de referencia en 1.0 (igualdad)
fig.add_hline(y=1.0, line_dash="dash", line_color="rgba(170,205,240,0.65)", line_width=1.5,
annotation_text="Igualdad (1.0x)",
annotation_font_color="#8b949e", annotation_font_size=13,
annotation_yshift=5,
row=1, col=2)
fig.update_xaxes(dtick=2, title_text="Año", title_standoff=12, row=1, col=1)
fig.update_xaxes(dtick=2, title_text="Año", title_standoff=12, row=1, col=2)
fig.update_yaxes(title_text="Ingreso mediano por persona (pesos mensuales)",
tickprefix="$", separatethousands=True, title_standoff=12, row=1, col=1)
fig.update_yaxes(title_text="Veces que el Norte gana más que el Sur",
tickformat=".1f", ticksuffix="x",
range=[0.8, max(ratio_v) * 1.2],
title_standoff=12, row=1, col=2)
fig.update_layout(
height=560,
margin=dict(l=90, r=40, t=160, b=70),
title=dict(
text="Ocho años de datos. La distancia no cierra.<br><span style='color:#5a6e84;'>Por cada paso que da el Sur, el Norte da dos.</span>",
y=0.97, yanchor="top",
),
legend=dict(
orientation="h", y=1.08, x=0.27, xanchor="center", yanchor="bottom",
title=dict(text="<b>Región:</b> "), font=dict(size=11),
),
)
for ann in fig["layout"]["annotations"]:
txt = (ann.text or "") if hasattr(ann, "text") else ""
if not txt or txt.startswith(("Ingreso","Veces","Año","Igualdad","COVID")):
continue
ann.font = dict(color="white", size=13)
fig.show()
```
::: {.ashira-insight .accent-blue}
El ingreso subió en ambas regiones, pero la brecha creció: la diferencia entre Norte y Sur pasó de **$1,400** a **$3,300** mensuales. El Sur avanzó, sí, pero con dinero prestado: transferencias, remesas y salario mínimo. Su estructura productiva no cambió.
> *Lo que se sostiene con dinero externo puede desaparecer cuando ese dinero deja de llegar.*
¿De qué vive realmente el Sur? Eso es lo que veremos a continuación.
:::
---
## Las raíces de la pobreza
::: {.ashira-insight .accent-purple}
La pobreza en México no es aleatoria. Tiene raíces en la historia, en la etnicidad, en el mercado de trabajo. [**No es coincidencia. Es estructura.**]{style="color:#7b68ee;"}
:::
::: {.panel-tabset}
### Etnicidad y pobreza
```{python}
#| label: fig-etnicidad
#| fig-cap: "Relación entre porcentaje de población indígena y pobreza multidimensional por estado.<br><span class='fig-source'>Fuente: Censo de Población y Vivienda 2020, Instituto Nacional de Estadística y Geografía (INEGI); Consejo Nacional de Evaluación de la Política de Desarrollo Social (CONEVAL), Medición de la Pobreza 2024.</span>"
df_etnia_pobreza = maestro.dropna(subset=["pct_indigena","pct_pobreza_2024"]).copy()
correlacion_etnia = df_etnia_pobreza["pct_indigena"].corr(df_etnia_pobreza["pct_pobreza_2024"])
# Etiquetar solo estados destacados para evitar choque visual
estados_destacados = {"Chiapas","Oaxaca","Guerrero","Yucatán","Quintana Roo",
"Nuevo León","Baja California","Coahuila","Veracruz","Campeche"}
df_etnia_pobreza["label"] = df_etnia_pobreza["estado"].apply(
lambda e: e if e in estados_destacados else ""
)
fig = px.scatter(
df_etnia_pobreza, x="pct_indigena", y="pct_pobreza_2024",
color="region", color_discrete_map=REGION_COLORS,
hover_name="estado", trendline="ols", text="label",
custom_data=["region","pct_indigena","pct_pobreza_2024"],
category_orders={"region": ["Norte", "Centro", "Sur", "CDMX"]},
labels={"pct_indigena":"% Población indígena (Censo 2020)",
"pct_pobreza_2024":"% Pobreza multidimensional (CONEVAL 2024)",
"region":"Región"},
)
fig.update_traces(marker=dict(size=14, line=dict(width=1.5, color="white")),
textposition="top center",
textfont=dict(size=11, color="#e6edf3", family="Inter,sans-serif"),
hovertemplate=(
"<b style='font-size:15px;color:#e6edf3'>%{hovertext}</b><br>"
"<span style='color:#a0aec0'>Región: </span><b style='color:#e6edf3'>%{customdata[0]}</b><br>"
"<span style='color:#a0aec0'>Población indígena: </span><b style='color:#e6edf3'>%{customdata[1]:.1f}%</b><br>"
"<span style='color:#a0aec0'>Pobreza multidimensional: </span><b style='color:#e6edf3'>%{customdata[2]:.1f}%</b>"
"<extra></extra>"
),
selector=dict(mode="markers+text"))
# Posición de etiqueta por punto (Baja California va abajo para evitar choque)
for trace in fig.data:
if hasattr(trace, "text") and trace.text is not None and len(trace.text) > 0:
positions = ["bottom center" if t == "Baja California" else "top center" for t in trace.text]
trace.textposition = positions
# Colorear cada línea de tendencia con el color de su región
for trace in fig.data:
if hasattr(trace, "mode") and trace.mode == "lines":
color = REGION_COLORS.get(trace.name, "#c9d1d9")
trace.update(line=dict(dash="dash", color=color, width=2.5),
hovertemplate="<extra></extra>")
# Línea de tendencia nacional en gris punteada
df_clean_etnia = df_etnia_pobreza.dropna(subset=["pct_indigena","pct_pobreza_2024"])
_m, _b = np.polyfit(df_clean_etnia["pct_indigena"], df_clean_etnia["pct_pobreza_2024"], 1)
_x_rng = np.linspace(df_clean_etnia["pct_indigena"].min(), df_clean_etnia["pct_indigena"].max(), 100)
fig.add_trace(go.Scatter(
x=_x_rng, y=_m*_x_rng+_b, mode="lines", name="Nacional",
line=dict(color="#c9d1d9", dash="dot", width=2),
hovertemplate="Tendencia nacional<extra></extra>",
showlegend=True,
))
# r por región
_r_etnia = {}
for _reg in df_etnia_pobreza["region"].dropna().unique():
_sub = df_etnia_pobreza[df_etnia_pobreza["region"]==_reg].dropna(subset=["pct_indigena","pct_pobreza_2024"])
if len(_sub) >= 3:
_r_etnia[_reg] = _sub["pct_indigena"].corr(_sub["pct_pobreza_2024"])
_r_lines_etnia = [
f"<b style='font-size:15px'>r nacional = {correlacion_etnia:.2f}</b>",
f"<span style='color:#8b949e;font-size:11px'>32 estados</span>",
"",
]
for _reg, _col in [("Norte", TEAL), ("Centro", GOLD), ("Sur", ACCENT)]:
if _reg in _r_etnia:
_r_lines_etnia.append(f"<span style='color:{_col};'>■ {_reg}: r = {_r_etnia[_reg]:.2f}</span>")
fig.add_annotation(
x=0.02, y=0.97, xref="paper", yref="paper",
text="<br>".join(_r_lines_etnia),
showarrow=False, align="left",
font=dict(size=13, color="#c9d1d9"), bgcolor="rgba(13,17,23,0.88)",
bordercolor="rgba(255,255,255,0.12)", borderwidth=1, borderpad=14,
)
fig.update_layout(
height=680,
margin=dict(l=90, r=40, t=140, b=80),
xaxis=dict(ticksuffix="%", title=dict(text="Porcentaje de población indígena (Censo 2020)", standoff=15)),
yaxis=dict(ticksuffix="%", title=dict(text="Porcentaje en pobreza multidimensional (2024)", standoff=15)),
title=dict(
text="Más población indígena → más pobreza",
y=0.98, yanchor="top",
),
legend=dict(
orientation="h", y=1.06, x=0.5, xanchor="center", yanchor="bottom",
title=dict(text="<b>Región:</b> "), font=dict(size=11),
bgcolor="rgba(0,0,0,0)",
),
)
fig.show()
```
### Informalidad e ingreso
```{python}
#| label: fig-informalidad
#| fig-cap: "Relación entre informalidad laboral e ingreso mediano estatal.<br><span class='fig-source'>Fuente: Índice de Competitividad Estatal 2018, Instituto Mexicano para la Competitividad (IMCO); Encuesta Nacional de Ingresos y Gastos de los Hogares (ENIGH), Instituto Nacional de Estadística y Geografía (INEGI).</span>"
df_informalidad_ingreso = maestro.dropna(subset=["ice_informalidad","ing_pc_mediana"]).copy()
correlacion_informalidad = df_informalidad_ingreso["ice_informalidad"].corr(
df_informalidad_ingreso["ing_pc_mediana"]
)
# Etiquetar solo los extremos
top_ricos = df_informalidad_ingreso.nlargest(4, "ing_pc_mediana")["estado"].tolist()
top_pobres = df_informalidad_ingreso.nsmallest(4, "ing_pc_mediana")["estado"].tolist()
top_informales = df_informalidad_ingreso.nlargest(3, "ice_informalidad")["estado"].tolist()
estados_label = set(top_ricos + top_pobres + top_informales)
df_informalidad_ingreso["label"] = df_informalidad_ingreso["estado"].apply(
lambda e: e if e in estados_label else ""
)
fig = px.scatter(
df_informalidad_ingreso, x="ice_informalidad", y="ing_pc_mediana",
color="region", color_discrete_map=REGION_COLORS,
hover_name="estado", trendline="ols", text="label",
custom_data=["region","ice_informalidad","ing_pc_mediana"],
category_orders={"region": ["Norte", "Centro", "Sur", "CDMX"]},
labels={"ice_informalidad":"Informalidad laboral · ICE 2018 (%)",
"ing_pc_mediana":"Ingreso mediano mensual (pesos)",
"region":"Región"},
)
fig.update_traces(marker=dict(size=14, line=dict(width=1.5, color="white")),
textposition="top center",
textfont=dict(size=11, color="#e6edf3", family="Inter,sans-serif"),
hovertemplate=(
"<b style='font-size:15px;color:#e6edf3'>%{hovertext}</b><br>"
"<span style='color:#a0aec0'>Región: </span><b style='color:#e6edf3'>%{customdata[0]}</b><br>"
"<span style='color:#a0aec0'>Informalidad laboral: </span><b style='color:#e6edf3'>%{customdata[1]:.1f}%</b><br>"
"<span style='color:#a0aec0'>Ingreso mediano: </span><b style='color:#e6edf3'>$%{customdata[2]:,.0f}/mes</b>"
"<extra></extra>"
),
selector=dict(mode="markers+text"))
# Posición de etiqueta por punto (Baja California va a la derecha)
for trace in fig.data:
if hasattr(trace, "text") and trace.text is not None and len(trace.text) > 0:
positions = ["middle right" if t == "Baja California" else "top center" for t in trace.text]
trace.textposition = positions
# Colorear trendlines por región
for trace in fig.data:
if hasattr(trace, "mode") and trace.mode == "lines":
color = REGION_COLORS.get(trace.name, "#c9d1d9")
trace.update(line=dict(dash="dash", color=color, width=2.5),
hovertemplate="<extra></extra>")
# Tendencia nacional
df_clean_inf = df_informalidad_ingreso.dropna(subset=["ice_informalidad","ing_pc_mediana"])
_m2, _b2 = np.polyfit(df_clean_inf["ice_informalidad"], df_clean_inf["ing_pc_mediana"], 1)
_x_rng2 = np.linspace(df_clean_inf["ice_informalidad"].min(), df_clean_inf["ice_informalidad"].max(), 100)
fig.add_trace(go.Scatter(
x=_x_rng2, y=_m2*_x_rng2+_b2, mode="lines", name="Nacional",
line=dict(color="#c9d1d9", dash="dot", width=2),
hovertemplate="Tendencia nacional<extra></extra>",
showlegend=True,
))
# r por región
_r_inf = {}
for _reg in df_informalidad_ingreso["region"].dropna().unique():
_sub = df_informalidad_ingreso[df_informalidad_ingreso["region"]==_reg].dropna(subset=["ice_informalidad","ing_pc_mediana"])
if len(_sub) >= 3:
_r_inf[_reg] = _sub["ice_informalidad"].corr(_sub["ing_pc_mediana"])
_r_lines_inf = [
f"<b style='font-size:15px'>r nacional = {correlacion_informalidad:.2f}</b>",
f"<span style='color:#8b949e;font-size:11px'>32 estados</span>",
"",
]
for _reg, _col in [("Norte", TEAL), ("Centro", GOLD), ("Sur", ACCENT)]:
if _reg in _r_inf:
_r_lines_inf.append(f"<span style='color:{_col};'>■ {_reg}: r = {_r_inf[_reg]:.2f}</span>")
fig.add_annotation(
x=0.97, y=0.97, xref="paper", yref="paper",
text="<br>".join(_r_lines_inf),
showarrow=False, align="right", xanchor="right",
font=dict(size=13, color="#c9d1d9"), bgcolor="rgba(13,17,23,0.88)",
bordercolor="rgba(255,255,255,0.12)", borderwidth=1, borderpad=14,
)
fig.update_layout(
height=680,
margin=dict(l=110, r=40, t=140, b=80),
xaxis=dict(ticksuffix="%", title=dict(standoff=15)),
yaxis=dict(tickprefix="$", separatethousands=True, title=dict(standoff=15)),
title=dict(
text="Más informalidad laboral → menor ingreso",
y=0.98, yanchor="top",
),
legend=dict(
orientation="h", y=1.06, x=0.5, xanchor="center", yanchor="bottom",
title=dict(text="<b>Región:</b> "), font=dict(size=11),
),
)
fig.show()
```
### Inseguridad alimentaria
```{python}
#| label: fig-hambre
#| fig-cap: "Distribución estatal de inseguridad alimentaria con promedio nacional de referencia.<br><span class='fig-source'>Fuente: Censo de Población y Vivienda 2020, Instituto Nacional de Estadística y Geografía (INEGI).</span>"
df_inseg_alim = maestro.dropna(subset=["pct_inseg_alim"]).sort_values("pct_inseg_alim", ascending=True)
promedio_nacional_alim = df_inseg_alim["pct_inseg_alim"].mean()
fig = px.bar(
df_inseg_alim, x="pct_inseg_alim", y="estado", color="region",
color_discrete_map=REGION_COLORS, orientation="h",
text=df_inseg_alim["pct_inseg_alim"].apply(lambda x: f"{x:.1f}%"),
custom_data=["region","pct_inseg_alim"],
category_orders={"region": ["Norte", "Centro", "Sur", "CDMX"]},
labels={"pct_inseg_alim":"Inseguridad alimentaria (%)", "estado":"", "region":"Región"},
)
fig.update_traces(
textposition="outside", textfont_size=11, marker_line_width=0,
cliponaxis=False,
hovertemplate="<b style='color:#e6edf3'>%{y}</b><br><span style='color:#a0aec0'>Región: </span><b style='color:#e6edf3'>%{customdata[0]}</b><br><span style='color:#a0aec0'>Inseguridad alimentaria: </span><b style='color:#e6edf3'>%{customdata[1]:.1f}%</b><extra></extra>",
)
fig.add_vline(x=promedio_nacional_alim, line_dash="dash", line_color="#f5a623", opacity=0.85,
line_width=2,
annotation_text=f" Promedio nacional · {promedio_nacional_alim:.1f}% ",
annotation_position="top",
annotation_font_color="#0d1117", annotation_font_size=13,
annotation_bgcolor="#f5a623", annotation_bordercolor="#f5a623",
annotation_borderwidth=1, annotation_borderpad=4)
fig.update_layout(
height=950,
margin=dict(l=155, r=55, t=130, b=60),
xaxis=dict(title=dict(text="Porcentaje de población con inseguridad alimentaria", standoff=12), ticksuffix="%",
range=[0, df_inseg_alim["pct_inseg_alim"].max() * 1.32],
showgrid=True, gridwidth=0.5, gridcolor="rgba(100,120,150,0.15)"),
yaxis=dict(categoryorder="total ascending", tickfont=dict(size=13), showgrid=False),
title=dict(
text="El hambre también tiene código postal",
y=0.97, yanchor="top",
),
legend=dict(
orientation="h", y=1.04, x=0.5, xanchor="center", yanchor="bottom",
title=dict(text="<b>Región:</b> "), font=dict(size=11),
),
bargap=0.25,
)
fig.show()
```
:::
::: {.ashira-insight .accent-blue}
Las tres gráficas anteriores cuentan la misma historia desde ángulos distintos: más población indígena, más informalidad laboral, más hambre. Todo ocurre en los mismos estados del Sur.
> *No es coincidencia. Es la misma desigualdad estructural vista desde tres dimensiones distintas.*
:::
---
## De qué vive el sur
::: {.ashira-insight .accent-blue}
Las tres gráficas anteriores muestran de dónde viene el ingreso en el Sur: trabajo, **apoyos del gobierno** y **remesas** de familiares que emigraron.
> *La pobreza bajó, pero no porque el Sur creó mejores empleos. Bajó porque llegó dinero de afuera. Si ese dinero se corta, la mejora desaparece.*
:::
::: {.panel-tabset}
### Evolución programas gubernamentales
```{python}
#| label: fig-bene-gob
#| fig-cap: "Dependencia del ingreso proveniente de programas gubernamentales por región, 2016-2024.<br><span class='fig-source'>Fuente: Encuesta Nacional de Ingresos y Gastos de los Hogares (ENIGH), Instituto Nacional de Estadística y Geografía (INEGI).</span>"
fig = go.Figure()
for region in ["Norte","Centro","Sur"]:
serie = enigh_reg[enigh_reg["region"]==region].sort_values("anio")
fig.add_trace(go.Scatter(
x=serie["anio"], y=serie["pct_bene_gob"], name=region,
mode="lines+markers", line=dict(color=REGION_COLORS[region], width=3.5),
marker=dict(size=11, line=dict(width=1.5, color="white")),
hovertemplate="<b style='color:#e6edf3'>%{x}</b><span style='color:#a0aec0'>: </span><b style='color:#e6edf3'>%{y:.1f}%</b><span style='color:#a0aec0'> del ingreso corriente</span><extra>" + region + "</extra>",
))
fig.update_layout(
height=500,
margin=dict(l=80, r=40, t=120, b=70),
xaxis=dict(dtick=2, title=dict(text="Año", standoff=15)),
yaxis=dict(title=dict(text="Porcentaje del ingreso que proviene de programas gubernamentales", standoff=15),
ticksuffix="%"),
title=dict(
text="Dependencia de programas gubernamentales por región · 2016–2024",
y=0.97, yanchor="top",
),
legend=dict(
orientation="h", y=1.06, x=0.5, xanchor="center", yanchor="bottom",
title=dict(text="<b>Región:</b> "), font=dict(size=11),
),
)
fig.show()
```
### Remesas por estado
```{python}
#| label: fig-remesas
#| fig-cap: "Peso de las remesas en el ingreso corriente por estado en 2024.<br><span class='fig-source'>Fuente: Encuesta Nacional de Ingresos y Gastos de los Hogares (ENIGH) 2024, Instituto Nacional de Estadística y Geografía (INEGI).</span>"
df_remesas_2024 = enigh_est[enigh_est["anio"]==2024][["estado","region","pct_remesas"]].dropna()
df_remesas_2024 = df_remesas_2024[df_remesas_2024["pct_remesas"] > 0.01].sort_values("pct_remesas", ascending=True)
fig = px.bar(
df_remesas_2024, x="pct_remesas", y="estado", color="region",
color_discrete_map=REGION_COLORS, orientation="h",
text=df_remesas_2024["pct_remesas"].apply(lambda x: f"{x:.1f}%"),
custom_data=["region","pct_remesas"],
category_orders={"region": ["Norte", "Centro", "Sur", "CDMX"]},
labels={"pct_remesas":"Remesas (% del ingreso)", "estado":"", "region":"Región"},
)
fig.update_traces(
textposition="outside", textfont_size=11, marker_line_width=0,
cliponaxis=False,
hovertemplate="<b style='color:#e6edf3'>%{y}</b><br><span style='color:#a0aec0'>Región: </span><b style='color:#e6edf3'>%{customdata[0]}</b><br><span style='color:#a0aec0'>Remesas: </span><b style='color:#e6edf3'>%{customdata[1]:.1f}%</b><span style='color:#a0aec0'> del ingreso</span><extra></extra>",
)
fig.update_layout(
height=950,
margin=dict(l=155, r=55, t=130, b=60),
xaxis=dict(title=dict(text="Porcentaje del ingreso corriente que proviene de remesas", standoff=12), ticksuffix="%",
range=[0, df_remesas_2024["pct_remesas"].max() * 1.17],
showgrid=True, gridwidth=0.5, gridcolor="rgba(100,120,150,0.15)"),
yaxis=dict(categoryorder="total ascending", tickfont=dict(size=13), showgrid=False),
title=dict(
text="¿Quién vive de los que se fueron?",
y=0.97, yanchor="top",
),
legend=dict(
orientation="h", y=1.04, x=0.5, xanchor="center", yanchor="bottom",
title=dict(text="<b>Región:</b> "), font=dict(size=11),
),
bargap=0.22,
)
fig.show()
```
### Composición del ingreso
```{python}
#| label: fig-composicion
#| fig-cap: "Composición porcentual del ingreso por región en 2024.<br><span class='fig-source'>Fuente: Encuesta Nacional de Ingresos y Gastos de los Hogares (ENIGH) 2024, Instituto Nacional de Estadística y Geografía (INEGI).</span>"
regiones_ord = ["Sur","Centro","Norte","CDMX"]
comp_2024 = enigh_reg[enigh_reg["anio"]==2024].set_index("region")
comps = [
("pct_ingtrab", "Ingreso laboral", "#38bdf8"), # azul cielo
("pct_bene_gob", "Prog. gubernamentales", "#0284c7"), # azul medio-oscuro
("pct_remesas", "Remesas", "#075985"), # azul navy
("pct_transfer", "Otras transferencias", "#bae6fd"), # azul muy claro
]
fig = go.Figure()
for col, label, color in comps:
vals = [comp_2024.loc[r, col] if r in comp_2024.index else 0 for r in regiones_ord]
fig.add_trace(go.Bar(
x=regiones_ord, y=vals, name=label, marker_color=color,
text=[f"{v:.1f}%" if v > 1.5 else "" for v in vals],
textposition="outside", textfont=dict(color="white"),
hovertemplate="<b style='color:#e6edf3'>%{x}</b><span style='color:#a0aec0'>: </span><b style='color:#e6edf3'>%{y:.1f}%</b><extra>" + label + "</extra>",
))
fig.update_layout(
barmode="group", height=620, bargap=0.12, bargroupgap=0.05,
margin=dict(l=80, r=40, t=130, b=70),
yaxis=dict(title=dict(text="Porcentaje del ingreso corriente", standoff=12),
ticksuffix="%", range=[0, 100]),
xaxis=dict(title="", range=[-0.45, 3.45]),
title=dict(
text="¿De dónde viene el ingreso? Composición por región · 2024",
y=0.97, yanchor="top",
),
legend=dict(
orientation="h", y=1.06, x=0.5, xanchor="center", yanchor="bottom",
title=dict(text="<b>Fuente de ingreso:</b> "), font=dict(size=11),
),
)
fig.show()
```
:::
---
## La exportación de talento por necesidad
::: {.ashira-insight .accent-blue}
Los estados más pobres del Sur son también los que más gente pierden. Los que se van mandan dinero de vuelta (eso son las remesas), y ese dinero hace que las estadísticas mejoren.
> *Pero un estado que mejora porque su gente se fue no está progresando. Está vaciándose.*
:::
```{python}
#| label: fig-migracion
#| fig-cap: "Asociación entre pobreza multidimensional y migración neta estatal.<br><span class='fig-source'>Fuente: Índice de Competitividad Estatal 2018, Instituto Mexicano para la Competitividad (IMCO); Consejo Nacional de Evaluación de la Política de Desarrollo Social (CONEVAL), Medición de la Pobreza 2024.</span>"
df_pobreza_migracion = maestro.dropna(subset=["ice_migracion","pct_pobreza_2024"]).copy()
correlacion_migracion = df_pobreza_migracion["pct_pobreza_2024"].corr(
df_pobreza_migracion["ice_migracion"]
)
# Solo etiquetar los extremos
top_pobres = df_pobreza_migracion.nlargest(5, "pct_pobreza_2024")["estado"].tolist()
top_recibe = df_pobreza_migracion.nlargest(3, "ice_migracion")["estado"].tolist()
top_pierde = df_pobreza_migracion.nsmallest(3, "ice_migracion")["estado"].tolist()
estados_label = set(top_pobres + top_recibe + top_pierde)
df_pobreza_migracion["label"] = df_pobreza_migracion["estado"].apply(
lambda e: e if e in estados_label else ""
)
fig = px.scatter(
df_pobreza_migracion, x="pct_pobreza_2024", y="ice_migracion",
color="region", color_discrete_map=REGION_COLORS,
hover_name="estado", trendline="ols", text="label",
custom_data=["region","pct_pobreza_2024","ice_migracion"],
category_orders={"region": ["Norte", "Centro", "Sur", "CDMX"]},
labels={"pct_pobreza_2024":"Pobreza multidimensional 2024 (%)",
"ice_migracion":"Migración neta · ICE 2018",
"region":"Región"},
)
fig.update_traces(marker=dict(size=14, line=dict(width=1.5, color="white")),
textposition="top center",
textfont=dict(size=11, color="#e6edf3", family="Inter,sans-serif"),
hovertemplate=(
"<b style='font-size:15px;color:#e6edf3'>%{hovertext}</b><br>"
"<span style='color:#a0aec0'>Región: </span><b style='color:#e6edf3'>%{customdata[0]}</b><br>"
"<span style='color:#a0aec0'>Pobreza multidimensional: </span><b style='color:#e6edf3'>%{customdata[1]:.1f}%</b><br>"
"<span style='color:#a0aec0'>Migración neta: </span><b style='color:#e6edf3'>%{customdata[2]:.2f}</b>"
"<extra></extra>"
),
selector=dict(mode="markers+text"))
# Posición de etiqueta por punto
for trace in fig.data:
if hasattr(trace, "text") and trace.text is not None and len(trace.text) > 0:
_pos_map = {"Veracruz": "bottom center", "Oaxaca": "bottom center", "Michoacán": "bottom center"}
positions = [_pos_map.get(t, "top center") for t in trace.text]
trace.textposition = positions
# Colorear trendlines por región
for trace in fig.data:
if hasattr(trace, "mode") and trace.mode == "lines":
color = REGION_COLORS.get(trace.name, "#c9d1d9")
trace.update(line=dict(dash="dash", color=color, width=2.5),
hovertemplate="<extra></extra>")
# Tendencia nacional
df_clean_mig = df_pobreza_migracion.dropna(subset=["pct_pobreza_2024","ice_migracion"])
_m3, _b3 = np.polyfit(df_clean_mig["pct_pobreza_2024"], df_clean_mig["ice_migracion"], 1)
_x_rng3 = np.linspace(df_clean_mig["pct_pobreza_2024"].min(), df_clean_mig["pct_pobreza_2024"].max(), 100)
fig.add_trace(go.Scatter(
x=_x_rng3, y=_m3*_x_rng3+_b3, mode="lines", name="Nacional",
line=dict(color="#c9d1d9", dash="dot", width=2),
hovertemplate="Tendencia nacional<extra></extra>",
showlegend=True,
))
fig.add_hline(y=0, line_dash="solid", line_color="rgba(255,255,255,0.25)", line_width=2)
fig.add_annotation(x=54, y=0.18, text="↑ Recibe migrantes",
showarrow=False, font=dict(color="#0d1117", size=11, family="Inter, sans-serif"),
xanchor="left", bgcolor=TEAL, borderpad=6)
fig.add_annotation(x=54, y=-0.18, text="↓ Pierde población",
showarrow=False, font=dict(color="white", size=11, family="Inter, sans-serif"),
xanchor="left", bgcolor=ACCENT, borderpad=6)
# r por región
_r_mig = {}
for _reg in df_pobreza_migracion["region"].dropna().unique():
_sub = df_pobreza_migracion[df_pobreza_migracion["region"]==_reg].dropna(subset=["pct_pobreza_2024","ice_migracion"])
if len(_sub) >= 3:
_r_mig[_reg] = _sub["pct_pobreza_2024"].corr(_sub["ice_migracion"])
_r_lines_mig = [
f"<b style='font-size:15px'>r nacional = {correlacion_migracion:.2f}</b>",
f"<span style='color:#8b949e;font-size:11px'>32 estados</span>",
"",
]
for _reg, _col in [("Norte", TEAL), ("Centro", GOLD), ("Sur", ACCENT)]:
if _reg in _r_mig:
_r_lines_mig.append(f"<span style='color:{_col};'>■ {_reg}: r = {_r_mig[_reg]:.2f}</span>")
fig.add_annotation(
x=0.97, y=0.97, xref="paper", yref="paper",
text="<br>".join(_r_lines_mig),
showarrow=False, align="right", xanchor="right",
font=dict(size=13, color="#c9d1d9"), bgcolor="rgba(13,17,23,0.88)",
bordercolor="rgba(255,255,255,0.12)", borderwidth=1, borderpad=14,
)
fig.update_layout(
height=600,
margin=dict(l=80, r=40, t=130, b=70),
xaxis=dict(ticksuffix="%", title=dict(standoff=15)),
yaxis=dict(title=dict(standoff=15)),
title=dict(
text="Los estados más pobres son los que más gente pierden",
y=0.98, yanchor="top",
),
legend=dict(
orientation="h", y=1.06, x=0.5, xanchor="center", yanchor="bottom",
title=dict(text="<b>Región:</b> "), font=dict(size=11),
),
)
fig.show()
```
::: {.ashira-globe-intro}
El mapa de arriba muestra **cuánta gente sale** de cada estado. El mapa de abajo muestra **a dónde van**.
La respuesta es siempre la misma: al Norte, o a las ciudades turísticas. Son los únicos lugares que ofrecen trabajo a quien no tiene título universitario.
:::
::: {style="text-align:center; margin: 10px 0;"}
<img src="untitled.GIF" alt="Flujos de migración interestatal 2015–2020" style="max-width:100%; border-radius:8px; display:block; margin:0 auto;">
::: {.figure-caption}
Flujos de migración interestatal 2015–2020 · Top 40 pares por volumen · Puntos: balance neto (azul = receptor, rojo = emisor).[Fuente: Migración origen-destino, Censo de Población y Vivienda 2020, INEGI.]{.fig-source}
:::
:::
::: {.ashira-insight .accent-blue}
Cuando alguien del Sur migra al Norte buscando trabajo, las cifras de pobreza en su estado de origen bajan. Pero esa persona sigue siendo pobre. Solo cambió de código postal.
**El número mejoró. La vida, no.**
> *La estadística mide territorios. La dignidad se juega en personas.*
:::
## El panorama completo
::: {.ashira-insight .accent-blue}
Cada indicador cuenta una parte. Juntos revelan el patrón: [**los mismos estados aparecen siempre arriba en pobreza, informalidad, remesas e inseguridad alimentaria.**]{style="color:#38bdf8;"} No es azar. Es estructura.
:::
```{python}
#| label: fig-heatmap
#| fig-cap: "Mapa de calor de desigualdad territorial con cinco indicadores estructurales normalizados.<br><span class='fig-source'>Fuentes: Consejo Nacional de Evaluación de la Política de Desarrollo Social (CONEVAL), Encuesta Nacional de Ingresos y Gastos de los Hogares (ENIGH), Censo de Población y Vivienda (INEGI) e Índice de Competitividad Estatal (IMCO).</span>"
indicadores = {
"Pobreza<br>2024": "pct_pobreza_2024",
"Informalidad<br>laboral 2018":"ice_informalidad",
"Remesas<br>2024": "pct_remesas",
"Población<br>indígena 2020": "pct_indigena",
"Inseguridad<br>alimentaria": "pct_inseg_alim",
}
df_heat = maestro[["estado","region"] + list(indicadores.values())].dropna().copy()
df_heat = df_heat.sort_values("pct_pobreza_2024", ascending=True)
mat = df_heat[list(indicadores.values())].values.astype(float)
mat_norm = (mat - mat.min(axis=0)) / (mat.max(axis=0) - mat.min(axis=0) + 1e-9)
# Text con valores reales
text_vals = [[f"{mat[i,j]:.0f}" for j in range(mat.shape[1])] for i in range(mat.shape[0])]
fig = go.Figure(data=go.Heatmap(
z=mat_norm,
x=list(indicadores.keys()),
y=df_heat["estado"].values.tolist(),
text=text_vals, texttemplate="<span style='color:#f0f6fc'>%{text}</span>",
textfont=dict(size=11),
colorscale=[
[0.00, "#1e3a5f"],
[0.25, "#2d5a87"],
[0.50, "#6b5b8c"],
[0.75, "#a04968"],
[1.00, "#c9384f"],
],
hovertemplate="<b style='color:#e6edf3'>%{y}</b><br><span style='color:#a0aec0'>%{x}: </span><b style='color:#e6edf3'>%{text}</b><extra></extra>",
showscale=True,
colorbar=dict(
title=dict(text="Nivel", font=dict(color="white", size=12)),
tickvals=[0, 0.5, 1],
ticktext=["Mejor", "Medio", "Peor"],
tickfont=dict(color="white", size=11),
len=0.7, thickness=14, outlinewidth=0,
),
))
fig.update_layout(
height=900,
xaxis=dict(side="top", tickangle=0, tickfont=dict(size=12)),
yaxis=dict(tickfont=dict(size=12)),
title=dict(
text="Los mismos estados, siempre en rojo",
y=0.985, yanchor="top",
),
margin=dict(l=140, r=60, t=160, b=40),
)
fig.show()
```
## El ciclo que se perpetúa
::: {.ashira-ciclo}
::: {.ashira-ciclo-sub}
Cada paso lleva al siguiente y el último vuelve al primero. Por eso la pobreza no se rompe sola.
:::
::: {.ashira-ciclo-grid .ciclo-circle}
::: {.ciclo-card .pos1 style="--c:#e94560;"}
::: {.ciclo-num}
1
:::
::: {.ciclo-titulo}
Naces en el Sur
:::
::: {.ciclo-desc}
Alta pobreza desde el inicio
:::
:::
::: {.ciclo-card .pos2 style="--c:#00bcd4;"}
::: {.ciclo-num}
2
:::
::: {.ciclo-titulo}
Educación limitada
:::
::: {.ciclo-desc}
Acceso desigual y menor calidad educativa
:::
:::
::: {.ciclo-card .pos3 style="--c:#c084fc;"}
::: {.ciclo-num}
3
:::
::: {.ciclo-titulo}
Empleo informal
:::
::: {.ciclo-desc}
Más del 60% trabaja sin prestaciones
:::
:::
::: {.ciclo-card .pos4 style="--c:#f5a623;"}
::: {.ciclo-num}
4
:::
::: {.ciclo-titulo}
Bajos ingresos
:::
::: {.ciclo-desc}
1.7 veces menos que el Norte
:::
:::
::: {.ciclo-card .pos5 style="--c:#4aa8ff;"}
::: {.ciclo-num}
5
:::
::: {.ciclo-titulo}
Migras
:::
::: {.ciclo-desc}
Sales de tu estado o del país
:::
:::
::: {.ciclo-card .pos6 style="--c:#6bcf8a;"}
::: {.ciclo-num}
6
:::
::: {.ciclo-titulo}
Llegan remesas
:::
::: {.ciclo-desc}
El estado "mejora" en el papel
:::
:::
::: {.ciclo-arrow .a12}
➜
:::
::: {.ciclo-arrow .a23}
➜
:::
::: {.ciclo-arrow .a34}
➜
:::
::: {.ciclo-arrow .a45}
➜
:::
::: {.ciclo-arrow .a56}
➜
:::
::: {.ciclo-arrow .a61}
➜
:::
::: {.ciclo-core title="Es un ciclo de seis eslabones que los datos confirman y que ningún promedio nacional puede ocultar."}
Pobreza estructural
:::
:::
:::
---
## ¿Qué sigue?
::: {.callout-important .accent-blue}
### El sur no está condenado
Este dashboard analizó los estados y sectores donde la pobreza persiste con más fuerza, y los que ya encontraron una salida.
Aguascalientes, Querétaro, Brasil y Colombia lo lograron: redujeron su pobreza con programas focalizados, sostenidos y con rendición de cuentas pública. No fue magia, fue saber exactamente dónde invertir.
> Lo que necesitamos no es más gasto general: es saber dónde tiene más impacto.
:::
::: {.que-sigue-grid}
::: {.que-sigue-card}
<span class="que-sigue-card-icon"><svg width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg></span>
::: {.que-sigue-card-title}
Si eres ciudadano
:::
Consulta [Datos Abiertos](https://datos.gob.mx){target="_blank" rel="noopener"}, [IMCO](https://imco.org.mx){target="_blank" rel="noopener"} y [MejoraTuEscuela](https://www.mejoratuescuela.org){target="_blank" rel="noopener"}. Escríbele a tu diputado federal sobre el presupuesto de transferencias sociales. Exige metas estatales públicas.
:::
::: {.que-sigue-card}
<span class="que-sigue-card-icon"><svg width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/><line x1="2" y1="20" x2="22" y2="20"/></svg></span>
::: {.que-sigue-card-title}
Si eres investigador o estudiante
:::
Replica este análisis a nivel municipal. Explora el efecto intergeneracional de las remesas. El código y datos están disponibles en [GitHub del equipo ASHIRA](https://github.com){target="_blank" rel="noopener"} bajo CC BY 4.0.
:::
::: {.que-sigue-card}
<span class="que-sigue-card-icon"><svg width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="22" x2="21" y2="22"/><line x1="6" y1="18" x2="6" y2="11"/><line x1="10" y1="18" x2="10" y2="11"/><line x1="14" y1="18" x2="14" y2="11"/><line x1="18" y1="18" x2="18" y2="11"/><polygon points="12 2 20 7 4 7"/></svg></span>
::: {.que-sigue-card-title}
Si eres funcionario público
:::
Adopta metas estatales focalizadas. Diseña mecanismos de asignación asimétrica del gasto. Reporta trimestralmente los indicadores de convergencia Norte–Sur para cada entidad.
:::
::: {.que-sigue-card}
<span class="que-sigue-card-icon"><svg width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg></span>
::: {.que-sigue-card-title}
Si eres periodista o comunicador
:::
Descarga los gráficos en PNG y los datos en CSV desde nuestro repositorio. Los hallazgos clave están listos para citar. Cubre la brecha Norte–Sur como la historia estructural que es, no como dato puntual.
:::
:::
::: {.que-sigue-aliados}
#### Aliados que trabajan en esto
- [México Evalúa](https://mexicoevalua.org){target="_blank" rel="noopener"}
- [IMCO](https://imco.org.mx){target="_blank" rel="noopener"}
- [CIESAS](https://www.ciesas.edu.mx){target="_blank" rel="noopener"}
- [Oxfam México](https://www.oxfammexico.org){target="_blank" rel="noopener"}
- [Fundación IDEA](https://www.fundacionidea.org.mx){target="_blank" rel="noopener"}
- [CONEVAL](https://www.coneval.org.mx){target="_blank" rel="noopener"}
- [INEGI Datos Abiertos](https://www.inegi.org.mx){target="_blank" rel="noopener"}
:::
::: {.que-sigue-compromiso}
#### Compromiso del equipo ASHIRA
Este análisis se actualizará cuando INEGI publique ENIGH 2026. El código y los datos están disponibles en GitHub bajo licencia **Creative Commons CC BY 4.0**. Si detectas un error, quieres colaborar, o quieres usar estos datos como funcionario, investigador o periodista, escríbenos al [equipo ASHIRA]{.color-azul}.
:::
::: {.cta-share}
<a href="https://twitter.com/intent/tweet?text=En+México+no+puedes+hablar+de+pobreza+sin+hablar+de+dónde+naciste.+El+análisis+de+@ASHIRA_HackODS+lo+demuestra+con+8+años+de+datos+oficiales." target="_blank" rel="noopener" class="cta-share-btn twitter"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.744l7.736-8.85L1.254 2.25H8.08l4.253 5.622zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>Compartir en X</a>
<a href="https://www.linkedin.com/sharing/share-offsite/?url=https://ashira.github.io" target="_blank" rel="noopener" class="cta-share-btn linkedin"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>Compartir en LinkedIn</a>
:::
---
## Hallazgos clave
::: {.ashira-closing}
::: {.closing-eyebrow}
Lo que descubrimos
:::
::: {.closing-title}
El talento está en todo México.\
[Lo que no está distribuido son las oportunidades.]{.closing-title-accent}
:::
::: {.closing-pillars}
::: {.pillar}
::: {.pillar-num}
01
:::
::: {.pillar-head}
Historia
:::
::: {.pillar-body}
La brecha Norte a Sur no es nueva. Lleva décadas. Lo que cambia son los nombres de los programas, no el mapa de la pobreza.
:::
:::
::: {.pillar}
::: {.pillar-num}
02
:::
::: {.pillar-head}
Estructura
:::
::: {.pillar-body}
La informalidad laboral, la baja cobertura educativa y la composición étnica del estado explican más de dos tercios de la variación entre entidades.
:::
:::
::: {.pillar}
::: {.pillar-num}
03
:::
::: {.pillar-head}
Dependencia
:::
::: {.pillar-body}
Las mejoras visibles en el Sur descansan en transferencias, programas y remesas, no en generación propia de ingreso. Son frágiles.
:::
:::
::: {.pillar}
::: {.pillar-num}
04
:::
::: {.pillar-head}
Migración
:::
::: {.pillar-body}
Los estados más pobres pierden a sus jóvenes. Esa fuga de talento convierte la desigualdad en un ciclo que se alimenta a sí mismo.
:::
:::
:::
::: {.closing-policy}
### ¿Qué implica para la política pública? {.closing-policy-title}
Invertir en reducir pobreza sin desagregar por estado es ineficiente. Los recursos se diluyen donde ya hubo recuperación, mientras el sur permanece atrapado. La convergencia requiere **intervenciones asimétricas**, con metas estatales y rendición de cuentas pública.
La Agenda 2030 no pide promedios. **Pide que nadie se quede atrás.**
[**Este análisis muestra que en México, el lugar donde naciste todavía decide si te quedas o te vas.**]{.closing-quote}
:::
::: {.closing-cta}
[Volver al inicio](#inicio){.closing-cta-btn}
:::
:::
::: {.ashira-footer}
::: {.ashira-footer-inner}
::: {.ashira-footer-brand}
[ASHIRA]{.ashira-footer-logo}
[Análisis de pobreza y migración en México]{.ashira-footer-desc}
[HackODS · UNAM · 2026]{.ashira-footer-tag}
:::
::: {.ashira-footer-team}
[Equipo]{.ashira-footer-label}
[Melisa Arano]{.footer-member}
[Roberto Alegre]{.footer-member}
[Israel Martínez]{.footer-member}
:::
::: {.ashira-footer-sources}
[Fuentes]{.ashira-footer-label}
[ENIGH 2016–2024]{.source-line}
[Pobreza Multidimensional CONEVAL 2024]{.source-line}
[Censo de Población y Vivienda 2020]{.source-line}
[ICE IMCO 2020 · Migración OD INEGI]{.source-line}
:::
:::
::: {.ashira-footer-bottom}
[ODS 1 · Sin pobreza]{.footer-ods-pill}
[ODS 10 · Reducción de desigualdades]{.footer-ods-pill}
[· CC BY-SA 4.0 · Datos públicos oficiales ·]{.footer-bottom-text}
:::
:::