Sembrar Base de Datos
Cómo sembrar bases de datos con datos realistas para desarrollo, testing y staging usando scripts de seed, migraciones y factories en PostgreSQL, MongoDB y Prisma.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Descripción General
Una base de datos sembrada es la forma más rápida de incorporar nuevos desarrolladores, reproducir bugs y ejecutar tests de integración que dependen de distribuciones de datos realistas. Sin seeding, cada entorno comienza vacío, obligando a los desarrolladores a crear manualmente cuentas, órdenes y relaciones antes de poder testear cualquier capacidad. Lo siguiente demuestra estrategias de seeding seguras y repetibles que no polucionan producción.
Cuándo Usar
-
For alternatives, see Complete Guide to MongoDB Indexing.
-
Incorporar nuevos desarrolladores que necesitan una base de datos local funcional en minutos
-
Tests de integración y E2E que requieren que usuarios, productos o transacciones existan
-
Entornos de staging que deberían reflejar distribuciones de datos de producción
-
Testing de carga con volúmenes y relaciones de datos realistas
-
Demostrar capacidades a stakeholders sin crear datos demo manualmente
Cuándo NO Usar
- Siembra de bases de datos de producción — usa migraciones controladas y scripts de importación en su lugar
- Entornos con PII o datos sensibles — nunca siembres datos reales de clientes desde exports
- Sistemas donde el script de seed toma más que el timeout de CI (típicamente 10 minutos)
- Microservicios con arquitecturas event-sourced donde el seeding debe emitir eventos de dominio
Implementación Paso a Paso
PostgreSQL (SQL + pg)
# Directorio: db/seeds/
# 01_users.sql
INSERT INTO users (email, name, role, created_at)
VALUES
('admin@example.com', 'Admin User', 'admin', NOW()),
('user1@example.com', 'Alice', 'user', NOW()),
('user2@example.com', 'Bob', 'user', NOW())
ON CONFLICT (email) DO NOTHING;
# 02_products.sql
INSERT INTO products (sku, name, price, stock)
VALUES
('SKU-001', 'Wireless Mouse', 29.99, 150),
('SKU-002', 'Mechanical Keyboard', 89.99, 75),
('SKU-003', 'USB-C Hub', 49.99, 200)
ON CONFLICT (sku) DO NOTHING;
# 03_orders.sql (con referencias)
INSERT INTO orders (user_id, total, status, created_at)
SELECT id, 119.98, 'completed', NOW() - INTERVAL '2 days'
FROM users WHERE email = 'user1@example.com';
// seed.js — runner Node.js usando pg
import { Client } from 'pg';
import { readdirSync, readFileSync } from 'fs';
import { join } from 'path';
async function seed() {
const client = new Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
const seedsDir = './db/seeds';
const files = readdirSync(seedsDir)
.filter(f => f.endsWith('.sql'))
.sort();
for (const file of files) {
const sql = readFileSync(join(seedsDir, file), 'utf8');
console.log(`Ejecutando: ${file}`);
await client.query(sql);
}
await client.end();
console.log('Seeding completo');
}
seed().catch(console.error);
# Ejecutar con protección de entorno
if [ "$NODE_ENV" != "production" ]; then
node db/seed.js
else
echo "Rehusando sembrar base de datos de producción"
exit 1
fi
MongoDB (mongosh / Node.js)
// seeds/users.js
const { MongoClient } = require('mongodb');
async function seed() {
const client = new MongoClient(process.env.MONGODB_URI);
await client.connect();
const db = client.db('myapp');
// Idempotente: eliminar y recrear, o usar inserts ordenados con upsert
const users = db.collection('users');
await users.deleteMany({ email: { $regex: '@example\\.com$' } });
await users.insertMany([
{ email: 'admin@example.com', name: 'Admin', role: 'admin', createdAt: new Date() },
{ email: 'user1@example.com', name: 'Alice', role: 'user', createdAt: new Date() }
]);
// Productos con reviews embebidas
const products = db.collection('products');
await products.deleteMany({ sku: /^SKU-/ });
await products.insertMany([
{
sku: 'SKU-001',
name: 'Wireless Mouse',
price: 29.99,
reviews: [
{ userId: 'user1@example.com', rating: 5, comment: 'Great mouse' }
]
}
]);
// Crear índices que la aplicación espera
await users.createIndex({ email: 1 }, { unique: true });
await products.createIndex({ sku: 1 }, { unique: true });
await client.close();
}
seed().catch(console.error);
Prisma (TypeScript)
// prisma/seed.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// Upsert asegura idempotencia
const admin = await prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
name: 'Admin User',
role: 'ADMIN'
}
});
const categories = await prisma.$transaction(
['Electronics', 'Books', 'Home'].map(name =>
prisma.category.upsert({
where: { name },
update: {},
create: { name }
})
)
);
const product = await prisma.product.create({
data: {
sku: 'SKU-001',
name: 'Wireless Mouse',
price: 29.99,
categoryId: categories[0].id,
stock: { create: { quantity: 150 } }
}
});
console.log(`Creado ${admin.name}, ${product.name}`);
}
main()
.catch(e => { console.error(e); process.exit(1); })
.finally(() => prisma.$disconnect());
// package.json
{
"scripts": {
"db:seed": "ts-node prisma/seed.ts"
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
}
# Comando nativo de seed de Prisma
npx prisma db seed
# Resetear base de datos y re-sembrar (solo desarrollo)
npx prisma migrate reset --force
Python (SQLAlchemy + Alembic)
# scripts/seed_database.py
import os
import sys
from sqlalchemy.orm import Session
from app.models import User, Product, Order
from app.database import SessionLocal
from app.core.security import get_password_hash
def seed():
if os.getenv("ENV") == "production":
raise RuntimeError("No se puede sembrar base de datos de producción")
db: Session = SessionLocal()
try:
# Seeding idempotente
if db.query(User).filter(User.email == "admin@example.com").first():
print("Base de datos ya sembrada")
return
admin = User(
email="admin@example.com",
name="Admin User",
role="admin",
hashed_password=get_password_hash("changeme")
)
db.add(admin)
products = [
Product(sku="SKU-001", name="Wireless Mouse", price=29.99, stock=150),
Product(sku="SKU-002", name="Keyboard", price=89.99, stock=75),
]
db.add_all(products)
db.flush()
order = Order(
user_id=admin.id,
total=119.98,
status="completed"
)
db.add(order)
db.commit()
print("Seeding completo")
finally:
db.close()
if __name__ == "__main__":
seed()
Lo que funciona
- Haz los seeds idempotentes. Usa
ON CONFLICT DO NOTHING,upserto verificaciones de existencia para que ejecutar el script de seed dos veces no cree duplicados ni falle. - Nunca siembres producción. Protege los scripts de seed con una verificación de entorno. Los datos de producción deberían entrar a través de migraciones controladas, herramientas de admin o pipelines ETL.
- Mantén los datos de seed realistas pero pequeños. 10-50 filas representativas por tabla son suficientes para desarrollo. Usa factories (no scripts de seed) para testing de carga que necesita millones de filas.
- Versiona los archivos de seed como migraciones. Nombrarlos
01_users.sql,02_products.sqlasegura que corren en orden determinístico y pueden ser trackeados en git. - Siembra en CI para tests de integración. Un paso
db:seedantes del suite de test asegura que cada ejecución de CI comienza desde un estado conocido.
Errores Comunes
- Hardcodear IDs auto-incrementales. Insertar
id = 1en una columna auto-incremental puede causar conflictos cuando la aplicación posteriormente crea registros. Deja que la base de datos asigne IDs o usa UUIDs. - Sembrar sin orden de foreign keys. Insertar una orden antes de que el usuario exista causa una violación de foreign key. Ordena archivos de seed por grafo de dependencias.
- Olvidar crear índices. El seeding evade la aplicación, por lo que los índices que la app espera pueden no existir si el script de seed omite declaraciones
CREATE INDEX. - Usar dumps de producción como seeds. Un dump SQL de producción puede contener PII, datos sensibles GDPR o IDs internos que no deberían estar en git o máquinas de desarrollo.
- Seeds no determinísticos. Datos de seed generados aleatoriamente hacen imposible reproducir bugs entre entornos. Usa una seed fija para generadores aleatorios en scripts de seed.
Errores Comunes Adicionales
- Ejecutar scripts de seed contra bases de datos de producción — siempre añade un check de entorno que aborte si
NODE_ENV === 'production' - No limpiar datos viejos antes de seedear — usa
TRUNCATEoDELETEen una transacción antes de insertar datos frescos - Usar datos random sin un seed fijo — los tests se vuelven no reproducibles y fallan intermitentemente
- Hardcodear seed data en archivos de migración — mantén los scripts de seed separados de las migraciones de schema por claridad
- No verificar idempotencia en scripts de seed — ejecutar seed dos veces duplica registros a menos que uses
INSERT ... ON CONFLICT DO NOTHING - Seedear passwords en plaintext — siempre hashea passwords en scripts de seed usando la misma config de bcrypt/argon2 que producción
- No envolver operaciones de seed en una transacción — un fallo a mitad de camino deja datos parciales que rompen las restricciones de foreign key
- No usar funciones factory para seed data — objetos JSON hardcodeados son difíciles de mantener y no pueden ser parametrizados para diferentes entornos
- No limpiar secuencias de auto-increment después del seeding — los IDs empiezan desde donde el seed los dejó, causando confusión en assertions de test que esperan IDs específicos
- No documentar las relaciones de seed data — los nuevos miembros del equipo no pueden entender qué registros dependen de cuáles sin un data dictionary o ERD
- No versionar los scripts de seed — cuando el schema cambia, los scripts de seed antiguos pueden fallar silenciosamente o insertar datos inconsistentes
- No separar seed data por entorno — usar datos similares a producción en entornos de test puede causar problemas de privacidad y romper principios de data minimization
- No usar un seed runner CLI — scripts ad-hoc son difíciles de reproducir y documentar, usa un runner dedicado como
knex seed:runo un CLI custom con opciones claras
¿Cómo hago seed de datos relacionados con foreign keys?
Inserta los registros padre primero, captura sus IDs, luego inserta los registros hijo con esos IDs. Usa una función factory que retorne los IDs creados. Para datasets grandes, deshabilita las verificaciones de foreign key durante el seeding y rehabilítalas después — esto es más rápido pero requiere ordering cuidadoso.
¿Debo usar los mismos datos de seed para dev y test?
No. Los datos de seed de dev deben ser realistas y suficientemente grandes para testear paginación y búsqueda en la UI. Los datos de seed de test deben ser mínimos y deterministas — solo lo que cada caso de test necesita. Compartir seed data entre dev y test crea acoplamiento y hace los tests frágiles.
Related Resources
Generar Datos de Test
Cómo generar datos de test realistas y deterministas con Faker, factory-boy y generadores type-aware para suites de test confiables en Python, JavaScript y Java.
RecipeConfigurar Fixtures de Test
Cómo gestionar fixtures de test con patrones factory, hooks de setup/teardown y datos deterministas para tests unitarios e integración confiables en Python, JavaScript y Java.
GuideSharding de Base de Datos
Guía práctica sobre sharding de base de datos: elegir claves de shard, enrutar consultas, rebalancear datos y evitar errores comunes al escalar más allá de un solo nodo de base de datos.
Preguntas frecuentes
- ¿Cuál es la diferencia entre seeding y migrar una base de datos?
- Las migraciones cambian la estructura del esquema (tablas, índices). El seeding popula la base de datos con datos de referencia o registros de prueba después de que el esquema está listo.
- ¿Debo hacer seeding en bases de datos de producción?
- Solo con datos de referencia necesarios para que la aplicación funcione, como roles, monedas o configuración. Nunca hagas seeding de usuarios reales o datos de prueba en producción.
- ¿Cómo mantengo los datos de seed deterministas?
- Usa IDs fijos, scripts de inserción ordenados y factories que produzcan la misma salida para la misma entrada. Esto hace que los tests sean reproducibles en cualquier entorno.