Seeders: datos iniciales para la base de datos
Un Seeder es una clase PHP que inserta datos en la base de datos. Son ideales para:
- Datos de referencia que toda la aplicación necesita (roles, categorías, países…)
- Datos de prueba para desarrollo
- Datos demo para entornos de staging
Crear un Seeder
php artisan make:seeder CategoriaSeeder
<?php
// database/seeders/CategoriaSeeder.php
namespace Database\Seeders;
use App\Models\Categoria;
use Illuminate\Database\Seeder;
class CategoriaSeeder extends Seeder
{
public function run(): void
{
$categorias = [
['nombre' => 'Electrónica', 'slug' => 'electronica', 'activa' => true],
['nombre' => 'Ropa', 'slug' => 'ropa', 'activa' => true],
['nombre' => 'Hogar', 'slug' => 'hogar', 'activa' => true],
['nombre' => 'Deportes', 'slug' => 'deportes', 'activa' => true],
];
foreach ($categorias as $datos) {
Categoria::firstOrCreate(['slug' => $datos['slug']], $datos);
}
}
}
DatabaseSeeder: el seeder raíz
<?php
// database/seeders/DatabaseSeeder.php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
CategoriaSeeder::class,
UserSeeder::class,
ProductoSeeder::class,
]);
}
}
Ejecutar Seeders
# Ejecutar DatabaseSeeder completo
php artisan db:seed
# Ejecutar un seeder específico
php artisan db:seed --class=CategoriaSeeder
# Refresh de migraciones + seeder en un solo comando
php artisan migrate:fresh --seed
# En tests (Pest/PHPUnit)
$this->seed(CategoriaSeeder::class);
$this->seed(); // Ejecuta DatabaseSeeder
Factories: generación de datos realistas con Faker
Las Factories generan instancias de modelos con datos falsos pero realistas, usando la librería Faker. Son fundamentales para testing.
Crear una Factory
php artisan make:factory ProductoFactory --model=Producto
<?php
// database/factories/ProductoFactory.php
namespace Database\Factories;
use App\Models\Categoria;
use Illuminate\Database\Eloquent\Factories\Factory;
class ProductoFactory extends Factory
{
public function definition(): array
{
return [
'nombre' => fake()->words(3, true),
'descripcion' => fake()->paragraph(2),
'precio' => fake()->randomFloat(2, 5.0, 999.99),
'stock' => fake()->numberBetween(0, 200),
'activo' => true,
'categoria_id' => Categoria::factory(),
'sku' => strtoupper(fake()->bothify('??-####-??')),
'created_at' => fake()->dateTimeBetween('-1 year', 'now'),
];
}
}
El modelo debe usar HasFactory
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Producto extends Model
{
use HasFactory;
// ...
}
Crear instancias con la Factory
// Crear y guardar en BD
$producto = Producto::factory()->create();
// Crear sin guardar (solo el objeto PHP)
$producto = Producto::factory()->make();
// Crear múltiples
$productos = Producto::factory()->count(10)->create();
// Pasar atributos concretos
$productoBarato = Producto::factory()->create(['precio' => 9.99]);
// Crear solo el array de datos
$datos = Producto::factory()->raw();
Estados de Factory
Los estados permiten crear variantes del modelo con diferentes configuraciones:
class ProductoFactory extends Factory
{
public function definition(): array
{
return [
'nombre' => fake()->words(3, true),
'precio' => fake()->randomFloat(2, 5.0, 999.99),
'stock' => fake()->numberBetween(1, 200),
'activo' => true,
];
}
// Estado: producto sin stock
public function sinStock(): static
{
return $this->state(fn (array $attr) => ['stock' => 0]);
}
// Estado: producto en oferta
public function enOferta(float $descuento = 20.0): static
{
return $this->state(fn (array $attr) => [
'precio_original' => $attr['precio'],
'precio' => round($attr['precio'] * (1 - $descuento / 100), 2),
'en_oferta' => true,
]);
}
// Estado: producto desactivado
public function inactivo(): static
{
return $this->state(fn (array $attr) => ['activo' => false]);
}
}
// Usar estados
$productoSinStock = Producto::factory()->sinStock()->create();
$productoOferta = Producto::factory()->enOferta(30)->create();
$productoInactivo = Producto::factory()->inactivo()->create();
// Combinar estados
$ofertaAgotada = Producto::factory()->enOferta()->sinStock()->create();
Factories con relaciones
// Crear un usuario con sus pedidos asociados
$usuario = User::factory()
->has(Pedido::factory()->count(3), 'pedidos')
->create();
// Shorthand con el nombre de la relación en plural
$usuario = User::factory()->hasPedidos(3)->create();
// Crear un pedido con su usuario (belongsTo)
$pedido = Pedido::factory()
->for(User::factory(), 'usuario')
->create();
// Crear producto con categoría específica
$categoria = Categoria::factory()->create(['nombre' => 'Electrónica']);
$producto = Producto::factory()->create(['categoria_id' => $categoria->id]);
Factories en tests
// tests/Feature/PedidoTest.php
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('calcula el total del pedido correctamente', function () {
$productos = Producto::factory()->count(3)->create();
$pedido = Pedido::factory()
->hasLineas(3, function (array $attr, Pedido $pedido) use ($productos) {
static $i = 0;
return [
'producto_id' => $productos[$i++]->id,
'cantidad' => 2,
'precio' => $productos[$i - 1]->precio,
];
})
->create();
$totalEsperado = $pedido->lineas->sum(fn($l) => $l->cantidad * $l->precio);
expect($pedido->calcularTotal())->toBe($totalEsperado);
});
it('solo muestra productos activos en el catálogo', function () {
Producto::factory()->count(5)->create(['activo' => true]);
Producto::factory()->count(3)->inactivo()->create();
$response = $this->get('/productos');
$response->assertStatus(200);
expect(Producto::activos()->count())->toBe(5);
});
Faker en español
Para generar datos en español, configura el locale de Faker en config/app.php:
// config/app.php
'faker_locale' => 'es_ES',
Con esto, fake()->name() genera nombres españoles, fake()->address() direcciones españolas, etc.
RefreshDatabase vs. DatabaseTransactions
| Trait | Comportamiento | Cuándo usar |
|-------|---------------|------------|
| RefreshDatabase | Ejecuta migrate:fresh antes de cada test | Cuando necesitas el esquema limpio (más lento) |
| DatabaseTransactions | Envuelve cada test en una transacción y hace rollback | Cuando el esquema no cambia entre tests (más rápido) |
| RefreshDatabase con $seed = true | También ejecuta los seeders | Cuando el test necesita datos de referencia |
uses(RefreshDatabase::class);
// Si también necesitas los seeders:
class MiTest extends TestCase
{
protected $seed = true;
protected $seeder = CategoriaSeeder::class;
}
Los Seeders y Factories son herramientas imprescindibles para trabajar con datos de prueba en Laravel: los Seeders pueblan entornos de forma predecible y los Factories generan datos variados y realistas para tests robustos.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Laravel
Documentación oficial de Laravel
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Laravel es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Laravel
Explora más contenido relacionado con Laravel y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Crear y ejecutar Seeders para poblar la base de datos con datos iniciales. Definir Factories con Faker para generar datos de prueba realistas. Usar estados en las Factories para escenarios específicos. Combinar Factories con relaciones Eloquent (hasMany, belongsTo). Usar RefreshDatabase y Factories en tests con Pest y PHPUnit.