🎯 Definición Teórica: ¿Por Qué Migraciones?

El Problema Sin Migraciones

Sin MigracionesCon Migraciones
Compartir DB = SQL dumpsCódigo compartido
Conflictos en equipoResolución limpia
Perder cambiosHistorial completo
Diff imposibleRollback fácil
Dependencia manualIndependencia 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 ⚔️