8: Model Factories en Laravel 12 - Guía Práctica

 

Introducción

Los Model Factories son una herramienta poderosa en Laravel que te permite generar datos de prueba de manera rápida y consistente. Utilizan el componente Faker para crear datos realistas y te ayudan a poblar tu base de datos para pruebas, desarrollo y demostraciones. En este tutorial, aprenderás todo sobre Model Factories en Laravel 12.

Índice

  1. ¿Qué son los Model Factories?

  2. Configuración Inicial

  3. Creación de Factories

  4. Tipos de Factories

  5. Relaciones entre Factories

  6. Métodos de Factory

  7. Uso en Seeders

  8. Factories para Pruebas

  9. Personalización Avanzada

  10. Ejercicio Práctico: Sistema de Pastelería


1. ¿Qué son los Model Factories?

Los Model Factories son clases que definen cómo crear instancias de tus modelos con datos de prueba. Trabajan con el componente Faker para generar datos realistas y te permiten:

  • Generar grandes cantidades de datos de prueba

  • Mantener la consistencia en tus datos

  • Definir diferentes "estados" para tus modelos

  • Crear relaciones entre modelos fácilmente

Estructura básica de un Factory

php
<?php

namespace Database\Factories;

use App\Models\Pastel;
use Illuminate\Database\Eloquent\Factories\Factory;

class PastelFactory extends Factory
{
    protected $model = Pastel::class;

    public function definition(): array
    {
        return [
            'nombre' => $this->faker->name(),
            'sabor' => $this->faker->randomElement(['chocolate', 'vainilla', 'cheesecake']),
            'precio' => $this->faker->randomFloat(2, 10, 100),
            'created_at' => now(),
            'updated_at' => now(),
        ];
    }
}

2. Configuración Inicial

Instalación y verificación

bash
# Laravel 12 ya incluye los factories por defecto
# Verificar que está instalado en composer.json
composer require --dev laravel/legacy-factories

# Crear un nuevo proyecto con factories
composer create-project laravel/laravel pasteleria-app

Estructura de carpetas

text
database/
├── factories/
│   ├── PastelFactory.php
│   ├── UserFactory.php
│   └── ...
├── migrations/
│   └── ...
└── seeders/
    ├── DatabaseSeeder.php
    └── ...

Archivo de configuración

php
// config/app.php

'faker_locale' => 'es_ES', // Para datos en español

3. Creación de Factories

Comando para crear un Factory

bash
# Crear factory para un modelo específico
php artisan make:factory PastelFactory --model=Pastel

# Crear factory sin modelo específico
php artisan make:factory PastelFactory

# Crear factory con modelo y migración
php artisan make:factory PastelFactory --model=Pastel --migration

Factory básico

php
<?php

namespace Database\Factories;

use App\Models\Pastel;
use Illuminate\Database\Eloquent\Factories\Factory;

class PastelFactory extends Factory
{
    protected $model = Pastel::class;

    public function definition(): array
    {
        return [
            'nombre' => 'Pastel ' . $this->faker->firstName() . ' ' . $this->faker->lastName(),
            'sabor' => $this->faker->randomElement(['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco']),
            'precio' => $this->faker->randomFloat(2, 50, 500),
            'disponible' => $this->faker->boolean(80),
            'descripcion' => $this->faker->sentence(10),
            'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
            'updated_at' => $this->faker->dateTimeBetween('-1 month', 'now'),
        ];
    }
}

Factory con datos personalizados

php
<?php

namespace Database\Factories;

use App\Models\Pastel;
use Illuminate\Database\Eloquent\Factories\Factory;

class PastelFactory extends Factory
{
    protected $model = Pastel::class;

    public function definition(): array
    {
        // Definir sabores disponibles
        $sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
        $sabor = $this->faker->randomElement($sabores);
        
        return [
            'nombre' => $this->generarNombre($sabor),
            'sabor' => $sabor,
            'precio' => $this->generarPrecio($sabor),
            'disponible' => $this->faker->boolean(80),
            'descripcion' => $this->generarDescripcion($sabor),
            'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
            'updated_at' => $this->faker->dateTimeBetween('-1 month', 'now'),
        ];
    }

