Bases de Datos para Vibe Coders
Cuando necesitas una base de datos
No todos los proyectos necesitan una base de datos desde el dia uno. Pero en el momento en que tu app necesita recordar algo despues de que el usuario cierra el navegador, necesitas almacenamiento persistente.
Estas son las senales:
- Los usuarios crean cuentas y esperan que sus datos esten ahi cuando vuelvan
- Estas almacenando contenido que los usuarios generan — publicaciones, tareas, mensajes, archivos
- Necesitas buscar, filtrar u ordenar datos
- Multiples usuarios necesitan ver los mismos datos
- Necesitas rastrear historial o cambios a lo largo del tiempo
Una base de datos es una forma estructurada de almacenar, recuperar y gestionar datos. No es un archivo lleno de JSON (aunque puede sentirse asi cuando estas empezando). Es un sistema disenado para acceso concurrente, integridad de datos y consultas eficientes.
Tipos de bases de datos
Bases de datos relacionales (PostgreSQL, MySQL, SQLite) almacenan datos en tablas con filas y columnas. Las tablas pueden referenciarse entre si a traves de relaciones. Usan SQL (Structured Query Language) para consultas. Esta es la opcion por defecto para la mayoria de aplicaciones web.
Bases de datos de documentos (MongoDB, Firebase Firestore) almacenan datos como documentos flexibles tipo JSON. Son mas faciles para empezar, pero mas dificiles de mantener a medida que tus datos se vuelven mas complejos e interconectados.
Para este curso, nos enfocamos en bases de datos relacionales porque son el estandar de la industria para apps web, funcionan de maravilla con Prisma, y la IA genera excelente SQL y schemas relacionales.
SQLite vs. PostgreSQL
Estas son las dos bases de datos que encontraras con mas frecuencia en el desarrollo web moderno. Cada una tiene un caso de uso claro.
SQLite: La base de datos sin configuracion
SQLite almacena toda tu base de datos en un solo archivo. Sin servidor que instalar, sin configuracion, sin puertos que gestionar. Esta integrada en cada smartphone y es el motor de base de datos mas ampliamente desplegado en el mundo.
Ideal para:
- Desarrollo local y prototipado
- Apps pequenas a medianas con un solo servidor
- Aplicaciones embebidas
- Aprendizaje y experimentacion
Limitaciones:
- Un solo escritor a la vez (sin escrituras concurrentes desde multiples servidores)
- No soporta algunas caracteristicas avanzadas de SQL
- No ideal para apps de produccion con mucho trafico
PostgreSQL: El estandar de produccion
PostgreSQL es una base de datos cliente-servidor con todas las funcionalidades. Se ejecuta como un proceso (o servicio) separado y maneja conexiones concurrentes, consultas complejas, busqueda de texto completo, almacenamiento JSON y mucho mas.
Ideal para:
- Aplicaciones web de produccion
- Apps con multiples servidores o funciones serverless
- Consultas y relaciones complejas
- Apps que necesitan busqueda de texto completo o columnas JSON
Guia de decision
Empezando un nuevo proyecto? Usa SQLite en desarrollo por velocidad y simplicidad. Planea cambiar a PostgreSQL para produccion. Prisma hace este cambio practicamente indoloro — cambias una linea en tu configuracion.
Desarrollo: SQLite → rapido, cero configuracion, funciona en cualquier lugar
Produccion: PostgreSQL → escalable, concurrente, completo en funcionalidades
Prisma ORM: Tu capa de base de datos amigable con IA
Un ORM (Object-Relational Mapping) te permite trabajar con tu base de datos usando tu lenguaje de programacion en lugar de escribir SQL crudo. Prisma es el mejor ORM para vibe coding porque:
- Schema declarativo — describes tu modelo de datos en un archivo de schema legible, y Prisma genera todo lo demas
- Consultas type-safe — TypeScript conoce la forma de tus datos, asi que obtienes autocompletado y verificacion de errores
- API legible —
prisma.user.findMany()se lee como ingles - Amigable con IA — el formato de schema y la API de consultas de Prisma estan bien representados en los datos de entrenamiento de la IA
Cuando le dices a la IA "crea un usuario con email y contrasena," y estas usando Prisma, la IA genera codigo limpio y type-safe que funciona al primer intento.
Configurando Prisma
Instalacion
npm install prisma @prisma/client
npx prisma init
Esto crea un directorio prisma/ con un archivo schema.prisma y un archivo .env para la URL de tu base de datos.
Configurando el datasource
Para SQLite (desarrollo):
// prisma/schema.prisma
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
generator client {
provider = "prisma-client-js"
}
Para PostgreSQL (produccion):
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
El env("DATABASE_URL") lee de tu archivo .env:
# .env
DATABASE_URL="postgresql://user:password@localhost:5432/myapp"
El archivo de schema
El archivo schema.prisma es la unica fuente de verdad para la estructura de tu base de datos. Todo — tablas, columnas, relaciones, indices — se define aqui. Prisma usa este archivo para generar el cliente de base de datos, crear migraciones y proporcionar seguridad de tipos.
Diseno de schemas con IA
Aqui es donde el vibe coding brilla. Describes tu modelo de datos en lenguaje natural, y la IA crea el schema de Prisma. Asi es como debes pensarlo.
Describiendo tus datos a la IA
Prompt: "Crea un schema de Prisma para una plataforma de blog. Los usuarios pueden crear publicaciones. Las publicaciones pueden tener multiples etiquetas. Los usuarios pueden comentar en publicaciones. Cada usuario tiene un perfil con bio y URL de avatar."
La IA traduce esto en un schema:
model User {
id String @id @default(cuid())
email String @unique
name String
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
profile Profile?
posts Post[]
comments Comment[]
}
model Profile {
id String @id @default(cuid())
bio String?
avatarUrl String?
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Post {
id String @id @default(cuid())
title String
content String
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
comments Comment[]
tags Tag[]
}
model Comment {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
postId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
}
model Tag {
id String @id @default(cuid())
name String @unique
posts Post[]
}
Desglosemos los tipos de relacion que usa este schema.
Relaciones uno a muchos
Un usuario tiene muchas publicaciones. Una publicacion pertenece a un usuario:
model User {
posts Post[] // Un usuario → muchas publicaciones
}
model Post {
authorId String
author User @relation(fields: [authorId], references: [id])
}
El campo authorId almacena la clave foranea. El campo author es una relacion virtual — no crea una columna pero te permite navegar entre registros.
Relaciones muchos a muchos
Las publicaciones pueden tener muchas etiquetas. Las etiquetas pueden pertenecer a muchas publicaciones:
model Post {
tags Tag[]
}
model Tag {
posts Post[]
}
Prisma crea una tabla de union implicita detras de escena. No necesitas definirla tu mismo (aunque puedes hacerlo si necesitas campos adicionales en la relacion).
Relaciones uno a uno
Un usuario tiene exactamente un perfil:
model User {
profile Profile? // Opcional: el usuario podria no tener perfil aun
}
model Profile {
userId String @unique // @unique impone uno a uno
user User @relation(fields: [userId], references: [id])
}
La restriccion @unique en userId asegura que cada usuario puede tener como maximo un perfil.
Indices para rendimiento
A medida que tus datos crecen, las consultas se vuelven mas lentas. Los indices aceleran las busquedas en columnas especificas:
model Post {
authorId String
createdAt DateTime @default(now())
@@index([authorId])
@@index([createdAt])
@@index([authorId, createdAt]) // Indice compuesto
}
Prompt: "Agrega indices al modelo Post para consultas comunes: encontrar publicaciones por autor, listar publicaciones recientes, y encontrar publicaciones por autor ordenadas por fecha."
Flujo de migraciones
Las migraciones son cambios versionados a tu schema de base de datos. Cuando modificas tu schema de Prisma, creas una migracion que describe lo que cambio, y luego la aplicas a tu base de datos.
Flujo de trabajo en desarrollo
# Despues de cambiar schema.prisma:
npx prisma migrate dev --name add-post-status
Este comando:
- Compara tu schema con la base de datos actual
- Genera un archivo de migracion SQL
- Aplica la migracion a tu base de datos
- Regenera el cliente de Prisma
Despliegue a produccion
npx prisma migrate deploy
Esto aplica todas las migraciones pendientes en orden. Nunca genera nuevas migraciones — solo ejecuta las existentes.
Por que importan las migraciones
Las migraciones son un historial de los cambios en tu base de datos. Te permiten:
- Rastrear que cambio y cuando
- Avanzar (aplicar cambios) en nuevos entornos
- Asegurar que todos los entornos tengan la misma estructura de base de datos
- Colaborar con otros desarrolladores sin conflictos de schema
Nombra tus migraciones de forma significativa: add-user-roles, create-comments-table, add-post-published-field. Tu yo del futuro agradecera a tu yo del presente.
Datos semilla
Los scripts de seed crean datos iniciales para desarrollo. Son esenciales para tener datos realistas con los que trabajar.
// prisma/seed.ts
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
async function main() {
// Crear usuarios
const alice = await prisma.user.create({
data: {
email: "alice@example.com",
name: "Alice Johnson",
password: "hashed-password-here",
profile: {
create: { bio: "Desarrolladora full-stack y entusiasta del cafe" },
},
},
})
// Crear publicaciones con etiquetas
await prisma.post.create({
data: {
title: "Getting Started with Prisma",
content: "Prisma hace el acceso a bases de datos facil...",
published: true,
authorId: alice.id,
tags: {
connectOrCreate: [
{ where: { name: "prisma" }, create: { name: "prisma" } },
{ where: { name: "database" }, create: { name: "database" } },
],
},
},
})
console.log("Base de datos sembrada exitosamente")
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect())
Agrega a tu package.json:
{
"prisma": {
"seed": "tsx prisma/seed.ts"
}
}
Ejecuta con:
npx prisma db seed
Prompt: "Crea un script de seed con 5 usuarios, 20 publicaciones distribuidas entre ellos, etiquetas relevantes y comentarios de ejemplo."
Patrones comunes de consultas con Prisma
Estas son las operaciones que usaras todos los dias. Aprende los patrones, y sabras exactamente como darle prompts a la IA para operaciones de base de datos.
Crear
// Crear uno
const user = await prisma.user.create({
data: { email: "bob@example.com", name: "Bob" },
})
// Crear muchos
const users = await prisma.user.createMany({
data: [
{ email: "user1@example.com", name: "Usuario 1" },
{ email: "user2@example.com", name: "Usuario 2" },
],
})
Leer
// Buscar por campo unico
const user = await prisma.user.findUnique({
where: { email: "alice@example.com" },
})
// Buscar muchos con condiciones
const posts = await prisma.post.findMany({
where: { published: true, authorId: userId },
orderBy: { createdAt: "desc" },
take: 10,
skip: 0,
})
// Incluir datos relacionados
const userWithPosts = await prisma.user.findUnique({
where: { id: userId },
include: { posts: true, profile: true },
})
Actualizar
// Actualizar uno
const updated = await prisma.post.update({
where: { id: postId },
data: { title: "Titulo Actualizado" },
})
// Actualizar muchos
await prisma.post.updateMany({
where: { authorId: userId },
data: { published: false },
})
// Upsert (crear si no existe, actualizar si existe)
const profile = await prisma.profile.upsert({
where: { userId: userId },
update: { bio: "Bio actualizada" },
create: { userId: userId, bio: "Nueva bio" },
})
Eliminar
// Eliminar uno
await prisma.post.delete({ where: { id: postId } })
// Eliminar muchos
await prisma.comment.deleteMany({ where: { postId: postId } })
El onDelete: Cascade en tu schema maneja la eliminacion automatica de registros relacionados. Cuando eliminas un usuario, todas sus publicaciones y comentarios se eliminan tambien.
Filtrado, ordenamiento y paginacion
const results = await prisma.post.findMany({
where: {
published: true,
title: { contains: "prisma", mode: "insensitive" },
createdAt: { gte: new Date("2026-01-01") },
tags: { some: { name: "database" } },
},
orderBy: [{ createdAt: "desc" }, { title: "asc" }],
skip: (page - 1) * pageSize,
take: pageSize,
include: { author: { select: { name: true, email: true } } },
})
Agregaciones
const stats = await prisma.post.aggregate({
_count: true,
_avg: { wordCount: true },
where: { published: true },
})
const postsByAuthor = await prisma.post.groupBy({
by: ["authorId"],
_count: true,
orderBy: { _count: { id: "desc" } },
})
Drizzle como alternativa
Prisma no es el unico ORM. Drizzle ORM toma un enfoque diferente — es SQL-first, lo que significa que tu codigo TypeScript refleja de cerca el SQL que genera.
// Schema de Drizzle
import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core"
export const posts = pgTable("posts", {
id: text("id").primaryKey(),
title: text("title").notNull(),
published: boolean("published").default(false),
createdAt: timestamp("created_at").defaultNow(),
})
// Consulta de Drizzle
const results = await db.select().from(posts).where(eq(posts.published, true))
Cuando elegir Drizzle sobre Prisma:
- Prefieres escribir codigo similar a SQL
- Necesitas maximo rendimiento en consultas (Drizzle genera SQL mas eficiente en algunos casos)
- Quieres un runtime mas ligero (Drizzle no tiene binario de query engine)
Cuando elegir Prisma:
- Quieres la opcion mas amigable para la IA (Prisma tiene mas datos de entrenamiento)
- Prefieres una API de nivel mas alto y mas abstracta
- Valoras la GUI de Prisma Studio para explorar datos
- Trabajas con un equipo que incluye desarrolladores que no conocen SQL
Ambos son excelentes. Para este curso, usamos Prisma porque la IA genera mejor codigo de Prisma de entrada.
Hospedaje de bases de datos
Necesitas un lugar donde alojar tu base de datos de produccion. Aqui estan las mejores opciones con tiers gratuitos:
Neon
PostgreSQL serverless. Tu base de datos escala a cero cuando esta inactiva y se activa instantaneamente cuando se necesita. Perfecto para apps con trafico impredecible.
- Tier gratuito: 0.5 GB de almacenamiento, 190 horas de computo
- Ideal para: Apps Next.js, arquitecturas serverless
Supabase
PostgreSQL con extras — autenticacion integrada, suscripciones en tiempo real, almacenamiento y una API REST auto-generada. Es como Firebase pero con PostgreSQL.
- Tier gratuito: 500 MB de almacenamiento, 2 proyectos
- Ideal para: Apps que quieren un backend todo-en-uno
Turso
SQLite desplegado en el edge. Tu base de datos se ejecuta cerca de tus usuarios globalmente. Es SQLite para produccion, usando un protocolo llamado libSQL.
- Tier gratuito: 9 GB de almacenamiento, 500 bases de datos
- Ideal para: Apps con muchas lecturas, edge computing
PlanetScale
MySQL serverless con un flujo de trabajo de branching similar a Git para cambios de schema. Cada branch es una base de datos aislada que puedes mergear.
- Ideal para: Equipos que quieren cambios de schema seguros con migraciones basadas en branches
Prompt: "Configura el datasource de Prisma para usar Neon PostgreSQL. Configura el string de conexion para serverless con el parametro ?sslmode=require."
Patrones de prompts para trabajo con bases de datos
Aqui hay prompts probados para tareas comunes de base de datos:
Diseno de schema: "Crea un schema de Prisma para una app de gestion de tareas con usuarios, proyectos y tareas. Los usuarios pertenecen a proyectos a traves de una tabla de membresia con roles (admin, member). Las tareas tienen estado (todo, in_progress, done), prioridad (low, medium, high) y fechas de vencimiento opcionales."
Agregar caracteristicas: "Agrega un modelo de comentarios al schema. Los usuarios pueden comentar en tareas. Los comentarios tienen contenido y un timestamp de creacion. Incluye la relacion tanto con User como con Task."
Optimizacion de consultas: "Agrega paginacion a la consulta de tareas. Acepta parametros page y limit, retorna las tareas junto con el conteo total y el total de paginas."
Migracion: "Crea una migracion para agregar un campo status a la tabla de posts con valores 'draft', 'published' y 'archived'. Por defecto 'draft'."
Consultas complejas: "Escribe una consulta que retorne los top 10 usuarios por cantidad de publicaciones, incluyendo su conteo total de publicaciones y la fecha de su publicacion mas reciente."
Cada uno de estos prompts le da a la IA suficiente contexto para generar codigo correcto y listo para produccion. Se especifico sobre tipos de campos, relaciones y restricciones.
Que sigue
Ahora sabes como elegir, configurar y trabajar con bases de datos. Puedes disenar schemas, ejecutar migraciones, sembrar datos y escribir consultas — todo describiendo lo que quieres a la IA y entendiendo lo que genera.
Pero todos estos datos necesitan proteccion. En la siguiente leccion, implementaremos autenticacion y autorizacion — asegurandonos de que los usuarios son quienes dicen ser y solo pueden acceder a lo que se les permite. Configuraras login con OAuth, protegeras rutas e implementaras control de acceso basado en roles.