Implementar Migraciones y Seed de Datos

Lectura
25 min~9 min lectura

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 = '[email protected]';
    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).
De lección a portfolio

Convertí esta lección en una prueba técnica visible.

Una app pequeña publicada, con README y decisiones explicadas, funciona mejor que una lista de tecnologías sueltas.

Paso 1

Creá una demo mínima que use el concepto de la lección.

Paso 2

Escribí un README corto con objetivo, stack, decisión técnica y mejora futura.

Paso 3

Publicá la demo y enlazala desde tu perfil profesional.

Newsletter Cursalo

Recibí rutas y cursos nuevos

Sumate para recibir recursos orientados a empleo y portfolio.

  • Rutas de empleo
  • Cursos prácticos
  • Portfolio y entrevistas

Sin spam. También podés entrar con tu cuenta para guardar progreso. Iniciá sesión