🎯 Definición Teórica: ¿Por Qué Migraciones?
El Problema Sin Migraciones
| Sin Migraciones | Con Migraciones |
|---|---|
| Compartir DB = SQL dumps | Código compartido |
| Conflictos en equipo | Resolución limpia |
| Perder cambios | Historial completo |
| Diff imposible | Rollback fácil |
| Dependencia manual | Independencia total |
El Por Qué Técnico
Migración = Blueprint del Schema
↓
Definición declarativa
↓
Transformaciones controladas
↓
Versionamiento del DB
🗡️ Guía de Implementación
1. Crear Migraciones
# ╔═══════════════════════════════════════════════════════════════╗
# ║ COMANDOS DE MIGRACIONES ║
# ╠═══════════════════════════════════════════════════════════════╣
# ║ php artisan make:migration create_users_table ║ Crear║
# ║ php artisan make:migration create_posts_table ║ ║
# ║ php artisan make:migration add_role_to_users_table ║ Modif║
# ║ php artisan migrate ║ Eject║
# ║ php artisan migrate:status ║ Stats║
# ║ php artisan migrate:rollback ║Undo 1║
# ║ php artisan migrate:rollback --step=3 ║Undo 3║
# ║ php artisan migrate:reset ║Undo T║
# ║ php artisan migrate:fresh ║Fresh ║
# ║ php artisan migrate:refresh ║RFRESH ║
# ╚═══════════════════════════════════════════════════════════════╝
2. Crear Tablas
<?php
// database/migrations/2024_01_15_000001_create_users_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
// ═══════════════════════════════════════════════════════
// COLUMNAS BÁSICAS
// ═══════════════════════════════════════════════════════
$table->id(); // UnsignedBigInteger + AI
$table->string('name'); // VARCHAR(255)
$table->string('email')->unique(); // VARCHAR + unique
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
// ═══════════════════════════════════════════════════════
// COLUMNAS DE TEXTO
// ═══════════════════════════════════════════════════════
$table->text('bio')->nullable();
$table->mediumText('content')->nullable();
$table->longText('description')->nullable();
// ═══════════════════════════════════════════════════════
// COLUMNAS NUMÉRICAS
// ═══════════════════════════════════════════════════════
$table->integer('age')->unsigned();
$table->bigInteger('views')->default(0);
$table->decimal('price', 8, 2); // 999999.99
$table->float('latitude', 10, 6);
$table->double('longitude', 15, 12);
// ═══════════════════════════════════════════════════════
// COLUMNAS DE FECHA
// ═══════════════════════════════════════════════════════
$table->date('birthdate')->nullable();
$table->dateTime('published_at')->nullable();
$table->timestamp('last_login')->nullable();
$table->timestamps(); // created_at + updated_at
$table->softDeletes(); // deleted_at
// ═══════════════════════════════════════════════════════
// ÍNDICES
// ═══════════════════════════════════════════════════════
$table->index('email');
$table->index(['created_at', 'published_at']);
$table->unique(['email', 'role']);
// ═══════════════════════════════════════════════════════
// CLAVES FORÁNEAS (más tarde en relación)
// ═══════════════════════════════════════════════════════
// $table->foreignId('category_id')
// ->constrained()
// ->onDelete('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
};
3. Tipos de Columnas
// ╔═══════════════════════════════════════════════════════════════╗
// ║ TIPOS DE COLUMNAS ║
// ╠═══════════════════════════════════════════════════════════════╣
// ║ ID Y LLAVES ║ DESCRIPCIÓN ║
// ╠════════════════════════════╬═══════════════════════════════════╣
// ║ $table->id() ║ BigInt + Auto-increment ║
// ║ $table->foreignId('x') ║ UnsignedBigInt + FK ║
// ║ $table->uuid('id') ║ UUID ║
// ║ $table->morphs('taggable')║ + _id + _type para polimórfico ║
// ╠════════════════════════════╬═══════════════════════════════════╣
// ║ STRING Y TEXTO ║ ║
// ╠════════════════════════════╬═══════════════════════════════════╣
// ║ $table->string('x') ║ VARCHAR(255) ║
// ║ $table->string('x', 100) ║ VARCHAR(100) ║
// ║ $table->text('x') ║ TEXT ║
// ║ $table->mediumText() ║ MEDIUMTEXT ║
// ║ $table->longText() ║ LONGTEXT ║
// ╠════════════════════════════╬═══════════════════════════════════╣
// ║ NUMÉRICOS ║ ║
// ╠════════════════════════════╬═══════════════════════════════════╣
// ║ $table->tinyInteger('x') ║ TINYINT (-128 a 127) ║
// ║ $table->smallInteger('x') ║ SMALLINT ║
// ║ $table->integer('x') ║ INT ║
// ║ $table->bigInteger('x') ║ BIGINT ║
// ║ $table->decimal('x', 8, 2) ║ DECIMAL(8,2) ║
// ║ $table->float('x') ║ FLOAT ║
// ║ $table->double('x', 15, 8) ║ DOUBLE ║
// ║ $table->boolean('x') ║ BOOLEAN ║
// ╠════════════════════════════╬═══════════════════════════════════╣
// ║ FECHA Y HORA ║ ║
// ╠════════════════════════════╬═══════════════════════════════════╣
// ║ $table->date('x') ║ DATE ║
// ║ $table->datetime('x') ║ DATETIME ║
// ║ $table->timestamp('x') ║ TIMESTAMP ║
// ║ $table->time('x') ║ TIME ║
// ║ $table->year('x') ║ YEAR ║
// ║ $table->timestamps() ║ created_at + updated_at ║
// ║ $table->softDeletes() ║ deleted_at ║
// ╚═══════════════════════════════════════════════════════════════╝
4. Modificar Tablas Existentes
// Agregar columna
Schema::table('users', function (Blueprint $table) {
$table->string('avatar')->after('email')->nullable();
});
// Renombrar columna
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('name', 'nombre');
});
// Eliminar columna
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['avatar', 'bio']);
});
// Modificar columna (requiere doctrine/dbal)
Schema::table('users', function (Blueprint $table) {
$table->string('email', 100)->change();
});
5. Relaciones y Foreign Keys
// En migración de posts
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->foreignId('user_id') // users_id
->constrained() // Tabla users
->onUpdate('cascade') // Actualizar en cascada
->onDelete('cascade'); // Eliminar en cascada
// Opciones de delete:
// ->onDelete('cascade') // Elimina posts si se borra usuario
// ->onDelete('set null') // Pone NULL si se borra usuario
// ->onDelete('restrict') // Previene borrar si hay posts
// Relación polimórfica
$table->morphs('taggable'); // taggable_id + taggable_type
// Relación muchos a muchos (tabla pivote)
$table->foreignId('role_id')
->constrained()
->onDelete('cascade');
});
Schema::create('role_user', function (Blueprint $table) {
$table->id();
$table->foreignId('role_id')->constrained()->onDelete('cascade');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
$table->unique(['role_id', 'user_id']); // Evitar duplicados
});
6. Índices y Keys
Schema::create('products', function (Blueprint $table) {
$table->id();
// Index simple
$table->index('sku');
// Index compuesto
$table->index(['category_id', 'published', '-created_at']);
// Unique
$table->string('sku')->unique();
// Primary key compuesta
$table->primary(['order_id', 'product_id']);
// Foreign con índice automático
$table->foreignId('category_id')->constrained()->index();
});
🥷 Reto Ninja
Nivel: Jonin → ANBU
Misión 1: Crear Esquema de Blog
// Crea migraciones para un blog:
// 1. users (con softDeletes)
// 2. categories (jerárquica con parent_id)
// 3. posts (con slug único, FKs)
// 4. tags (many-to-many con posts)
// 5. comments (polimórfica: post o video)
// 6. media (imágenes para posts)
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->foreignId('parent_id')
->nullable()
->constrained('categories')
->onDelete('set null');
$table->timestamps();
});
Misión 2: Modificar Producción
//: En producción, SIEMPRE crea migración
// NUNCA modifiques migraciones existentes
// Agregar columna con valor por defecto
Schema::table('users', function (Blueprint $table) {
$table->enum('plan', ['free', 'pro', 'enterprise'])
->default('free')
->after('password');
});
// Mover datos antes de cambiar tipo
// 1. Crear nueva columna
// 2. Migrar datos
// 3. Eliminar columna antigua
Misión 3:Seeder para Migración
// Después de crear tablas, poblar
public function run(): void
{
// Importante: ejecutar en orden de dependencias
// 1. Roles primero
Role::create(['name' => 'admin']);
Role::create(['name' => 'user']);
// 2. Usuarios
User::create([
'name' => 'Admin',
'email' => 'admin@test.com',
'password' => 'xxx'
]);
}
📜 Mejores Prácticas
Convention de Nombres
Formatos aceptados:
- 2024_01_15_000001_create_users_table.php
- 2024_01_15_000002_add_role_to_users_table.php
- 2024_01_15_000003_create_posts_and_comments_tables.php
Errores Comunes
// ❌ NO modifiques migraciones ya ejecutadas
// ✅ Crea nueva migración para cambios
// ❌ NO ejecutes migrate en producción sin testing
// ✅ Prueba en staging primero
// ❌ NO ignores el método down()
// ✅ Siempre implementa rollback
Comandos de Emergencia
# Ver estado
php artisan migrate:status
# Ver qué se ejecutará (sin ejecutar)
php artisan migrate --pretend
# Rollback específico
php artisan migrate:rollback --step=1
# Reset y recrear (cuidado: borra todo)
php artisan migrate:fresh --seed
🎓 Conclusión del Maestro
“Las migraciones son la memoria de tu base de datos. Un Ninja que no controla sus migraciones perderá el control de su aplicación.” — Maestro de Datos
✅ Checklist de Dominio
- Crear migración desde cero
- Implementar relaciones (1:1, 1:N, N:N)
- Agregar/modificar columna existente
- Implementar softDeletes
- Ejecutar rollback exitoso
- Trabajar con schema builder
Recompensa XP: 75 XP ⚔️