    private function generarNombre(string $sabor): string
    {
        $prefijos = ['Delicioso', 'Exquisito', 'Especial', 'Fabuloso', 'Increíble'];
        $sufijos = ['Pastel', 'Torta', 'Bocado', 'Dulce'];
        
        return $this->faker->randomElement($prefijos) . ' ' . 
               ucfirst($sabor) . ' ' . 
               $this->faker->randomElement($sufijos);
    }

    private function generarPrecio(string $sabor): float
    {
        $preciosBase = [
            'chocolate' => [80, 200],
            'vainilla' => [60, 150],
            'cheesecake' => [120, 300],
            'fresa' => [70, 180],
            'coco' => [90, 220]
        ];
        
        list($min, $max) = $preciosBase[$sabor] ?? [50, 200];
        return $this->faker->randomFloat(2, $min, $max);
    }

    private function generarDescripcion(string $sabor): string
    {
        $descripciones = [
            'chocolate' => 'Un exquisito pastel de chocolate con cobertura de ganache.',
            'vainilla' => 'Delicado pastel de vainilla con crema de mantequilla.',
            'cheesecake' => 'Cremoso cheesecake con base de galleta y topping de fresa.',
            'fresa' => 'Pastel de fresa con relleno de crema y fresas naturales.',
            'coco' => 'Pastel de coco con crema de coco y coco rallado.'
        ];
        
        return $descripciones[$sabor] ?? $this->faker->sentence(10);
    }
}

4. Tipos de Factories

Factory con estados (States)

Los estados te permiten definir variaciones de tu factory:

php
<?php

namespace Database\Factories;

use App\Models\Pastel;
use Illuminate\Database\Eloquent\Factories\Factory;

class PastelFactory extends Factory
{
    protected $model = Pastel::class;

    public function definition(): array
    {
        return [
            'nombre' => 'Pastel ' . $this->faker->firstName(),
            'sabor' => $this->faker->randomElement(['chocolate', 'vainilla', 'cheesecake']),
            'precio' => $this->faker->randomFloat(2, 50, 500),
            'disponible' => true,
            'descripcion' => $this->faker->sentence(10),
        ];
    }

    /**
     * Indicar que el pastel está disponible
     */
    public function disponible(): static
    {
        return $this->state(fn (array $attributes) => [
            'disponible' => true,
        ]);
    }

    /**
     * Indicar que el pastel no está disponible
     */
    public function noDisponible(): static
    {
        return $this->state(fn (array $attributes) => [
            'disponible' => false,
        ]);
    }

    /**
     * Indicar que el pastel es de chocolate
     */
    public function chocolate(): static
    {
        return $this->state(fn (array $attributes) => [
            'sabor' => 'chocolate',
            'precio' => $this->faker->randomFloat(2, 80, 300),
            'descripcion' => 'Delicioso pastel de chocolate con ganache',
        ]);
    }

    /**
     * Indicar que el pastel es de vainilla
     */
    public function vainilla(): static
    {
        return $this->state(fn (array $attributes) => [
            'sabor' => 'vainilla',
            'precio' => $this->faker->randomFloat(2, 60, 250),
            'descripcion' => 'Exquisito pastel de vainilla con crema de mantequilla',
        ]);
    }

    /**
     * Indicar que el pastel es caro
     */
    public function caro(): static
    {
        return $this->state(fn (array $attributes) => [
            'precio' => $this->faker->randomFloat(2, 200, 500),
        ]);
    }

    /**
     * Indicar que el pastel es económico
     */
    public function economico(): static
    {
        return $this->state(fn (array $attributes) => [
            'precio' => $this->faker->randomFloat(2, 30, 80),
        ]);
    }
}

Factory con secuencias

php
public function definition(): array
{
    return [
        'nombre' => $this->faker->name(),
        'sabor' => $this->faker->randomElement(['chocolate', 'vainilla']),
        'precio' => $this->faker->randomFloat(2, 10, 100),
        'disponible' => $this->faker->boolean(),
    ];
}

