Saltar al contenido

Lección 14 de 17 · Lectura y práctica

Implementar Migraciones y Seed de Datos

Introducción: El Pilar de la Persistencia en Producción En el desarrollo de aplicaciones profesionales, la base de datos no es un componente estático. Evoluciona junto con el negocio, requiriendo cambios en su estructura, ajustes.

Del concepto a la práctica

Taller de esta lección

Un seed útil contiene datos de referencia y casos que ejercitan relaciones, no cientos de filas arbitrarias. Debe ser idempotente: ejecutarlo dos veces conserva el mismo número de usuarios, salas y estados. Para lograrlo, usa claves estables y operaciones de creación que reconozcan duplicados.

Ejemplo resuelto

Define salas con códigos NORTE-01 y SUR-01, y estados PENDING y CONFIRMED. Un upsert por código permite repetir el seed sin duplicar salas. Añade tres reservas con usuarios y fechas distintas para probar filtros.

Datos exactos: User1/Ana,User2/Bruno; Room1/NORTE-01/AulaNorte/capacidad4,Room2/SUR-01/AulaSur/capacidad6. Reservas externalRefr1/user1/room1/2026-01-01T10:00Z–11:00Z/CONFIRMED; r2/user2/room1/12:00Z–13:00Z/PENDING; r3/user1/room2/10:00Z–11:00Z/CONFIRMED. Sus id son Int generados; r1–r3 pertenecen a externalRef String@unique. Usa upsert por cada externalRef con update:{} y create completo: no basta con repetir upsert de salas mientras create duplica reservas. Declara si seed se ejecuta explícitamente con el comando7 y la configuración de scripts de tu proyecto.

Pruébalo

  1. Elige claves naturales estables para referencias.

  2. Inserta usuarios y salas antes de reservas.

  3. Ejecuta el seed una vez y cuenta registros.

  4. Ejecútalo otra vez y compara los conteos.

Comprueba tu resultado

Tras dos ejecuciones hay2 salas y3 reservas. PENDING/CONFIRMED son dos valores del enum en este diseño, no dos filas de una tabla de estados.

Error frecuente: usar create siempre para datos que tienen una clave única. Corrígelo con upsert o una limpieza controlada solo en la base local.

Ejercicio: registra reservas r1, r2, r3; el resultado esperado mantiene exactamente esos tres identificadores y conserva sus relaciones.

Tablas de clientes y pedidos: INNER JOIN conserva coincidencias y LEFT JOIN conserva también a Luis sin pedidos.
CursaloMapa de práctica
Relacionar tablas sin perder filas

Una clave conecta cada pedido con su cliente. INNER JOIN devuelve las coincidencias y puede producir varias filas por cliente. LEFT JOIN conserva también los clientes sin pedido y representa los campos ausentes con NULL.

Ver la infografía en tamaño completo ↗

Introducción: El Pilar de la Persistencia en Producción

En el desarrollo de aplicaciones profesionales, la base de datos no es un componente estático. Evoluciona junto con el negocio, requiriendo cambios en su estructura, ajustes en sus relaciones y una población inicial de datos de referencia. Implementar migraciones y seed de datos de manera robusta y reproducible es lo que separa un entorno de desarrollo caótico de uno listo para producción. Esta lección se centra en utilizar Prisma Migrate y los scripts de seeding para establecer un flujo de trabajo confiable que garantice la consistencia de tu esquema de datos en todos los entornos, desde el local hasta el de producción.

Dominar este proceso implica entender que las migraciones son la fuente única de verdad para el esquema de tu base de datos. Cada migración generada por Prisma es un archivo SQL que documenta un cambio específico. Por otro lado, el seed es el mecanismo para inicializar tu base de datos con datos esenciales para que la aplicación funcione correctamente, como roles de usuario, configuraciones por defecto o catálogos. Juntos, forman el kit de implementación de tu capa de datos.

Un error común es subestimar la planificación de este flujo, llevando a migraciones fallidas en producción, pérdida de datos o inconsistencias entre entornos. Aquí aprenderás a evitar esos escollos mediante prácticas como la revisión de SQL generado, el uso de transacciones y la creación de seeds idempotentes. Este conocimiento es fundamental para cualquier desarrollador backend que aspire a desplegar aplicaciones resilientes y mantenibles.

Concepto Clave: El Viaje Controlado del Esquema de Datos

Imagina que estás construyendo un rascacielos. Los planos arquitectónicos (tu schema.prisma) definen la estructura final: los cimientos, los pisos, las columnas y las conexiones eléctricas. Sin embargo, no construyes el edificio completo de una sola vez. Lo haces por fases: primero la cimentación, luego la estructura del primer piso, después el segundo, y así sucesivamente. Cada una de estas fases de construcción es una migración. Prisma Migrate traduce los cambios en tus planos (el schema) en instrucciones de construcción concretas (archivos SQL) que se aplican en un orden específico e irreversible, llevando la base de datos de un estado conocido a otro.

