⛩️ El Arte de la Creación
Un Ninja sin datos de prueba es como un samurái sin katana. Seeds y Factories son las herramientas que te permiten crear universos de prueba con un solo comando.
🎯 Definición Teórica: ¿Por Qué Seeds y Factories?
El Problema
Desarrollar sin datos reales causa:
- Bugs que solo aparecen en producción
- Dificultad para probar edge cases
- UI mal configurada (sin contenido)
- Incapacidad de reproducir problemas
El Por Qué Técnico
| Método | Ventaja | Uso |
|---|---|---|
| Insert manual | Control total | Un registro |
| Seed | Repetible | Dataset base |
| Factory | Datos dinámicos | Testing masivo |
| Faker | Datos realistas | Variedad |
La Diferencia
Seed → "Poblar la base con datos iniciales"
Factory → "Crear instancias de un modelo"
Faker → "Generar datos falsos realistas"
🗡️ Guía de Implementación
1. Estructura
database/
├── migrations/
├── seeders/
│ ├── DatabaseSeeder.php
│ └── UserSeeder.php
└── factories/
└── UserFactory.php
2. Crear una Factory
<?php
// database/factories/UserFactory.php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
protected $model = User::class;
public function definition(): array
{
return [
'name' => fake()->name(), // Nombre falso realista
'email' => fake()->unique()->safeEmail(),
'password' => bcrypt('password'), // Siempre la misma para tests
'role' => fake()->randomElement(['user', 'admin', 'editor']),
'avatar' => fake()->imageUrl(200, 200, 'people'),
'bio' => fake()->paragraph(),
'birthdate' => fake()->date('Y-m-d', '-18 years'),
'activo' => fake()->boolean(80), // 80% probabilidad true
'email_verified_at' => now(),
'created_at' => fake()->dateTimeBetween('-2 years', 'now'),
'updated_at' => now(),
];
}
// States: Variations de la factory
public function admin(): static
{
return $this->state(fn (array $attributes) => [
'role' => 'admin',
'bio' => 'Administrator del sistema',
]);
}
public function inactive(): static
{
return $this->state(fn (array $attributes) => [
'activo' => false,
]);
}
public function verified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => now(),
]);
}
}
3. Crear un Seeder
<?php
// database/seeders/UserSeeder.php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class UserSeeder extends Seeder
{
public function run(): void
{
// ═══════════════════════════════════════════════════════
// MÉTODOS DE CREACIÓN
// ═══════════════════════════════════════════════════════
// 1. Crear UN usuario
User::create([
'name' => 'Admin',
'email' => 'admin@8devmx.com',
'password' => bcrypt('password'),
'role' => 'admin',
]);
// 2. Crear MÚLTIPLES con create()
User::factory()->count(10)->create();
// 3. Crear y obtener (sin guardar)
$users = User::factory()->count(5)->make();
// 4. Usar states
User::factory()
->count(5)
->admin()
->create();
// 5. Con callbacks
User::factory()
->count(20)
->create()
->each(function ($user) {
// Ejecutar después de crear cada usuario
// Ej: asignar roles, crear perfiles, etc.
});
}
}
4. Faker: El Generador de Datos
// ╔═══════════════════════════════════════════════════════════════╗
// ║ MÉTODOS DE FAKER ║
// ╠═══════════════════════════════════════════════════════════════╣
// ║ name() ║ Nombre completo ║
// ║ firstName() ║ Primer nombre ║
// ║ lastName() ║ Apellido ║
// ║ email() ║ Email ║
// ║ safeEmail() ║ Email @example.com ║
// ║ password() ║ Contraseña (bcrypt) ║
// ║ phoneNumber() ║ Teléfono ║
// ║ address() ║ Dirección completa ║
// ║ city() ║ Ciudad ║
// ║ country() ║ País ║
// ║ zipCode() ║ Código postal ║
// ║ company() ║ Nombre empresa ║
// ║ jobTitle() ║ Título trabajo ║
// ║ text($maxChars) ║ Texto aleatorio ║
// ║ paragraph($nbSentences) ║ Párrafo ║
// ║ sentence($nbWords) ║ Oración ║
// ║ randomNumber($nbDigits) ║ Número aleatorio ║
// ║ randomFloat() ║ Decimal aleatorio ║
// ║ date() ║ Fecha ║
// ║ dateTime() ║ Fecha y hora ║
// ║ url() ║ URL ║
// ║ imageUrl() ║ URL de imagen ║
// ║ uuid() ║ UUID único ║
// ║ boolean($probability) ║ Booleano ║
// ║ randomElement() ║ Elemento aleatorio de array ║
// ║ randomElements() ║ Múltiples elementos ║
// ╚═══════════════════════════════════════════════════════════════╝
5. DatabaseSeeder Principal
<?php
// database/seeders/DatabaseSeeder.php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// ORDEN IMPORTA: Dependencias primero
$this->call([
// 1. Roles y permisos primero
RoleSeeder::class,
// 2. Usuarios (referencia a roles)
UserSeeder::class,
// 3. Categorías
CategorySeeder::class,
// 4. Posts (referencia a usuarios y categorías)
PostSeeder::class,
// 5. Comentarios (referencia a posts y usuarios)
CommentSeeder::class,
// 6. Datos de configuración
SettingSeeder::class,
]);
}
}
6. Relaciones en Factories
<?php
// Factories con relaciones
// PostFactory.php
class PostFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->sentence(6),
'content' => fake()->paragraphs(3, true),
'user_id' => User::factory(), // Crea usuario automáticamente
'category_id' => Category::factory(), // Crea categoría
'published' => true,
];
}
}
// CommentFactory.php
class CommentFactory extends Factory
{
public function definition(): array
{
return [
'content' => fake()->paragraph(),
'user_id' => User::factory(),
'post_id' => Post::factory(),
'approved' => fake()->boolean(90),
];
}
}
🥷 Reto Ninja
Nivel: Jonin
Misión 1: Factory Completa con Estados
// Crea una Factory para Productos con:
// - Estados: published, draft, archived
// - Relaciones: category, user (vendor)
// - Datos realistas
class ProductFactory extends Factory
{
protected $model = Product::class;
public function definition(): array
{
$precio = fake()->randomFloat(2, 10, 1000);
return [
'name' => fake()->unique()->words(3, true),
'slug' => fake()->unique()->slug(),
'description' => fake()->paragraph(),
'price' => $precio,
'stock' => fake()->numberBetween(0, 100),
'category_id' => Category::factory(),
'user_id' => User::factory(),
'featured' => fake()->boolean(20),
'created_at' => fake()->dateTimeBetween('-1 year'),
];
}
public function published(): static
{
return $this->state(fn() => [
'published_at' => now(),
'status' => 'published',
]);
}
public function outOfStock(): static
{
return $this->state(fn() => [
'stock' => 0,
]);
}
public function featured(): static
{
return $this->state(fn() => [
'featured' => true,
'discount_price' => $this->faker->randomFloat(2, 10, 500),
]);
}
}
Misión 2: Seed con Datos Relacionales
// DatabaseSeeder.php - Orden correcto
public function run(): void
{
// Primero: Roles
$roles = Role::factory()->createMany([
['name' => 'admin', 'description' => 'Administrator'],
['name' => 'vendor', 'description' => 'Tienda vendedora'],
['name' => 'customer', 'description' => 'Cliente'],
]);
// Segundo: Usuarios con roles
User::factory()
->count(5)
->admin()
->create();
User::factory()
->count(20)
->vendor()
->create();
// Tercero: Productos
Product::factory()
->count(100)
->published()
->create();
}
Misión 3: Ejecutar y Verificar
# ╔═══════════════════════════════════════════════════════════════╗
# ║ COMANDOS DE SEEDING ║
# ╠═══════════════════════════════════════════════════════════════╣
# ║ php artisan migrate:fresh ║ Reset + migrate ║
# ║ php artisan migrate:fresh --seed ║ + ejecutar seeds ║
# ║ php artisan db:seed ║ Ejecutar seeds ║
# ║ php artisan db:seed --class=UserSeeder║ Seed específico ║
# ║ php artisan migrate:fresh --seeder=UserSeeder║ Solo User ║
# ╚═══════════════════════════════════════════════════════════════╝
# Desarrollo: poblar rápido
php artisan migrate:fresh --seed
# Testing: cada test tiene datos frescos
php artisan migrate:fresh --seed --env=testing
📜 Técnicas Avanzadas
Events en Seeders
// After creating users
User::factory()
->count(10)
->create()
->each(function ($user) {
// Evento: crear perfil
Profile::factory()->create(['user_id' => $user->id]);
// Evento: asignar rol aleatorio
$user->roles()->attach(Role::all()->random()->id);
});
Pagination con Seeds
// Poblar con paginación
User::factory()
->count(1000)
->create()
->chunk(100)
->each(function ($chunk) {
foreach ($chunk as $user) {
// Procesar en grupos de 100
}
});
Seeders Condicionales
public function run(): void
{
// Soloseedear si no hay datos
if (User::count() > 0) {
$this->command->info('Users already exist, skipping...');
return;
}
User::factory()->count(50)->create();
}
🎓 Conclusión del Maestro
“Los datos de prueba son el espejo de tu aplicación. Si el espejo está roto, tu aplicación estará ciega en producción. Domina Seeds y Factories.” — Maestro de Laravel
✅ Checklist de Dominio
- Crear Factory con definición básica
- Implementar states (variaciones)
- Configurar relaciones en Factories
- Ejecutar seed completo
- Poblar con 1000+ registros
- Verificar con DB
Recompensa XP: 75 XP ⚔️