Divino
El mapa del vino español
Divino es un mapa interactivo para descubrir bodegas españolas. La idea nació de una frustración, en la cual en el mundo del vino siempre ves los mismos cuatro nombres y las mismas denominaciones, mientras cientos de bodegas pequeñas que merecen la pena se quedan invisibles. En Divino cada bodega ocupa el mismo espacio en el mapa, sin importar su tamaño, y quien descubre una aterriza directamente en su propia página. Es, por encima de todo, un proyecto sobre dar visibilidad a quienes normalmente no la tienen. Lo que empezó como un mapa con unas chinchetas acabó siendo un ejercicio de ingeniería de principio a fin, y lo que sigue es cómo está construido y por qué tomé cada decisión.
Arquitectura: Next.js 16 renderizado en el edge
La aplicación es Next.js 16 con React 19 (Server Components y renderizado
en servidor), pero no corre en un servidor Node tradicional siempre encendido. Se compila con el adaptador
OpenNext y se despliega sobre Cloudflare Workers, ejecutándose en el edge,
cerca del usuario. Elegí este modelo frente a un backend Node clásico por tres motivos: escala a cero (no
pago por una máquina ociosa), la latencia baja al servirse desde el nodo más cercano, y me obliga a un
código más disciplinado. En el runtime del edge no se tiene la
librería estándar de Node, solo APIs web estándar. Por eso, por ejemplo, la criptografía del proyecto usa
Web Crypto (crypto.subtle) en lugar de node:crypto. Trabajar con
esa restricción es lo más cerca que he estado de tocar los compromisos reales de la computación serverless
en vez de solo leer sobre ellos.
Datos: SQLite en el edge con Cloudflare D1
La base de datos de bodegas y denominaciones es Cloudflare D1, que es SQLite gestionado y
co-localizado con los Workers. Se descartó un Postgres gestionado tradicional a propósito, ya que para una carga
dominada por lecturas y un esquema modesto, un Postgres siempre encendido es sobredimensionar el problema y
añade otra pieza de infraestructura que mantener. D1 vive en la misma plataforma que el resto, no hay
servidor que administrar y las consultas van con sentencias preparadas y parametrizadas
(prepare().bind()), lo que además cierra la puerta a inyección SQL por diseño. Los datos del mundo real son un caos, como por ejemplo,
la misma región escrita de cinco formas,
etiquetas inconsistentes, duplicados..., así que construí un proceso de normalización e integridad
(limpieza, deduplicado y reconciliación contra una única fuente de verdad) antes de fiarme de nada en el
mapa. Tratar la calidad del dato es clave para la gestión.
El mapa: teselas vectoriales sobre WebGL
El mapa combina Leaflet (para la capa de interacción, marcadores y controles) con MapLibre GL como renderizador de teselas vectoriales sobre WebGL, sin rasterizar ningun imagen. Con vectores el mapa se mantiene nítido a cualquier zoom o densidad de píxeles, el reestilizado ocurre en el cliente sobre un único fichero de estilo, y el zoom y el paneo son fluidos porque el dibujado lo hace la GPU. Las teselas las sirve OpenFreeMap (datos de OpenStreetMap). Descarté los proveedores de mapas de pago tipo Mapbox o Stadia para no depender de una API key con límites y facturación por uso. En su lugar parto del estilo Positron de OpenFreeMap y lo reestilizo por completo en código para clavar el look Alidade Smooth que quería, paleta, límites administrativos, etiquetas en español, tipografía servida self-hosted...
Límites reales de las denominaciones
Sobre el mapa base dibujo las fronteras reales de cada denominación de origen como polígonos geoespaciales (GeoJSON), no como círculos aproximados. El nivel de detalle se adapta al zoom para que costas y fronteras se mantengan nítidas de cerca y se difuminen con elegancia al alejarse. La clave aquí está en encontrar el equilibrio entre calidad y coste de recursos. Hubo que simplificar los polígonos sin destrozar su forma y mantener las cargas pequeñas para no penalizar el arranque. Nunca antes toqué datos geoespaciales (proyecciones, simplificación de geometrías, equilibrio entre fidelidad y peso) resolviéndolos como problemas concretos.
Correo transaccional y entregabilidad
Divino también se comunica con las bodegas, lo que me metió en la madriguera de conseguir que el correo realmente llegue. Enviar un mensaje de forma programática es fácil, que pase los filtros de spam y llegue hasta la bandeja de entrada es en sí un pequeño problema de sistemas. El envío se hace con Resend, configurando la pila de autenticación completa sobre un dominio propio, SPF, DKIM y DMARC, y tratando de forma práctica cómo funciona la propagación de DNS y el caché negativo (el mismo registro puede ser visible en un resolver e invisible en otro durante un rato). En el lado de la app, el estado de entrega llega por webhooks firmados. Antes de fiarme de un evento verifico su firma HMAC-SHA256 con Web Crypto, rechazo timestamps fuera de tolerancia para evitar replays y comparo la firma en tiempo constante.
Front-end: hecho para todos, mobile-first
La interfaz está construida con React 19 y Tailwind CSS 4, con un enfoque responsive y mobile-first. El filtrado, las interacciones del mapa y el diseño tenían que sentirse nativos en un móvil, no adaptados a la fuerza, así que tocó planear también este diseño casi que desde 0.
Lo que me llevo
Divino es el proyecto donde se plasma toda la teoría de la carrera. Computación en el edge, bases de datos serverless, renderizado geoespacial sobre WebGL, modelado y limpieza de datos, DNS y protocolos de correo, criptografía aplicada, rendimiento en el front-end… Me llevo una forma de pensar sobre construir software de principio a fin.
Ahora, a seguir manteniendo y ampliando funcionalidades de la web. Añadiré una incrustación de la web aquí más adelante, cuando amplíe el desarrollo para uso en terceros. De mientras, se puede ver en mapa.wine.