// Uso con secuencia
Pastel::factory()
    ->count(10)
    ->sequence(
        ['sabor' => 'chocolate'],
        ['sabor' => 'vainilla'],
        ['sabor' => 'cheesecake'],
    )
    ->create();

// Secuencia con función
Pastel::factory()
    ->count(5)
    ->sequence(fn ($sequence) => [
        'nombre' => 'Pastel ' . ($sequence->index + 1),
        'precio' => 50 + ($sequence->index * 10),
    ])
    ->create();

Factory con relaciones

php
<?php

namespace Database\Factories;

use App\Models\Pastel;
use App\Models\Categoria;
use Illuminate\Database\Eloquent\Factories\Factory;

class PastelFactory extends Factory
{
    protected $model = Pastel::class;

    public function definition(): array
    {
        return [
            'nombre' => 'Pastel ' . $this->faker->firstName(),
            'sabor' => $this->faker->randomElement(['chocolate', 'vainilla', 'cheesecake']),
            'precio' => $this->faker->randomFloat(2, 50, 500),
            'categoria_id' => Categoria::factory(),
            'disponible' => true,
            'descripcion' => $this->faker->sentence(10),
        ];
    }

    /**
     * Relación con una categoría específica
     */
    public function categoria(Categoria $categoria): static
    {
        return $this->state(fn (array $attributes) => [
            'categoria_id' => $categoria->id,
        ]);
    }

    /**
     * Relación con múltiples ingredientes
     */
    public function withIngredientes(int $count = 3): static
    {
        return $this->afterCreating(function (Pastel $pastel) use ($count) {
            $pastel->ingredientes()->attach(
                Ingrediente::factory($count)->create()
            );
        });
    }
}

5. Relaciones entre Factories

Relación uno a muchos

php
// Factory de Categoría
class CategoriaFactory extends Factory
{
    public function definition(): array
    {
        return [
            'nombre' => $this->faker->word(),
            'descripcion' => $this->faker->sentence(),
        ];
    }

    public function withPasteles(int $count = 3): static
    {
        return $this->has(Pastel::factory($count));
    }
}

// Factory de Pastel con categoría
class PastelFactory extends Factory
{
    public function definition(): array
    {
        return [
            'nombre' => 'Pastel ' . $this->faker->firstName(),
            'sabor' => $this->faker->randomElement(['chocolate', 'vainilla']),
            'categoria_id' => Categoria::factory(),
        ];
    }
}

Relación muchos a muchos

php
class PastelFactory extends Factory
{
    public function definition(): array
    {
        return [
            'nombre' => 'Pastel ' . $this->faker->firstName(),
            'sabor' => $this->faker->randomElement(['chocolate', 'vainilla']),
        ];
    }

    public function withIngredientes(int $count = 3): static
    {
        return $this->afterCreating(function (Pastel $pastel) use ($count) {
            $ingredientes = Ingrediente::factory($count)->create();
            $pastel->ingredientes()->attach($ingredientes);
        });
    }
}

class IngredienteFactory extends Factory
{
    public function definition(): array
    {
        return [
            'nombre' => $this->faker->word(),
            'cantidad' => $this->faker->randomFloat(2, 1, 10),
            'unidad' => $this->faker->randomElement(['g', 'kg', 'ml', 'L', 'unidad']),
        ];
    }
}

Relación recursiva

php
class PastelFactory extends Factory
{
    public function definition(): array
    {
        return [
            'nombre' => 'Pastel ' . $this->faker->firstName(),
            'sabor' => $this->faker->randomElement(['chocolate', 'vainilla']),
            'parent_id' => null, // Por defecto sin padre
        ];
    }

    public function conSubpasteles(int $count = 2): static
    {
        return $this->has(Pastel::factory($count), 'subpasteles');
    }

    public function subpastel(): static
    {
        return $this->state(fn (array $attributes) => [
            'parent_id' => Pastel::factory(),
        ]);
    }
}

6. Métodos de Factory

Métodos principales

