Laravel
Cuándo usar seeders, factories o migraciones para cargar datos
Por Víctor Peña · Publicado el
Hola, ¿cómo están? En el curso ya vimos cómo se crean los seeders y cómo funcionan los factories. Este artículo no repite eso: responde la pregunta que viene después.
Tienes datos que meter en la base de datos. ¿Van en un seeder, en un factory, en una migración o los cargas a mano?
Elegir mal no rompe nada al principio. Rompe cosas meses después, normalmente en producción.
¡Empecemos!
Las cuatro opciones
| Herramienta | Para qué es | ¿Va a producción? |
|---|---|---|
| Factory | Datos falsos para pruebas | Nunca |
| Seeder | Datos que el sistema necesita para funcionar | Sí, con cuidado |
| Migración | Datos que acompañan un cambio de estructura | Sí, siempre |
| Carga manual | Datos del cliente | Sí |
La confusión más frecuente es entre las tres primeras. Vamos una por una.
Factory: datos que no importan
Un factory genera registros con datos inventados. Su propósito es tener con qué probar.
Paciente::factory()->count(500)->create();
La clave es esta: a nadie le importa qué dice ese registro. El nombre puede ser cualquiera, la fecha cualquiera. Lo único que importa es que haya 500 pacientes para ver cómo se comporta la paginación.
Úsalo para:
- Pruebas automáticas
- Llenar el entorno local para desarrollar
- Ver cómo se ve una pantalla con datos reales
- Probar rendimiento con volumen
Nunca en producción. Un factory en un servidor real es basura mezclada con los datos del cliente, y separarla después es un trabajo horrible.
Seeder: datos que el sistema necesita
Aquí está la distinción importante, y la que casi nadie explica bien.
Hay datos que no son del cliente, pero el sistema no funciona sin ellos:
- Los estados posibles de una cita: programada, atendida, cancelada
- Los roles: administrador, doctor, recepción
- Los permisos
- Departamentos, monedas, tipos de documento
- El usuario administrador inicial
Nadie los va a cargar desde una pantalla. Vienen con el sistema.
class RolSeeder extends Seeder
{
public function run(): void
{
$roles = [
['nombre' => 'admin', 'etiqueta' => 'Administrador'],
['nombre' => 'doctor', 'etiqueta' => 'Doctor'],
['nombre' => 'recepcion', 'etiqueta' => 'Recepción'],
];
foreach ($roles as $rol) {
Rol::updateOrCreate(['nombre' => $rol['nombre']], $rol);
}
}
}
Fíjate en el updateOrCreate. Es lo que hace que este seeder se pueda ejecutar diez veces sin duplicar nada. Un seeder de datos del sistema debe poder correrse otra vez sin causar daño, porque tarde o temprano alguien lo va a hacer.
Es la misma regla que aplicamos a los comandos programados.
Separa los dos tipos de seeder
Este es el consejo más práctico del artículo:
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Siempre, en cualquier entorno
$this->call([
RolSeeder::class,
PermisoSeeder::class,
EstadoCitaSeeder::class,
AdminSeeder::class,
]);
// Solo en desarrollo
if (app()->environment('local')) {
$this->call([
PacientesDePruebaSeeder::class,
CitasDePruebaSeeder::class,
]);
}
}
}
Sin esa separación, el día que ejecutes los seeders en producción vas a meter quinientos pacientes inventados en la base de datos del cliente. Pasa, y es incómodo de explicar.
Migración: datos atados a un cambio de estructura
Este caso es el que menos se conoce y el que evita más problemas.
Cuando añades una columna nueva a una tabla que ya tiene datos, hay que decidir qué pasa con los registros existentes:
public function up(): void
{
Schema::table('citas', function (Blueprint $tabla) {
$tabla->string('codigo')->nullable()->after('id');
});
// rellenar los registros que ya existían
Cita::whereNull('codigo')->each(function (Cita $cita) {
$cita->update(['codigo' => 'CITA-' . str_pad($cita->id, 6, '0', STR_PAD_LEFT)]);
});
Schema::table('citas', function (Blueprint $tabla) {
$tabla->string('codigo')->nullable(false)->change();
});
}
Por qué en la migración y no en un seeder: porque ese dato solo tiene sentido junto a ese cambio de estructura. Va a ejecutarse una vez, en el momento exacto en que la columna se crea, en todos los entornos, en orden.
Un seeder no te garantiza nada de eso.
La regla: si el dato existe por culpa de un cambio de estructura, va en la migración.
Carga manual: los datos del cliente
Los pacientes reales, los productos reales, los precios reales. Eso lo carga el cliente desde el sistema, o se importa desde sus planillas con lo que vimos en la lección de Excel.
No va en un seeder. Un seeder con datos reales del cliente en el repositorio significa que la información de sus pacientes está en GitHub.
Cómo decidir en diez segundos
Cuatro preguntas, en orden:
1. ¿El dato es inventado y da igual qué diga? → Factory.
2. ¿Existe porque acabas de cambiar la estructura de una tabla? → Migración.
3. ¿El sistema no funciona sin él, y no lo carga ningún usuario? → Seeder.
4. ¿Es información real del cliente? → Carga manual o importación.
El error que cuesta caro
Es este, y merece su propia sección porque destruye datos:
php artisan migrate:fresh --seed
Ese comando borra todas las tablas y las vuelve a crear. En desarrollo es comodísimo. En producción borra el negocio de tu cliente.
Tres protecciones que valen la pena:
Que el usuario de base de datos de producción no pueda borrar tablas. Es la única barrera de verdad.
Un aviso en el seeder:
public function run(): void
{
if (app()->isProduction()) {
$this->command->error('Este seeder no se ejecuta en producción.');
return;
}
// ...
}
Y respaldos automáticos verificados, como vimos en la lección de despliegue. No para evitar el error, sino para sobrevivirlo.
Seeders combinados con factories
Los dos se llevan bien, y esta es la forma correcta de usarlos juntos:
class CitasDePruebaSeeder extends Seeder
{
public function run(): void
{
$doctores = Doctor::factory()->count(5)->create();
Paciente::factory()
->count(200)
->has(
Cita::factory()
->count(3)
->state(fn () => ['doctor_id' => $doctores->random()->id])
)
->create();
}
}
Doscientos pacientes con tres citas cada uno, repartidas entre cinco doctores. Un entorno de desarrollo realista en un comando.
Y este seeder es exactamente el que va dentro del if (app()->environment('local')).
Un detalle de rendimiento
Si tu seeder inserta miles de registros y va lento, casi siempre es porque los eventos del modelo se disparan uno por uno:
Paciente::withoutEvents(function () {
Paciente::factory()->count(10000)->create();
});
O directamente, saltándose Eloquent:
DB::table('pacientes')->insert($filas); // una sola consulta
La diferencia entre diez mil inserciones individuales y una masiva son minutos contra segundos.
Para cerrar
La distinción que ordena todo esto es simple: factory para datos que no importan, seeder para datos que el sistema necesita, migración para datos que nacen de un cambio de estructura, y carga manual para lo del cliente.
Y una costumbre que evita el peor accidente: separa en el DatabaseSeeder lo que va siempre de lo que va solo en local. Son cinco líneas y el día que ejecutes los seeders en el servidor equivocado, te van a salvar.
El cómo está en las lecciones de seeders y factories del curso de Laravel.
Saludos y éxitos.
Norvic Software
Desarrollamos el software que tu empresa necesita
Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.
- Chatbots con Inteligencia Artificial
- Creación de agentes de IA
- Integración de IA en tus sistemas
- Desarrollo de software a medida
- Aplicaciones móviles iOS y Android
- Consultoría y asesoramiento técnico
Cotización sin costo · Respuesta directa por WhatsApp