Arquitectura
Cómo encajan las piezas — vista del sistema, cascada de identificación, sincronización offline-first y superficie de auth + tokens.
Rastrum es un sitio Astro estático con un service worker, un outbox Dexie y un backend Supabase de Postgres + Edge Functions. Todo lo dinámico vive en el navegador (cliente PWA) o en Edge Functions Deno — no hay servidor Node propio. Las cuatro vistas siguientes muestran cómo fluyen los datos entre esas capas.
01 Vista del sistema
Cinco capas. El cliente PWA es el centro: lee/escribe en la BD local Dexie y sirve pixeles mientras el service worker cachea el shell. La API y BD viven en Supabase; los binarios viven en R2; los servicios externos son terceros.
Flujos principales: el cliente carga modelos / fotos desde R2, llama a Edge Functions para identificar y exportar, y nunca habla directamente con servicios externos — la Edge Function intermedia para mantener las API keys del operador en el backend.
02 Cascada de identificación
Los plugins se ordenan por costo de licencia (Free → Free-NC → Free-cuota → BYO → Pago) y luego por tope de confianza descendente. El motor llama a cada uno en orden hasta que uno devuelve confianza ≥ 0.7. El mejor resultado siempre se guarda, incluso cuando ninguno cruza el umbral.
El motor (src/lib/identifiers/cascade.ts) usa la misma firma del lado del cliente y dentro de la Edge Function identify. Esa simetría es lo que mantiene la cascada offline coherente con la online.
03 Sincronización offline-first
Cada captura en el campo se escribe primero al outbox Dexie. Cuando vuelve la red, syncOutbox() sube los blobs a R2 vía URL presignada, inserta la fila de observación, y dispara la cascada de identificación en la Edge Function.
El outbox es la única ruta — incluso cuando estás online. Eso evita una bifurcación de código entre el camino feliz y el camino offline.
04 Auth + superficie de tokens
Cinco caminos de inicio de sesión convergen en auth.users de Supabase, un trigger pobla public.users, y desde ahí el usuario crea tokens rst_* personales. Los tokens se hashean con SHA-256 — la API REST y el servidor MCP verifican el hash al llamar.
Los tokens rst_* se muestran en texto plano una sola vez al crearlos; después de eso solo el hash SHA-256 vive en la BD. La REST API y el servidor MCP comparten el mismo verificador, lo que mantiene los scopes consistentes en ambas superficies.
05 Decisiones de stack
| Capa | Elección | Justificación |
|---|---|---|
| Frontend | Astro 5 (output: static) | Sitio estático + PWA shell, sin servidor Node, deploy a GitHub Pages. |
| BD local | Dexie (IndexedDB) | Outbox offline-first; observaciones se guardan localmente y sincronizan al reconectar. |
| Backend | Supabase (Postgres + PostGIS + RLS) | Edge Functions Deno, RLS por fila, particionado mensual, cron en pg_cron. |
| Almacenamiento | Cloudflare R2 | Egreso cero; observaciones, modelos ONNX y pmtiles en media.rastrum.org. |
| IA en cliente | WebLLM + onnxruntime-web | Phi-3.5-vision (~2.4 GB), Llama-3.2-1B, EfficientNet-Lite0, BirdNET-Lite, MegaDetector. |
| Mapas | MapLibre + pmtiles | OpenFreeMap online, archivo Mexico pmtiles offline desde R2 (~48 MB). |
06 Servicios externos
PlantNet API
ID de plantas (cuota gratis 500/día)
Anthropic Claude
Visión Haiku 4.5 (BYO key)
OAuth providers
Google + GitHub para auth Supabase
OpenMeteo
Backfill de clima (sin key)
OpenFreeMap
Mosaicos del estilo base
07 Compromisos clave
- Sitio estático, sin servidor Node. Hosting gratis en GitHub Pages; toda la lógica dinámica corre en navegador o en Edge Functions Deno.
- Cascada por costo de licencia, no por accuracy. Los plugins gratuitos van primero; con BYO key entras a APIs cloud sin cambiar UX.
- R2 sobre Supabase Storage para medios. Egreso cero en R2.
- Un outbox, un motor de cascada. La misma firma corre offline en el cliente y online en la Edge Function.
- Oscurecimiento de especies sensibles desnormalizado: RLS lee de location_obscured (mantenido por trigger).
- Sin build móvil nativo aún. La PWA instala a pantalla de inicio en iOS Safari y Android Chrome; Capacitor es plan v1.2.