<code>
{ }
</>
[ ]

Blog

Desarrollo 13 de agosto de 2026 8 min de lectura

Cómo hacer un mapa interactivo de México

Un mapa de los 32 estados en SVG puro: 139 KB, sin API key, sin librerías de mapas y sin una sola petición al cargar. El código está en GitHub y aquí explico cómo funciona por dentro.

A

Azael Garcia Jaimes

TalkWeb

Cómo hacer un mapa interactivo de México

Al final de esto vas a tener un mapa de los 32 estados de México que pesa 139 KB, se acerca a un estado al hacer clic, no necesita API key y no hace ni una petición de red para dibujarse. Todo con HTML, CSS y JavaScript.

El código completo está aquí, listo para clonar:

github.com/AzaelW3b/mapa-web

No tiene dependencias ni paso de compilación. Abres el index.html con doble clic y ya está corriendo. El resto del artículo explica cómo funciona por dentro y cómo adaptarlo.

Cómo se usa

Tres archivos y un objeto de configuración:

<link rel="stylesheet" href="css/mapa-web.css">
<div id="mapa"></div>

<script src="data/mexico.js"></script>
<script src="js/mapa-web.js"></script>
<script>
  MapaWeb.crear('#mapa', {
    datos: MAPA_MEXICO,
    puntos: [
      { nombre: 'Veracruz', estado: 'MX-VER', lat: 19.1738, lon: -96.1342, base: true },
      { nombre: 'Cancún',   estado: 'MX-ROO', lat: 21.1619, lon: -86.8515 },
    ],
  });
</script>

Los puntos se ponen con latitud y longitud, no con coordenadas de pantalla. Eso fue a propósito y es la parte que más trabajo dio: el generador guarda los parámetros de la proyección dentro del archivo de datos, así que el navegador puede colocar cualquier coordenada del mundo real sin volver a calcular nada.

El campo estado es opcional. Cuando lo pones, esa entidad se ilumina y es a donde se acerca la cámara al hacer clic. Es la clave ISO 3166-2 y viene en el archivo de datos, así que la puedes consultar desde la consola:

MAPA_MEXICO.estados.map(e => e.codigo + ' ' + e.nombre)

Los colores salen de variables CSS, así que el tema se cambia sin tocar el JavaScript.

Son tres capas y conviene no confundirlas

Mucha gente asume que el diseño del mapa viene incluido en el archivo de datos. No es así, y entender la separación es lo que te permite hacer el tuyo con la pinta que quieras.

1. Los datos son puros números

Todo empieza con un GeoJSON: un archivo de texto con listas de coordenadas que trazan el contorno de cada entidad.

