# 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://.supabase.co VITE_SUPABASE_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 signOut(): Promise getCurrentUser(): Promise 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://.supabase.co VITE_SUPABASE_ANON_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.