Tablas y Schema
Una tabla guarda un tipo de cosa — productos, pedidos, mensajes, facturas. Cada columna es un dato sobre esa cosa (un precio, una fecha, un user ID). Tener el schema bien desde el principio te ahorra dolores después.
Crear una tabla
Abre la pestaña Database
En el dashboard de tu proyecto, haz clic en Database en el sidebar.
Haz clic en New Table
Dale a la tabla un nombre en minúsculas y plural — products, orders, messages. Usa snake_case.
Añade columnas
Cada tabla recibe una clave primaria id y dos timestamps por defecto. Añade lo que más necesites.
Guarda
La tabla se crea al momento. Puedes añadir o quitar columnas después.

Tipos de columna
DYPAI soporta todos los tipos de PostgreSQL. Los que más usarás:
| Tipo | Úsalo para | Ejemplo |
|---|---|---|
text | Nombres, descripciones, cualquier string | "Camiseta azul de algodón" |
varchar(n) | Strings con límite duro de longitud | varchar(50) para un username |
int / bigint | Conteos, números pequeños, IDs | 42 |
numeric(10,2) | Dinero, decimales exactos | 199.99 |
float | Decimales aproximados (física, analítica) | 3.14159 |
bool | Flags sí/no | is_active, email_verified |
timestamptz | Fechas + horas con zona horaria | 2026-04-16 10:30:00+00 |
date | Solo una fecha | 2026-04-16 |
uuid | IDs únicos globales | 550e8400-e29b-41d4-... |
jsonb | Datos anidados o flexibles | {"color": "red", "size": "M"} |
text[] | Arrays de valores | {"admin", "editor"} |
En la duda, prefiere text a varchar
varchar(n) parece más seguro pero text es idéntico en rendimiento y no rompe cuando necesitas ese carácter extra. Usa varchar solo cuando la longitud máxima sea una regla de negocio real.
Claves primarias
Usa uuid con un default. Es aleatoria, segura contra colisiones y no filtra el conteo de filas:
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
Usa bigserial (entero autoincremental) solo cuando necesites orden de verdad o URLs más cortas. Para la mayoría de apps, UUID es la respuesta correcta.
Timestamps
Cada tabla debería tener estas dos columnas:
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
El editor visual las añade automáticamente al crear una tabla. Si la creas por SQL, añádelas tú — las querrás para debugging, ordenar y audit trails después.
Para mantener updated_at actualizado, o lo actualizas en tu workflow (SET updated_at = now()) o añades un trigger. La mayoría de apps lo actualizan en el workflow — más simple.
⚠️ Referencias a usuarios: usa TEXT, no UUID
Este es el error #1 que comete la gente. El sistema de auth de DYPAI usa un ID de 32 caracteres (no un UUID), así que cualquier columna que apunte a un usuario debe ser TEXT.
Correcto
CREATE TABLE public.tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id TEXT NOT NULL REFERENCES system.users(id) ON DELETE CASCADE,
title TEXT NOT NULL,
done BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Incorrecto (rompe en runtime)
-- ❌ No hagas esto
user_id UUID REFERENCES system.users(id)
Cuando un endpoint intente WHERE user_id = ${current_user_id}, Postgres falla con:
invalid input syntax for type uuid: "G1LIBXsbMLxUrs99ebCaL9X4auxW26AC"
Regla: cualquier columna que enlaza una fila con un usuario = TEXT. Otras claves foráneas (product_id → products.id, order_id → orders.id) mantienen su UUID natural.
En los placeholders del workflow
Escribe WHERE user_id = ${current_user_id} — sin comillas, sin cast ::uuid. El engine bindea el parámetro con el tipo correcto automáticamente.
Índices
Añade un índice a cualquier columna por la que filtres u ordenes a menudo. El editor visual tiene un panel Indexes separado por tabla:

Patrones comunes:
- Indexa cada clave foránea (
user_id,product_id, etc.) — la mayoría de queries filtran por ellas - Indexa
created_at DESCsi listas items recientes - Sáltate índices en tablas pequeñas (menos de ~1k filas) — Postgres es suficientemente rápido sin ellos
Los índices cuestan en escrituras
Cada índice hace los inserts y updates un poco más lentos. Añádelos cuando las queries vayan lentas, no preventivamente.
Ejemplo: una tabla tasks bien modelada
Así queda una tabla típica de un usuario con todas las convenciones aplicadas:
CREATE TABLE public.tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id TEXT NOT NULL REFERENCES system.users(id) ON DELETE CASCADE,
title TEXT NOT NULL,
description TEXT,
done BOOLEAN NOT NULL DEFAULT false,
due_date DATE,
priority INT NOT NULL DEFAULT 0,
tags TEXT[] NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_tasks_user_id ON public.tasks(user_id);
CREATE INDEX idx_tasks_created_at ON public.tasks(created_at DESC);
Copia esto como punto de partida cuando construyas algo que pertenece a un usuario.