Saltar al contenido
Lección 12 de 22

Bases de Datos para Vibe Coders

14 min read

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:

  1. Schema declarativo — describes tu modelo de datos en un archivo de schema legible, y Prisma genera todo lo demas
  2. Consultas type-safe — TypeScript conoce la forma de tus datos, asi que obtienes autocompletado y verificacion de errores
  3. API legibleprisma.user.findMany() se lee como ingles
  4. 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:

  1. Compara tu schema con la base de datos actual
  2. Genera un archivo de migracion SQL
  3. Aplica la migracion a tu base de datos
  4. 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.