Decisiones de diseño¶
Decisiones confirmadas de la reconciliación del plan original (LAMULA-WebViewer — Project Plan, que asumía FastAPI + PostgreSQL + GeoTIFF por FTP) con la realidad de ejecución: Cloudflare Pages con D1/R2 compartidos con nexrad-l3-pipeline. No re-litigar sin motivo.
Confirmadas¶
-
Nuxt 3 sobre Cloudflare Pages, un solo deploy. D1 solo se lee vía binding de Worker/Pages Functions (misma cuenta Cloudflare), así que el backend pasa a las server routes de Nuxt (Nitro preset
cloudflare-pages). Alternativas descartadas: SPA + Pages Functions sueltas (dos capas menos integradas), Worker Hono separado + SPA (dos deploys, CORS interno). -
SSR encendido, mapa client-only. OpenLayers no se renderiza en servidor; el SSR aporta shell, meta tags y resolución de deep links. En Nuxt el costo marginal es bajo (
<ClientOnly>alrededor del mapa). No es un requisito que justifique complejidad extra — si estorba, se degrada a SPA sin cambiar arquitectura. -
DAL con dos adaptadores. Live (server routes → binding D1 + URLs R2) y fixture (respuestas grabadas + COGs golden en el repo, switch por env). Es lo que permite CI determinista, desarrollo offline y — estratégicamente — apuntar el mismo viewer al contrato de LAMULA-Ingest (PostgreSQL + HTTP) en el futuro escribiendo solo un adaptador.
-
Renderizado desde datos, no desde píxeles. El COG trae niveles crudos uint8 +
value_scale/value_offset; la paleta se aplica en el cliente como color ramp WebGL. La paleta es fuente única para raster y leyenda; el valor bajo el cursor es posible; cambiar paleta no regenera nada. -
Paletas versionadas en este repo, no en la base. No existe (a propósito) tabla
paletteen D1. Módulo TS porproduct_codecon colores, stops, unidad y ticks. Coherente con "paleta en cliente": el pipeline no sabe de presentación. Igual para nombre display, categoría y rango de cada producto — catálogo estático del viewer keyed porcode; D1productssolo aporta code/mnemonic/unit/kind. -
Radar-agnóstico, proyección dinámica. Ningún radar ni endpoint hardcodeado.
radars.proj4se registra tal cual (proj4.defs+register); el catálogo, el switch de radares y la frescura ("minutos desde último scan") salen de la tablaradars. -
Base Web Mercator + OSM. Los radares del demo son Florida/Puerto Rico; la base Lambert-conformal de Cuba (EPSG:2085) del legado no aplica. Cuando el viewer se apunte a radares cubanos, la base vuelve como configuración, no como código.
-
Storm charts vía extensión del pipeline (acordada). El parser actual de NST solo extrae posición + vector movimiento. El tabular de NST trae POH/POSH/tamaño de granizo/VIL/dBZ máx/top por celda, y los packets 23/24 del symbology traen posiciones pasadas/pronóstico. El pipeline se extiende para volcar eso en
phenomena.attrs(JSON — cero migración D1). Los charts de tendencia se construyen cliente-side como serie temporal porcell_id(estable entre volúmenes) consultandophenomenacross-volumen. Las claves deattrsporkindse documentan como parte del contrato. -
VWP sin componente vertical. D1 trae dir/speed/rms/height por nivel; u/v se derivan en cliente,
wno existe en el contrato. Barbas y tabla completas; si el canvas legado usaba w, esa serie se omite. -
Oracle de regresión visual: goldens propios + QGIS. Los PNGs del legado (radares cubanos, productos retirados del feed) no sirven de oracle píxel-a-píxel para el demo. Verdad de referencia: screenshots golden propios (COG conocido + paleta → render esperado, Playwright) y validación cruzada en QGIS — el mismo mecanismo que la puerta F6 del pipeline. La fidelidad al legado se valida cualitativamente por el experto de dominio (misma semántica de paleta/leyenda), no por diff de píxeles.
-
Timeline asume ventana de retención de 72 h. El pipeline barre datos a los 3 días; el date picker y la UX del timeline lo hacen explícito en vez de mostrar días vacíos.
-
Mosaico en alcance. N capas WebGLTile (una por radar, cada una en su AEQD) reproyectadas en GPU sobre la vista común. Sin servidor de tiles: es la misma maquinaria de la vista single-radar, multiplicada.
-
Demo público, sin auth. Los datos NEXRAD son públicos; solo se expone el viewer. Si más adelante hace falta gatear el acceso, Cloudflare Access delante de Pages es cero código.
-
PrimeVue v4 (unstyled + Tailwind) como librería de componentes. La app es pesada en DataTable, DatePicker y Slider; batteries-included gana sobre shadcn-vue aquí. Tailwind sigue siendo la capa de estilos.
-
i18n:
@nuxtjs/i18n, es + en, locale en URL, catálogo extensible. -
Charts de tormenta y VWP: canvas/SVG propio portado del legado. La matemática validada (barbas, geometría az/range, trends) se porta de Svelte a composables Vue; no se introduce librería de charts.
-
Solo lectura, por disciplina. D1 no tiene roles: el contrato es que este proyecto solo ejecuta
SELECTy las migraciones viven endb/del pipeline. Cualquier cambio de schema es una migración negociada allí.
Qué murió del plan original (y por qué)¶
| Ítem del plan original | Destino | Motivo |
|---|---|---|
| Backend FastAPI + Python 3.12 + Uvicorn | Muerto | D1 se lee por binding de Worker; el backend es TypeScript en el edge (server routes de Nuxt) |
| PostgreSQL como store de metadata | Sustituido por D1 | Compartida con nexrad-l3-pipeline; schema migrable a PostgreSQL si esto pasa a producción con LAMULA-Ingest |
| Gap FTP ↔ HTTP range (§6, "decision to confirm") | Resuelto | R2 sirve COGs con CORS + range requests; la opción (a) del plan es la realidad desde el día uno |
| Fallback titiler / rio-tiler server-side | Innecesario | Sin el gap FTP, el fallback pierde su razón; los COG están bajo el cap de textura WebGL (peor caso 3680×3680) |
Tabla palette en la base |
Muerta | Paletas en el repo del viewer (decisión 5) |
product_def rico (nombre, categoría, rango, ref. paleta) |
Catálogo estático del viewer | D1 products es mínimo a propósito |
| Base cartográfica Cuba EPSG:2085 | Diferida a config | Radares demo en Florida/PR (decisión 7) |
| Oracle visual = PNGs legados | Sustituido | Radares y productos distintos; goldens propios + QGIS (decisión 10) |
| Auth de sesión ligera | Sin auth | Demo público (decisión 13) |
| Restos de Svelte en el texto ("SvelteKit server routes", "runes", "svelte-i18n") | Purgados | Inconsistencias de redacción del plan original; la decisión Vue 3 ya estaba tomada |
Riesgos residuales¶
- Fidelidad del render GeoTIFF (riesgo #1 del plan original, sigue vivo): el color-mapping WebGL debe reproducir la semántica de las paletas legadas sobre productos nuevos. Mitigación: es la primera rebanada vertical (F2), goldens desde el día uno, validación QGIS del experto de dominio.
- Co-evolución del contrato: el schema D1 lo posee el pipeline y aún evoluciona (extensión de
attrsacordada). Mitigación: contract tests versionados en este repo que fallan CI ante drift; claves deattrsdocumentadas en el contrato; cambios = migración negociada. - Rendimiento WebGL en hardware objetivo con animación multi-frame y mosaico multi-radar. Mitigación: prefetch medido en F3, presupuesto de frames en la puerta de F5; el fallback server-side queda documentado como stage 2, no construido.
- Límites del tier gratuito de Cloudflare (D1 lecturas/día, requests de Functions) bajo uso de demo público. Mitigación: caching de respuestas API (
Cache-Controlen server routes — las listas de datetimes y el catálogo cambian lento), medición en F6.