- 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>
9.1 KiB
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
- Instalar
@supabase/supabase-jsy crear el cliente singleton ensrc/lib/supabase.ts - Definir la interfaz
AuthServicey el tipoAuthUserensrc/auth/types.ts - Implementar
SupabaseAuthServiceensrc/auth/SupabaseAuthService.ts - Crear la tabla
usersen Supabase con script SQL documentado - Crear
LoginPage(/login) con botón "Iniciar sesión con Google" - Proteger rutas: cualquier ruta excepto
/loginredirige a/loginsi no hay sesión - Documentar
.env.examplecon 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_roleen la UI (la tablausersse 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
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
usersde la DB:- Si el email es
@inquality.com.py→platform_role = 'platform_admin' - Cualquier otro dominio →
platform_role = 'guest'
- Si el email es
- Retorna la función de cleanup para desuscribirse
src/lib/supabase.ts
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:
-- 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 | nullyloading: 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
#F59845o 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 buildpasa sin errores TypeScriptnpm run test— los 561 tests existentes siguen verdes (no se rompe nada)- Navegar a
http://localhost:5173sin sesión → redirige a/login - La página
/loginmuestra 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
usersde Supabase aparece el registro del usuario conplatform_role = 'platform_admin' - No hay importaciones de
@supabase/supabase-jsfuera desrc/lib/supabase.tsysrc/auth/SupabaseAuthService.ts - Validación visual del director: captura de pantalla de LoginPage + workspace post-login
Reporte esperado al completar
Claude Code debe reportar:
- Lista de archivos creados/modificados
- Resultado de
npm run test(número de tests, pass/fail) - Resultado de
npm run build(sin errores) - Captura de pantalla o descripción de la LoginPage
- Confirmación de que el upsert en la tabla
usersfunciona (puede ser manual: "corrí el flujo y aparece el registro") - 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.