Files
collective-lists/plan/fase-1-auth-colectivo.md
Oier Bravo Urtasun f5903ef442 Add planning docs, design system, and Stitch mockups
- CLAUDE.md: project guidance (stack, domain model, auth, i18n, gotchas)
- DESIGN.md: full design system spec derived from Stitch mockups (slate
  monochrome, Inter, shadcn-svelte, screen patterns for all 5 views)
- README.md: development plan with Justfile commands
- analysis/: functional spec (domain model, use cases, business rules)
- plan/: phase-by-phase implementation plans (Fase 0–4)
- .stitch/designs/: generated mockups (lists, shopping session, notes,
  tasks, list detail with frequency chips)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 06:35:07 +02:00

94 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### Fase 1 — Autenticación y Colectivo
**Duración estimada: 23 semanas**
**Objetivo: la base del dominio. Sin esto no existe nada más.**
#### 1.1 Base de datos
- [ ] Migración: tabla `users` — espejo de `auth.users` de Supabase, poblada por trigger al primer login OIDC:
- `display_name`, `email`
- `language: enum(en | es)` — default `en`, sobrescrito por preferencia de navegador en primer login
- `avatar_type: enum(initials | emoji | upload)` — default `initials`
- `avatar_emoji: text | null`
- `avatar_url: text | null` — URL firmada de Supabase Storage
- [ ] Migración: tabla `collectives` con campos `id`, `name`, `emoji`, `created_by`, `created_at`
- [ ] Migración: tabla `collective_members` con `role: enum(admin | member | guest)`
- [ ] Migración: tabla `collective_invitations` con token, expiración y estado
- [ ] **RLS completo** — políticas críticas:
- Un usuario solo puede leer colectivos a los que pertenece
- Solo administradores pueden insertar en `collective_invitations`
- Solo administradores pueden actualizar roles en `collective_members`
- Un usuario solo puede actualizar su propia fila en `users`
- [ ] Trigger: `on_auth_user_created` — sincroniza el perfil de Keycloak (display_name, email) a `users` en cada login
- [ ] Trigger: promoción automática al miembro más antiguo si el último admin elimina su cuenta (RN-10)
- [ ] Función SQL: `is_active_member(collective_id, user_id)` — usada en todas las políticas RLS
- [ ] Bucket Supabase Storage: `avatars` — privado, acceso vía signed URLs, un fichero por usuario (`{user_id}/avatar`)
#### 1.2 Integración Keycloak → Supabase
El JWT que emite Keycloak debe ser validable por Supabase RLS. Hay dos estrategias; elegir una antes de implementar:
**Opción A — JWT de Keycloak directo en Supabase (recomendada para self-hosted):**
- Configurar `JWT_SECRET` de Supabase con la clave pública de Keycloak (RS256)
- Supabase valida el token de Keycloak directamente en RLS via `auth.uid()`
- Sin intermediarios, latencia mínima
- Requiere que el claim `sub` de Keycloak sea el UUID que identifica al usuario en Supabase
**Opción B — GoTrue como proxy OIDC:**
- Supabase Auth recibe el token de Keycloak, lo valida y emite su propio JWT
- Más aislamiento, pero un salto extra en cada request
> **Decisión recomendada: Opción A.** Más simple operacionalmente en self-hosted. El `sub` de Keycloak se convierte en el `auth.uid()` de Supabase.
- [ ] Configurar `supabase/config.toml` con Keycloak como OIDC externo
- [ ] Verificar que `auth.uid()` en las políticas RLS resuelve al `sub` de Keycloak
- [ ] Test de integración: token de Keycloak → query a Supabase con RLS activo → respuesta correcta
#### 1.3 Edge Functions
- [ ] `invite-member`: genera token único, inserta invitación, envía email vía Resend
- [ ] `accept-invitation`: valida token + expiración, une al usuario al colectivo, invalida el token
#### 1.4 Internacionalización (i18n)
Librería: **Paraglide JS** (`@inlang/paraglide-sveltekit`). Compile-time, tree-shaken por idioma, sin overhead en runtime.
- [ ] Instalar y configurar `@inlang/paraglide-sveltekit` con el plugin de Vite
- [ ] Crear ficheros de mensajes: `messages/en.json` y `messages/es.json`
- [ ] Configurar detección de idioma: `Accept-Language` header en primer load → guardado en `users.language` tras login
- [ ] Cambio de idioma en runtime: actualizar `users.language` en Supabase + llamar a `setLanguageTag()` de Paraglide — sin recarga de página
- [ ] Traducir todas las cadenas UI de la Fase 1 (onboarding, gestión de colectivo, settings)
> Las cadenas de UI van en `messages/`. Nunca hardcodear texto visible al usuario directamente en componentes.
#### 1.5 Frontend
- [ ] Integración de `keycloak-js` en SvelteKit:
- Inicialización en `+layout.ts` con `checkLoginIframe: false` (compatibilidad PWA/Safari)
- Store Svelte `auth` — expone `user`, `token`, `isAuthenticated`, `login()`, `logout()`
- `login()` redirige al login page de Keycloak; el callback vuelve a la app con el token
- Refresco silencioso del token antes de expiración (`updateToken(60)`)
- [ ] Rutas protegidas: hook `+layout.server.ts` que verifica sesión y redirige si no hay token válido
- [ ] Flujo de onboarding post-primer-login:
- Detectar si el usuario no tiene colectivo → redirigir a `/onboarding`
- Opción A: Crear nuevo colectivo (nombre + emoji)
- Opción B: Introducir link de invitación
- [ ] Pantalla `/collective/manage` — miembros, roles, botón invitar, expulsar
- [ ] Pantalla `/invitation/[token]` — aceptar invitación (el usuario debe estar autenticado en Keycloak)
- [ ] Selector de colectivo activo en la barra de navegación principal
- [ ] Store Svelte `collective` — colectivo activo, lista de miembros, rol del usuario actual
- [ ] Pantalla `/settings` — configuración de usuario:
- Campo display name (input de texto, guardado automático con debounce)
- Selector de avatar con tres modos:
- *Initials* — preview generado en tiempo real desde el display name
- *Emoji* — grid de ~40 emoji curados, tap para seleccionar
- *Upload* — input de fichero; preview antes de subir; subida a Supabase Storage bucket `avatars`; soporte para recortar en proporción 1:1
- Selector de idioma (English / Español) con efecto inmediato
- Enlace externo a Keycloak Account Console para cambio de email/contraseña
- [ ] Componente `Avatar.svelte` — renderiza según `avatar_type`: initials (letra sobre fondo slate), emoji, o `<img>` con fallback a initials si la URL falla
> **Nota sobre registro de nuevos usuarios:** Con Keycloak, el registro lo gestiona Keycloak (su propia pantalla o self-registration habilitado en el realm). No hay pantalla de registro en SvelteKit. Si un usuario llega desde un link de invitación sin cuenta, Keycloak gestiona el registro y luego redirige de vuelta al callback de invitación.
**Criterio de aceptación:** los 5 usuarios de prueba pueden autenticarse contra Keycloak, llegar a la app con sesión activa, y `auth.uid()` en Supabase resuelve correctamente para las políticas RLS.
---