Migraciones de Base de Datos en Supabase

Las migraciones son archivos SQL que describen cambios en tu esquema de base de datos de forma incremental y ordenada. Cada migración captura un cambio específico: crear una tabla, agregar una columna, definir una política de RLS. Cuando las aplicas en orden, reconstruyen tu base de datos desde cero hasta el estado actual.

Por qué usar migraciones

Sin migraciones, los cambios en tu base de datos viven solo en el dashboard. Si trabajas en equipo, cada dev tendría que replicar los cambios manualmente. Si necesitas recrear la base de datos, tienes que recordar cada paso.

Con migraciones:

  • Control de versiones: cada cambio es un archivo SQL que va al repositorio
  • Reproducibilidad: cualquiera puede recrear el esquema completo desde cero
  • Colaboración: los cambios de esquema se revisan en PRs como cualquier otro código
  • Rollback: puedes revertir cambios si algo sale mal
  • Automatización: CI/CD puede aplicar migraciones automáticamente

Crear una migración

Desde el CLI (manual)

bash
supabase migration new crear_tabla_productos

Esto crea un archivo vacío en supabase/migrations/:

plaintext
supabase/migrations/20250312143000_crear_tabla_productos.sql

Ahora escribes el SQL del cambio:

sql
-- supabase/migrations/20250312143000_crear_tabla_productos.sql
 