php
// Crear una instancia sin guardar en BD
$pastel = Pastel::factory()->make();

// Crear una instancia y guardarla en BD
$pastel = Pastel::factory()->create();

// Crear múltiples instancias y guardarlas
$pasteles = Pastel::factory()->count(10)->create();

// Crear con datos específicos
$pastel = Pastel::factory()->create([
    'nombre' => 'Pastel Especial',
    'precio' => 150.00
]);

// Crear con estado específico
$pastel = Pastel::factory()->chocolate()->create();

// Crear con múltiples estados
$pastel = Pastel::factory()->chocolate()->disponible()->create();

// Crear y devolver la instancia sin persistir
$pastel = Pastel::factory()->make([
    'precio' => 200
]);

// Crear y persistir múltiples instancias
Pastel::factory()->count(5)->create();

// Crear y forzar campos específicos
$pastel = Pastel::factory()->state([
    'disponible' => false
])->create();

// Crear y ejecutar callbacks después de crear
$pastel = Pastel::factory()->afterCreating(function ($pastel) {
    $pastel->ingredientes()->attach([1, 2, 3]);
})->create();

Métodos para relaciones

php
// Crear con relación hasMany
$categoria = Categoria::factory()
    ->has(Pastel::factory()->count(5))
    ->create();

// Crear con relación belongsTo
$pastel = Pastel::factory()
    ->for(Categoria::factory())
    ->create();

// Crear con relación hasOne
$pastel = Pastel::factory()
    ->has(Detalle::factory())
    ->create();

// Crear con relación belongsToMany
$pastel = Pastel::factory()
    ->hasAttached(Ingrediente::factory()->count(3))
    ->create();

// Crear con relación y datos específicos
$pastel = Pastel::factory()
    ->hasAttached(
        Ingrediente::factory()->count(3),
        ['cantidad' => 100, 'unidad' => 'g']
    )
    ->create();

Métodos de configuración

php
// Crear con secuencia
Pastel::factory()
    ->count(10)
    ->sequence(
        ['sabor' => 'chocolate', 'precio' => 100],
        ['sabor' => 'vainilla', 'precio' => 80],
        ['sabor' => 'cheesecake', 'precio' => 150]
    )
    ->create();

// Crear con callback por cada iteración
Pastel::factory()
    ->count(10)
    ->sequence(fn ($sequence) => [
        'nombre' => 'Pastel ' . ($sequence->index + 1),
        'precio' => 50 + ($sequence->index * 10),
    ])
    ->create();

// Crear con afterMaking y afterCreating
Pastel::factory()
    ->afterMaking(function ($pastel) {
        // Log antes de crear
        Log::info('Creando pastel: ' . $pastel->nombre);
    })
    ->afterCreating(function ($pastel) {
        // Log después de crear
        Log::info('Pastel creado: ' . $pastel->id);
    })
    ->create();

7. Uso en Seeders

DatabaseSeeder básico

php
<?php

namespace Database\Seeders;

use App\Models\Pastel;
use App\Models\User;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // Crear pasteles con factory
        Pastel::factory(50)->create();
        
        // Crear usuarios
        User::factory(10)->create();
        
        // Crear pasteles con estados específicos
        Pastel::factory(10)->chocolate()->create();
        Pastel::factory(10)->vainilla()->create();
        Pastel::factory(5)->noDisponible()->create();
        Pastel::factory(5)->caro()->create();
    }
}

Seeders específicos

bash
# Crear un seeder específico
php artisan make:seeder PastelSeeder
php
<?php

namespace Database\Seeders;

use App\Models\Pastel;
use App\Models\Categoria;
use Illuminate\Database\Seeder;

class PastelSeeder extends Seeder
{
    public function run(): void
    {
        // Crear categorías
        $categorias = Categoria::factory(5)->create();
        
        // Crear pasteles para cada categoría
        foreach ($categorias as $categoria) {
            Pastel::factory(10)
                ->categoria($categoria)
                ->create();
        }
        
        // Crear pasteles especiales
        Pastel::factory(3)
            ->chocolate()
            ->disponible()
            ->caro()
            ->create();
        
        // Crear pasteles económicos
        Pastel::factory(10)
            ->economico()
            ->create();
        
        // Crear pasteles con ingredientes
        Pastel::factory(5)
            ->withIngredientes(5)
            ->create();
    }
}