El seed de datos, por su parte, es el amueblado y equipamiento inicial del edificio. Una vez la estructura de un piso está lista, necesitas instalar los ascensores, poner las puertas, los extintores y los muebles de las áreas comunes para que el espacio sea habitable y útil. Estos son los datos esenciales sin los cuales la aplicación no puede operar. El seed no es opcional en un proyecto profesional; es una parte integral del setup del entorno. La clave es que este "amueblado" debe ser idempotente: puedes ejecutar el script una, dos o cien veces, y el resultado final será el mismo, sin duplicados ni errores.

Tip Profesional: Trata tu historial de migraciones como un registro sagrado. Nunca edites manualmente o elimines migraciones que ya han sido aplicadas en entornos compartidos (como staging o producción). Si necesitas revertir un cambio, crea una nueva migración que lo deshaga. Esto mantiene el historial predecible para todos los miembros del equipo.

Cómo Funciona en la Práctica: El Flujo de Trabajo Paso a Paso

El proceso comienza con un cambio en tu archivo schema.prisma. Supongamos que necesitas agregar un nuevo campo premiumUntil de tipo DateTime? al modelo User. Primero, modificas el schema. Luego, ejecutas el comando prisma migrate dev --name add_premium_until_to_user. Prisma realiza varias acciones cruciales: 1) Calcula la diferencia (diff) entre el esquema actual de la base de datos y el nuevo schema.prisma. 2) Genera un nuevo directorio con un archivo SQL que contiene las instrucciones ALTER TABLE necesarias. 3) Aplica inmediatamente esa migración a tu base de datos de desarrollo. 4) Actualiza el esquema de la sombra de Prisma para llevar el registro.

Una vez aplicadas las migraciones, es momento de poblar la base de datos. Ejecutas prisma db seed. Este comando invoca el script configurado en tu package.json, típicamente un archivo TypeScript o JavaScript. Dentro de este script, utilizas el cliente de Prisma para crear registros. La lógica debe verificar primero si los datos ya existen (por ejemplo, buscando un rol "ADMIN") para evitar duplicados. Es recomendable envolver las operaciones de seeding en una transacción para asegurar la atomicidad: o se crean todos los datos base, o no se crea ninguno, previniendo estados inconsistentes.

Para entornos de producción, el flujo es más conservador. No usas prisma migrate dev directamente en el servidor. En su lugar, usas prisma migrate deploy. Este comando es más seguro: simplemente aplica todas las migraciones que aún no han sido aplicadas, según el registro de Prisma, sin generar nuevas. La generación de migraciones es una actividad exclusiva del entorno de desarrollo. El seed en producción suele ejecutarse como parte del script de despliegue, pero con extrema precaución, a menudo solo para datos maestros que no existen.

Código en Acción: Script de Seed Idempotente y Completo

A continuación, un ejemplo real de un archivo de seed robusto para una API de blog. Observa el uso de transacciones, las comprobaciones de existencia y el manejo de relaciones.

// prisma/seed.ts
import { PrismaClient, Prisma } from '@prisma/client';
const prisma = new PrismaClient();

async function main() {
  console.log(`Iniciando seeding...`);

  // Usamos una transacción para asegurar atomicidad
  await prisma.$transaction(async (tx) => {
    // 1. Seed de Roles - Idempotente
    console.log(`Creando roles...`);
    const rolesData: Prisma.RoleCreateInput[] = [
      { name: 'ADMIN', description: 'Administrador del sistema' },
      { name: 'EDITOR', description: 'Puede editar y publicar artículos' },
      { name: 'AUTHOR', description: 'Puede escribir artículos' },
      { name: 'READER', description: 'Usuario básico' },
    ];

    for (const roleData of rolesData) {
      await tx.role.upsert({
        where: { name: roleData.name },
        update: {}, // Si existe, no cambia nada
        create: roleData,
      });
    }

    // 2. Seed de un Usuario Administrador - Solo si no existe
    const adminEmail = 'admin@blog.com';
    const existingAdmin = await tx.user.findUnique({
      where: { email: adminEmail },
    });

    if (!existingAdmin) {
      console.log(`Creando usuario administrador...`);
      const adminRole = await tx.role.findUnique({ where: { name: 'ADMIN' } });
      if (adminRole) {
        await tx.user.create({
          data: {
            email: adminEmail,
            name: 'Administrador Principal',
            hashedPassword: '$2b$10$EjemploDeHashSeguroPeroFalso123456', // En la práctica, usa bcrypt
            roleId: adminRole.id,
            profile: {
              create: {
                bio: 'El administrador principal de la plataforma.',
              },
            },
          },
        });
      }
    }

    // 3. Seed de Categorías
    console.log(`Creando categorías...`);
    const categories = ['Tecnología', 'Ciencia', 'Desarrollo Personal', 'Negocios'];
    for (const catName of categories) {
      await tx.category.upsert({
        where: { name: catName },
        update: {},
        create: { name: catName, slug: catName.toLowerCase().replace(/\s+/g, '-') },
      });
    }

    // 4. Seed de Etiquetas
    console.log(`Creando etiquetas...`);
    const tags = ['JavaScript', 'Prisma', 'Node.js', 'TypeScript', 'Bases de Datos'];
    for (const tagName of tags) {
      await tx.tag.upsert({
        where: { name: tagName },
        update: {},
        create: { name: tagName },
      });
    }
  });

  console.log(`Seeding finalizado con éxito.`);
}

