Técnica · cuaderno

El día que la cumbre estaba cuatro kilómetros mal.

Cinco fichas tenían la cumbre marcada al final del recorrido cuando físicamente está en mitad. Por qué el bug llegó a producción y cómo lo detectamos

Sant Jeroni de Montserrat es el punto más alto del macizo: 1.236 metros sobre el nivel del mar y mirador absoluto del valle del Llobregat. La ruta circular desde Sant Joan tiene unos 9 kilómetros, sube por la cordal este, pasa por la cumbre y desciende por la vertiente sur. Cumbre en el kilómetro 4,6. Final del circuito en el kilómetro 9,2. Hasta ahí, todo bien.

El problema llegó cuando una lectora escribió al formulario de avisos editoriales para preguntar por qué nuestro mapa interactivo marcaba la cumbre en el kilómetro 8,4. Que ella había seguido el orden de los puntos del mapa esperando coronar al final y se había encontrado con el coche en mitad de la subida.

Abrí la ficha. El campo mapaWaypoints tenía la cumbre marcada al final. Era un error en el dato, no en el cálculo. Y, lo más incómodo: lo había escrito yo mismo seis meses atrás cuando publicamos la ficha por primera vez.

Cómo se cuelan los datos manuales

El proyecto tiene un esquema de datos para cada ficha de ruta. Algunos campos son calculados — la longitud total y el desnivel positivo salen del GPX, sin opción a intervención humana. Otros son híbridos: los waypoints semánticos (cumbre, fuente, mirador, refugio, paso clave) se calculan del GPX si están etiquetados como POI, o se escriben a mano cuando el GPX no los lleva.

Sant Jeroni venía de una primera versión sin POI en el GPX. Tocaba escribirlos a mano. En el archivo de datos, el campo mapaWaypoints es un array de objetos:

{ km: 4.6, etiqueta: 'Cumbre Sant Jeroni (1.236 m)', tipo: 'cumbre' }

La intención era escribir km: 4.6. Lo que quedó en producción era km: 8.4. Por algún motivo el dato que copié pegué fue el del último mirador antes del descenso final, no el de la cumbre. No lo recuerdo: pasaron seis meses.

Por qué nadie lo detectó antes

Las primeras revisiones cruzaron el dato consigo mismo, no contra la realidad. El validador comprobaba que los kilómetros de los waypoints fueran monótonos crecientes en el caso lineal y coherentes con la distancia total. Como 8,4 km es menor que 9,2 km, pasaba el validador. Como hay una etiqueta llamada "cumbre" en algún punto del recorrido, pasaba el contador de POI esperables. Como el ratio cumbre/total era 91 % en lugar del 50 % real, no había ninguna alarma — la regla previa decía solo que la cumbre tenía que estar en algún sitio del recorrido.

El error solo aparecía si comparabas la coordenada del waypoint cumbre contra la coordenada real del pico. Cosa que ningún validador automatizado estaba haciendo. Y cosa que los lectores que pisan la ruta sí hacen al primer intento.

El mismo patrón en cinco fichas

Cuando lo descubrí, la pregunta inmediata fue cuántas más. Hice una pasada manual por todas las fichas con campo mapaWaypoints y waypoint marcado como cumbre:

  • Sant Jeroni de Montserrat. Cumbre escrita km 8,4, real km 4,6. Diferencia: 3,8 km.
  • Roque Nublo (Gran Canaria). Cumbre escrita al final, real cerca del principio del tramo final. Diferencia: ~1,2 km.
  • Ermites de Montserrat. Mismo patrón: cumbre al final del circuito en vez de en su punto culminante real. Diferencia: ~2 km.
  • Miranda Santa Magdalena (Montserrat). Igual. Diferencia: ~1,5 km.
  • Roca Corbatera (Montsant). Igual. Diferencia: ~1 km.

Cinco fichas. Todas circulares. Todas con la cumbre escrita en el último waypoint del circuito en lugar de en su punto real. Patrón inconfundible: probablemente las redacté seguidas en algún momento y copié-pegué la misma plantilla cambiando los nombres. La cumbre se quedó atascada en la posición del "último" waypoint plantilla.

El validador que faltaba

Escribí un script de auditoría con cuatro reglas pensadas específicamente contra este patrón. Una: el kilómetro del waypoint no puede exceder la distancia total de la ruta. Dos: en rutas lineales, los waypoints deben ser monótonos crecientes salvo que se declare ida-vuelta. Tres: en rutas circulares, la cumbre no puede estar en el último waypoint salvo declaración explícita de "cumbre final" (cumbre y descenso = mismo punto). Cuatro: el kilómetro declarado de la cumbre tiene que coincidir, con tolerancia, con el kilómetro real del punto más alto del perfil altimétrico.

El script forma ahora parte del npm run prebuild y falla el build si alguna ficha viola las reglas. Catorce líneas de código que cierran un agujero por el que ya se habían filtrado cinco errores en producción.

Lo que la incidencia me enseñó

No tanto sobre código. Sí sobre la diferencia entre validar formato y validar contenido. Casi todos los validadores que llevábamos en el proyecto eran del primer tipo: comprueban que el dato cumple el esquema, que el JSON parsea, que las cadenas no superan los 160 caracteres del meta description. Lo que faltaba era validar que el dato se corresponde con la realidad que dice describir. Que la cumbre que dices que está a 4,6 km está donde tu propio GPX dice que está el pico más alto del perfil. Que la coordenada que llamas "inicio" coincide con el primer punto del trazado oficial. Que el lugar geográfico que mencionas existe en OpenStreetMap.

Eso es más difícil de programar porque requiere cruzar dos fuentes — el dato manual y la fuente calculada — y aceptar una tolerancia. Pero es el tipo de validación que detecta los errores que un usuario que pisa la ruta detecta a la primera. Y un sitio de senderismo que no sea capaz de validar contra el propio terreno está construido sobre una base muy frágil.

El email de la lectora

Le contesté el mismo día. Le expliqué qué había pasado, cuáles eran las cinco fichas con el mismo bug, qué validador había añadido y cuándo se iba a aplicar el fix en producción. Subí los cambios esa noche.

Una semana después escribió de nuevo. Había vuelto a Sant Jeroni con la versión corregida y la nueva ruta le había salido bien. Cerró el correo dándonos las gracias por contestar.

Si la regla del proyecto es que el dato tiene que poder defenderse, esa lectora era la que estaba defendiendo el dato en nuestro lugar. Hay sitios donde un email así no llega nunca, no porque no haya errores, sino porque no hay manera abierta de avisar. El formulario funcional vale lo que cuesta diseñarlo. Esta vez fueron cuatro fichas más detectadas y un validador nuevo de regalo. Si llega otro mañana, ya sabemos qué hacer.

Cristian Corrales
· Tras detección bug Sant Jeroni en H48 y validador en H52