Registrar seeders

php
// database/seeders/DatabaseSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            CategoriaSeeder::class,
            PastelSeeder::class,
            UserSeeder::class,
            IngredienteSeeder::class,
        ]);
    }
}

Ejecutar seeders

bash
# Ejecutar todos los seeders
php artisan db:seed

# Ejecutar un seeder específico
php artisan db:seed --class=PastelSeeder

# Ejecutar con migración fresca
php artisan migrate:fresh --seed

# Ejecutar con opción de fuerza
php artisan db:seed --force

# Ejecutar en entorno de testing
php artisan db:seed --env=testing

8. Factories para Pruebas

Usar factories en tests

php
<?php

namespace Tests\Feature;

use App\Models\Pastel;
use Tests\TestCase;

class PastelControllerTest extends TestCase
{
    public function test_crear_pastel()
    {
        $data = Pastel::factory()->make()->toArray();
        
        $response = $this->post('/pasteles', $data);
        
        $response->assertStatus(201);
        $this->assertDatabaseHas('pasteles', [
            'nombre' => $data['nombre']
        ]);
    }

    public function test_ver_pastel()
    {
        $pastel = Pastel::factory()->create();
        
        $response = $this->get("/pasteles/{$pastel->id}");
        
        $response->assertStatus(200);
        $response->assertSee($pastel->nombre);
    }

    public function test_listar_pasteles()
    {
        Pastel::factory(10)->create();
        
        $response = $this->get('/pasteles');
        
        $response->assertStatus(200);
        $response->assertViewHas('pasteles', function ($pasteles) {
            return $pasteles->count() === 10;
        });
    }
}

Factories en tests de API

php
public function test_api_listar_pasteles()
{
    $pasteles = Pastel::factory(5)
        ->chocolate()
        ->create();
    
    $response = $this->getJson('/api/pasteles');
    
    $response->assertStatus(200)
        ->assertJsonCount(5, 'data')
        ->assertJsonStructure([
            'data' => [
                '*' => ['id', 'nombre', 'sabor', 'precio']
            ]
        ]);
}

public function test_api_crear_pastel()
{
    $data = Pastel::factory()->make()->toArray();
    
    $response = $this->postJson('/api/pasteles', $data);
    
    $response->assertStatus(201)
        ->assertJson([
            'success' => true,
            'message' => 'Pastel creado exitosamente'
        ]);
    
    $this->assertDatabaseHas('pasteles', $data);
}

9. Personalización Avanzada

Factory con dependencias

php
class PastelFactory extends Factory
{
    protected $model = Pastel::class;

    private function obtenerPrecioPorSabor(string $sabor): float
    {
        $precios = [
            'chocolate' => [80, 200],
            'vainilla' => [60, 150],
            'cheesecake' => [120, 300],
        ];
        
        list($min, $max) = $precios[$sabor] ?? [50, 200];
        return $this->faker->randomFloat(2, $min, $max);
    }

    public function definition(): array
    {
        $sabor = $this->faker->randomElement(['chocolate', 'vainilla', 'cheesecake']);
        
        return [
            'nombre' => $this->generarNombre($sabor),
            'sabor' => $sabor,
            'precio' => $this->obtenerPrecioPorSabor($sabor),
            'disponible' => $this->faker->boolean(80),
            'descripcion' => $this->generarDescripcion($sabor),
        ];
    }

    private function generarNombre(string $sabor): string
    {
        $adjetivos = ['Delicioso', 'Exquisito', 'Increíble', 'Especial'];
        $nombres = ['Pastel', 'Torta', 'Bocado'];
        
        return $this->faker->randomElement($adjetivos) . ' ' .
               ucfirst($sabor) . ' ' .
               $this->faker->randomElement($nombres);
    }

