mx-geo
JavaScriptMapa de México por estados (división política con claves INEGI) para dashboards: TopoJSON + catálogo tipado + componente React choropleth, sin API keys.
pnpm add @webrek/mx-geo@webrek/mx-geo
Mapa de México por estados para dashboards, con la división política llaveada por
claves de INEGI (CVE_ENT). Trae tres cosas que normalmente tendrías que armar a mano:
- Un catálogo tipado de los 32 estados (clave INEGI, nombre, ISO 3166-2, capital, región, población del Censo 2020, superficie y huso horario).
- Un TopoJSON de las 32 geometrías en alta resolución (disueltas de los
municipios de INEGI, ~75 KB gzip), ya llaveado por
CVE_ENT. - Un componente React choropleth (
<MapaMexico>) que se pinta solo con tus datos — SVG puro, sin librería de mapas ni API key— con paletas con nombre, modo categórico (por región o tus zonas) y un componente<Leyenda>.
El núcleo de datos (catálogo + TopoJSON + helpers) no depende de React; el componente
vive en el subpath @webrek/mx-geo/react.
Paquete hermano:
@webrek/mx-cpresuelve códigos postales (SEPOMEX) a estado, municipio (CVEGEO) y colonias; sucvegeocruza directo con los mapas de aquí.
Instalación
pnpm add @webrek/mx-geo
# para el componente, además:
pnpm add react
Catálogo y normalización (sin React)
import { ESTADOS, estado, normalizaEstado, buscaEstado } from "@webrek/mx-geo";
ESTADOS.length; // 32
estado("09")?.nombre; // "Ciudad de México"
// Normaliza casi cualquier forma de escribir un estado a su CVE_ENT.
normalizaEstado("CDMX"); // "09"
normalizaEstado("Distrito Federal"); // "09"
normalizaEstado("nuevo leon"); // "19"
normalizaEstado("MX-JAL"); // "14"
normalizaEstado("Edomex"); // "15"
normalizaEstado("Narnia"); // null
buscaEstado("yucatán"); // { cve: "31", nombre: "Yucatán", capital: "Mérida", … }
Esto es justo lo que necesitas para normalizar una columna de texto a clave antes de cruzarla (join) con tus datos de ventas, tiendas, usuarios, etc.
Diagnóstico de joins (sin perder filas en silencio)
Al cruzar una columna de texto con el catálogo, normalizaEstado devuelve null en las
que no reconoce — pero no te dice cuáles. uneEstados hace el join y te entrega el
diagnóstico completo: qué se emparejó, qué no, y qué estados quedan vacíos en el mapa.
import { uneEstados } from "@webrek/mx-geo";
const { emparejados, sinMatch, faltantes } = uneEstados(ventas, (r) => r.estado);
emparejados; // [{ cve: "09", estado: {…}, fila: { estado: "CDMX", monto: 100 } }, …]
sinMatch; // [{ valor: "Sin especificar", fila: {…} }] — limpia estos valores en tu fuente
faltantes; // ["01", "03", …] — estados sin datos, que el mapa pintará vacíos
Acepta clave ("09"/9), ISO, nombre, abreviatura o alias — lo mismo que buscaEstado.
Geocodificación inversa (coordenada → estado / municipio)
¿Tienes puntos [lon, lat] (GPS, tiendas, tickets) y quieres saber en qué estado o
municipio caen? Point-in-polygon sobre las mismas geometrías de INEGI — sin API keys
ni red.
import { estadoDeCoordenada } from "@webrek/mx-geo";
estadoDeCoordenada([-99.1332, 19.4326]); // "09" (Ciudad de México)
estadoDeCoordenada([-103.3496, 20.6597]); // "14" (Jalisco)
estadoDeCoordenada([-120, 20]); // null (Pacífico, fuera de México)
Para el nivel municipio usa el subpath @webrek/mx-geo/municipios (carga la geometría del
estado bajo demanda, por eso es asíncrono):
import { municipioDeCoordenada } from "@webrek/mx-geo/municipios";
await municipioDeCoordenada([-99.1332, 19.4326]); // "09015" (Cuauhtémoc)
El
cvegeoque devuelve cruza directo con@webrek/mx-cpy con el drill-down de<MapaMunicipios>.
Componente choropleth
"use client";
import { MapaMexico } from "@webrek/mx-geo/react";
const ventasPorEstado = { "09": 1200, "14": 980, "19": 760, "15": 1500 };
export function Tablero() {
return (
<MapaMexico
data={ventasPorEstado} // { CVE_ENT: número }
colorRange={["#dbeafe", "#1e3a8a"]}
onSelect={(e) => console.log("clic en", e.nombre, e.cve)}
formatValue={(v) => `$${v.toLocaleString("es-MX")}`}
/>
);
}
El SVG es responsivo (ocupa el ancho del contenedor). Los estados sin valor en data
se pintan con emptyColor, así que también sirve como mapa base estático.
Props
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
data | Record<string, number> | — | Valores por CVE_ENT. Sin esto, mapa base. |
paleta | nombre | string[] | "azul" | Paleta del choropleth (ver abajo). |
colorRange | [string, string] | — | Atajo de dos colores; paleta tiene prioridad. |
categorias | Record<string, string> | — | Modo categórico: CVE_ENT -> categoría. |
paletaCategorica | string[] | PALETA_CATEGORICA | Colores del modo categórico. |
emptyColor | string | "#e5e7eb" | Color de un estado sin dato. |
stroke | string | "#ffffff" | Color del borde. |
onSelect | (estado: Estado) => void | — | Clic en un estado. |
formatValue | (v, estado) => string | — | Formato del valor en el tooltip. |
ariaLabel | string | "Mapa de México por estados" | Etiqueta accesible. |
className | string | — | Clase del <svg>. |
Paletas y temas
En vez de pasar dos colores a mano, elige una paleta con nombre. Hay
secuenciales (azul, verde, rojo, naranja, morado, teal, rosa,
ambar, gris, walmart), divergentes (rojoVerde, azulRojo,
moradoVerde) o puedes pasar tu propia lista de colores.
<MapaMexico data={ventas} paleta="walmart" />
<MapaMexico data={crecimiento} paleta="rojoVerde" /> // divergente, con centro
<MapaMexico data={ventas} paleta={["#f1f5f9", "#0ea5e9", "#0c4a6e"]} /> // a la medida
Las paletas y las escalas también viven sueltas (sin React), por si dibujas la leyenda o el mapa con otra herramienta:
import { escalaSecuencial, escalaCuantil, PALETAS } from "@webrek/mx-geo";
const color = escalaSecuencial([0, 1000], "azul");
color(750); // "#3b82f6"
// Cuando los datos están sesgados, agrupa por cuantil:
const { color: c, tramos } = escalaCuantil(Object.values(ventas), "verde", 5);
Además del cuantil hay dos clasificaciones escalonadas más — las tres devuelven
{ color, tramos }, listas para la <Leyenda tipo="cuantil">:
import { escalaJenks, escalaUmbral, rupturasJenks } from "@webrek/mx-geo";
// Rupturas naturales (Fisher-Jenks): agrupa valores parecidos y corta en los
// saltos reales de los datos — el clásico de los choropleth.
const jenks = escalaJenks(Object.values(ventas), "azul", 5);
// Umbrales manuales: tú decides los cortes (extremos abiertos -∞/+∞).
const umbral = escalaUmbral([100, 500, 1000], "rojo");
umbral.color(750); // color de la clase [500, 1000)
// ¿Solo quieres los cortes? (n-1 rupturas internas)
rupturasJenks(Object.values(ventas), 5); // p. ej. [120, 480, 950, 2100]
Regiones / zonas
El paquete trae la regionalización de Banxico (Norte, Centro Norte, Centro y Sur) y helpers para agrupar. Combínala con el modo categórico para pintar el país por región:
import { MapaMexico } from "@webrek/mx-geo/react";
import { REGION_POR_ESTADO, REGIONES, estadosDeRegion } from "@webrek/mx-geo";
<MapaMexico categorias={REGION_POR_ESTADO} />; // cada región, un color
estadosDeRegion("norte").map((e) => e.nombreCorto); // ["Baja California", …]
Para que la leyenda use exactamente los mismos colores que el mapa, calcula
el mapa categoría -> color con coloresCategorias (es el que usa <MapaMexico>
por dentro; determinista, no depende del orden del objeto):
import { coloresCategorias, REGIONES } from "@webrek/mx-geo";
const colores = coloresCategorias(REGION_POR_ESTADO);
const leyenda = REGIONES.map((r) => [r.nombre, colores.get(r.reg)!] as [string, string]);
<Leyenda tipo="categorias" titulo="Región" categorias={leyenda} />;
¿Tus propias zonas de venta? Pasa tu mapa CVE_ENT -> zona a categorias:
<MapaMexico categorias={{ "19": "Bajío", "14": "Bajío", "09": "Metro" }} />
Catálogo enriquecido
Cada estado trae datos listos para tableros: región, población (Censo INEGI 2020), superficie (km²) y huso horario (IANA).
import { estado } from "@webrek/mx-geo";
const e = estado("15");
e?.poblacion; // 16992418 (Estado de México)
e?.region; // "centro"
e?.superficie; // 22357
e?.huso; // "America/Mexico_City"
Leyenda
El componente <Leyenda> (en @webrek/mx-geo/react) acompaña al mapa en sus
tres modos:
import { Leyenda } from "@webrek/mx-geo/react";
import { escalaCuantil } from "@webrek/mx-geo";
<Leyenda dominio={[0, 1000]} paleta="azul" titulo="Ventas" />; // degradado
<Leyenda tipo="cuantil" tramos={escalaCuantil(vals, "verde", 5).tramos} />; // escalones
<Leyenda tipo="categorias" categorias={{ Norte: "#2563eb", Sur: "#16a34a" }} />;
De municipios a estados
¿Tienes cifras por municipio pero quieres el mapa nacional? Súbelas con
agregaMunicipiosAEstado (suma por defecto):
import { agregaMunicipiosAEstado } from "@webrek/mx-geo";
const porEstado = agregaMunicipiosAEstado({ "20067": 1200, "20001": 300 });
// → { "20": 1500 } listo para <MapaMexico data={porEstado} />
Tasas y densidad
Los valores absolutos engañan (los estados grandes siempre “ganan”). Con la población (Censo 2020) y la superficie del catálogo puedes normalizar:
import { porCapita, porKm2, tasa, densidadPoblacion } from "@webrek/mx-geo";
porCapita(casos, 100_000); // casos por 100 mil habitantes
porKm2(ventas); // ventas por km²
tasa(datos, "poblacion"); // genérico; "poblacion" | "superficie" | tu propio mapa
densidadPoblacion()["09"]; // hab/km² de la CDMX (la más densa)
Etiquetas
<MapaMexico etiquetas /> pinta el texto sobre el centroide de cada estado:
<MapaMexico etiquetas /> {/* abreviatura: "Jal.", "CDMX"… */}
<MapaMexico etiquetas="nombre" /> {/* nombre corto */}
<MapaMexico etiquetas={(e) => e.cve} /> {/* lo que tú devuelvas */}
¿Necesitas la coordenada para colocar tus propios marcadores?
centroideEstado("14") → [lon, lat] (o CENTROIDES_ESTADOS).
Tooltip a la medida
Por defecto el tooltip es el <title> nativo del SVG. Con renderTooltip
pintas una tarjeta flotante con tu propio HTML (formato, varias líneas, íconos):
<MapaMexico
data={ventas}
renderTooltip={(estado, valor) => (
<div>
<strong>{estado.nombre}</strong>
<div>{valor === null ? "Sin dato" : `$${valor.toLocaleString("es-MX")}`}</div>
</div>
)}
/>
Funciona igual en <MapaBurbujas>.
Zoom y pan
zoom activa acercar con la rueda (centrado en el cursor), mover arrastrando y
reiniciar con doble clic. Ideal para los estados chicos del centro. Disponible
en <MapaMexico> y <MapaMunicipios>:
<MapaMexico data={ventas} zoom />
<MapaMexico data={ventas} zoom={{ min: 1, max: 12 }} /> {/* límites de escala */}
<MapaMunicipios estado="09" zoom />
Mapa de burbujas
Cuando quieres comparar magnitudes absolutas, <MapaBurbujas> dibuja un
círculo por estado con área proporcional al valor, sobre su centroide:
import { MapaBurbujas } from "@webrek/mx-geo/react";
<MapaBurbujas
data={{ "09": 9209944, "15": 16992418, "14": 8348151 }}
radioMax={28}
color="#2563eb"
formatValue={(v) => v.toLocaleString("es-MX")}
/>;
Vue
Además de React, hay un componente para Vue 3 en @webrek/mx-geo/vue (mismo
choropleth: data/categorias, paletas, etiquetas, evento select):
<script setup>
import { MapaMexico } from "@webrek/mx-geo/vue";
const ventas = { "09": 1200, 14: 980, 19: 760 };
</script>
<template>
<MapaMexico :data="ventas" paleta="walmart" @select="(e) => console.log(e.nombre)" />
</template>
El núcleo (catálogo, escalas, mapaSVG, y @webrek/mx-cp) es framework-free, así
que se usa igual desde Vue.
Leyenda embebida y animación
leyenda dibuja la leyenda en una esquina del mapa; el relleno se anima al
cambiar los datos (se desactiva solo con prefers-reduced-motion o animar={false}):
<MapaMexico data={ventas} paleta="verde" leyenda leyendaTitulo="Ventas" />
Vecinos (adyacencia)
Qué estados colindan con cuál. Ideal para análisis o para resaltar la zona de un estado en el mapa:
import { vecinos, sonVecinos, estadosVecinos } from "@webrek/mx-geo";
vecinos("19"); // ["05", "24", "28", "32"] Nuevo León y sus colindantes
sonVecinos("09", "13"); // false — CDMX no colinda con Hidalgo
// Resaltar Jalisco + sus vecinos en el mapa:
const zona = ["14", ...vecinos("14")];
const cats = Object.fromEntries(zona.map((c) => [c, "zona"]));
<MapaMexico categorias={cats} />;
Exportar a PNG / SVG
Con una referencia al <svg> del mapa puedes descargarlo:
import { useRef } from "react";
import { MapaMexico, descargaPNG, descargaSVG } from "@webrek/mx-geo/react";
function Tablero() {
const ref = useRef<HTMLDivElement>(null);
const svg = () => ref.current!.querySelector("svg")!;
return (
<div ref={ref}>
<MapaMexico data={ventas} />
<button onClick={() => descargaPNG(svg(), "mapa.png", { escala: 2, fondo: "#fff" })}>
Descargar PNG
</button>
<button onClick={() => descargaSVG(svg(), "mapa.svg")}>Descargar SVG</button>
</div>
);
}
Cartograma de mosaicos
<MapaMosaico> acomoda los 32 estados en una rejilla, cada uno del mismo
tamaño. Resuelve que CDMX, Morelos o Tlaxcala se pierdan por chiquitos y da peso
visual parejo. Se colorea igual que el choropleth (data o categorias):
import { MapaMosaico } from "@webrek/mx-geo/react";
<MapaMosaico data={ventas} paleta="walmart" formatValue={(v) => fmt(v)} />
<MapaMosaico categorias={REGION_POR_ESTADO} /> {/* por región */}
Cada mosaico muestra la abreviatura y, si hay dato, el valor debajo. Para el
servidor está mosaicoSVG(opts) en @webrek/mx-geo/svg.
Render en el servidor (para PDF / correo)
@webrek/mx-geo/svg genera el choropleth como cadena SVG sin React ni
navegador — ideal para un reporte en Node, un correo o un PDF:
import { mapaSVG } from "@webrek/mx-geo/svg";
const svg = mapaSVG({
data: ventasPorEstado,
paleta: "walmart",
etiquetas: true,
background: "#ffffff",
titulo: "Ventas por estado",
});
// escribe el SVG, conviértelo a PNG, o pégalo en tu HTML/PDF
Acepta lo mismo que el choropleth (data/categorias, paleta/colorRange,
emptyColor, stroke, width/height, etiquetas) y escapa el texto para XML.
Interactivo sin React (vanilla JS)
Cada <path> (y cada celda del mosaicoSVG) lleva data-cve y
data-nombre, así que en el navegador el mismo SVG se vuelve interactivo con
delegación de eventos estándar — sin React, sin D3, sin build:
const mapa = document.querySelector("#mapa");
mapa.innerHTML = mapaSVG({ data: ventas, paleta: "azul" });
mapa.addEventListener("click", (ev) => {
const cve = ev.target.closest("path[data-cve]")?.dataset.cve;
if (cve) console.log(estado(cve).nombre); // catálogo completo, también sin React
});
Receta completa (hover + clic + repintado con escalaJenks, vía CDN):
examples/22-vanilla.html.
El TopoJSON directo
Si quieres dibujarlo con tu propia herramienta (D3, etc.):
import { estadosTopoJSON } from "@webrek/mx-geo";
// estadosTopoJSON.objects.estados.geometries[i].properties = { cve, nombre, abr }
Municipios (drill-down)
El subpath @webrek/mx-geo/municipios (solo ESM) trae los 2,475 municipios en
alta resolución (Marco Geoestadístico de INEGI), llaveados por CVEGEO. La
geometría se carga por estado bajo demanda (un chunk por estado): solo bajas
el estado que abres.
"use client";
import { MapaMunicipios, municipios, municipio } from "@webrek/mx-geo/municipios";
municipios("09").length; // 16 alcaldías de la CDMX (índice ligero, sin geometría)
municipio("09012")?.nombre; // "Tlalpan"
// choropleth de los municipios de un estado (CVE_ENT); carga su geometría sola
<MapaMunicipios
estado="20"
data={{ "20067": 1200 }}
onSelect={(m) => console.log(m.cvegeo, m.nombre)}
/>;
¿Necesitas el TopoJSON crudo de un estado? await cargaMunicipios("20").
Para un drill-down completo: en el onSelect de <MapaMexico> guardas el
estado y renderizas <MapaMunicipios estado={cve} /> (ver examples/demo.tsx).
Ejemplos
En examples/ hay recetas cortas y copiables: choropleth con
paleta, mapa por región, zonas a la medida, drill-down, normalizar una columna
de texto, subir cifras municipales al mapa nacional, escalas sin React,
cuantiles, Jenks y umbrales, geocodificación inversa, joins con diagnóstico y
el patrón de Next.js App Router.
Datos, fuentes y precisión
- Geometría de estados: disuelta del Marco Geoestadístico de INEGI
(los mismos municipios, fundidos por
CVE_ENT), así los bordes de estado coinciden exactamente con el drill-down y se ven con la misma resolución. Simplificada para web (~75 KB gzip). - Claves y nombres:
CVE_ENTy nomenclatura oficial de INEGI. - Municipios: Marco Geoestadístico de INEGI (servicio ArcGIS), 2,475 municipios en alta resolución, simplificados para web y partidos por estado.
- Vigencia: la geometría es de referencia/visualización, no catastral. CDMX usa el
ISO vigente
MX-CMX.
Desarrollo
pnpm install
pnpm build:data # regenera el catálogo tipado (src/estados.generated.ts)
pnpm build:data:municipios # baja y arma los municipios (INEGI ArcGIS)
pnpm build:data:estados # disuelve los municipios -> TopoJSON de estados
pnpm check # format + typecheck + test + build
Licencia
MIT © webrek. Geometría derivada del Marco Geoestadístico de INEGI.