Diseño de bases de datos en Supabase: tablas, relaciones y Row Level Security

Miras el aviso rojo en el Dashboard de Supabase: «RLS not enabled». Te vienen a la cabeza un montón de preguntas: ¿se filtrarán los datos de los artículos de los usuarios? ¿Está bien esta relación de clave foránea? ¿Cómo se modela de verdad una relación muchos a muchos?
Cuando empecé con Supabase, caí en demasiadas trampas. Creaba tablas y olvidaba activar RLS, así que cualquiera podía leer todo. Diseñaba mal las claves foráneas: borraba un usuario y los artículos seguían ahí. En relaciones muchos a muchos, hasta probé guardar IDs en arrays: un desastre total.
Después de varios meses de prueba y error, por fin entendí el patrón del diseño de bases de datos en Supabase. Este artículo recoge esa experiencia.
1. Estructura de tablas: convenciones de nombres en PostgreSQL
1.1 Convención de nombres: snake_case es lo correcto
PostgreSQL tiene un comportamiento peculiar: si no usas comillas dobles, convierte los identificadores a minúsculas. Si las usas, respeta exactamente lo que escribiste.
¿Qué implica? Con camelCase (UserProfile) tendrías que poner comillas en cada referencia. Demasiado trabajo.
Por eso la convención de la comunidad PostgreSQL es: snake_case (guiones bajos), nombres de tabla en plural y nombres de columna en singular.
-- ✅ Recomendado
CREATE TABLE users (
id UUID PRIMARY KEY,
email TEXT UNIQUE,
created_at TIMESTAMPTZ
);
1.2 Tipos de columna: no te quedes atrapado en la mentalidad MySQL
Error 1: usar VARCHAR en lugar de TEXT
En PostgreSQL, TEXT y VARCHAR tienen el mismo rendimiento. La diferencia es que VARCHAR(n) impone un límite de longitud. Si no necesitas limitarlo, usa TEXT directamente.
Error 2: usar TIMESTAMP en lugar de TIMESTAMPTZ
TIMESTAMP no guarda información de zona horaria. Si el servidor está en EE. UU. y el usuario en China, las horas se mostrarán mal. TIMESTAMPTZ convierte la zona horaria automáticamente.
Error 3: usar SERIAL en lugar de UUID
SERIAL es un entero autoincremental. En una app monolítica puede valer, pero en sistemas distribuidos choca. UUID es único a escala global.
2. Tres tipos de relaciones: uno a uno, uno a muchos, muchos a muchos
2.1 Uno a uno: basta con UNIQUE
El caso más habitual: usuario y perfil.
CREATE TABLE profiles (
id UUID PRIMARY KEY,
user_id UUID UNIQUE REFERENCES users(id) ON DELETE CASCADE,
bio TEXT
);
Punto clave: user_id UUID UNIQUE. La restricción UNIQUE garantiza un solo perfil por usuario.
2.2 Uno a muchos: la clave foránea clásica
Autores y libros. Un autor puede escribir muchos libros.
CREATE TABLE books (
id UUID PRIMARY KEY,
author_id UUID REFERENCES authors(id) ON DELETE CASCADE,
title TEXT
);
Al consultar, el cliente JS de Supabase puede anidar datos relacionados directamente.
2.3 Muchos a muchos: la tabla puente es clave
Estudiantes y cursos. Un estudiante puede inscribirse en varios cursos y un curso puede tener varios estudiantes.
Solución: crear una tabla de unión.
CREATE TABLE enrollments (
student_id UUID REFERENCES students(id) ON DELETE CASCADE,
course_id UUID REFERENCES courses(id) ON DELETE CASCADE,
PRIMARY KEY (student_id, course_id)
);
3. Row Level Security: la base de datos como guardia de seguridad
3.1 La filosofía de «denegar por defecto» de RLS
La primera vez que usé Supabase, creé una tabla posts y consulté desde el frontend con la anon key. Resultado: devolvió todos los datos. Me asusté.
Resulta que Supabase no activa Row Level Security (RLS) por defecto. Sin RLS, quien tenga la anon key puede leer y escribir todo.
Primera regla de oro: activa RLS en cuanto crees la tabla.
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
¿Y después de activarlo? No basta. RLS activado sin políticas equivale a «denegar todo acceso». Necesitas al menos una política.
3.2 Sintaxis de políticas: USING y WITH CHECK
- USING: filtra filas existentes (SELECT, UPDATE, DELETE)
- WITH CHECK: valida filas nuevas (INSERT, UPDATE)
3.3 Cuatro patrones habituales de políticas
Patrón 1: el usuario accede solo a sus datos
CREATE POLICY "Users manage own data"
ON posts FOR ALL
TO authenticated
USING (user_id = auth.uid());
Patrón 2: mezcla de datos públicos y privados
Lo publicado lo ve todo el mundo; los borradores solo el autor.
Patrón 3: aislamiento multi-tenant
Los miembros del equipo solo acceden a datos de su propio equipo.
Patrón 4: control de roles RBAC
Los administradores tienen permisos especiales.
4. Optimización del rendimiento de RLS
4.1 El asesino del rendimiento: subconsultas por fila
Las subconsultas dentro de una política RLS se ejecutan una vez por cada fila. Con 100 000 filas y una subconsulta que comprueba pertenencia a un equipo, la consulta puede tardar 3 minutos.
4.2 Optimización 1: añadir índices
Las columnas usadas en políticas RLS deben tener índice.
CREATE INDEX idx_posts_user_id ON posts(user_id);
Pruebas oficiales de Supabase: sin índice 450 ms, con índice 45 ms. Diez veces más rápido.
4.3 Optimización 2: funciones SECURITY DEFINER
Encapsula la subconsulta en una función que se ejecuta una sola vez.
CREATE OR REPLACE FUNCTION user_teams()
RETURNS SETOF UUID
LANGUAGE SQL SECURITY DEFINER STABLE
AS $$ SELECT team_id FROM team_members WHERE user_id = auth.uid(); $$;
5. Casos prácticos
5.1 Sistema de blog: artículos, categorías y etiquetas
Implementación completa con estructura de tablas, políticas RLS e índices.
5.2 SaaS multi-tenant: colaboración en equipo
Aislamiento de datos por equipo, acceso de miembros y permisos de administrador.
Resumen
Puntos clave:
- Nomenclatura snake_case
- Clave primaria UUID, texto TEXT, tiempo TIMESTAMPTZ
- RLS siempre activado
- Índices + funciones SECURITY DEFINER para optimizar
FAQ
¿Hay que activar RLS inmediatamente después de crear una tabla?
¿Por qué PostgreSQL recomienda snake_case?
¿Por qué no conviene usar FOR ALL en las políticas RLS?
¿Cómo comprobar el estado de RLS?
5 min de lectura · Publicado el: 4 abr 2026 · Actualizado el: 21 ago 2026
Supabase en práctica
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Primeros pasos con Supabase: backend integral con PostgreSQL + Auth + Storage
Supabase es una alternativa open source a Firebase que ofrece base de datos PostgreSQL, autenticación de nivel empresarial, almacenamiento de objetos y sincronización en tiempo real. Este tutorial te guía por las tres funciones principales con ejemplos de código completos y casos prácticos, ideal para desarrolladores frontend que quieren construir aplicaciones full stack
Parte 1 de 10
Siguiente
Supabase Auth en la práctica: verificación por email, OAuth y sesiones
Guía de Supabase Auth: verificación por email, integración OAuth, gestión de sesiones JWT y flujo PKCE. Autenticación completa en un solo sitio.
Parte 3 de 10



Comentarios
Inicia sesión con GitHub para dejar un comentario