6: Migraciones y Seeders en Laravel 12 - Guía Práctica
Introducción
Las migraciones son el sistema de control de versiones para tu base de datos en Laravel. Te permiten definir y compartir la estructura de tu base de datos entre todos los miembros del equipo, manteniendo un historial de cambios. Los seeders, por su parte, te permiten poblar tu base de datos con datos de prueba. En este tutorial, aprenderás todo sobre migraciones y seeders en Laravel 12.
Índice
¿Qué son las Migraciones?
Creación de Migraciones
Estructura de una Migración
Tipos de Datos en Migraciones
Modificaciones de Tablas
Comandos de Migración
¿Qué son los Seeders?
Creación de Seeders
Uso de Faker
DatabaseSeeder y Orden de Ejecución
Ejercicio Práctico: Sistema de Pastelería
Buenas Prácticas
1. ¿Qué son las Migraciones?
Las migraciones son archivos PHP que definen cambios en la estructura de tu base de datos. Funcionan como un sistema de control de versiones para tu esquema de base de datos.
Beneficios de las Migraciones
Control de versiones: Cada cambio queda registrado en un archivo
Portabilidad: Funciona con diferentes sistemas de bases de datos (MySQL, PostgreSQL, SQLite, etc.)
Colaboración en equipo: Todos los miembros pueden mantener su base de datos sincronizada
Rollback: Puedes deshacer cambios fácilmente
Historial: Puedes ver todos los cambios realizados
Ejemplo de estructura
database/
├── migrations/
│ ├── 2014_10_12_000000_create_users_table.php
│ ├── 2014_10_12_100000_create_password_reset_tokens_table.php
│ ├── 2024_01_01_000000_create_pasteles_table.php
│ ├── 2024_01_02_000000_add_precio_to_pasteles_table.php
│ └── 2024_01_03_000000_create_ventas_table.php
└── seeders/
├── DatabaseSeeder.php
├── PastelSeeder.php
└── VentaSeeder.php2. Creación de Migraciones
Comandos básicos
# Crear una migración básica
php artisan make:migration nombre_migracion
# Crear migración con plantilla para tabla
php artisan make:migration nombre_migracion --create=nombre_tabla
# Crear migración para modificar tabla existente
php artisan make:migration nombre_migracion --table=nombre_tabla
# Crear migración con modelo y factory
php artisan make:migration nombre_migracion --create=nombre_tabla --model=NombreModeloEjemplos prácticos
# Crear tabla pasteles
php artisan make:migration crear_tabla_pasteles --create=pasteles
# Crear tabla categorías
php artisan make:migration crear_tabla_categorias --create=categorias
# Agregar campos a tabla existente
php artisan make:migration agregar_campos_a_pasteles --table=pasteles
# Crear tabla con modelo
php artisan make:migration crear_tabla_ingredientes --create=ingredientes --model=Ingrediente3. Estructura de una Migración
Migración básica para crear tabla
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('pasteles', function (Blueprint $table) {
$table->id();
$table->string('nombre', 100);
$table->enum('sabor', ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco']);
$table->decimal('precio', 8, 2)->nullable();
$table->boolean('disponible')->default(true);
$table->text('descripcion')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('pasteles');
}
};Migración para modificar tabla
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('pasteles', function (Blueprint $table) {
$table->string('codigo', 20)->unique()->after('id');
$table->integer('stock')->default(0)->after('precio');
$table->dropColumn('descripcion');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('pasteles', function (Blueprint $table) {
$table->dropColumn(['codigo', 'stock']);
$table->text('descripcion')->nullable();
});
}
};4. Tipos de Datos en Migraciones
Tipos de datos principales
// En la función up()
Schema::create('tabla', function (Blueprint $table) {
// Tipos básicos
$table->id(); // BIGINT UNSIGNED auto-increment
$table->bigIncrements('id'); // BIGINT UNSIGNED auto-increment
$table->increments('id'); // INT UNSIGNED auto-increment
$table->integer('numero'); // INT
$table->bigInteger('grande'); // BIGINT
$table->string('texto', 100); // VARCHAR(100)
$table->text('descripcion'); // TEXT
$table->longText('contenido'); // LONGTEXT
$table->decimal('precio', 8, 2); // DECIMAL(8,2)
$table->float('porcentaje'); // FLOAT
$table->double('valor'); // DOUBLE
$table->boolean('activo'); // BOOLEAN
$table->date('fecha'); // DATE
$table->dateTime('fecha_hora'); // DATETIME
$table->timestamp('creado_en'); // TIMESTAMP
$table->time('hora'); // TIME
$table->enum('estado', ['activo', 'inactivo']); // ENUM
$table->json('datos'); // JSON
$table->uuid('uuid'); // UUID
// Campos especiales
$table->timestamps(); // created_at, updated_at
$table->softDeletes(); // deleted_at
$table->rememberToken(); // remember_token
// Relaciones
$table->foreignId('categoria_id')->constrained();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->unsignedBigInteger('pastel_id')->nullable();
});Modificadores de columnas
Schema::create('ejemplo', function (Blueprint $table) {
$table->id();
$table->string('nombre')->nullable(); // Puede ser NULL
$table->string('apellido')->default('Sin apellido'); // Valor por defecto
$table->string('email')->unique(); // Único
$table->integer('edad')->unsigned(); // Sin signo
$table->string('codigo', 10)->after('id'); // Después de id
$table->boolean('activo')->default(true)->index(); // Índice
$table->string('slug')->unique()->comment('Slug para URL'); // Comentario
});5. Modificaciones de Tablas
Agregar columnas
// Crear migración para agregar campos
php artisan make:migration agregar_campos_a_pasteles --table=pasteles
// En la migración
Schema::table('pasteles', function (Blueprint $table) {
$table->string('imagen', 255)->nullable()->after('descripcion');
$table->integer('stock')->default(0)->after('precio');
$table->timestamp('fecha_vencimiento')->nullable()->after('stock');
});Renombrar columnas
// Asegúrate de tener el paquete doctrine/dbal instalado
composer require doctrine/dbal
Schema::table('pasteles', function (Blueprint $table) {
$table->renameColumn('precio', 'precio_unitario');
$table->renameColumn('disponible', 'en_stock');
});Cambiar tipo de columna
Schema::table('pasteles', function (Blueprint $table) {
$table->decimal('precio', 10, 2)->change();
$table->string('nombre', 150)->change();
$table->text('descripcion')->nullable()->change();
});Eliminar columnas
Schema::table('pasteles', function (Blueprint $table) {
$table->dropColumn('imagen');
$table->dropColumn(['stock', 'fecha_vencimiento']);
});
// Eliminar con índices
Schema::table('pasteles', function (Blueprint $table) {
$table->dropColumn(['nombre', 'email']);
});Índices en migraciones
Schema::create('pasteles', function (Blueprint $table) {
$table->id();
$table->string('nombre');
$table->string('sabor');
$table->decimal('precio', 8, 2);
$table->boolean('disponible');
$table->timestamps();
// Índices
$table->index('nombre'); // Índice simple
$table->unique('email'); // Índice único
$table->index(['nombre', 'sabor']); // Índice compuesto
$table->fullText('descripcion'); // Índice de texto completo
});
// Índices en tablas existentes
Schema::table('pasteles', function (Blueprint $table) {
$table->index('nombre');
$table->unique('email');
});
// Eliminar índices
Schema::table('pasteles', function (Blueprint $table) {
$table->dropIndex('pasteles_nombre_index');
$table->dropUnique('pasteles_email_unique');
});6. Comandos de Migración
Comandos principales
# Ejecutar migraciones pendientes
php artisan migrate
# Ejecutar migraciones en entorno específico
php artisan migrate --env=testing
# Forzar migración en producción
php artisan migrate --force
# Deshacer última migración
php artisan migrate:rollback
# Deshacer todas las migraciones
php artisan migrate:reset
# Deshacer y volver a ejecutar migraciones
php artisan migrate:refresh
# Deshacer, volver a ejecutar y ejecutar seeders
php artisan migrate:refresh --seed
# Ver estado de las migraciones
php artisan migrate:status
# Instalar migración (crea tabla migrations)
php artisan migrate:install
# Crear una migración nueva
php artisan make:migration nombre_migracion
# Crear migración con tabla
php artisan make:migration nombre_migracion --create=tabla
# Crear migración para modificar
php artisan make:migration nombre_migracion --table=tablaEjemplos de uso
# Crear y ejecutar migración para pasteles
php artisan make:migration crear_tabla_pasteles --create=pasteles
php artisan migrate
# Agregar campo y ejecutar
php artisan make:migration agregar_precio_a_pasteles --table=pasteles
php artisan migrate
# Deshacer último cambio
php artisan migrate:rollback
# Ver estado
php artisan migrate:status
# Resetear y empezar de nuevo
php artisan migrate:refresh --seed
# Ejecutar migraciones específicas
php artisan migrate --path=database/migrations/2024_01_01_000000_create_pasteles_table.php7. ¿Qué son los Seeders?
Los seeders son archivos que te permiten poblar tu base de datos con datos de prueba o iniciales. Son especialmente útiles para:
Generar datos de prueba para desarrollo
Crear datos iniciales para producción
Mantener la consistencia en el equipo de desarrollo
Realizar pruebas automatizadas
Estructura de un Seeder
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class PastelSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Código para insertar datos
}
}8. Creación de Seeders
Comandos básicos
# Crear un seeder
php artisan make:seeder PastelSeeder
# Crear seeder con modelo y factory
php artisan make:seeder PastelSeeder --model=PastelSeeder básico sin Faker
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class PastelSeeder extends Seeder
{
public function run(): void
{
DB::table('pasteles')->insert([
[
'nombre' => 'Pastel de Chocolate Clásico',
'sabor' => 'chocolate',
'precio' => 250.00,
'disponible' => true,
'descripcion' => 'Delicioso pastel de chocolate con cobertura de ganache',
'created_at' => now(),
'updated_at' => now(),
],
[
'nombre' => 'Pastel de Vainilla Especial',
'sabor' => 'vainilla',
'precio' => 180.00,
'disponible' => true,
'descripcion' => 'Pastel de vainilla con crema de mantequilla',
'created_at' => now(),
'updated_at' => now(),
],
]);
}
}Seeder con Faker
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Faker\Factory as Faker;
use Illuminate\Support\Facades\DB;
class PastelSeeder extends Seeder
{
public function run(): void
{
$faker = Faker::create('es_ES');
for ($i = 0; $i < 50; $i++) {
DB::table('pasteles')->insert([
'nombre' => 'Pastel ' . $faker->firstNameFemale() . ' ' . $faker->lastName(),
'sabor' => $faker->randomElement(['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco']),
'precio' => $faker->randomFloat(2, 50, 500),
'disponible' => $faker->boolean(80),
'descripcion' => $faker->sentence(10),
'created_at' => $faker->dateTimeBetween('-1 year', 'now'),
'updated_at' => $faker->dateTimeBetween('-1 month', 'now'),
]);
}
}
}9. Uso de Faker
Instalación y configuración
# Faker ya viene incluido en Laravel 12
# Verificar en composer.json
composer require fakerphp/fakerFaker básico en Seeder
use Faker\Factory as Faker;
public function run(): void
{
$faker = Faker::create('es_ES'); // Configurar en español
for ($i = 0; $i < 100; $i++) {
DB::table('pasteles')->insert([
'nombre' => 'Pastel ' . $faker->firstName(),
'sabor' => $faker->randomElement(['chocolate', 'vainilla', 'cheesecake']),
'precio' => $faker->randomFloat(2, 50, 500),
'disponible' => $faker->boolean(75),
'descripcion' => $faker->sentence(8),
'created_at' => $faker->dateTimeBetween('-6 months', 'now'),
'updated_at' => $faker->dateTimeBetween('-1 month', 'now'),
]);
}
}Tipos de datos de Faker
// Nombres y personas
$faker->name(); // Juan Pérez
$faker->firstName(); // María
$faker->lastName(); // García
$faker->firstNameFemale(); // Ana
$faker->firstNameMale(); // Carlos
// Direcciones
$faker->address(); // Calle Principal 123
$faker->city(); // Madrid
$faker->country(); // España
$faker->postcode(); // 28001
// Teléfonos y emails
$faker->phoneNumber(); // 612345678
$faker->email(); // usuario@email.com
$faker->safeEmail(); // usuario@example.com
// Texto
$faker->sentence(); // Esto es una frase de ejemplo.
$faker->paragraph(); // Párrafo completo de ejemplo.
$faker->text(); // Texto más largo
// Fechas
$faker->date(); // 2024-01-15
$faker->dateTime(); // 2024-01-15 14:30:00
$faker->dateTimeBetween('-1 year', 'now'); // Fecha entre año pasado y ahora
// Otros
$faker->randomElement(['a', 'b', 'c']); // Elemento aleatorio
$faker->randomNumber(5); // Número de 5 dígitos
$faker->boolean(); // true o false
$faker->uuid(); // UUID
$faker->ipv4(); // Dirección IPv4Faker para diferentes tipos de datos
class PastelSeeder extends Seeder
{
public function run(): void
{
$faker = Faker::create('es_ES');
for ($i = 0; $i < 50; $i++) {
$sabor = $faker->randomElement(['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco']);
$precio = $this->obtenerPrecioPorSabor($sabor, $faker);
DB::table('pasteles')->insert([
'nombre' => $this->generarNombre($sabor, $faker),
'sabor' => $sabor,
'precio' => $precio,
'disponible' => $faker->boolean(80),
'descripcion' => $this->generarDescripcion($sabor, $faker),
'codigo' => 'PST-' . strtoupper($faker->bothify('??####')),
'stock' => $faker->numberBetween(0, 100),
'created_at' => $faker->dateTimeBetween('-1 year', 'now'),
'updated_at' => $faker->dateTimeBetween('-1 month', 'now'),
]);
}
}
private function obtenerPrecioPorSabor(string $sabor, $faker): float
{
$precios = [
'chocolate' => [80, 250],
'vainilla' => [60, 200],
'cheesecake' => [120, 350],
'fresa' => [70, 220],
'coco' => [90, 250]
];
list($min, $max) = $precios[$sabor] ?? [50, 200];
return $faker->randomFloat(2, $min, $max);
}
private function generarNombre(string $sabor, $faker): string
{
$adjetivos = ['Delicioso', 'Exquisito', 'Especial', 'Increíble', 'Fabuloso'];
$sustantivos = ['Pastel', 'Torta', 'Bocado', 'Dulce'];
return $faker->randomElement($adjetivos) . ' ' .
ucfirst($sabor) . ' ' .
$faker->randomElement($sustantivos);
}
private function generarDescripcion(string $sabor, $faker): 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] ?? $faker->sentence(10);
}
}10. DatabaseSeeder y Orden de Ejecución
DatabaseSeeder básico
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
CategoriaSeeder::class,
PastelSeeder::class,
IngredienteSeeder::class,
VentaSeeder::class,
]);
}
}Orden de ejecución con relaciones
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Primero categorías (sin dependencias)
$this->call(CategoriaSeeder::class);
// Luego pasteles (dependen de categorías)
$this->call(PastelSeeder::class);
// Luego ingredientes (dependen de pasteles)
$this->call(IngredienteSeeder::class);
// Finalmente ventas (dependen de pasteles)
$this->call(VentaSeeder::class);
}
}Ejecutar seeders con opciones
# Ejecutar todos los seeders
php artisan db:seed
# Ejecutar un seeder específico
php artisan db:seed --class=PastelSeeder
# Ejecutar en entorno de testing
php artisan db:seed --env=testing
# Forzar en producción
php artisan db:seed --force
# Combinar con migración
php artisan migrate:fresh --seed
php artisan migrate:refresh --seed11. Ejercicio Práctico: Sistema de Pastelería
Paso 1: Crear migraciones
# Crear migraciones para todas las tablas
php artisan make:migration crear_tabla_categorias --create=categorias
php artisan make:migration crear_tabla_pasteles --create=pasteles
php artisan make:migration crear_tabla_ingredientes --create=ingredientes
php artisan make:migration crear_tabla_pastel_ingrediente --create=pastel_ingrediente
php artisan make:migration crear_tabla_ventas --create=ventasPaso 2: Definir las migraciones
// database/migrations/xxxx_create_categorias_table.php
Schema::create('categorias', function (Blueprint $table) {
$table->id();
$table->string('nombre', 100);
$table->text('descripcion')->nullable();
$table->string('color', 20)->nullable();
$table->timestamps();
});
// database/migrations/xxxx_create_pasteles_table.php
Schema::create('pasteles', function (Blueprint $table) {
$table->id();
$table->string('nombre', 100);
$table->string('slug')->unique();
$table->enum('sabor', ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco', 'cafe', 'limon']);
$table->decimal('precio', 8, 2);
$table->integer('stock')->default(0);
$table->boolean('disponible')->default(true);
$table->text('descripcion')->nullable();
$table->string('imagen')->nullable();
$table->foreignId('categoria_id')->constrained()->onDelete('cascade');
$table->softDeletes();
$table->timestamps();
$table->index('sabor');
$table->index('disponible');
});
// database/migrations/xxxx_create_ingredientes_table.php
Schema::create('ingredientes', function (Blueprint $table) {
$table->id();
$table->string('nombre', 100);
$table->decimal('stock', 8, 2)->default(0);
$table->string('unidad', 20)->default('g');
$table->decimal('precio_unitario', 8, 2)->default(0);
$table->timestamps();
});
// database/migrations/xxxx_create_pastel_ingrediente_table.php
Schema::create('pastel_ingrediente', function (Blueprint $table) {
$table->id();
$table->foreignId('pastel_id')->constrained()->onDelete('cascade');
$table->foreignId('ingrediente_id')->constrained()->onDelete('cascade');
$table->decimal('cantidad', 8, 2);
$table->string('unidad', 20);
$table->timestamps();
});
// database/migrations/xxxx_create_ventas_table.php
Schema::create('ventas', function (Blueprint $table) {
$table->id();
$table->foreignId('pastel_id')->constrained()->onDelete('cascade');
$table->integer('cantidad');
$table->decimal('precio_unitario', 8, 2);
$table->decimal('subtotal', 10, 2);
$table->decimal('iva', 10, 2);
$table->decimal('total', 10, 2);
$table->string('cliente_nombre', 100);
$table->string('cliente_email', 100)->nullable();
$table->string('cliente_telefono', 20)->nullable();
$table->enum('estado', ['pendiente', 'pagado', 'entregado', 'cancelado'])->default('pendiente');
$table->timestamp('fecha_venta');
$table->timestamps();
$table->index('estado');
$table->index('fecha_venta');
});Paso 3: Crear seeders
php artisan make:seeder CategoriaSeeder
php artisan make:seeder PastelSeeder
php artisan make:seeder IngredienteSeeder
php artisan make:seeder VentaSeederPaso 4: Implementar seeders
// database/seeders/CategoriaSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class CategoriaSeeder extends Seeder
{
public function run(): void
{
$categorias = [
['nombre' => 'Tortas Clásicas', 'descripcion' => 'Tortas tradicionales', 'color' => '#FF6B6B'],
['nombre' => 'Pasteles Especiales', 'descripcion' => 'Pasteles para ocasiones especiales', 'color' => '#4ECDC4'],
['nombre' => 'Repostería Fina', 'descripcion' => 'Repostería de alta calidad', 'color' => '#45B7D1'],
['nombre' => 'Sin Gluten', 'descripcion' => 'Pasteles libres de gluten', 'color' => '#96CEB4'],
['nombre' => 'Veganos', 'descripcion' => 'Pasteles 100% vegetales', 'color' => '#FFEAA7'],
];
foreach ($categorias as $categoria) {
DB::table('categorias')->insert([
'nombre' => $categoria['nombre'],
'descripcion' => $categoria['descripcion'],
'color' => $categoria['color'],
'created_at' => now(),
'updated_at' => now(),
]);
}
}
}
// database/seeders/PastelSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Faker\Factory as Faker;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class PastelSeeder extends Seeder
{
public function run(): void
{
$faker = Faker::create('es_ES');
$categorias = DB::table('categorias')->pluck('id')->toArray();
$sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco', 'cafe', 'limon'];
$descripciones = [
'chocolate' => 'Exquisito pastel de chocolate con cobertura de ganache y decoración de chocolate',
'vainilla' => 'Delicado pastel de vainilla con crema de mantequilla y frutos rojos',
'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',
'cafe' => 'Pastel de café con crema de mantequilla y nueces',
'limon' => 'Pastel de limón con merengue y ralladura de limón'
];
for ($i = 0; $i < 30; $i++) {
$sabor = $faker->randomElement($sabores);
$precio = $this->obtenerPrecioPorSabor($sabor);
DB::table('pasteles')->insert([
'nombre' => $this->generarNombre($sabor, $faker),
'slug' => Str::slug('pastel-' . $faker->unique()->firstName() . '-' . $sabor),
'sabor' => $sabor,
'precio' => $precio,
'stock' => $faker->numberBetween(0, 50),
'disponible' => $faker->boolean(80),
'descripcion' => $descripciones[$sabor] . '. ' . $faker->sentence(5),
'imagen' => 'pasteles/' . $sabor . '_' . $faker->numberBetween(1, 5) . '.jpg',
'categoria_id' => $faker->randomElement($categorias),
'created_at' => $faker->dateTimeBetween('-6 months', 'now'),
'updated_at' => $faker->dateTimeBetween('-1 month', 'now'),
]);
}
}
private function obtenerPrecioPorSabor(string $sabor): float
{
$precios = [
'chocolate' => [80, 250],
'vainilla' => [60, 200],
'cheesecake' => [120, 350],
'fresa' => [70, 220],
'coco' => [90, 250],
'cafe' => [80, 230],
'limon' => [60, 180]
];
list($min, $max) = $precios[$sabor] ?? [50, 200];
return round(rand($min * 100, $max * 100) / 100, 2);
}
private function generarNombre(string $sabor, $faker): string
{
$adjetivos = ['Delicioso', 'Exquisito', 'Especial', 'Increíble', 'Fabuloso', 'Sensacional'];
return $faker->randomElement($adjetivos) . ' ' . ucfirst($sabor) . ' ' . $faker->randomElement(['Pastel', 'Torta', 'Dulce']);
}
}
// database/seeders/IngredienteSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Faker\Factory as Faker;
use Illuminate\Support\Facades\DB;
class IngredienteSeeder extends Seeder
{
public function run(): void
{
$faker = Faker::create('es_ES');
$ingredientes = ['Harina', 'Azúcar', 'Huevo', 'Mantequilla', 'Chocolate', 'Vainilla', 'Fresa', 'Coco', 'Café', 'Limón'];
foreach ($ingredientes as $ingrediente) {
DB::table('ingredientes')->insert([
'nombre' => $ingrediente,
'stock' => $faker->randomFloat(2, 10, 1000),
'unidad' => $faker->randomElement(['g', 'kg', 'ml', 'L', 'unidad']),
'precio_unitario' => $faker->randomFloat(2, 1, 50),
'created_at' => now(),
'updated_at' => now(),
]);
}
}
}
// database/seeders/VentaSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Faker\Factory as Faker;
use Illuminate\Support\Facades\DB;
class VentaSeeder extends Seeder
{
public function run(): void
{
$faker = Faker::create('es_ES');
$pasteles = DB::table('pasteles')->pluck('id', 'precio')->toArray();
$estados = ['pendiente', 'pagado', 'entregado', 'cancelado'];
for ($i = 0; $i < 50; $i++) {
$pastelId = $faker->randomElement(array_keys($pasteles));
$precioUnitario = $pasteles[$pastelId];
$cantidad = $faker->numberBetween(1, 5);
$subtotal = $precioUnitario * $cantidad;
$iva = $subtotal * 0.16;
$total = $subtotal + $iva;
$estado = $faker->randomElement($estados);
DB::table('ventas')->insert([
'pastel_id' => $pastelId,
'cantidad' => $cantidad,
'precio_unitario' => $precioUnitario,
'subtotal' => $subtotal,
'iva' => $iva,
'total' => $total,
'cliente_nombre' => $faker->name(),
'cliente_email' => $faker->email(),
'cliente_telefono' => $faker->phoneNumber(),
'estado' => $estado,
'fecha_venta' => $faker->dateTimeBetween('-3 months', 'now'),
'created_at' => $faker->dateTimeBetween('-3 months', 'now'),
'updated_at' => $faker->dateTimeBetween('-1 month', 'now'),
]);
}
}
}Paso 5: Configurar DatabaseSeeder
<?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 migraciones y seeders
# Crear las tablas y poblar con datos
php artisan migrate:fresh --seed
# O paso a paso
php artisan migrate
php artisan db:seed
# Verificar en la base de datos
# Deberías tener datos en las tablas: categorias, pasteles, ingredientes, pastel_ingrediente, ventas12. Buenas Prácticas
1. Usar convenciones de nombres
// ✅ Buenas prácticas
Schema::create('pasteles', function (Blueprint $table) { ... });
$table->foreignId('categoria_id')->constrained();
// ❌ Malas prácticas
Schema::create('pastel', function (Blueprint $table) { ... });
$table->integer('cat_id');2. Mantener migraciones ordenadas
// Crear migraciones por módulos
php artisan make:migration crear_tabla_pasteles --create=pasteles
php artisan make:migration crear_tabla_categorias --create=categorias
php artisan make:migration crear_tabla_relaciones_pasteles_categorias3. Usar timestamps siempre
// ✅ Siempre incluir timestamps
$table->timestamps();
$table->softDeletes();
// ❌ No hacer
// omitir timestamps4. Índices para rendimiento
$table->index('sabor');
$table->index(['categoria_id', 'disponible']);
$table->unique('codigo');5. Seeders con datos realistas
// ✅ Usar Faker para datos realistas
$faker = Faker::create('es_ES');
// ❌ Datos genéricos y repetitivos
DB::table('pasteles')->insert([
['nombre' => 'Pastel 1'],
['nombre' => 'Pastel 2'],
]);6. Mantener DatabaseSeeder limpio
// ✅ Llamar a otros seeders
$thiscall([
CategoriaSeeder::class,
PastelSeeder::class,
]);
// ❌ No meter toda la lógica aquí7. Rollback y control de versiones
// ✅ Siempre implementar down()
public function down(): void
{
Schema::dropIfExists('pasteles');
}
// ❌ No implementar down()
// Sin down no se puede hacer rollback8. Verificar el estado de migraciones
# Antes de hacer cambios importantes
php artisan migrate:status
# Siempre hacer backup antes de migrar en producción
# Usar migraciones con --pretend para ver qué va a ejecutar
php artisan migrate --pretend9. Separar datos de prueba de los datos reales
// En desarrollo: datos realistas
// En producción: datos mínimos necesarios
if (app()->environment('local')) {
$this->call(TestingSeeder::class);
} else {
$this->call(ProductionSeeder::class);
}Comandos Útiles
# Crear migración
php artisan make:migration nombre_migracion --create=tabla
# Ejecutar migraciones
php artisan migrate
# Deshacer última migración
php artisan migrate:rollback
# Ver estado
php artisan migrate:status
# Crear seeder
php artisan make:seeder NombreSeeder
# Ejecutar seeders
php artisan db:seed
# Ejecutar todo (migración fresca + seeder)
php artisan migrate:fresh --seed
# En producción
php artisan migrate --forceResumen
| Concepto | Descripción | Comando/Archivo |
|---|---|---|
| Migración | Define estructura de tabla | database/migrations/ |
| Seeder | Puebla datos de prueba | database/seeders/ |
| Faker | Genera datos realistas | Faker\Factory |
| DatabaseSeeder | Orquesta todos los seeders | call([...]) |
| Rollback | Deshace migraciones | migrate:rollback |
| Refresh | Resetea migraciones | migrate:refresh |
¡Con las migraciones y seeders tienes un control completo sobre la estructura y datos de tu base de datos en Laravel 12
Comentarios
Publicar un comentario