Files
inq-roi-simulador-web/sprints/sprint-4/ETAPA_1_PROMPT.md
Marcos Benítez 78e776df6b 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>
2026-05-27 23:53:12 -03:00

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

  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

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.pyplatform_role = 'platform_admin'
    • Cualquier otro dominio → platform_role = 'guest'
  • 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 | 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.