Ya hemos escrito antes sobre MeteoTrail y sobre el agregador de alertas meteorológicas del gobierno que lo alimenta — ambos surgieron del mismo instinto: los operadores de deportes acuáticos necesitan saber, con precisión y con antelación, cuándo las condiciones se les ponen en contra. Buceo, kayak, clases de surf — las cinco verticales que describimos en "Un motor de reservas, cinco deportes" — comparten todas una dependencia en la que casi nadie piensa: la altura real del mar en un momento dado. Algunos puntos de inmersión y embarcaderos de kayak del catálogo de Lueira solo son accesibles con seguridad dentro de una ventana de marea concreta. Así que este año construimos un servicio pequeño, deliberadamente estrecho, para responder bien a una sola pregunta: cuál será la marea astronómica, en este punto, en este momento.
1. Una pregunta estrecha y precisa
tides-service es un servicio interno en FastAPI. Predice mareas astronómicas usando el atlas global de marea oceánica FES2022b, a través de la librería de código abierto pyfes. Se consume de máquina a máquina, principalmente desde el backend de Lueira, y no está expuesto públicamente. Responde exactamente a tres preguntas:
GET /v1/tides— una serie temporal de alturas entre dos timestampsGET /v1/tides/extremes— los eventos de pleamar/bajamar de un día UTC dadoGET /v1/locations/check— si esta coordenada está en el agua o en tierra, y si está en tierra, cuál es la celda mojada más cercana dentro de un radio configurado
Las tres están protegidas con Bearer token. CORS está desactivado por defecto — la misma postura de fallo cerrado que el requisito de autenticación. Y hasta que el atlas de marea, de varios gigabytes, ha terminado de cargarse en memoria al arrancar, los dos endpoints de predicción devuelven 503 con una cabecera Retry-After y un cuerpo {"code": "model_loading"}. GET /healthz es el único endpoint abierto, y reporta si ese atlas ha terminado de cargarse.
Una respuesta simplificada de /v1/tides/extremes:
{
"location": {"lat": 43.283, "lon": -2.170},
"date": "2026-06-19",
"reference": "MSL",
"extremes": [
{"type": "low", "time": "2026-06-19T02:14:00Z", "height_m": 0.62},
{"type": "high", "time": "2026-06-19T08:31:00Z", "height_m": 3.41},
{"type": "low", "time": "2026-06-19T14:42:00Z", "height_m": 0.58},
{"type": "high", "time": "2026-06-19T20:59:00Z", "height_m": 3.55}
],
"attribution": "FES2022 was produced by LEGOS, NOVELTIS and CLS; funded by CNES; distributed by AVISO, DOI 10.24400/527896/A01-2024.004."
}
2. Lo que la predicción de marea NO te dice
tides-service predice solo la marea astronómica, relativa al Nivel Medio del Mar. Esa es la altura que la luna y el sol, mediante su atracción gravitatoria, producirían en un punto y momento dados si no estuviera pasando nada más. No incluye el temporal costero (storm surge), los efectos de la presión atmosférica, el empuje del viento (wind setup), ni ninguna otra contribución no astronómica al nivel del mar observado.
Un sistema de bajas presiones profundo puede elevar el agua notablemente por encima de lo que dice la marea astronómica. Un viento de componente marítimo sostenido amontona agua contra la costa por encima de eso. Nada de eso aparece en una respuesta de tides-service, por diseño. Una funcionalidad de seguridad para reservas de deportes acuáticos no puede simplemente tratar un número de tides-service como "el nivel del agua" — es un dato preciso más entre varios, junto con los datos de viento y presión.
3. Reducir un atlas de varios gigabytes a lo que realmente necesitas
El atlas global completo FES2022b pesa varios gigabytes, y tenemos licencia para usarlo pero no para redistribuirlo públicamente según los términos de AVISO, así que vive en un DigitalOcean Space privado. En lugar de que cada pod descargue el atlas global completo al arrancar, escribimos un script que recorta el atlas a una caja delimitadora alrededor de nuestra área de operación real — la península ibérica, el golfo de Vizcaya y las Islas Canarias. El arranque pasó de minutos a segundos.
La parte delicada es que el atlas almacena la longitud en la convención de 0–360°. Nuestra área de operación cruza el meridiano de Greenwich, así que la caja delimitadora se monta a horcajadas sobre la costura 0/360:
REGION_BBOX = {
"lat_min": 27.0, "lat_max": 46.5,
"lon_ranges_0_360": [(342.0, 360.0), (0.0, 4.5)],
}
def crop_region(atlas, bbox):
west, east = bbox["lon_ranges_0_360"]
west_slice = atlas.sel(lon=slice(*west))
east_slice = atlas.sel(lon=slice(*east))
west_slice = west_slice.assign_coords(lon=west_slice.lon - 360)
return xr.concat([west_slice, east_slice], dim="lon").sortby("lon")
Si te equivocas en esa costura no obtienes un error — obtienes una predicción de marea que está silenciosamente equivocada justo en el borde occidental de la región.
4. Comprobar nuestro trabajo contra una autoridad portuaria real
Junto con la suite de tests habitual, ejecutamos tests "golden" que comparan los extremos de marea predichos por tides-service en una ubicación real — Zarautz, en la costa vasca — contra las tablas de marea publicadas por Puertos del Estado. Es la diferencia entre "nuestro modelo está de acuerdo consigo mismo" y "nuestro modelo está de acuerdo con la realidad".
Nada de esto es un trabajo glamuroso. Pero una respuesta estrecha y precisa a una sola pregunta de física, honestamente delimitada, vale más para una funcionalidad de seguridad que un servicio amplio de "condiciones oceánicas" que difumina silenciosamente la marea astronómica con todo lo demás que afecta al nivel del mar.