- Turborepo + pnpm workspaces with apps/web and packages/types - SvelteKit app: Tailwind, Paraglide i18n (en/es), keycloak-js, Supabase client, auth/collective stores, PWA service worker skeleton, all route placeholders - packages/types: domain types (User, Collective, ShoppingList, etc.) and database.ts placeholder for generated types - Justfile with dev, db-*, kc-*, build, backup, restore, deploy recipes - infra/docker-compose.dev.yml: Postgres 15, GoTrue, PostgREST, Realtime v2.83, Storage, Kong (port 8001), Studio, Keycloak 24 - infra/docker-compose.prod.yml, kong.yml, db-init scripts, backup/deploy scripts - keycloak/realm-export.json with colectivo realm and 5 dev test users - supabase/config.toml and seed.sql with sample collective and items - GitHub Actions: ci.yml (lint+typecheck+build) and deploy.yml (GHCR + SSH) - .env.example documenting all required variables - Fixed docker-compose issues: Studio image tag, Kong port conflict (8001), internal role passwords init script, Realtime METRICS_JWT_SECRET/APP_NAME Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
10 KiB
10 KiB
Plan de Desarrollo — App Gestión del Colectivo
Stack: SvelteKit + Supabase self-hosted + PWA
Infraestructura: Docker Compose (dev local + producción VPS)
Última actualización: Abril 2026
Índice de Fases
| Fase | Archivo | Duración estimada |
|---|---|---|
| Fase 0 | fase-0-infraestructura.md | 1–2 semanas |
| Fase 1 | fase-1-auth-colectivo.md | 2–3 semanas |
| Fase 2a | fase-2a-lista-compra-crud.md | 2 semanas |
| Fase 2b | fase-2b-realtime-modo-compra.md | 2–3 semanas |
| Fase 3 | fase-3-tareas-notas.md | 1–2 semanas |
| Fase 4 | fase-4-busqueda-pulido.md | 1 semana |
Total estimado: 9–13 semanas
Stack Tecnológico Confirmado
| Capa | Tecnología | Justificación |
|---|---|---|
| Frontend / PWA | SvelteKit 2 | Reactividad nativa, bundle pequeño, PWA de primera clase |
| Backend / BaaS | Supabase self-hosted | Realtime, RLS, Storage — Auth delegada a Keycloak |
| Identidad / Auth | Keycloak self-hosted | IdP OIDC/OAuth2, gestión de usuarios, federación futura |
| Base de datos | PostgreSQL 15 (vía Supabase) | Full-text search nativo, pg_cron para jobs |
| Tiempo real | Supabase Realtime (WebSocket) | Presencia + cambios en < 1s sin infraestructura extra |
| Offline | IndexedDB vía idb |
PWA-compatible, sin dependencias nativas |
| PWA | @vite-pwa/sveltekit + Workbox |
Service Worker, caché offline, instalable |
| Infraestructura dev | Docker Compose + Supabase CLI | Entorno local con Studio + Keycloak + usuarios de prueba |
| Infraestructura prod | Docker Compose + nginx (existente) | Reverse proxy ya operativo en el VPS, gestionado externamente |
| CI/CD | GitHub Actions | Lint, test, deploy vía SSH |
| Email transaccional | Resend | SMTP para invitaciones (Keycloak también lo usa para reset) |
| Backups | pg_dump → Backblaze B2 (S3) |
Retención 30 días, coste mínimo |
Estructura del Repositorio
colectivo/
├── apps/
│ └── web/ # SvelteKit — web + PWA
│ ├── src/
│ │ ├── lib/
│ │ │ ├── supabase.ts # cliente Supabase singleton
│ │ │ ├── auth.ts # cliente OIDC (keycloak-js) + helpers de sesión
│ │ │ ├── stores/ # Svelte stores globales
│ │ │ ├── sync/ # lógica offline + cola de operaciones
│ │ │ └── components/ # componentes UI reutilizables
│ │ ├── routes/ # rutas SvelteKit
│ │ └── service-worker.ts # SW personalizado (Workbox)
│ ├── static/
│ │ ├── manifest.webmanifest
│ │ └── icons/ # iconos PWA (512, 192, 180...)
│ └── vite.config.ts
│
├── packages/
│ └── types/ # tipos TypeScript compartidos
│ └── src/
│ ├── database.ts # tipos generados desde schema Supabase
│ └── domain.ts # tipos de dominio (Colectivo, Item, etc.)
│
├── supabase/
│ ├── migrations/ # migraciones SQL versionadas
│ ├── seed.sql # datos de prueba para dev
│ ├── functions/ # Edge Functions (invitaciones, etc.)
│ └── config.toml # configuración Supabase CLI + OIDC Keycloak
│
├── keycloak/
│ ├── realm-export.json # realm "colectivo" exportado — importado al arrancar dev
│ └── themes/ # tema personalizado (opcional, v2)
│
├── infra/
│ ├── docker-compose.dev.yml # Supabase + Keycloak dev (Kong en puerto 8001)
│ ├── docker-compose.prod.yml # producción en VPS
│ ├── kong.yml # Kong declarativo (rutas API)
│ ├── db-init/
│ │ └── 00-role-passwords.sh # ajusta passwords de roles internos de Supabase al arrancar
│ └── scripts/
│ ├── backup.sh # backup manual de una BD concreta
│ ├── backup-cron.sh # ejecutado por cron: decide tipo (diario/semanal/mensual) y purga
│ ├── restore.sh # restaura un dump concreto en una BD
│ └── deploy.sh # SSH + docker compose pull + up
│
├── .github/
│ └── workflows/
│ ├── ci.yml # lint + test en PR
│ └── deploy.yml # deploy en push a main
│
├── .env.example # todas las variables documentadas con valores de ejemplo
├── Justfile # comandos de desarrollo frecuentes
├── turbo.json # Turborepo pipeline
└── package.json # workspaces
Justfile — Comandos de referencia
just dev # levanta docker-compose.dev.yml + SvelteKit dev server
just dev-stop # para el entorno local
just db-reset # drop + migrate + seed (dev)
just db-migrate # aplica migraciones pendientes (dev)
just db-seed # aplica supabase/seed.sql a la BD dev en marcha
just db-types # genera tipos TS desde schema Supabase → packages/types
just kc-export # exporta realm de Keycloak → keycloak/realm-export.json
just kc-open # abre Keycloak Admin Console en el navegador (localhost:8080)
just deploy # ejecuta infra/scripts/deploy.sh (prod)
just backup supabase # backup manual inmediato de supabase
just backup keycloak # backup manual inmediato de keycloak
just restore supabase <file> # restaura un dump concreto en supabase
just restore keycloak <file> # restaura un dump concreto en keycloak
just logs # docker compose logs -f (prod)
Endpoints en desarrollo local
| Servicio | URL | Notas |
|---|---|---|
| App SvelteKit | http://localhost:5173 | |
| Supabase Studio | http://localhost:54323 | |
| Supabase API (Kong) | http://localhost:8001 | Puerto 8000 puede estar ocupado por otros servicios |
| Keycloak Admin | http://localhost:8080/admin | admin / admin (solo dev) |
| PostgreSQL | localhost:5432 | postgres / postgres (solo dev) |
Decisiones Previas al Inicio
Antes de arrancar la Fase 0, confirmar:
| Decisión | Estado |
|---|---|
| Estrategia JWT Keycloak → Supabase (Opción A vs B) | Pendiente — Opción A recomendada |
| Base de datos de Keycloak en prod | ✅ PostgreSQL independiente |
| Entorno de producción dockerizado | ✅ Ya operativo |
| Reverse proxy en producción | ✅ nginx existente, gestionado externamente |
| Self-registration habilitado en Keycloak o invitación obligatoria | Pendiente |
| Proveedor SMTP para Keycloak y Resend (emails de invitación) | Pendiente — Resend recomendado |
| Almacenamiento de backups | Pendiente — Backblaze B2 recomendado |
| Dominio y DNS del VPS | Pendiente |
| Subdominio para Keycloak | Pendiente — auth.tudominio.com recomendado |
| Registry de imágenes Docker | Pendiente — GitHub Container Registry recomendado |
| OAuth Google (federated en Keycloak) | Pendiente — configurar en Keycloak, no en Supabase |
| OAuth Apple (federated en Keycloak) | Pendiente — requiere Apple Developer Program ($99/año) |
Advertencias Técnicas
Keycloak y JWT con Supabase RLS:
- Supabase RLS usa
auth.uid()que lee el claimsubdel JWT. Verificar que elsubde Keycloak es un UUID v4 estándar — por defecto lo es, pero algunos realms lo configuran diferente. - En la Opción A (JWT directo), la clave pública de Keycloak (RS256) debe estar en
JWT_SECRETde Supabase. Si rotas las claves de firma en Keycloak, debes actualizar Supabase también. - Keycloak tiene sesiones propias independientes de Supabase. Un
logout()debe invalidar en ambos lados: llamar akeycloak.logout()(que invalida en Keycloak) y limpiar el cliente de Supabase localmente.
Keycloak self-hosted en producción:
- Keycloak 24+ requiere mínimo 512MB de RAM asignados. En un VPS pequeño (2GB), monitorizar el consumo conjunto con Supabase.
- El endpoint
/realms/{realm}/.well-known/openid-configurationdebe ser accesible públicamente para que Supabase pueda validar tokens. No poner Keycloak detrás de autenticación básica. - Habilitar
SSL Required: external requestsen el realm de producción. Nunca desactivarlo.
OAuth federado (Google, Apple):
- Con Keycloak, Google y Apple se configuran como Identity Providers en Keycloak, no en Supabase. El flujo es: usuario → Keycloak → Google/Apple → Keycloak → app. Supabase solo ve el token de Keycloak.
- Esto simplifica la configuración de Supabase pero requiere configurar los Identity Providers en Keycloak Admin Console.
iOS Safari y PWA:
- Background Sync API no está soportada en Safari. Implementar fallback manual con el evento
online. - Push notifications solo disponibles desde iOS 16.4 y únicamente con la PWA instalada en pantalla de inicio.
keycloak-jsusa iframes para el refresco silencioso del token (checkLoginIframe). Deshabilitarlo en la config (checkLoginIframe: false) porque Safari bloquea cookies de terceros en iframes — usarupdateToken()manual en su lugar.- Probar el Modo Compra en un iPhone real en paralelo al desarrollo, no solo en DevTools.
Supabase Realtime self-hosted:
- Revisar
MAX_REPLICATION_SLOTSen PostgreSQL yREALTIME_MAX_CONNECTIONSantes de producción. - Las suscripciones de Presence consumen más recursos que Postgres Changes. Limitar a listas con sesión activa.
Conflictos de sincronización:
- Last-write-wins es suficiente para este dominio. No implementar CRDT en el MVP.
- Registrar conflictos detectados en tabla
sync_conflictspara depuración.
Estimación Total
| Fase | Duración |
|---|---|
| Fase 0 — Infraestructura | 1–2 semanas |
| Fase 1 — Auth y Colectivo | 2–3 semanas |
| Fase 2a — Lista de Compra CRUD | 2 semanas |
| Fase 2b — Realtime y Modo Compra | 2–3 semanas |
| Fase 3 — Tareas y Notas | 1–2 semanas |
| Fase 4 — Búsqueda y Pulido | 1 semana |
| Total | 9–13 semanas |
Plan generado como base para Claude Code. Stack: SvelteKit + Supabase self-hosted + Keycloak + PWA. Cada fase puede usarse como contexto independiente para sesiones de trabajo.