    private function generarDescripcion(string $sabor): string
    {
        $descripciones = [
            'chocolate' => 'Un exquisito pastel de chocolate con cobertura de ganache',
            'vainilla' => 'Delicado pastel de vainilla con crema de mantequilla',
            'cheesecake' => 'Cremoso cheesecake con base de galleta y topping de fresa',
        ];
        
        return $descripciones[$sabor] ?? $this->faker->sentence(10);
    }
}

Factory con callbacks

php
class PastelFactory extends Factory
{
    public function definition(): array
    {
        return [
            'nombre' => 'Pastel ' . $this->faker->firstName(),
            'sabor' => $this->faker->randomElement(['chocolate', 'vainilla']),
            'precio' => $this->faker->randomFloat(2, 50, 500),
        ];
    }

    // After making callback
    public function afterMaking()
    {
        return $this->state(function (array $attributes) {
            // Modificar atributos antes de crear
            return [
                'nombre' => $this->faker->unique()->firstName() . ' Pastel'
            ];
        });
    }

    // After creating callback
    public function afterCreating()
    {
        return $this->afterCreating(function (Pastel $pastel) {
            // Ejecutar después de crear
            $pastel->update([
                'codigo' => 'PASTEL-' . str_pad($pastel->id, 4, '0', STR_PAD_LEFT)
            ]);
        });
    }
}

Factory para usuarios con roles

php
class UserFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
            'password' => bcrypt('password'),
            'remember_token' => Str::random(10),
        ];
    }

    public function admin(): static
    {
        return $this->state(fn (array $attributes) => [
            'role' => 'admin',
        ]);
    }

    public function encargado(): static
    {
        return $this->state(fn (array $attributes) => [
            'role' => 'encargado',
        ]);
    }

    public function cliente(): static
    {
        return $this->state(fn (array $attributes) => [
            'role' => 'cliente',
        ]);
    }
}

// Uso en seeder
User::factory(10)->admin()->create();
User::factory(5)->encargado()->create();
User::factory(20)->cliente()->create();

10. Ejercicio Práctico: Sistema de Pastelería

Paso 1: Crear modelos y migraciones

bash
# Crear modelos con migraciones
php artisan make:model Pastel -m
php artisan make:model Categoria -m
php artisan make:model Ingrediente -m
php artisan make:model Venta -m

Paso 2: Crear factories

bash
php artisan make:factory PastelFactory --model=Pastel
php artisan make:factory CategoriaFactory --model=Categoria
php artisan make:factory IngredienteFactory --model=Ingrediente
php artisan make:factory VentaFactory --model=Venta

Paso 3: Implementar factories

php
// database/factories/CategoriaFactory.php
<?php

namespace Database\Factories;

use App\Models\Categoria;
use Illuminate\Database\Eloquent\Factories\Factory;

class CategoriaFactory extends Factory
{
    protected $model = Categoria::class;

    public function definition(): array
    {
        $categorias = ['Tortas', 'Pasteles', 'Galletas', 'Brownies', 'Cupcakes'];
        
        return [
            'nombre' => $this->faker->randomElement($categorias),
            'descripcion' => $this->faker->sentence(6),
        ];
    }
}

// database/factories/PastelFactory.php
<?php

namespace Database\Factories;

use App\Models\Pastel;
use App\Models\Categoria;
use Illuminate\Database\Eloquent\Factories\Factory;

class PastelFactory extends Factory
{
    protected $model = Pastel::class;

    public function definition(): array
    {
        $sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
        $sabor = $this->faker->randomElement($sabores);
        
        return [
            'nombre' => $this->generarNombre($sabor),
            'sabor' => $sabor,
            'precio' => $this->generarPrecio($sabor),
            'disponible' => $this->faker->boolean(80),
            'descripcion' => $this->generarDescripcion($sabor),
            'categoria_id' => Categoria::factory(),
            'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
            'updated_at' => $this->faker->dateTimeBetween('-1 month', 'now'),
        ];
    }

