Oier Bravo Urtasun 12b7b7443f docs(fase-5.14): document inline add-form UX change
Recorded in plan/fase-5-mobile-ux.md (new §5.14 with the six UX answers)
and CLAUDE.md "Fase 5 — what has been built" section. Also corrected the
"Fase 2a — what has been built" line that still claimed a sticky create
input for /lists, which was replaced by the masthead "New list" button
back in Fase 5.8.

Code change was committed in 3a11914.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 03:12:49 +02:00

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


Estado actual

Fase Estado
Fase 0 — Infraestructura Completa
Fase 1 — Auth y Colectivo Completa
Fase 2a — Lista de Compra CRUD Completa
Fase 2b — Realtime y Modo Compra Completa (presence bloqueado por bug upstream de supabase/realtime)
Fase 3 — Tareas y Notas Completa
Fase 4 — Búsqueda y Pulido 🟡 Búsqueda + RLS audit completos; PWA install/push/rate-limit/JWT rotation diferidos a sprint de deploy
Fase 5 — Rediseño UX Mobile Completa en scope automatizable (shell + masthead + big-button create + row redesign + selection mode + sticky detail chrome); presence avatars en cards + M-swipe/SEL-long-press touch E2E diferidos a WebKit install
Suite de tests (pgTAP + Vitest + Playwright) 233 tests verdes (34 pgTAP + 140 integración + 15 unit + 44 E2E), 2 skipped, ejecutables con just test-all

Metodología por fase (TDD)

Desde la Fase 1 en adelante, cada fase está estructurada así:

  1. X.0 Tests primero — se escriben los tests (Vitest + pgTAP + Playwright) que definen el éxito de la fase, antes de cualquier implementación.
  2. X.1 … X.N — implementación en el orden que mejor sirva a los tests.
  3. X.Z Verificación finaljust test-all debe devolver 0 failures. Sólo entonces la fase se considera cerrada.

Las fases ya cerradas (0, 1, 2a) reflejan este patrón retroactivamente: la sección X.0 enumera los tests que hoy cubren la fase, y X.Z confirma que just test-all pasa.

Índice de Fases

Fase Archivo Duración estimada
Fase 0 fase-0-infraestructura.md 12 semanas
Fase 1 fase-1-auth-colectivo.md 23 semanas
Fase 2a fase-2a-lista-compra-crud.md 2 semanas
Fase 2b fase-2b-realtime-modo-compra.md 23 semanas
Fase 3 fase-3-tareas-notas.md 12 semanas
Fase 4 fase-4-busqueda-pulido.md 1 semana
Fase 5 fase-5-mobile-ux.md 12 semanas

Total estimado: 1015 semanas (con Fase 5 mobile)

Los mockups de referencia viven en design/ (organizados por pantalla — ver design/slate_collective/DESIGN.md para la dirección "Monolith Editorial").


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 (PKCE, GoTrue auth)
│       │   │   ├── auth.ts          # login() / logout() via supabase.auth.signInWithOAuth
│       │   │   ├── stores/          # Svelte stores globales
│       │   │   ├── sync/            # lógica offline + cola de operaciones
│       │   │   └── components/      # componentes UI reutilizables
│       │   ├── routes/              # rutas SvelteKit
│       │   └── service-worker.ts    # SW personalizado (Workbox)
│       ├── tests/
│       │   ├── fixtures/
│       │   │   ├── users.ts         # constantes de usuarios semilla
│       │   │   └── global-setup.ts  # login por usuario vía Keycloak, cachea storageState
│       │   └── e2e/                 # tests Playwright (A, C, D series)
│       ├── 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.)
│   └── test-utils/                  # infraestructura de tests de integración
│       └── src/
│           ├── seed-constants.ts    # UUIDs fijos de usuarios y colectivo de prueba
│           ├── supabase-clients.ts  # createClientAs(userId) + createAdminClient()
│           └── db-helpers.ts        # sql() vía pg.Pool (bypassa RLS para setup/teardown)
│
├── supabase/
│   ├── migrations/                  # migraciones SQL versionadas
│   ├── seed.sql                     # datos de prueba para dev
│   ├── tests/                       # tests pgTAP (SQL puro, sin mocking)
│   ├── 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, con ${VAR} placeholders)
│   ├── kong-start.sh                # entrypoint Kong: sustituye ${VAR} vía perl antes de arrancar
│   ├── 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 setup            # preparación del entorno local (idempotente, seguro relanzar)
just dev              # levanta docker-compose.dev.yml + SvelteKit dev server
just serve            # build + servidor Node prod en :3000 contra el stack Docker dev
just stop             # para todos los servicios (Docker stack + servidor Vite)
just dev-stop         # para solo el stack de Docker
just dev-clean        # para Docker y elimina todos los volúmenes (reset completo)
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)
just test-all         # ejecuta todas las suites (pgTAP + Vitest + Playwright); requiere just dev activo
just test-integration # Vitest: tests de integración RLS (requiere just dev activo)
just test-db          # pgTAP: tests SQL directamente contra la BD dev
just test-e2e         # Playwright: tests E2E en modo headless (requiere just dev activo)
just test-e2e-headed  # Playwright: tests E2E con navegador visible (debugging)

