⛩️ 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étodoVentajaUso
Insert manualControl totalUn registro
SeedRepetibleDataset base
FactoryDatos dinámicosTesting masivo
FakerDatos realistasVariedad

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 ⚔️