diff --git a/MIGRATION_EXPERIMENTS.md b/MIGRATION_EXPERIMENTS.md new file mode 100644 index 0000000..ce07785 --- /dev/null +++ b/MIGRATION_EXPERIMENTS.md @@ -0,0 +1,96 @@ +# Migración de Experimentos - Supabase + +## Pasos para configurar la tabla de experimentos: + +### 1. Ve a tu proyecto de Supabase +Abre: https://supabase.com/dashboard/project/qjioywarwdbxmdihyrti + +### 2. Navega a SQL Editor +- Click en **"SQL Editor"** en la barra lateral izquierda +- Click en **"New query"** + +### 3. Copia y ejecuta el siguiente SQL: + +```sql +-- Crear tabla de experimentos +CREATE TABLE IF NOT EXISTS experiments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + description TEXT, + type TEXT NOT NULL CHECK (type IN ('pricing', 'advertising', 'content', 'promotion')), + status TEXT NOT NULL DEFAULT 'planned' CHECK (status IN ('planned', 'active', 'completed', 'paused')), + + asins TEXT[] NOT NULL DEFAULT '{}', + marketplace TEXT NOT NULL, + + start_date DATE NOT NULL, + end_date DATE, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + + hypothesis TEXT, + primary_metric TEXT NOT NULL DEFAULT 'units', + target_lift_percent NUMERIC, + + baseline_units NUMERIC, + baseline_revenue NUMERIC, + experiment_units NUMERIC, + experiment_revenue NUMERIC, + actual_lift_percent NUMERIC, + statistical_significance NUMERIC, + + learnings TEXT, + owner TEXT +); + +-- Crear índices para mejorar rendimiento +CREATE INDEX IF NOT EXISTS idx_experiments_status ON experiments(status); +CREATE INDEX IF NOT EXISTS idx_experiments_marketplace ON experiments(marketplace); +CREATE INDEX IF NOT EXISTS idx_experiments_type ON experiments(type); +CREATE INDEX IF NOT EXISTS idx_experiments_dates ON experiments(start_date, end_date); +CREATE INDEX IF NOT EXISTS idx_experiments_asins ON experiments USING GIN(asins); + +-- Función para actualizar updated_at +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Trigger para actualizar updated_at automáticamente +DROP TRIGGER IF EXISTS update_experiments_updated_at ON experiments; +CREATE TRIGGER update_experiments_updated_at + BEFORE UPDATE ON experiments + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +-- Deshabilitar RLS para desarrollo (opcional, habilitar en producción) +ALTER TABLE experiments DISABLE ROW LEVEL SECURITY; +``` + +### 4. Click en **"Run"** para ejecutar el SQL + +### 5. Verifica que se creó correctamente +- Ve a **"Table Editor"** en la barra lateral +- Deberías ver la tabla `experiments` en la lista + +### 6. ¡Listo! +La pestaña de Experiments en tu aplicación ya debería funcionar correctamente. + +--- + +## Solución de problemas: + +### Error: "relation already exists" +La tabla ya existe. No necesitas hacer nada más. + +### Error: "permission denied" +Asegúrate de estar usando la **service_role key** en tu `.env.local`: +``` +SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +### Error: "type already exists" +Los tipos ya están definidos. El resto del SQL debería ejecutarse sin problemas. diff --git a/api/migrate-experiments.ts b/api/migrate-experiments.ts new file mode 100644 index 0000000..bf73b28 --- /dev/null +++ b/api/migrate-experiments.ts @@ -0,0 +1,106 @@ +import type { VercelRequest, VercelResponse } from '@vercel/node'; +import { createClient } from '@supabase/supabase-js'; + +export default async function handler(req: VercelRequest, res: VercelResponse) { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + if (req.method === 'OPTIONS') return res.status(200).end(); + if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' }); + + const supabaseUrl = process.env.SUPABASE_URL; + const supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY; + + if (!supabaseUrl || !supabaseServiceKey) { + return res.status(500).json({ error: 'Supabase credentials not configured' }); + } + + const supabase = createClient(supabaseUrl, supabaseServiceKey); + + const sql = ` + CREATE TABLE IF NOT EXISTS experiments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + description TEXT, + type TEXT NOT NULL CHECK (type IN ('pricing', 'advertising', 'content', 'promotion')), + status TEXT NOT NULL DEFAULT 'planned' CHECK (status IN ('planned', 'active', 'completed', 'paused')), + + asins TEXT[] NOT NULL DEFAULT '{}', + marketplace TEXT NOT NULL, + + start_date DATE NOT NULL, + end_date DATE, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + + hypothesis TEXT, + primary_metric TEXT NOT NULL DEFAULT 'units', + target_lift_percent NUMERIC, + + baseline_units NUMERIC, + baseline_revenue NUMERIC, + experiment_units NUMERIC, + experiment_revenue NUMERIC, + actual_lift_percent NUMERIC, + statistical_significance NUMERIC, + + learnings TEXT, + owner TEXT + ); + + CREATE INDEX IF NOT EXISTS idx_experiments_status ON experiments(status); + CREATE INDEX IF NOT EXISTS idx_experiments_marketplace ON experiments(marketplace); + CREATE INDEX IF NOT EXISTS idx_experiments_type ON experiments(type); + CREATE INDEX IF NOT EXISTS idx_experiments_dates ON experiments(start_date, end_date); + CREATE INDEX IF NOT EXISTS idx_experiments_asins ON experiments USING GIN(asins); + + -- Create updated_at trigger function if not exists + CREATE OR REPLACE FUNCTION update_updated_at_column() + RETURNS TRIGGER AS $$ + BEGIN + NEW.updated_at = NOW(); + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + -- Create trigger + DROP TRIGGER IF EXISTS update_experiments_updated_at ON experiments; + CREATE TRIGGER update_experiments_updated_at + BEFORE UPDATE ON experiments + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + `; + + try { + // Execute SQL via Supabase RPC or direct connection + // Since we can't run raw SQL via JS client, we'll create the table using the query builder + + // First check if table exists + const { error: checkError } = await supabase.from('experiments').select('id').limit(1); + + if (checkError && checkError.message.includes('relation') && checkError.message.includes('does not exist')) { + // Table doesn't exist - we need to create it via SQL + // Unfortunately the JS client can't run CREATE TABLE directly + // We'll return instructions + return res.status(200).json({ + message: 'Table needs to be created via SQL Editor', + instructions: 'Please go to Supabase Dashboard → SQL Editor and run the migration SQL', + sql: sql + }); + } + + // Table exists, create indexes + await supabase.rpc('create_experiment_indexes'); + + return res.status(200).json({ + success: true, + message: 'Experiments table is ready' + }); + } catch (error: any) { + return res.status(500).json({ + error: error.message, + instructions: 'Please go to Supabase Dashboard → SQL Editor and run the migration SQL manually' + }); + } +}