CREATE TABLE productos (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  nombre TEXT NOT NULL,
  descripcion TEXT,
  precio NUMERIC(10, 2) NOT NULL CHECK (precio >= 0),
  stock INTEGER NOT NULL DEFAULT 0 CHECK (stock >= 0),
  categoria TEXT,
  activo BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
 
-- Habilitar RLS
ALTER TABLE productos ENABLE ROW LEVEL SECURITY;
 
-- Política de lectura pública
CREATE POLICY "Productos visibles para todos"
ON productos FOR SELECT
USING (activo = true);
 
-- Indice para busquedas por categoria
CREATE INDEX idx_productos_categoria ON productos (categoria);

Desde db diff (automático)

Si ya hiciste cambios en tu base de datos local usando Studio:

bash
supabase db diff --use-migra -f agregar_columna_imagen

El CLI detecta las diferencias y genera el SQL automáticamente. Revisa siempre el archivo generado -- db diff a veces genera SQL innecesario o en un orden inesperado.

Estructura del archivo de migración

El nombre del archivo sigue el formato:

plaintext
YYYYMMDDHHMMSS_nombre_descriptivo.sql

El timestamp garantiza que las migraciones se ejecuten en orden cronológico. Esto es importante porque una migración puede depender de otra anterior (no puedes agregar una columna a una tabla que no existe todavía).

Convenciones para nombres

AcciónNombre sugerido
Crear tablacrear_tabla_productos
Agregar columnaagregar_imagen_a_productos
Crear índiceindice_busqueda_productos
Agregar RLSrls_productos_lectura
Crear funciónfuncion_calcular_total
Modificar tipocambiar_precio_a_numeric

Aplicar migraciones

En tu base de datos local

bash
supabase db reset

Este comando:

  1. Borra toda la base de datos local
  2. Re-ejecuta todas las migraciones en orden
  3. Ejecuta el archivo seed.sql si existe

Es destructivo pero necesario para verificar que tus migraciones funcionan desde cero.

db reset borra todo

supabase db reset elimina todos los datos de tu base de datos local. Úsalo solo en desarrollo. Tus datos de prueba se pierden a menos que los tengas en seed.sql.

En el proyecto remoto (producción)

bash
supabase db push

Este comando aplica las migraciones pendientes (las que todavía no se ejecutaron en el proyecto remoto). No borra datos, no hace reset -- solo ejecuta las migraciones nuevas.

bash
# Ver que migraciones se van a aplicar antes de hacerlo
supabase db push --dry-run
Antes de pushear a producción

Siempre haz un --dry-run primero. Revisa el SQL que se va a ejecutar. Una migración mal escrita puede borrar datos o romper tu esquema en producción.

Rollback de migraciones

Supabase no tiene un mecanismo automático de rollback. Si una migración causa problemas, tienes que crear una nueva migración que revierta los cambios.

Ejemplo de migración de rollback

Si la migración original agregó una columna:

sql
-- 20250312143000_agregar_columna_imagen.sql
ALTER TABLE productos ADD COLUMN imagen_url TEXT;

La migración de rollback la elimina:

sql
-- 20250312150000_revertir_columna_imagen.sql
ALTER TABLE productos DROP COLUMN imagen_url;
Patrón recomendado

Cuando escribas migraciones, piensa si la puedes revertir fácilmente. Crear una tabla se revierte con DROP TABLE. Agregar una columna se revierte con DROP COLUMN. Pero borrar datos no tiene reversión -- esos datos se pierden.

Revertir localmente

Si quieres deshacer la última migración en tu entorno local, borra el archivo de migración y resetea:

bash
# Borrar el archivo de la ultima migracion
rm supabase/migrations/20250312150000_migracion_mala.sql
 
# Resetear para aplicar todas las migraciones menos la borrada
supabase db reset

Seed data

El archivo supabase/seed.sql contiene datos iniciales que se cargan después de ejecutar todas las migraciones. Es útil para datos de prueba en desarrollo.

sql
-- supabase/seed.sql
 
-- Categorias por defecto
INSERT INTO categorias (nombre, slug) VALUES
  ('Electronica', 'electronica'),
  ('Ropa', 'ropa'),
  ('Hogar', 'hogar'),
  ('Deportes', 'deportes');
 
-- Productos de prueba
INSERT INTO productos (nombre, precio, stock, categoria) VALUES
  ('Laptop Pro 15', 1299.99, 50, 'electronica'),
  ('Mouse Inalambrico', 29.99, 200, 'electronica'),
  ('Camiseta Básica', 19.99, 500, 'ropa'),
  ('Silla Ergonomica', 349.99, 30, 'hogar'),
  ('Pelota de Futbol', 24.99, 100, 'deportes');
 
-- Usuario de prueba (para auth, usa el SQL de GoTrue)
-- Esto depende de tu setup de auth

El seed se ejecuta automáticamente con supabase db reset. No se ejecuta con supabase db push (no quieres cargar datos de prueba en producción).

Workflow en equipo

Cuando trabajas con más personas, las migraciones son la única fuente de verdad del esquema. El flujo es:

1. Cada dev trabaja en local

bash
# Dev A crea una tabla
supabase migration new crear_tabla_pedidos
# Escribe el SQL
# Prueba localmente con supabase db reset

2. Push al repositorio

bash
git add supabase/migrations/20250312143000_crear_tabla_pedidos.sql
git commit -m "feat: crear tabla pedidos"
git push

3. Otro dev sincroniza

bash
git pull
supabase db reset  # Aplica todas las migraciones incluyendo la nueva

4. Deploy a producción

bash
supabase db push  # Solo aplica las migraciones nuevas

Conflictos de migración

Si dos devs crean migraciones al mismo tiempo, puede haber conflictos. Por ejemplo:

  • Dev A crea 20250312140000_tabla_pedidos.sql
  • Dev B crea 20250312140500_tabla_envios.sql
  • Dev B agrega una foreign key de envios a pedidos

Si la migración de Dev B se aplica antes que la de Dev A, falla porque la tabla pedidos no existe todavía.

La solución: revisar el orden de las migraciones después de un merge. Si hay dependencias, renombra el timestamp para que el orden sea correcto.

Una migración por PR

Como buena práctica, cada PR debería incluir como máximo una migración. Esto hace que los cambios de esquema sean fáciles de revisar y de revertir si algo sale mal.

Migraciones comunes

Crear tabla con RLS

sql
CREATE TABLE pedidos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  usuario_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  total NUMERIC(10, 2) NOT NULL,
  estado TEXT NOT NULL DEFAULT 'pendiente',
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
 
ALTER TABLE pedidos ENABLE ROW LEVEL SECURITY;
 
-- Los usuarios solo ven sus propios pedidos
CREATE POLICY "Usuarios ven sus pedidos"
ON pedidos FOR SELECT
USING (auth.uid() = usuario_id);
 
-- Los usuarios solo crean pedidos para si mismos
CREATE POLICY "Usuarios crean sus pedidos"
ON pedidos FOR INSERT
WITH CHECK (auth.uid() = usuario_id);

Agregar columna con valor por defecto

sql
ALTER TABLE productos
ADD COLUMN imagen_url TEXT DEFAULT '';
 
-- Actualizar registros existentes
UPDATE productos
SET imagen_url = 'https://placeholder.com/default.jpg'
WHERE imagen_url = '';

Crear función y trigger

sql
-- Función que actualiza updated_at automáticamente
CREATE OR REPLACE FUNCTION actualizar_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;
 
-- Aplicar a la tabla productos
CREATE TRIGGER trigger_updated_at
BEFORE UPDATE ON productos
FOR EACH ROW
EXECUTE FUNCTION actualizar_updated_at();

Crear índice para performance

sql
-- Indice simple
CREATE INDEX idx_pedidos_usuario ON pedidos (usuario_id);
 
-- Indice compuesto
CREATE INDEX idx_pedidos_usuario_estado ON pedidos (usuario_id, estado);
 
-- Indice para busqueda de texto
CREATE INDEX idx_productos_nombre_search
ON productos USING GIN (to_tsvector('spanish', nombre));

Resumen

TareaComando
Crear migración vacíasupabase migration new nombre
Generar migración desde diffsupabase db diff --use-migra -f nombre
Aplicar en local (reset)supabase db reset
Aplicar en producciónsupabase db push
Preview antes de pushsupabase db push --dry-run
Bajar esquema remotosupabase db pull
Seed dataArchivo supabase/seed.sql
RollbackCrear nueva migración que revierte los cambios