feat(sprint-4/etapa-1): Supabase auth + Google OAuth + LoginPage + rutas protegidas
- Cliente Supabase singleton en src/lib/supabase.ts - Interfaces AuthUser / AuthService en src/auth/types.ts - SupabaseAuthService: signInWithGoogle, signOut, getCurrentUser, onAuthStateChange con upsert a tabla users (platform_admin para @inquality.com.py, guest resto) - AuthProvider (React Context) wrappea toda la app; expone user, loading, signIn, signOut - LoginPage /login: logo InQuality color, título, fade-in, botón Google naranja #F59845 - RootComponent del router guarda todas las rutas — sin sesión → /login, con sesión + /login → / - Script SQL: supabase/migrations/001_create_users.sql (tabla users + RLS) - AppHeader: <Link> → <a href="/"> para evitar dependencia de RouterContext en tests - 561/561 tests verdes, build sin errores TS Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
240
sprints/sprint-4/ETAPA_1_PROMPT.md
Normal file
240
sprints/sprint-4/ETAPA_1_PROMPT.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# Sprint 4 — Etapa 1: Supabase setup + AuthService + Login page + Google OAuth
|
||||
|
||||
## Contexto
|
||||
|
||||
InQ ROI es una herramienta web de análisis de procesos BPMN y cálculo de ROI de automatización.
|
||||
Está construida con React 18 + Vite + TypeScript + TanStack Router + Zustand + shadcn/ui + Tailwind.
|
||||
La persistencia actual es IndexedDB via Dexie — este sprint la reemplaza por Supabase PostgreSQL.
|
||||
|
||||
Esta etapa no toca la persistencia de datos de negocio (procesos, actividades, etc.).
|
||||
Su único objetivo es: **la app tiene login con Google, las rutas están protegidas, y existe la abstracción `AuthService`**.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisito — verificar antes de empezar
|
||||
|
||||
Las siguientes variables de entorno deben estar disponibles en `.env.local`:
|
||||
|
||||
```
|
||||
VITE_SUPABASE_URL=https://<project-ref>.supabase.co
|
||||
VITE_SUPABASE_ANON_KEY=<anon-key>
|
||||
```
|
||||
|
||||
Si no existen, **parar y reportar**. No avanzar sin estas credenciales.
|
||||
|
||||
---
|
||||
|
||||
## Alcance estricto — qué entra en esta etapa
|
||||
|
||||
1. **Instalar `@supabase/supabase-js`** y crear el cliente singleton en `src/lib/supabase.ts`
|
||||
2. **Definir la interfaz `AuthService`** y el tipo `AuthUser` en `src/auth/types.ts`
|
||||
3. **Implementar `SupabaseAuthService`** en `src/auth/SupabaseAuthService.ts`
|
||||
4. **Crear la tabla `users`** en Supabase con script SQL documentado
|
||||
5. **Crear `LoginPage`** (`/login`) con botón "Iniciar sesión con Google"
|
||||
6. **Proteger rutas**: cualquier ruta excepto `/login` redirige a `/login` si no hay sesión
|
||||
7. **Documentar `.env.example`** con las variables requeridas
|
||||
|
||||
## Qué NO entra en esta etapa
|
||||
|
||||
- Migración de repositorios Dexie → Supabase (Etapa 2)
|
||||
- Migración de datos de negocio (Etapa 2-4)
|
||||
- Avatar/nombre en AppHeader (Etapa 6)
|
||||
- UI de "Actualizado por" (Etapa 6)
|
||||
- Tablas de `user_groups`, `group_memberships`, `process_access` (Etapa 2)
|
||||
- Lógica de `platform_role` en la UI (la tabla `users` se crea, pero la UI no lo usa aún)
|
||||
- Cualquier cambio en el motor de simulación, ROI, PDF o tests existentes
|
||||
|
||||
---
|
||||
|
||||
## Interfaces — contratos obligatorios
|
||||
|
||||
### `src/auth/types.ts`
|
||||
|
||||
```typescript
|
||||
export interface AuthUser {
|
||||
id: string
|
||||
email: string
|
||||
name: string
|
||||
avatarUrl?: string
|
||||
platformRole: 'platform_admin' | 'member' | 'guest'
|
||||
}
|
||||
|
||||
export interface AuthService {
|
||||
signInWithGoogle(): Promise<void>
|
||||
signOut(): Promise<void>
|
||||
getCurrentUser(): Promise<AuthUser | null>
|
||||
onAuthStateChange(callback: (user: AuthUser | null) => void): () => void
|
||||
}
|
||||
```
|
||||
|
||||
`platformRole` se lee de la tabla `users` de la DB de la app, no de los metadatos de Supabase Auth.
|
||||
|
||||
### `src/auth/SupabaseAuthService.ts`
|
||||
|
||||
Implementa `AuthService`. Es la **única clase** que importa `@supabase/supabase-js` o `src/lib/supabase.ts`.
|
||||
|
||||
Comportamiento de `signInWithGoogle()`:
|
||||
- Llama a `supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: window.location.origin } })`
|
||||
- El callback de redirect completa el flujo
|
||||
|
||||
Comportamiento de `onAuthStateChange()`:
|
||||
- Cuando el usuario se autentica por primera vez, hace upsert en la tabla `users` de la DB:
|
||||
- Si el email es `@inquality.com.py` → `platform_role = 'platform_admin'`
|
||||
- Cualquier otro dominio → `platform_role = 'guest'`
|
||||
- Retorna la función de cleanup para desuscribirse
|
||||
|
||||
### `src/lib/supabase.ts`
|
||||
|
||||
```typescript
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
|
||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
|
||||
|
||||
if (!supabaseUrl || !supabaseAnonKey) {
|
||||
throw new Error('Variables de entorno VITE_SUPABASE_URL y VITE_SUPABASE_ANON_KEY son requeridas')
|
||||
}
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schema SQL — tabla `users`
|
||||
|
||||
Crear este script en `supabase/migrations/001_create_users.sql`:
|
||||
|
||||
```sql
|
||||
-- Tabla users: extiende auth.users con atributos de la plataforma
|
||||
CREATE TABLE IF NOT EXISTS public.users (
|
||||
id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
email text NOT NULL,
|
||||
name text,
|
||||
avatar_url text,
|
||||
platform_role text NOT NULL DEFAULT 'guest'
|
||||
CHECK (platform_role IN ('platform_admin', 'member', 'guest')),
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- RLS
|
||||
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Cualquier usuario autenticado puede leer (para mostrar nombres)
|
||||
CREATE POLICY "read_users" ON public.users
|
||||
FOR SELECT USING (auth.uid() IS NOT NULL);
|
||||
|
||||
-- Solo el propio usuario puede actualizar su perfil
|
||||
CREATE POLICY "update_own_user" ON public.users
|
||||
FOR UPDATE USING (id = auth.uid());
|
||||
|
||||
-- El upsert inicial lo hace el trigger (SECURITY DEFINER)
|
||||
CREATE POLICY "insert_users" ON public.users
|
||||
FOR INSERT WITH CHECK (id = auth.uid());
|
||||
```
|
||||
|
||||
Documentar en `README.md` o en el archivo de la migración que este script debe correrse en el dashboard de Supabase SQL Editor o via CLI.
|
||||
|
||||
---
|
||||
|
||||
## Rutas protegidas — comportamiento requerido
|
||||
|
||||
El router de TanStack Router debe:
|
||||
- Detectar si hay sesión activa al cargar la app (llamando a `authService.getCurrentUser()`)
|
||||
- Si no hay sesión → redirigir a `/login`
|
||||
- Si hay sesión y el usuario va a `/login` → redirigir a `/` (workspace)
|
||||
- Durante la carga inicial (verificando sesión) → mostrar un spinner o pantalla en blanco, no el contenido
|
||||
|
||||
Implementar un `AuthProvider` (React Context) que:
|
||||
- Expone `user: AuthUser | null` y `loading: boolean`
|
||||
- Llama a `authService.onAuthStateChange()` al montar
|
||||
- Es el único lugar que llama a `AuthService` — los componentes consumen el contexto
|
||||
|
||||
No pasar `authService` como prop a ningún componente. Usar el contexto.
|
||||
|
||||
---
|
||||
|
||||
## LoginPage — especificación de UI
|
||||
|
||||
Ruta: `/login`
|
||||
|
||||
Layout:
|
||||
- Pantalla completa, centrada vertical y horizontalmente
|
||||
- Fondo: `#0F0F0F` (negro InQ) o blanco — usar blanco para este sprint, es más neutro
|
||||
- Logo de InQuality centrado (usar `assets/MARCA FINAL 2023 AREA DE SEGURIDAD 1 COLOR.png`)
|
||||
- Título: "InQ ROI" en tipografía grande, debajo del logo
|
||||
- Subtítulo: "Análisis de procesos y ROI de automatización"
|
||||
- Botón primario: "Iniciar sesión con Google" — usar ícono de Google (SVG inline o Lucide)
|
||||
- Color del botón: naranja InQ `#F59845` o botón con borde (sin color relleno) — elegir el que quede más limpio
|
||||
- Debajo del botón: texto pequeño en gris — "Solo para usuarios con cuenta @inquality.com.py"
|
||||
|
||||
**Versión wow**: agregar animación de entrada suave (fade-in) del contenido al cargar la página.
|
||||
|
||||
No mostrar campos de email/password ni opción de magic link — eso es Sprint futuro.
|
||||
|
||||
---
|
||||
|
||||
## .env.example
|
||||
|
||||
Crear o actualizar `.env.example` en la raíz del proyecto:
|
||||
|
||||
```
|
||||
# Supabase — InQ ROI
|
||||
# Obtener desde Supabase Dashboard → Project Settings → API
|
||||
VITE_SUPABASE_URL=https://<project-ref>.supabase.co
|
||||
VITE_SUPABASE_ANON_KEY=<anon-public-key>
|
||||
```
|
||||
|
||||
`.env.local` debe estar en `.gitignore` (verificar que ya está).
|
||||
|
||||
---
|
||||
|
||||
## Criterios de validación — esta etapa está completa cuando
|
||||
|
||||
- [ ] `npm run build` pasa sin errores TypeScript
|
||||
- [ ] `npm run test` — los 561 tests existentes siguen verdes (no se rompe nada)
|
||||
- [ ] Navegar a `http://localhost:5173` sin sesión → redirige a `/login`
|
||||
- [ ] La página `/login` muestra logo InQuality + botón de Google
|
||||
- [ ] Al hacer click en "Iniciar sesión con Google" → redirige a Google OAuth
|
||||
- [ ] Después de autenticarse con cuenta `@inquality.com.py` → redirige al workspace
|
||||
- [ ] En la tabla `users` de Supabase aparece el registro del usuario con `platform_role = 'platform_admin'`
|
||||
- [ ] No hay importaciones de `@supabase/supabase-js` fuera de `src/lib/supabase.ts` y `src/auth/SupabaseAuthService.ts`
|
||||
- [ ] Validación visual del director: captura de pantalla de LoginPage + workspace post-login
|
||||
|
||||
---
|
||||
|
||||
## Reporte esperado al completar
|
||||
|
||||
Claude Code debe reportar:
|
||||
1. Lista de archivos creados/modificados
|
||||
2. Resultado de `npm run test` (número de tests, pass/fail)
|
||||
3. Resultado de `npm run build` (sin errores)
|
||||
4. Captura de pantalla o descripción de la LoginPage
|
||||
5. Confirmación de que el upsert en la tabla `users` funciona (puede ser manual: "corrí el flujo y aparece el registro")
|
||||
6. Cualquier decisión de iniciativa tomada fuera del scope especificado (clasificar por nivel 1/2/3)
|
||||
|
||||
---
|
||||
|
||||
## Referencia de archivos existentes
|
||||
|
||||
| Archivo | Relevancia |
|
||||
| --- | --- |
|
||||
| `src/router.tsx` | Router actual de TanStack — agregar protección de rutas aquí |
|
||||
| `src/App.tsx` | Entry point de la app — agregar `AuthProvider` aquí |
|
||||
| `src/domain/types.ts` | Tipos de dominio actuales (NO tocar en esta etapa) |
|
||||
| `src/store/` | Stores Zustand (NO tocar en esta etapa) |
|
||||
| `src/persistence/` | Código Dexie (NO tocar en esta etapa — se migra en Etapas 2-4) |
|
||||
| `assets/` | Logos PNG para la LoginPage |
|
||||
| `.gitignore` | Verificar que `.env.local` está ignorado |
|
||||
|
||||
---
|
||||
|
||||
## Nota de arquitectura — convivencia Dexie + Supabase
|
||||
|
||||
En esta etapa Dexie **sigue funcionando**. La app autentica con Supabase pero persiste datos con Dexie. Esto es intencional — la migración de datos es trabajo de Etapas 2-4. No tocar los stores ni los repositorios Dexie en esta etapa.
|
||||
|
||||
Al terminar esta etapa, la app tiene:
|
||||
- Auth funcional con Google
|
||||
- Rutas protegidas
|
||||
- Datos en Dexie (sin cambios)
|
||||
|
||||
Las etapas siguientes migran los datos a Supabase manteniendo la app funcional en cada paso.
|
||||
Reference in New Issue
Block a user