Curso de Laravel
Migraciones en Laravel: versionar la base de datos
Por Víctor Peña · Publicado el
Hola, ¿cómo están? Continuando con el curso de Laravel, hoy veremos las migraciones. Es una de las funcionalidades que más se agradece cuando trabajas en equipo, y una de las que más cuesta valorar hasta que la usas.
¡Empecemos!
El problema que resuelven
Imagina que trabajas con dos compañeros. Tú añades una columna a la tabla de pacientes desde phpMyAdmin. Ellos siguen con su versión, sin esa columna. Su código falla y nadie sabe por qué.
La solución habitual —pasarse archivos .sql por chat— funciona hasta que alguien olvida ejecutar uno.
Las migraciones son el control de versiones de tu base de datos. Cada cambio de estructura se escribe en un archivo PHP que se versiona con Git. Tus compañeros hacen git pull, ejecutan un comando, y su base queda idéntica a la tuya.
Crear una migración
php artisan make:migration create_pacientes_table
Se crea en database/migrations/ con la fecha y hora en el nombre:
2026_05_07_143022_create_pacientes_table.php
Ese prefijo de fecha no es decorativo: determina el orden en que se ejecutan. Por eso una migración que crea una tabla con clave foránea debe ser posterior a la de la tabla referenciada.
Laravel deduce del nombre qué quieres hacer. Si empieza por create_ y termina en _table, genera el esqueleto para crear una tabla.
Anatomía de una migración
<?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('pacientes', function (Blueprint $table) {
$table->id();
$table->string('cedula', 20)->unique();
$table->string('nombre', 100);
$table->string('apellido', 100);
$table->date('fecha_nacimiento');
$table->string('telefono', 20)->nullable();
$table->string('correo')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('pacientes');
}
};
Dos métodos:
up()aplica el cambio.down()lo deshace.
down() es lo que permite revertir. Escríbelo siempre, aunque parezca que no lo vas a usar: el día que una migración salga mal en producción, es lo único que te salva.
Fíjate también en $table->id() y $table->timestamps(). El primero crea la clave primaria autoincremental; el segundo, las columnas created_at y updated_at que Eloquent mantiene solo.
Tipos de columna
Los que más vas a usar:
$table->id(); // clave primaria
$table->string('nombre', 100); // VARCHAR
$table->text('descripcion'); // TEXT
$table->integer('cantidad'); // INT
$table->unsignedInteger('stock'); // INT UNSIGNED
$table->decimal('precio', 10, 2); // DECIMAL para dinero
$table->boolean('activo'); // BOOLEAN
$table->date('fecha'); // DATE
$table->time('hora'); // TIME
$table->dateTime('creado_en'); // DATETIME
$table->timestamp('verificado_en'); // TIMESTAMP
$table->json('preferencias'); // JSON
$table->enum('estado', ['programada', 'atendida', 'cancelada']);
$table->foreignId('refugio_id'); // para claves foráneas
Para dinero, siempre decimal. Como vimos en el curso de MySQL, usar float para importes produce errores de céntimos que se acumulan.
Modificadores
Se encadenan al tipo:
$table->string('correo')->nullable(); // admite NULL
$table->string('cedula')->unique(); // sin duplicados
$table->boolean('activo')->default(true); // valor por defecto
$table->string('slug')->index(); // con índice
$table->text('notas')->nullable()->comment('Observaciones del doctor');
Y softDeletes(), que añade una columna para el borrado lógico que vimos en MySQL:
$table->softDeletes();
Claves foráneas
Aquí Laravel tiene una sintaxis muy cómoda:
Schema::create('citas', function (Blueprint $table) {
$table->id();
$table->foreignId('paciente_id')->constrained()->restrictOnDelete();
$table->foreignId('doctor_id')->constrained()->restrictOnDelete();
$table->date('fecha');
$table->time('hora');
$table->decimal('costo', 10, 2)->default(0);
$table->enum('estado', ['programada', 'atendida', 'cancelada'])
->default('programada');
$table->timestamps();
});
foreignId('paciente_id')->constrained() hace tres cosas de una vez: crea la columna, le pone el índice y declara la restricción hacia pacientes. Deduce la tabla del nombre de la columna.
Si el nombre no sigue la convención:
$table->foreignId('creado_por')->constrained('users');
Qué hacer al borrar
->restrictOnDelete() // impide borrar el padre
->cascadeOnDelete() // borra también los hijos
->nullOnDelete() // pone la columna en NULL
Como advertimos en la lección de relaciones de MySQL: restrictOnDelete() por defecto, y cascadeOnDelete() solo cuando el hijo no tenga sentido sin el padre.
Ejecutar las migraciones
php artisan migrate
Ejecuta todas las pendientes, en orden de fecha.
Laravel lleva la cuenta en una tabla llamada migrations, con el nombre de cada archivo ejecutado y su número de lote. Por eso sabe cuáles faltan.
php artisan migrate:status
Muestra cuáles se ejecutaron y cuáles no.
Revertir
php artisan migrate:rollback # deshace el último lote
php artisan migrate:rollback --step=1 # solo la última migración
php artisan migrate:reset # deshace todas
php artisan migrate:fresh # borra todo y vuelve a migrar
php artisan migrate:fresh --seed # además ejecuta los seeders
migrate:fresh borra todas las tablas y sus datos. En tu máquina es el comando que más vas a usar mientras diseñas; en producción es una catástrofe.
Un hábito que recomiendo: antes de ejecutarlo, mira el .env y confirma en qué base estás.
Modificar una tabla existente
Nunca edites una migración que ya se ejecutó en otro entorno. Si lo haces, tu base cambia pero la de tus compañeros y la de producción no, y las estructuras divergen en silencio.
Lo correcto es una migración nueva:
php artisan make:migration add_direccion_to_pacientes_table
public function up(): void
{
Schema::table('pacientes', function (Blueprint $table) {
$table->string('direccion')->nullable()->after('telefono');
});
}
public function down(): void
{
Schema::table('pacientes', function (Blueprint $table) {
$table->dropColumn('direccion');
});
}
Fíjate en Schema::table() en lugar de Schema::create(): la tabla ya existe.
Otras operaciones habituales:
$table->renameColumn('correo', 'email');
$table->string('telefono', 30)->change(); // cambiar tipo
$table->dropColumn(['direccion', 'zona']);
$table->dropForeign(['paciente_id']);
$table->index(['estado', 'fecha']); // índice compuesto
El orden importa
Las migraciones se ejecutan por fecha, así que la tabla referenciada debe crearse antes:
2026_05_07_100000_create_especialidades_table.php
2026_05_07_100100_create_doctores_table.php ← referencia a especialidades
2026_05_07_100200_create_pacientes_table.php
2026_05_07_100300_create_citas_table.php ← referencia a ambas
Si te equivocas, renombra el archivo cambiándole la marca de tiempo. Es válido siempre que la migración no se haya ejecutado en ningún lado.
Migraciones en equipo
El flujo de trabajo real:
- Creas la migración y la ejecutas en tu máquina.
- La subes al repositorio junto con tu código.
- Tus compañeros hacen
git pully ejecutanphp artisan migrate. - En el despliegue, el servidor ejecuta
php artisan migrate --force.
Ese --force es necesario porque en producción Laravel pide confirmación antes de tocar la base.
Y la regla de oro del trabajo en equipo: si ya subiste una migración al repositorio, no la edites. Crea otra.
Errores comunes
- No escribir
down(), y quedarte sin poder revertir. - Editar una migración ya ejecutada en otro entorno.
- Crear tablas en el orden equivocado y fallar en la clave foránea.
migrate:freshen producción.- Usar
floatpara dinero. - Olvidar
nullable()en columnas opcionales, y no poder insertar registros incompletos.
Para cerrar
Las migraciones convierten la estructura de tu base de datos en código versionado. Dejas de pasar archivos .sql y empiezas a tener un historial: quién cambió qué, cuándo, y la posibilidad de volver atrás.
Lo esencial: una migración por cambio, down() siempre escrito, y nunca editar una que ya viajó al repositorio.
En la siguiente lección veremos Eloquent, que es cómo se consultan las tablas que acabamos de crear.
Saludos y éxitos.
Norvic Software
Desarrollamos el software que tu empresa necesita
Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.
- Chatbots con Inteligencia Artificial
- Creación de agentes de IA
- Integración de IA en tus sistemas
- Desarrollo de software a medida
- Aplicaciones móviles iOS y Android
- Consultoría y asesoramiento técnico
Cotización sin costo · Respuesta directa por WhatsApp