    private function generarNombre(string $sabor): string
    {
        $prefijos = ['Delicioso', 'Exquisito', 'Especial', 'Increíble', 'Fabuloso'];
        return $this->faker->randomElement($prefijos) . ' ' . 
               ucfirst($sabor) . ' ' . 
               $this->faker->randomElement(['Pastel', 'Torta', 'Bocado']);
    }

    private function generarPrecio(string $sabor): float
    {
        $precios = [
            'chocolate' => [80, 200],
            'vainilla' => [60, 150],
            'cheesecake' => [120, 300],
            'fresa' => [70, 180],
            'coco' => [90, 220]
        ];
        
        list($min, $max) = $precios[$sabor] ?? [50, 200];
        return $this->faker->randomFloat(2, $min, $max);
    }

    private function generarDescripcion(string $sabor): string
    {
        $descripciones = [
            'chocolate' => 'Exquisito pastel de chocolate con cobertura de ganache',
            'vainilla' => 'Delicado pastel de vainilla con crema de mantequilla',
            'cheesecake' => 'Cremoso cheesecake con base de galleta y topping de fresa',
            'fresa' => 'Pastel de fresa con relleno de crema y fresas naturales',
            'coco' => 'Pastel de coco con crema de coco y coco rallado'
        ];
        return $descripciones[$sabor] ?? $this->faker->sentence(10);
    }

    // Estados
    public function chocolate(): static
    {
        return $this->state(fn (array $attributes) => [
            'sabor' => 'chocolate',
            'precio' => $this->faker->randomFloat(2, 80, 300),
        ]);
    }

    public function disponible(): static
    {
        return $this->state(fn (array $attributes) => [
            'disponible' => true,
        ]);
    }

    public function noDisponible(): static
    {
        return $this->state(fn (array $attributes) => [
            'disponible' => false,
        ]);
    }

    public function caro(): static
    {
        return $this->state(fn (array $attributes) => [
            'precio' => $this->faker->randomFloat(2, 200, 500),
        ]);
    }

    // Relaciones
    public function categoria(Categoria $categoria): static
    {
        return $this->state(fn (array $attributes) => [
            'categoria_id' => $categoria->id,
        ]);
    }

    public function withIngredientes(int $count = 3): static
    {
        return $this->afterCreating(function (Pastel $pastel) use ($count) {
            $ingredientes = Ingrediente::factory($count)->create();
            $pastel->ingredientes()->attach($ingredientes);
        });
    }
}

// database/factories/IngredienteFactory.php
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class IngredienteFactory extends Factory
{
    public function definition(): array
    {
        $ingredientes = ['Harina', 'Huevo', 'Azúcar', 'Mantequilla', 'Chocolate', 'Vainilla', 'Crema', 'Fresa'];
        
        return [
            'nombre' => $this->faker->randomElement($ingredientes),
            'cantidad' => $this->faker->randomFloat(2, 1, 10),
            'unidad' => $this->faker->randomElement(['g', 'kg', 'ml', 'L', 'unidad']),
            'precio_unitario' => $this->faker->randomFloat(2, 1, 50),
        ];
    }
}

// database/factories/VentaFactory.php
<?php

namespace Database\Factories;

use App\Models\Pastel;
use Illuminate\Database\Eloquent\Factories\Factory;

class VentaFactory extends Factory
{
    public function definition(): array
    {
        return [
            'pastel_id' => Pastel::factory(),
            'cantidad' => $this->faker->numberBetween(1, 10),
            'precio_total' => $this->faker->randomFloat(2, 50, 1000),
            'cliente_nombre' => $this->faker->name(),
            'cliente_email' => $this->faker->email(),
            'fecha_venta' => $this->faker->dateTimeBetween('-6 months', 'now'),
            'estado' => $this->faker->randomElement(['pendiente', 'pagado', 'entregado', 'cancelado']),
        ];
    }

    public function pagado(): static
    {
        return $this->state(fn (array $attributes) => [
            'estado' => 'pagado',
        ]);
    }

    public function entregado(): static
    {
        return $this->state(fn (array $attributes) => [
            'estado' => 'entregado',
        ]);
    }
}

Paso 4: Crear seeders