main()
  .catch((e) => {
    console.error('Error durante el seeding:', e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

Para ejecutar este seed, la configuración en package.json es esencial:

{
  "name": "api-blog",
  "version": "1.0.0",
  "prisma": {
    "seed": "ts-node prisma/seed.ts"
  },
  "scripts": {
    "db-seed": "prisma db seed",
    "migrate-dev": "prisma migrate dev",
    "migrate-deploy": "prisma migrate deploy"
  },
  "dependencies": {
    "@prisma/client": "^5.0.0"
  },
  "devDependencies": {
    "prisma": "^5.0.0",
    "ts-node": "^10.9.0",
    "typescript": "^5.0.0"
  }
}

Finalmente, un ejemplo del archivo SQL de migración generado automáticamente por Prisma para agregar el campo premiumUntil:

-- Migration SQL generada por Prisma Migrate
-- Añade la columna `premiumUntil` a la tabla `User`

ALTER TABLE "User" 
ADD COLUMN "premiumUntil" TIMESTAMP(3);

-- Crear un índice opcional para consultas frecuentes
CREATE INDEX "User_premiumUntil_idx" ON "User"("premiumUntil");

Errores Comunes y Cómo Evitarlos

1. No Revisar el SQL Generado: Confiar ciegamente en la migración generada puede ser peligroso, especialmente para cambios complejos (como renombrar columnas que tienen datos). Prisma puede generar un DROP y CREATE en lugar de un ALTER, lo que implica pérdida de datos. Solución: Siempre revisa el archivo SQL en la carpeta de la migración antes de aplicarla. Usa prisma migrate dev --create-only para generar la migración sin aplicarla, revísala y luego aplícala con prisma migrate dev.

2. Seed No Idempotente: Un script de seed que solo contiene create sin verificación fallará en la segunda ejecución con errores de violación de unicidad. Solución: Utiliza operaciones upsert (como en el ejemplo) o lógica de "buscar o crear". Siempre diseña tu seed para que pueda ejecutarse múltiples veces con el mismo resultado.

3. Aplicar Migraciones en Producción con `prisma migrate dev`: Este comando está diseñado para desarrollo y puede generar nuevas migraciones basadas en el estado de tu base de datos local, causando caos en producción. Solución: En producción, CI/CD o servidores, usa exclusivamente prisma migrate deploy. Este comando solo aplica migraciones pendientes del historial.

4. Olvidar los Datos de Relaciones en el Seed: Crear un User que requiere un roleId sin haber creado primero los roles, resultará en un error de clave foránea. Solución: Planifica el orden de creación. Siempre seedea primero las entidades independientes (Roles, Categorías) y luego las dependientes (Usuarios, Artículos). Usar transacciones ayuda a mantener la integridad durante este proceso.

5. Semilla con Datos Sensibles o de Prueba en Producción: Ejecutar un seed que contenga contraseñas de ejemplo, emails falsos o datos de prueba en la base de datos de producción es un grave error de seguridad y calidad. Solución: Divide tu seed en dos partes: datos maestros esenciales (roles, configuraciones) que sí van a producción, y datos de prueba/demo que solo se cargan en desarrollo. Usa variables de entorno para condicionar la ejecución de ciertas partes del script.

Checklist de Dominio

Antes de considerar esta lección dominada, asegúrate de poder verificar cada uno de los siguientes puntos:

  • Puedo explicar la diferencia entre prisma migrate dev y prisma migrate deploy y en qué entorno usar cada uno.
  • He escrito y ejecutado un script de seed idempotente que utiliza upsert o lógica similar para evitar duplicados.
  • Sé cómo revisar el archivo SQL generado por una migración antes de aplicarla a una base de datos importante.
  • He utilizado una transacción de Prisma dentro de un script de seed para garantizar que todas las inserciones se completen o ninguna se aplique.
  • Puedo configurar correctamente el comando prisma db seed en el archivo package.json de un proyecto.
  • Comprendo la importancia del orden de creación en el seed (entidades independientes primero) y puedo implementarlo.
  • Sé cómo revertir una migración en desarrollo usando prisma migrate reset o creando una nueva migración de reversión.
  • Puedo listar al menos tres tipos de datos que son candidatos para un seed (ej. roles de usuario, categorías de producto, configuraciones del sistema).

Sigue con Prisma ORM Profesional: Schemas, Migraciones y Queries Optimizadas para Producción

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.