Empezar un proyecto
Caso de estudio / 05

Zeintzeonk Nagusi?

Un juego de rey de la colina sobre el mapa de Euskal Herria. Elige tu pueblo, paga la última puja más un euro y tu nombre y tu mensaje se quedan ahí hasta que alguien pague más. Next.js, Stripe, Postgres y Realtime, de la idea a producción en un día.

  • Producto propio
  • 2026
  • Web
  • Rol: todo
  • Estado: en producción
Zeintzeonk Nagusi?: el mapa de los 681 pueblos de Euskal Herria, coloreados por precio
01
Zeintzeonk Nagusi?: Azpeitia seleccionado en el mapa, con su jefe actual y su mensaje
02
Zeintzeonk Nagusi?: pujas en directo, los pueblos más caros, los mayores jefes y el avance por territorio
03
01 — ContextoProducto

"Zeintzeonk nagusi?" significa en euskera "¿quién manda aquí?". El juego se toma la pregunta al pie de la letra: Araba, Bizkaia, Gipuzkoa, Nafarroa e Iparralde, 681 pueblos, y cada uno es de quien ha pagado el último. No se gana nada más que el título y ochenta caracteres de mensaje, y esa es la gracia: orgullo de pueblo y un poco de pique entre vecinos.

Es el quinto producto de ZubiLabs y el primero para la web. También fue una prueba de velocidad: de un proyecto Next.js vacío a una web en producción con pagos reales, SEO e imágenes para compartir en una sola jornada.

02 — ProblemaProducto

Las reglas caben en una línea, pero el dinero las vuelve estrictas. Dos personas pueden pagar por el mismo pueblo en el mismo segundo y solo una puede ganar. La otra ha pagado por nada y tiene que recuperar el dinero sin escribir un email. Un pago que Stripe confirma dos veces no puede contar dos veces.

Además es un muro público. Nombres y mensajes aparecen en un mapa que cualquiera puede compartir, así que enlaces, insultos y caracteres invisibles hay que pararlos antes del pago, no después. Y todo tenía que estar en euskera, incluidos 681 nombres de pueblos que los datos oficiales dan casi siempre en castellano o francés.

03 — SoluciónProducto

Una página con el mapa arriba. Cada pueblo tiene el color de su precio, de libre a 500 € o más; al tocarlo se abre su ficha con el jefe actual, el mensaje, desde cuándo manda y la puja mínima. Debajo del mapa: un buscador de los 681 pueblos, las pujas en directo, los pueblos más caros, los jefes con más pueblos y el avance de cada territorio.

La puja pasa por Stripe Checkout. El servidor valida nombre, mensaje e importe y abre una sesión; el webhook aplica la puja cuando el pago se confirma, y todos los mapas abiertos se actualizan con Supabase Realtime. Cada pueblo tiene su página, su imagen para compartir y una imagen vertical de 1080×1920 para las historias de Instagram y WhatsApp, así que cada compra es también una invitación al pueblo de al lado.

04 — Rol

Todo. Las reglas del juego, los datos geográficos y su limpieza, el diseño y los textos en euskera, la app Next.js, el esquema de base de datos y la función de puja, Stripe con reembolsos automáticos, moderación, tiempo real, SEO con una página por pueblo, imágenes para compartir, páginas legales y despliegue.

05 — ArquitecturaIngeniería
MAPMapLibre · React 19ROUTE HANDLERSNext.js 16 · VercelPOSTGRESSupabase · RLSGEOJSON · 681 TOWNSSTRIPE CHECKOUTWEBHOOK · REFUNDSUPABASE REALTIMEOG · STORY IMAGESpuja_egin · ROW LOCK

Decisiones clave, y por qué:

  • La puja es una función de Postgres. puja_egin comprueba si la sesión de Stripe ya se procesó, bloquea la fila del pueblo con SELECT … FOR UPDATE, compara el importe con el precio actual y escribe el pueblo y la puja en la misma transacción. Dos pagos simultáneos los serializa la base de datos; al código de la aplicación no le queda ninguna carrera que gestionar.
  • El webhook es el único que escribe. El navegador nunca escribe una puja. La ruta de pago solo abre una sesión; la puja se aplica cuando Stripe confirma el pago, métodos diferidos incluidos. Si la función responde que alguien pagó más mientras tanto, el webhook reembolsa el pago en el acto.
  • Solo lectura desde el navegador. Row Level Security deja leer pueblos y pujas a cualquiera y escribirlos a nadie; las escrituras usan la clave secreta, solo en el servidor. La clave publicable de la página basta para el mapa y Realtime, y no sirve para nada más.
  • Geografía estática. Los límites municipales vienen del IGN/INE para Hegoalde y de geo.api.gouv.fr para Iparralde, simplificados con mapshaper en un GeoJSON servido como archivo estático, con los nombres en euskera de Wikidata. La base de datos solo guarda lo que cambia: precio, jefe y mensaje.
06 — Tecnología
WebNext.js 16 · App Router · React 19 · TypeScript · MapLibre GL
DatosSupabase Postgres · Row Level Security · Realtime · Presence
PagosStripe Checkout · Webhooks · Reembolsos automáticos
SEO681 páginas de pueblo · Sitemap · JSON-LD · Imágenes next/og
GeoIGN / INE · geo.api.gouv.fr · Wikidata · mapshaper
OpsVercel · Modo demo sin claves
07 — RetosIngeniería
  • Dos ganadores para un pueblo. Stripe confirma los pagos de forma asíncrona y puede entregar el mismo evento dos veces. La puja es idempotente por id de sesión, el bloqueo de fila decide quién llegó primero y al perdedor se le reembolsa automáticamente. La página de reglas lo dice con palabras sencillas, para que nadie se lleve una sorpresa.
  • Un muro en el que escribe cualquiera. A nombres y mensajes se les quitan los caracteres de control y bidireccionales, se limitan a 30 y 80 caracteres, se buscan enlaces y se comparan con una lista de palabras tras normalizar acentos y leetspeak. Todo ocurre antes de llamar a Stripe, así que nadie paga por un mensaje que se va a rechazar.
  • Mismo nombre, dos pueblos. Algunos pueblos comparten nombre entre territorios. Los slugs se construyen con el nombre en euskera y solo añaden el territorio si hay colisión, así cada pueblo tiene una URL corta, estable y legible para compartir y para el sitemap.
08 — Resultado

Una web en producción con el juego completo: el mapa de 681 pueblos, pujas con pagos reales y reembolsos automáticos, actualizaciones en tiempo real con un respaldo cada cinco segundos, una página e imágenes para compartir por pueblo, imágenes para historias, clasificaciones, contador de visitas, páginas legales y un modo demo que funciona sin ninguna clave.

Para ZubiLabs es la prueba de velocidad: un producto con pagos, una base de datos con garantías de concurrencia y SEO en serio, en producción el mismo día en que se empezó.

09 — Aprendizajes
  • La regla, donde están los datos. Una puja solo es válida frente al precio del momento en que se aplica. Comprobarlo en una función de base de datos con la fila bloqueada convirtió la parte más difícil del producto en unas pocas líneas de SQL.
  • Reembolsar en vez de impedir. Reservar un pueblo durante el pago habría supuesto tiempos de espera, pagos abandonados y pueblos bloqueados. Dejar pagar a los dos y reembolsar al perdedor es más simple para el código y justo para el jugador.
  • Cada compra se comparte. Una página, una imagen y una historia por pueblo convierten cada puja en un mensaje al pueblo de al lado. El bucle de crecimiento está en el producto, no en una campaña.