bash
php artisan make:seeder CategoriaSeeder
php artisan make:seeder PastelSeeder
php artisan make:seeder IngredienteSeeder
php artisan make:seeder VentaSeeder
php
// database/seeders/CategoriaSeeder.php
<?php

namespace Database\Seeders;

use App\Models\Categoria;
use Illuminate\Database\Seeder;

class CategoriaSeeder extends Seeder
{
    public function run(): void
    {
        Categoria::factory(5)->create();
    }
}

// database/seeders/PastelSeeder.php
<?php

namespace Database\Seeders;

use App\Models\Pastel;
use App\Models\Categoria;
use Illuminate\Database\Seeder;

class PastelSeeder extends Seeder
{
    public function run(): void
    {
        // Crear pasteles para cada categoría
        Categoria::all()->each(function ($categoria) {
            Pastel::factory(10)
                ->categoria($categoria)
                ->disponible()
                ->create();
        });

        // Crear pasteles especiales
        Pastel::factory(3)
            ->chocolate()
            ->caro()
            ->withIngredientes(5)
            ->create();

        // Crear pasteles no disponibles
        Pastel::factory(5)
            ->noDisponible()
            ->create();
    }
}

// database/seeders/IngredienteSeeder.php
<?php

namespace Database\Seeders;

use App\Models\Ingrediente;
use Illuminate\Database\Seeder;

class IngredienteSeeder extends Seeder
{
    public function run(): void
    {
        Ingrediente::factory(20)->create();
    }
}

// database/seeders/VentaSeeder.php
<?php

namespace Database\Seeders;

use App\Models\Venta;
use App\Models\Pastel;
use Illuminate\Database\Seeder;

class VentaSeeder extends Seeder
{
    public function run(): void
    {
        // Crear ventas para pasteles disponibles
        Pastel::where('disponible', true)->each(function ($pastel) {
            Venta::factory(5)
                ->pagado()
                ->create([
                    'pastel_id' => $pastel->id,
                    'precio_total' => $pastel->precio * $this->faker->numberBetween(1, 3)
                ]);
        });

        // Crear algunas ventas entregadas
        Venta::factory(10)
            ->entregado()
            ->create();
    }
}

Paso 5: Actualizar DatabaseSeeder

php
// database/seeders/DatabaseSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            CategoriaSeeder::class,
            IngredienteSeeder::class,
            PastelSeeder::class,
            VentaSeeder::class,
        ]);
    }
}

Paso 6: Ejecutar los seeders

bash
# Resetear y ejecutar todos los seeders
php artisan migrate:fresh --seed

# O ejecutar solo el seeder de pasteles
php artisan db:seed --class=PastelSeeder

Comandos Útiles

bash
# Crear un nuevo factory
php artisan make:factory NombreFactory --model=Modelo

# Crear factory sin modelo
php artisan make:factory NombreFactory

# Crear factory con modelo y migración
php artisan make:factory NombreFactory --model=Modelo --migration

# Ejecutar seeders
php artisan db:seed

# Ejecutar un seeder específico
php artisan db:seed --class=NombreSeeder

# Resetear y ejecutar seeders
php artisan migrate:fresh --seed

# Ver los seeders disponibles
php artisan db:seed --help

Resumen

ConceptoDescripciónEjemplo
FactoryGenera datos de pruebaPastelFactory
StateVariaciones del factorychocolate(), disponible()
RelationsCrea relacioneshas(), for(), hasAttached()
CountNúmero de registroscount(10)
MakeCrea sin persistirmake()
CreateCrea y persistecreate()
SequenceSecuencia de datossequence(...)
CallbackEjecutar códigoafterCreating()

¡Con los Model Factories puedes generar datos de prueba de manera eficiente y mantener tu base de datos poblada para desarrollo y pruebas!

Comentarios

Entradas más populares de este blog

Tutorial CRUD con Laravel 12-Pastel::factory(50)->create();

CRUD Completo con Laravel 12 - Guía Práctica Paso a Paso-productos

CRUD en Laravel 12 con Git - Guía para Principiantes