Bases de Datos para Vibe Coders
Cuándo necesitas una base de datos
No todos los proyectos necesitan una base de datos desde el día uno. Pero en el momento en que tu app necesita recordar algo después de que el usuario cierra el navegador, necesitas almacenamiento persistente.
Estas son las señales:
- Los usuarios crean cuentas y esperan que sus datos estén ahí cuando vuelvan
- Estás almacenando contenido que los usuarios generan — publicaciones, tareas, mensajes, archivos
- Necesitas buscar, filtrar u ordenar datos
- Múltiples 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 así cuando estás empezando). Es un sistema diseñado 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 sí a través de relaciones. Usan SQL (Structured Query Language) para consultas. Esta es la opción por defecto para la mayoría de aplicaciones web.
Bases de datos de documentos (MongoDB, Firebase Firestore) almacenan datos como documentos flexibles tipo JSON. Son más fáciles para empezar, pero más difíciles de mantener a medida que tus datos se vuelven más complejos e interconectados.
Para este curso, nos enfocamos en bases de datos relacionales porque son el estándar 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 encontrarás con más frecuencia en el desarrollo web moderno. Cada una tiene un caso de uso claro.
SQLite: La base de datos sin configuración
SQLite almacena toda tu base de datos en un solo archivo. Sin servidor que instalar, sin configuración, sin puertos que gestionar. Está integrada en cada smartphone y es el motor de base de datos más ampliamente desplegado en el mundo.
Ideal para:
- Desarrollo local y prototipado
- Apps pequeñas a medianas con un solo servidor
- Aplicaciones embebidas
- Aprendizaje y experimentación
Limitaciones:
- Un solo escritor a la vez (sin escrituras concurrentes desde múltiples servidores)
- No soporta algunas características avanzadas de SQL
- No ideal para apps de producción con mucho tráfico
PostgreSQL: El estándar de producción
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, búsqueda de texto completo, almacenamiento JSON y mucho más.
Ideal para:
- Aplicaciones web de producción
- Apps con múltiples servidores o funciones serverless
- Consultas y relaciones complejas
- Apps que necesitan búsqueda de texto completo o columnas JSON
Guía de decisión
¿Empezando un nuevo proyecto? Usa SQLite en desarrollo por velocidad y simplicidad. Planea cambiar a PostgreSQL para producción. Prisma hace este cambio prácticamente indoloro — cambias una línea en tu configuración.
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 programación 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 demás
- Consultas type-safe — TypeScript conoce la forma de tus datos, así que obtienes autocompletado y verificación de errores
- API legible —
prisma.user.findMany()se lee como inglés - Amigable con IA — el formato de schema y la API de consultas de Prisma están bien representados en los datos de entrenamiento de la IA
Cuando le dices a la IA "crea un usuario con email y contraseña," y estás usando Prisma, la IA genera código limpio y type-safe que funciona al primer intento.
Configurando Prisma
Instalación
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 (producción):
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 única fuente de verdad para la estructura de tu base de datos. Todo — tablas, columnas, relaciones, índices — se define aquí. Prisma usa este archivo para generar el cliente de base de datos, crear migraciones y proporcionar seguridad de tipos.
Diseño de schemas con IA
Aquí es donde el vibe coding brilla. Describes tu modelo de datos en lenguaje natural, y la IA crea el schema de Prisma. Así 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 múltiples 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 relación que usa este schema.
Relaciones uno a muchos
Un usuario tiene muchas publicaciones. Una publicación 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 foránea. El campo author es una relación 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 unión implícita detrás de escena. No necesitas definirla tú mismo (aunque puedes hacerlo si necesitas campos adicionales en la relación).
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 restricción @unique en userId asegura que cada usuario puede tener como máximo un perfil.
Índices para rendimiento
A medida que tus datos crecen, las consultas se vuelven más lentas. Los índices aceleran las búsquedas en columnas específicas:
model Post {
authorId String
createdAt DateTime @default(now())
@@index([authorId])
@@index([createdAt])
@@index([authorId, createdAt]) // Indice compuesto
}
Prompt: "Agrega índices 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 migración que describe lo que cambió, 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 migración SQL
- Aplica la migración a tu base de datos
- Regenera el cliente de Prisma
Despliegue a producción
npx prisma migrate deploy
Esto aplica todas las migraciones pendientes en orden. Nunca genera nuevas migraciones — solo ejecuta las existentes.
Por qué importan las migraciones
Las migraciones son un historial de los cambios en tu base de datos. Te permiten:
- Rastrear qué cambió y cuándo
- 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 agradecerá 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 usarás todos los días. Aprende los patrones, y sabrás exactamente cómo 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 eliminación automática de registros relacionados. Cuando eliminas un usuario, todas sus publicaciones y comentarios se eliminan también.
Filtrado, ordenamiento y paginación
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 único ORM. Drizzle ORM toma un enfoque diferente — es SQL-first, lo que significa que tu código 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))
Cuándo elegir Drizzle sobre Prisma:
- Prefieres escribir código similar a SQL
- Necesitas máximo rendimiento en consultas (Drizzle genera SQL más eficiente en algunos casos)
- Quieres un runtime más ligero (Drizzle no tiene binario de query engine)
Cuándo elegir Prisma:
- Quieres la opción más amigable para la IA (Prisma tiene más datos de entrenamiento)
- Prefieres una API de nivel más alto y más 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 código de Prisma de entrada.
Hospedaje de bases de datos
Necesitas un lugar donde alojar tu base de datos de producción. Aquí están las mejores opciones con tiers gratuitos:
Neon
PostgreSQL serverless. Tu base de datos escala a cero cuando está inactiva y se activa instantáneamente cuando se necesita. Perfecto para apps con tráfico impredecible.
- Tier gratuito: 0.5 GB de almacenamiento, 190 horas de cómputo
- Ideal para: Apps Next.js, arquitecturas serverless
Supabase
PostgreSQL con extras — autenticación 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 producción, 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 conexión para serverless con el parámetro ?sslmode=require."
Patrones de prompts para trabajo con bases de datos
Aquí hay prompts probados para tareas comunes de base de datos:
Diseño de schema: "Crea un schema de Prisma para una app de gestión de tareas con usuarios, proyectos y tareas. Los usuarios pertenecen a proyectos a través de una tabla de membresía con roles (admin, member). Las tareas tienen estado (todo, in_progress, done), prioridad (low, medium, high) y fechas de vencimiento opcionales."
Agregar características: "Agrega un modelo de comentarios al schema. Los usuarios pueden comentar en tareas. Los comentarios tienen contenido y un timestamp de creación. Incluye la relación tanto con User como con Task."
Optimización de consultas: "Agrega paginación a la consulta de tareas. Acepta parámetros page y limit, retorna las tareas junto con el conteo total y el total de páginas."
Migración: "Crea una migración 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 publicación más reciente."
Cada uno de estos prompts le da a la IA suficiente contexto para generar código correcto y listo para producción. Sé específico sobre tipos de campos, relaciones y restricciones.
Qué sigue
Ahora sabes cómo elegir, configurar y trabajar con bases de datos. Puedes diseñar 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 protección. En la siguiente lección, implementaremos autenticación y autorización — asegurándonos de que los usuarios son quienes dicen ser y solo pueden acceder a lo que se les permite. Configurarás login con OAuth, protegerás rutas e implementarás control de acceso basado en roles.