Rastrum

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.

Cliente (PWA) browser Astro 5 static EN/ES paridad Service Worker shell cache Dexie outbox IndexedDB IA on-device WebLLM · ONNX CDN · R2 media.rastrum.org Fotos / audio / video observations/<id>/... Modelos ONNX birdnet · efficientnet pmtiles MX ~48 MB offline map API Edge Functions · Deno identify api · mcp export-dwca share-card enrich-environment recompute-streaks award-badges get-upload-url · tokens Database Postgres · PostGIS · RLS observations media_files · taxa identifications activity_events users · badges follows · watchlists user_api_tokens rst_* · SHA-256 pg_cron rachas + insignias Externos terceros PlantNet cuota gratis 500/día Anthropic Claude BYO key OAuth Google · GitHub OpenMeteo sin key OpenFreeMap estilo base

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.

Foto / audio / video Blob registry.findFor media · taxa Motor cascade sort · costo licencia ↑ luego confianza ↓ accept ≥ 0.7 PlantNet free-quota · cloud BirdNET-Lite free-nc · on-device EfficientNet-Lite0 free · on-device Claude Haiku 4.5 byo-key · cloud Phi-3.5-vision free · respaldo on-device MegaDetector v5a free-nc · cámara trampa IDResult primero gana free free-nc · free-quota byo-key

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.

Captura foto · audio · video navegador Dexie outbox observations mediaBlobs · idQueue offline ok evento online syncOutbox() visibilitychange R2 upload PUT presignado get-upload-url identify Edge Function cascada server-side INSERT obs Postgres + RLS media_files sync_primary_id identifications activity_ events getCurrentPosition · exifr sobrevive offline online · visible

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.

Magic link OAuth (Google, GitHub) OTP por correo Passkey (WebAuthn) Modo invitado (sin auth) auth.users Supabase JWT trigger public.users profile · is_expert RLS owner-self user_api_tokens rst_<base32> SHA-256 hashed · scopes[] REST API /functions/v1/api/* shells, scripts MCP server /functions/v1/mcp agentes IA · JSON-RPC observaciones locales en Dexie hasta sign-in verifica hash · scopes

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.

Reportar un problema

Incluiremos esto en tu reporte