Endpoints y credenciales en desarrollo local

Servicio URL Credenciales (solo dev)
App SvelteKit http://localhost:5173
Supabase Studio http://localhost:54323
Supabase API (Kong) http://localhost:8001 apikey: PUBLIC_SUPABASE_ANON_KEY del .env
Keycloak Admin http://localhost:8080/admin admin / admin
PostgreSQL localhost:5432 postgres / postgres

Secretos dev (generados en .env, nunca usar en producción):

Variable Valor dev
POSTGRES_PASSWORD postgres
SUPABASE_JWT_SECRET super-secret-jwt-token-with-at-least-32-characters-long
KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD admin / admin
KEYCLOAK_CLIENT_SECRET gotrue-dev-secret (secreto del cliente colectivo-web en GoTrue)

Usuarios de prueba (definidos en keycloak/realm-export.json y supabase/seed.sql):

Usuario Email Contraseña Rol en el colectivo de prueba
ana ana@dev.local test1234 Admin
borja borja@dev.local test1234 Member
carmen carmen@dev.local test1234 Member
david david@dev.local test1234 Guest
eva eva@dev.local test1234 (sin colectivo — para testing de onboarding)

Decisiones Previas al Inicio

Antes de arrancar la Fase 0, confirmar:

Decisión Estado
Estrategia JWT Keycloak → Supabase (Opción A vs B) Opción B — GoTrue como proxy OIDC
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 Self-registration habilitado en Keycloak
Invitaciones: email o link manual Link-only — admin genera link y lo comparte manualmente
Proveedor SMTP No necesario para invitaciones — Resend queda solo para Keycloak reset-password si se activa
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 (Opción B — GoTrue como proxy OIDC):

  • GoTrue recibe el token de Keycloak, lo valida contra el discovery endpoint de Keycloak, y emite su propio JWT de Supabase. auth.uid() resuelve al UUID de GoTrue (mapeado desde el sub de Keycloak).
  • El sub del realm de Keycloak es un UUID v4 estándar — verificado en las pruebas de aceptación.
  • No es necesario gestionar JWT_SECRET manualmente con la clave pública de Keycloak. GoTrue usa el endpoint OIDC de Keycloak para validar tokens.
  • El logout (supabase.auth.signOut()) invalida la sesión de GoTrue. La sesión SSO de Keycloak persiste hasta que expire — comportamiento esperado para esta app.

Integración GoTrue + Keycloak — requisitos no obvios (ya implementados):

  • Cliente colectivo-web confidencial: GoTrue necesita un secreto de cliente (KEYCLOAK_CLIENT_SECRET=gotrue-dev-secret) para el intercambio de código. Sin esto, GoTrue falla con "missing OAuth secret".
  • Scope openid personalizado en Keycloak: GoTrue v2.158.1 envía scope=profile email a Keycloak ignorando GOTRUE_EXTERNAL_KEYCLOAK_SCOPES. El endpoint userinfo de Keycloak requiere openid en el token. Solución: scope de cliente personalizado llamado openid con include.in.token.scope=true configurado como scope por defecto en colectivo-web.
  • GOTRUE_DISABLE_SIGNUP: "false": El flag bloquea también la creación de usuarios vía OIDC. Debe ser false; Keycloak es el guardián del registro.
  • auth.users pre-sembrado con UUIDs de Keycloak: seed.sql inserta auth.users (con instance_id='00000000-0000-0000-0000-000000000000' y campos de token como strings vacíos, no NULL) y auth.identities. Garantiza que auth.uid() coincida con las claves foráneas en tablas públicas.
  • Kong 2.8 no interpola variables de entorno: infra/kong-start.sh ejecuta perl para sustituir ${VAR} en kong.yml antes de arrancar Kong. Sin esto, Kong carga la cadena literal y rechaza todas las peticiones con 401.

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-configuration debe ser accesible públicamente para que Supabase pueda validar tokens. No poner Keycloak detrás de autenticación básica.
  • Habilitar SSL Required: external requests en 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:

  • La autenticación usa el flujo PKCE de Supabase OAuth (signInWithOAuth({ provider: 'keycloak' })). No hay keycloak-js ni iframes de refresco silencioso — compatible con Safari de base.
  • La sesión de GoTrue se persiste en localStorage y se refresca automáticamente; no requiere updateToken() manual.
  • 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.
  • Probar el Modo Compra en un iPhone real en paralelo al desarrollo, no solo en DevTools.

Supabase Realtime self-hosted:

  • Revisar MAX_REPLICATION_SLOTS en PostgreSQL y REALTIME_MAX_CONNECTIONS antes 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_conflicts para depuración.

Estimación Total

Fase Duración
Fase 0 — Infraestructura 12 semanas
Fase 1 — Auth y Colectivo 23 semanas
Fase 2a — Lista de Compra CRUD 2 semanas
Fase 2b — Realtime y Modo Compra 23 semanas
Fase 3 — Tareas y Notas 12 semanas
Fase 4 — Búsqueda y Pulido 1 semana
Total 913 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.

Description
No description provided
Readme 4.2 MiB
Languages
HTML 54.5%
TypeScript 20.5%
Svelte 13.7%
PLpgSQL 8.1%
Shell 2.3%
Other 0.9%