{ "type": "Feature",
  "properties": { "name": "Veracruz", "iso_3166_2": "MX-VER" },
  "geometry": { "type": "Polygon", "coordinates": [[[-94.04, 18.16], [-94.05, 18.17], ...

Ni un color ni una decisión visual. Solo dónde están las cosas. Si lo pintas tal cual, obtienes una mancha gris.

2. La proyección: de grados a píxeles

El GeoJSON trae grados. El SVG necesita unidades de lienzo. Convertir unos en otros es la proyección, y uso Mercator porque es la de casi todos los mapas web.

En Mercator la horizontal es la longitud tal cual. Solo la vertical se transforma:

const mercY = (lat) =>
  Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 180 / 2)) * 180 / Math.PI;

Con los extremos del país sacas la escala, y con eso ya puedes convertir cualquier coordenada al lienzo:

const escala = 1000 / (maxLon - minLon);   // lienzo de 1000 de ancho

const proyectar = ([lon, lat]) => [
  (lon - minLon) * escala,
  (maxY - mercY(lat)) * escala,
];

El script pasa cada coordenada por ahí, descarta las que quedan casi encima de la anterior para aligerar el trazo, y escupe rutas SVG del tipo M570.1 254.4L571.2 255.1...

Esas cuatro constantes —minLon, maxLon, maxY y escala— se guardan junto con los trazos. Por eso después el navegador puede colocar un punto nuevo con solo darle lat y lon.

3. El diseño es todo lo demás

Los rellenos, el contorno, el aura detrás del país, los puntos que laten, los arcos, la cámara, la tipografía. Nada de eso viene de los datos geográficos: es CSS y SVG escritos a mano.

El GeoJSON pone el calco. El diseño pone el mapa.

El truco del peso: compilar una vez

Esta es la razón de que cargue instantáneo. El script de generación corre en tu máquina, no en el navegador de quien visita la página.

39 MB de datos geográficos se convierten, una sola vez, en 139 KB de trazos listos para dibujar. Son 288 veces más chico, y ese trabajo no se repite en cada visita.

El GeoJSON original nunca se sube al repositorio ni lo descarga el navegador; se queda en la carpeta temporal de tu computadora. Al navegador solo le llega el resultado.

Es la diferencia de fondo contra una librería de mapas: ahí el navegador descarga la librería, pide los tiles y calcula la proyección en cada carga. Aquí eso ya pasó.

La cámara: por qué el zoom NO usa transform

Esta es la parte que más me costó y la que más te puede ahorrar tiempo si haces algo parecido.

Mi primera versión hacía lo obvio para acercarse a un estado: un transform: scale() de CSS sobre el grupo del SVG, con una transición. Funcionaba perfecto en mi máquina. En la de otra persona el mapa se cortaba a la mitad y aparecían bloques grises.

No hay error en consola. El navegador no falla, simplemente deja de pintar pedazos.

Lo que pasa es que al animar un transform, el navegador promueve ese elemento a una capa de GPU independiente. Al escalar 4.5 veces un SVG de ese tamaño, la capa termina midiendo unos 6000 por 4000 píxeles. Cuando esa textura no cabe, el resultado son huecos.

La solución fue animar el atributo viewBox del SVG cuadro por cuadro:

const paso = (ahora) => {
  const t = Math.min(1, (ahora - arranque) / 700);
  const e = 1 - Math.pow(1 - t, 3);           // suavizado
  encuadre = interpolar(desde, destino, e);
  svg.setAttribute('viewBox',
    encuadre.x + ' ' + encuadre.y + ' ' + encuadre.w + ' ' + encuadre.h);
  if (t < 1) requestAnimationFrame(paso);
};

No hay capa que promover ni textura gigante que quepa o no quepa: el navegador redibuja solo lo que entra en pantalla. Y como lo vuelve a dibujar en vez de estirar una imagen ya rasterizada, se ve nítido a cualquier acercamiento.

Tampoco filtros mientras algo se mueve

El mismo problema, más chico. Tenía un resplandor con feGaussianBlur y sombras con drop-shadow. Un filtro dentro de algo que se mueve se vuelve a rasterizar en cada cuadro, a una escala distinta cada vez, y eso se percibe como parpadeo.

Ahora el resplandor se apaga mientras la cámara viaja y el contorno resaltado usa un trazo grueso translúcido en lugar de una sombra. Se ve casi igual y no cuesta nada.

Los datos oficiales vienen sucios

Al probar el mismo código con Colombia me encontré con que Natural Earth le pone el mismo código CO-CUN a Bogotá y a Cundinamarca. Como yo identificaba cada entidad por su código, apuntar a Bogotá resaltaba Cundinamarca.

Es el peor tipo de error: silencioso. Nada truena, solo se ilumina el estado equivocado.

El script ahora detecta códigos repetidos, renombra el segundo y avisa en la terminal:

Códigos repetidos en la fuente, se renombraron 1:
  CO-CUN -> CO-CUN-CUNDIN  (Cundinamarca)

Si vas a trabajar con datos geográficos, cuenta con que traen sorpresas. No son una base de datos limpia: son décadas de gente distinta mapeando fronteras con criterios distintos.

Otro país

El generador no está casado con México. Le pasas el nombre del país en inglés, que es como lo maneja Natural Earth:

node scripts/build-map.mjs Colombia

Eso genera data/colombia.js con la variable window.MAPA_COLOMBIA. Cambias el <script src>, la opción datos y la lista de puntos, y ya tienes el mapa de otro país con el mismo código.

La primera vez el script descarga la fuente y la deja en caché, así que las siguientes son instantáneas.

Etiquetas: #svg #javascript #geojson #mapas #open source #desarrollo web

¿Te gustó este artículo? Compártelo

¿Necesitas ayuda con tu proyecto?

Nuestro equipo está listo para ayudarte a crear soluciones digitales a medida.

¿Buscas las mejores soluciones técnologicas para tu negocio?

¿Necesitas ayuda?