Appearance
Sistema de Tipos de Migraciones - Documentación Técnica
Módulo: Migrations Componente: Migration Type Classification System Versión: 3.14.0 Fecha: 2026-02-11
Descripción
Sistema de categorización de migraciones de base de datos que permite clasificar las ~300 migraciones del proyecto según su propósito: cambios estructurales (BASE), operaciones de datos (TRANSACCIONAL), o configuración del sistema (CONFIG).
Esta categorización habilita:
- Ejecución ordenada en scripts de deployment (BASE → TRANS → CONFIG)
- Rollback selectivo por categoría de migración
- Validación automática de tipos
- Mejor organización y documentación del proyecto
- Control de portabilidad de datos entre schemas del sistema multi-tenant
Portabilidad de Datos entre Schemas
Los tipos de migración determinan si los datos de una tabla pueden moverse entre diferentes schemas:
| Tipo | Portabilidad | Justificación |
|---|---|---|
| BASE 🏗️ | ✅ PORTABLES | Tablas maestras/catálogos compartidos (productos, clientes, provincias) - pueden centralizarse o distribuirse según necesidad |
| TRANSACCIONAL 🔄 | ❌ NO PORTABLES | Transacciones comerciales con trazabilidad fiscal/contable ligadas a ubicación específica (facturas, movimientos) |
| CONFIG ⚙️ | ❌ NO PORTABLES | Configuraciones particulares de cada schema (numeradores, permisos, parámetros específicos por ubicación) |
Implicancia práctica: El sistema de consolidación de schemas (ZZSchemaDataConsolidation) solo puede mover tablas BASE entre niveles jerárquicos. Las tablas TRANSACCIONAL y CONFIG permanecen en su schema de origen.
Características principales:
- Enum
MigrationTypecon 3 tipos (BASE, TRANSACCIONAL, CONFIG) - Método abstracto
getMigrationType()enConfigurableMigration(obliga definición) - API pública
MigrationTypeDetectorpara scripts externos - Validador recursivo
MigrationTypeValidator - Clasificador automático con heurísticas (
MigrationTypeClassifier) - Integración completa en CLI con flags
--validate-typesy--migration-type
ADRs (Architecture Decision Records)
ADR-001: Enum PHP 8 sobre Constantes
Contexto: Necesitamos categorizar migraciones con tipos limitados y seguros.
Decisión: Usar enum MigrationType: string de PHP 8.1+ en lugar de constantes de clase.
Razones:
- Type safety: El sistema de tipos de PHP garantiza valores válidos en compile-time
- IDE support: Autocomplete y refactoring automático
- Métodos útiles:
getDescription(),getEmoji(),getFullDescription() - Pattern matching:
match()expression con exhaustiveness checking
Implementación:
php
enum MigrationType: string
{
case BASE = 'base';
case TRANSACCIONAL = 'transaccional';
case CONFIG = 'config';
}Trade-offs:
- ➕ Seguridad en tipos garantizada por PHP
- ➕ Código más limpio y expresivo
- ➖ Requiere PHP 8.1+ (ya requerido en el proyecto)
ADR-002: Método Abstracto sobre Atributo PHP
Contexto: Podemos usar método abstracto getMigrationType() o atributo #[MigrationTypeAttribute] para definir tipos.
Decisión: Método abstracto como mecanismo principal, atributo como fallback opcional.
Razones:
- Mandatory: El método abstracto obliga a todas las migraciones a definir tipo
- Compile-time check: PHP valida que todas las clases implementen el método
- Runtime flexibility: Permite lógica condicional si fuera necesario
- Backward compatibility: Se puede detectar tipo desde atributo como fallback
Prioridad de detección (en MigrationTypeDetector):
- Método
getMigrationType()(prioridad alta) - Atributo
#[MigrationTypeAttribute](fallback)
Trade-offs:
- ➕ Imposible olvidar definir tipo (error en ejecución si se intenta extender
ConfigurableMigration) - ➕ Flexibilidad para futuras necesidades (ej: tipo dinámico basado en condición)
- ➖ Más verbose que un atributo (1 método vs 1 línea)
ADR-003: Clasificador Automático con Heurísticas
Contexto: Tenemos ~300 migraciones legacy sin tipo definido.
Decisión: MigrationTypeClassifier con análisis estático del código fuente usando regex patterns.
Razones:
- Escalabilidad: Clasificar 300 archivos en minutos vs días manual
- Consistencia: Heurísticas uniformes para todas las migraciones
- Confianza medible: Score de confianza (high/medium/low) para revisión manual
- Automatización completa:
--scan → --applysin intervención
Heurísticas implementadas:
- Path/nombre con "seed" o "config" → CONFIG (score +10)
- Operaciones DDL (CREATE/ALTER/DROP TABLE) → BASE (score +3 por match)
- Operaciones DML (INSERT/UPDATE/DELETE) → TRANSACCIONAL (score +2 por match)
- Métodos Phinx estructura (addIndex, addForeignKey) → BASE (score +3 por método)
- Docblock
@migration-type→ Tipo explícito (score +20)
Trade-offs:
- ➕ Ahorro de tiempo masivo (horas vs semanas)
- ➕ Clasificación revisable antes de aplicar (dry-run mode)
- ➖ Falsos positivos posibles en migraciones complejas (mitigado con confianza baja)
Estadísticas finales (aplicado en v3.14.0):
- 117 migraciones BASE (95.1%)
- 6 migraciones TRANSACCIONAL (4.9%)
- 0 migraciones CONFIG (0%)
- Total: 123 migraciones clasificadas
ADR-004: Integración en Logger con Métricas por Tipo
Contexto: El sistema de logging existente registra migraciones sin categorización.
Decisión: Actualizar Logger.php con métodos específicos que incluyen tipo de migración.
Razones:
- Trazabilidad: Logs estructurados permiten filtrar por tipo en producción
- Métricas: Dashboards pueden mostrar duración por tipo de migración
- Debugging: Identificar rápidamente qué tipo de migración causó un error
- Auditoría: Cumplimiento con políticas que requieren separación de DDL/DML
Nuevos métodos:
php
Logger::logMigrationStartWithType(string $name, string $schema, MigrationType $type)
Logger::logMigrationMetricsByType(array $stats) // ['base' => X, 'transaccional' => Y, 'config' => Z]Trade-offs:
- ➕ Mejor observabilidad en producción
- ➕ Compatibilidad con herramientas de monitoreo (DataDog, New Relic)
- ➖ Logs más grandes (campo adicional por migración)
ADR-005: CLI Flags para Ejecución Selectiva
Contexto: Deployment scripts necesitan ejecutar migraciones en orden específico.
Decisión: Flags --migration-type=TYPE y --validate-types en migrate-db-command.php.
Razones:
- Deployment control: Scripts pueden ejecutar
BASE → TRANSACCIONAL → CONFIGen stages separados - Validation gate: deployment scripts puede validar tipos antes de merge (
--validate-types) - Debugging: Desarrolladores pueden ejecutar solo migraciones BASE para probar estructura
- Rollback selectivo: Rollback solo de migraciones de datos sin afectar estructura
Ejemplos de uso:
bash
# Validación (deployment scripts)
php migrate-db-command.php --validate-types
# Deployment ordenado (staging/production)
php migrate-db-command.php --migrate --migration-type=base
php migrate-db-command.php --migrate --migration-type=transaccional
php migrate-db-command.php --migrate --migration-type=configTrade-offs:
- ➕ Control fino para DevOps
- ➕ Menor riesgo en deployments (ejecutar estructura antes que datos)
- ➖ Comandos más largos (mitigado con scripts de deployment)
Arquitectura
Componentes del Sistema
MigrationType (Enum)
↓ define 3 casos
├─ BASE: Cambios estructurales
├─ TRANSACCIONAL: Operaciones de datos
└─ CONFIG: Configuración
ConfigurableMigration (Abstract Class)
↓ método abstracto obligatorio
getMigrationType(): MigrationType
MigrationTypeDetector (API Pública)
├─ getTypeFromInstance(object): MigrationType|null
├─ getTypeFromAttribute(object|string): MigrationType|null
├─ filterByType(array, MigrationType): array
├─ validate(object): array
└─ getStatistics(array): array
MigrationTypeValidator (Validador Recursivo)
├─ validateAll(): array
├─ scanMigrationFiles(): array
└─ extractClassName(string): string|null
MigrationTypeClassifier (Clasificador Automático)
├─ scanAll(): array
├─ classifyFile(string): array
├─ generatePatch(array, string): void
└─ applyPatch(string, bool): array
migrate-db-command.php (CLI)
├─ --validate-types (Validar todos)
└─ --migration-type=<tipo> (Filtrar ejecución)
Logger (Logging)
├─ logMigrationStartWithType()
└─ logMigrationMetricsByType()Flujo de Detección de Tipo
ConfigurableMigration instance
↓
MigrationTypeDetector::getTypeFromInstance()
↓
├─ Prioridad 1: Método getMigrationType()
│ ├─ Reflection API para acceder a método (puede ser protected)
│ ├─ Invocar método y obtener MigrationType
│ └─ Retornar si existe
↓
├─ Prioridad 2: Atributo #[MigrationTypeAttribute]
│ ├─ Reflection API para obtener atributos de clase
│ ├─ Instanciar atributo si existe
│ └─ Retornar tipo del atributo
↓
└─ Retornar null si no se encuentra tipoFlujo de Clasificación Automática
MigrationTypeClassifier::scanAll()
↓
├─ Escanear recursivamente migrations/
├─ Excluir CustomMigration, ConfigurableMigration
↓
Para cada archivo:
├─ classifyFile(file)
│ ├─ Analizar path (seeds/ → CONFIG)
│ ├─ Regex: DDL patterns → BASE
│ ├─ Regex: DML patterns → TRANSACCIONAL
│ ├─ Regex: Phinx methods → BASE
│ ├─ Docblock: @migration-type → Explícito
│ ├─ Calcular scores por tipo
│ └─ Determinar ganador + confianza
↓
Retornar array con clasificaciones
generatePatch(classifications, output.json)
↓
Guardar JSON con:
├─ file path
├─ suggested_type
├─ confidence (high/medium/low)
└─ reasons (explicaciones)
applyPatch(patch.json, dry-run=false)
↓
Para cada migración:
├─ Verificar archivo existe
├─ Verificar ya tiene getMigrationType()
│ └─ Skip si ya tiene
├─ Agregar use App\MigrationType;
├─ Inyectar método getMigrationType() después de getDefaultLevels()
└─ Guardar archivo modificadoTipos de Migraciones
BASE 🏗️ - Migraciones de Estructura (PORTABLES)
Propósito: Cambios en la estructura de la base de datos (esquema DDL).
Portabilidad: ✅ PORTABLES - Las tablas BASE contienen datos maestros/catálogos compartidos que PUEDEN moverse entre schemas sin perder su función. Son tablas base del negocio (productos, clientes compartidos, provincias, etc.)
Casos de uso:
- Crear nuevas tablas (
CREATE TABLE) - Modificar tablas existentes (
ALTER TABLE ADD COLUMN,ALTER TABLE DROP COLUMN) - Agregar índices para performance (
CREATE INDEX) - Definir foreign keys (
ADD FOREIGN KEY) - Agregar constraints (
ADD CONSTRAINT) - Crear secuencias (
CREATE SEQUENCE) - Modificar tipos de columnas (
ALTER COLUMN TYPE)
Ejemplos de tablas portables:
producto- Catálogo de productos compartidoordcon- Clientes compartidos entre sucursalesprovincia- Datos maestros geográficosaliva- Alícuotas IVA compartidasrubro- Rubros de productos
Ejemplos del proyecto:
- Crear tabla nueva (
migrations/tenancy/20240101000000_create_producto_table.php):
php
final class CreateProductoTable extends ConfigurableMigration
{
protected function getTableName(): string
{
return 'producto';
}
protected function getDefaultLevels(): array
{
return [self::LEVEL_EMPRESA, self::LEVEL_SUCURSAL];
}
protected function getMigrationType(): MigrationType
{
return MigrationType::BASE; // 🏗️ Estructura
}
public function change(): void
{
$this->initSearchPath();
$this->table('producto')
->addColumn('codigo', 'string', ['limit' => 50])
->addColumn('nombre', 'string', ['limit' => 200])
->addColumn('precio', 'decimal', ['precision' => 10, 'scale' => 2])
->addIndex(['codigo'], ['unique' => true])
->create();
}
}- Agregar índice (
migrations/tenancy/20240201000000_add_index_producto_nombre.php):
php
final class AddIndexProductoNombre extends ConfigurableMigration
{
protected function getTableName(): string
{
return 'producto';
}
protected function getDefaultLevels(): array
{
return [self::LEVEL_EMPRESA, self::LEVEL_SUCURSAL];
}
protected function getMigrationType(): MigrationType
{
return MigrationType::BASE; // 🏗️ Índice de performance
}
public function change(): void
{
$this->initSearchPath();
$this->table('producto')
->addIndex(['nombre'], ['name' => 'idx_producto_nombre'])
->update();
}
}- Agregar foreign key (
migrations/tenancy/20240301000000_add_fk_factura_cliente.php):
php
final class AddFkFacturaCliente extends ConfigurableMigration
{
protected function getTableName(): string
{
return 'factura';
}
protected function getDefaultLevels(): array
{
return [self::LEVEL_SUCURSAL, self::LEVEL_CAJA];
}
protected function getMigrationType(): MigrationType
{
return MigrationType::BASE; // 🏗️ Relación estructural
}
public function change(): void
{
$this->initSearchPath();
$this->table('factura')
->addForeignKey('cliente_id', 'cliente', 'id', [
'delete' => 'RESTRICT',
'update' => 'CASCADE'
])
->update();
}
}Cuándo elegir BASE:
- ✅ Si tu migración usa
CREATE,ALTER,DROPsobre tablas - ✅ Si agregas índices, constraints, o foreign keys
- ✅ Si modificas tipos de columnas o estructura de tabla
- ✅ Si creas secuencias, views, o funciones PostgreSQL
TRANSACCIONAL 🔄 - Migraciones de Datos (NO PORTABLES)
Propósito: Operaciones masivas de inserción, actualización o eliminación de datos (DML).
Portabilidad: ❌ NO PORTABLES - Las tablas TRANSACCIONAL contienen transacciones comerciales que NO se pueden mover entre schemas porque están ligadas específicamente a una ubicación (sucursal/caja) y tienen trazabilidad fiscal/contable.
Casos de uso:
- Migrar datos entre tablas (
INSERT INTO ... SELECT FROM) - Actualizar masivamente registros (
UPDATE ... SET WHERE) - Transformar datos legacy a nuevo formato
- Eliminar datos obsoletos (
DELETE FROM WHERE) - Migrar datos entre schemas (multi-tenant)
- Corregir datos inconsistentes
Ejemplos de tablas NO portables:
factura- Facturas por punto de venta específicomovimi- Movimientos de tesorería por ubicaciónitefac- Ítems de factura vinculados a transacción específicarecfac- Recibos de factura con trazabilidad contablemov_sto- Movimientos de stock con origen/destino específico
Ejemplos del proyecto:
- Migrar datos entre tablas (
migrations/tenancy/20240401000000_migrate_cliente_legacy_to_new.php):
php
final class MigrateClienteLegacyToNew extends ConfigurableMigration
{
protected function getTableName(): string
{
return 'cliente_nuevo';
}
protected function getDefaultLevels(): array
{
return [self::LEVEL_EMPRESA];
}
protected function getMigrationType(): MigrationType
{
return MigrationType::TRANSACCIONAL; // 🔄 Migración de datos
}
public function up(): void
{
$this->execute("
INSERT INTO cliente_nuevo (codigo, razon_social, cuit, activo)
SELECT
codigo_legacy,
nombre,
REGEXP_REPLACE(cuit, '[^0-9]', '', 'g') as cuit_limpio,
CASE WHEN estado = 'A' THEN true ELSE false END
FROM cliente_legacy
WHERE deleted_at IS NULL
");
}
public function down(): void
{
$this->execute("DELETE FROM cliente_nuevo WHERE migrated = true");
}
}- Actualizar datos masivamente (
migrations/tenancy/20240501000000_update_producto_precio_iva.php):
php
final class UpdateProductoPrecioIva extends ConfigurableMigration
{
protected function getTableName(): string
{
return 'producto';
}
protected function getDefaultLevels(): array
{
return [self::LEVEL_EMPRESA];
}
protected function getMigrationType(): MigrationType
{
return MigrationType::TRANSACCIONAL; // 🔄 Actualización masiva
}
public function up(): void
{
$this->execute("
UPDATE producto
SET precio_con_iva = ROUND(precio * 1.21, 2)
WHERE precio_con_iva IS NULL
");
}
}- Eliminar datos obsoletos (
migrations/tenancy/20240601000000_delete_factura_cancelled_old.php):
php
final class DeleteFacturaCancelledOld extends ConfigurableMigration
{
protected function getTableName(): string
{
return 'factura';
}
protected function getDefaultLevels(): array
{
return [self::LEVEL_SUCURSAL];
}
protected function getMigrationType(): MigrationType
{
return MigrationType::TRANSACCIONAL; // 🔄 Eliminación de datos
}
public function up(): void
{
$this->execute("
DELETE FROM factura
WHERE estado = 'CANCELADA'
AND fecha < '2020-01-01'
AND deleted_at IS NULL
");
}
}Cuándo elegir TRANSACCIONAL:
- ✅ Si tu migración usa
INSERT,UPDATE,DELETEmasivos - ✅ Si migra datos de tablas legacy a nuevas tablas
- ✅ Si transforma datos existentes (formateo, limpieza)
- ✅ Si corrige inconsistencias en datos históricos
CONFIG ⚙️ - Migraciones de Configuración (NO PORTABLES)
Propósito: Datos de configuración del sistema, seeds, o datos de referencia.
Portabilidad: ❌ NO PORTABLES - Las tablas CONFIG contienen configuraciones específicas por schema/empresa que NO se pueden mover porque los valores configurados son particulares de cada ubicación (numeradores, parámetros, permisos específicos).
Casos de uso:
- Cargar datos iniciales requeridos (bootstrap data)
- Insertar datos de referencia (catálogos, listas)
- Configuraciones por defecto del sistema
- Permisos y roles iniciales
- Datos maestros (provincias, monedas, tipos de comprobante)
Ejemplos de tablas NO portables:
data_config- Configuraciones específicas por sucursalpermisos- Permisos configurados por empresanumeradores- Numeración de comprobantes por ubicacióngrupos- Grupos de usuarios configurados por empresa
Ejemplos del proyecto:
- Seed de provincias (
migrations/seeds/argentina_provincias.php):
php
final class ArgentinaProvincias extends ConfigurableMigration
{
protected function getTableName(): string
{
return 'provincia';
}
protected function getDefaultLevels(): array
{
return [self::LEVEL_EMPRESA];
}
protected function getMigrationType(): MigrationType
{
return MigrationType::CONFIG; // ⚙️ Datos de referencia
}
public function up(): void
{
$provincias = [
['codigo' => 'BA', 'nombre' => 'Buenos Aires'],
['codigo' => 'CABA', 'nombre' => 'Ciudad Autónoma de Buenos Aires'],
['codigo' => 'CO', 'nombre' => 'Córdoba'],
// ... resto de provincias
];
$this->table('provincia')->insert($provincias)->saveData();
}
}- Seed de permisos (
migrations/seeds/permisos_default.php):
php
final class PermisosDefault extends ConfigurableMigration
{
protected function getTableName(): string
{
return 'permiso';
}
protected function getDefaultLevels(): array
{
return [self::LEVEL_EMPRESA];
}
protected function getMigrationType(): MigrationType
{
return MigrationType::CONFIG; // ⚙️ Configuración inicial
}
public function up(): void
{
$permisos = [
['modulo' => 'ventas', 'accion' => 'crear_factura', 'descripcion' => 'Crear facturas'],
['modulo' => 'ventas', 'accion' => 'anular_factura', 'descripcion' => 'Anular facturas'],
['modulo' => 'compras', 'accion' => 'aprobar_orden', 'descripcion' => 'Aprobar órdenes de compra'],
// ... resto de permisos
];
$this->table('permiso')->insert($permisos)->saveData();
}
}- Configuración del sistema (
migrations/seeds/data_config_initial.php):
php
final class DataConfigInitial extends ConfigurableMigration
{
protected function getTableName(): string
{
return 'data_config';
}
protected function getDefaultLevels(): array
{
return [self::LEVEL_EMPRESA];
}
protected function getMigrationType(): MigrationType
{
return MigrationType::CONFIG; // ⚙️ Configuración
}
public function up(): void
{
$configs = [
['clave' => 'moneda.default', 'valor' => 'ARS', 'help' => 'Moneda por defecto del sistema'],
['clave' => 'factura.numeracion.inicio', 'valor' => '1', 'help' => 'Número inicial de facturación'],
['clave' => 'sistema.iva.default', 'valor' => '21', 'help' => 'IVA por defecto en %'],
];
$this->table('data_config')->insert($configs)->saveData();
}
}Cuándo elegir CONFIG:
- ✅ Si tu archivo está en directorio
seeds/ - ✅ Si nombre incluye "seed", "config", "inicial", "setup"
- ✅ Si carga datos maestros o de referencia
- ✅ Si configura valores por defecto del sistema
Decision Tree: ¿Qué Tipo Elegir?
┌─ ¿Tu migración crea/modifica ESTRUCTURA de tablas?
│
├─ SÍ → ¿Usa CREATE/ALTER/DROP TABLE/INDEX/CONSTRAINT?
│ │
│ └─ SÍ → MigrationType::BASE 🏗️
│
├─ NO → ¿Tu migración INSERT/UPDATE/DELETE DATOS masivamente?
│ │
│ ├─ SÍ → ¿Son datos de referencia o configuración?
│ │ │
│ │ ├─ SÍ → MigrationType::CONFIG ⚙️
│ │ │
│ │ └─ NO → MigrationType::TRANSACCIONAL 🔄
│ │
│ └─ NO → Por defecto → MigrationType::BASE 🏗️Casos especiales:
| Situación | Tipo Correcto | Justificación | Portable |
|---|---|---|---|
| Migración crea tabla Y carga datos iniciales | BASE | La estructura es lo crítico; datos pueden ser seed aparte | ✅ |
| Migración solo carga catálogo (provincias, monedas) | CONFIG | Datos de referencia sin lógica de negocio | ❌ |
| Migración corrige CUIT mal formateados | TRANSACCIONAL | Transformación de datos existentes | ❌ |
| Migración agrega columna nueva con valor default | BASE | Cambio estructural (default es parte del schema) | ✅ |
| Migración migra datos legacy de tabla vieja a nueva | TRANSACCIONAL | Operación de datos masiva | ❌ |
| Tabla de productos/clientes compartidos entre sucursales | BASE | Catálogo maestro que puede centralizarse | ✅ |
| Tabla de facturas por punto de venta | TRANSACCIONAL | Transacción con trazabilidad fiscal específica | ❌ |
| Tabla de configuración de numeradores por sucursal | CONFIG | Parámetros específicos de ubicación | ❌ |
Implementación para Desarrolladores
Crear Nueva Migración con Tipo
Template completo:
php
<?php
declare(strict_types=1);
use App\migrations\ConfigurableMigration;
use App\MigrationType;
/**
* [Descripción de la migración]
*
* Nivel: EMPRESA | SUCURSAL | CAJA
* Tipo: BASE | TRANSACCIONAL | CONFIG
*
* @since 3.14.0
*/
final class MiNuevaMigracion extends ConfigurableMigration
{
/**
* Nombre de la tabla principal afectada.
*/
protected function getTableName(): string
{
return 'mi_tabla'; // MANDATORY
}
/**
* Niveles de schema donde se ejecuta esta migración.
*/
protected function getDefaultLevels(): array
{
return [self::LEVEL_EMPRESA, self::LEVEL_SUCURSAL]; // MANDATORY
}
/**
* Tipo de migración: BASE, TRANSACCIONAL, o CONFIG.
*/
protected function getMigrationType(): MigrationType
{
return MigrationType::BASE; // MANDATORY - Elegir según Decision Tree
}
/**
* Ejecutar migración (up).
*/
public function change(): void
{
$this->initSearchPath(); // REQUIRED para multi-tenancy
// Tu lógica de migración aquí
$this->table('mi_tabla')
->addColumn('nueva_columna', 'string', ['limit' => 100])
->update();
}
/**
* Verificar si debe ejecutarse (opcional).
*/
public function shouldExecute(): bool
{
return $this->shouldRunOnLevel() && $this->haveTable('mi_tabla');
}
}Checklist para Nuevas Migraciones
- [ ] Extender
ConfigurableMigration(noCustomMigration) - [ ] Implementar
getTableName()con nombre de tabla principal - [ ] Implementar
getDefaultLevels()con niveles de schema - [ ] Implementar
getMigrationType()consultando Decision Tree - [ ] Agregar docblock con descripción, nivel, y tipo
- [ ] Ejecutar
--validate-typeslocalmente - [ ] Probar en ambiente de desarrollo con
--dry-run
API para Procesos Externos
MigrationTypeDetector
API pública para scripts de deployment, deployment scripts, y herramientas externas.
getTypeFromInstance(object): MigrationType|null
Detecta el tipo desde una instancia de migración.
php
use App\MigrationTypeDetector;
// Instanciar migración
$migration = new CreateProductoTable();
// Detectar tipo
$type = MigrationTypeDetector::getTypeFromInstance($migration);
if ($type === null) {
throw new Exception("Migración sin tipo definido");
}
echo $type->value; // "base"
echo $type->getEmoji(); // "🏗️"
echo $type->getDescription(); // "Migración de estructura..."filterByType(array, MigrationType): array
Filtra array de migraciones por tipo específico.
php
use App\MigrationTypeDetector;
use App\MigrationType;
// Array de instancias de migraciones
$allMigrations = [
new CreateProductoTable(),
new MigrateClienteLegacy(),
new SeedProvincias(),
];
// Filtrar solo migraciones BASE
$baseMigrations = MigrationTypeDetector::filterByType(
$allMigrations,
MigrationType::BASE
);
// Ejecutar solo BASE
foreach ($baseMigrations as $migration) {
$migration->up();
}validate(object): array
Valida que una migración tenga tipo definido.
php
use App\MigrationTypeDetector;
$migration = new MiMigracion();
$result = MigrationTypeDetector::validate($migration);
if (!$result['valid']) {
echo "❌ Errores:\n";
foreach ($result['errors'] as $error) {
echo " - $error\n";
}
} else {
echo "✅ Migración válida: {$result['type']->value}\n";
}Formato de retorno:
php
[
'valid' => bool, // true si tiene tipo definido
'errors' => string[], // Array de mensajes de error
'type' => MigrationType|null // Tipo detectado o null
]getStatistics(array): array
Obtiene estadísticas de tipos de un array de migraciones.
php
use App\MigrationTypeDetector;
$allMigrations = [...]; // Array de instancias
$stats = MigrationTypeDetector::getStatistics($allMigrations);
echo "📊 Estadísticas:\n";
echo " BASE: {$stats['base']}\n";
echo " TRANSACCIONAL: {$stats['transaccional']}\n";
echo " CONFIG: {$stats['config']}\n";
echo " Sin tipo: {$stats['without_type']}\n";Formato de retorno:
php
[
'base' => int,
'transaccional' => int,
'config' => int,
'without_type' => int
]MigrationTypeValidator
Validador recursivo para escanear directorios y validar tipos.
validateAll(): array
Escanea recursivamente el directorio de migraciones y valida tipos.
php
use App\MigrationTypeValidator;
$validator = new MigrationTypeValidator(__DIR__ . '/migrations');
$result = $validator->validateAll();
if (!$result['valid']) {
echo "❌ Errores de validación:\n";
foreach ($result['errors'] as $file => $reason) {
echo " - $file: $reason\n";
}
} else {
echo "✅ Todas las {$result['total']} migraciones tienen tipo definido\n";
}
echo "\n📊 Estadísticas:\n";
echo " BASE: {$result['stats']['base']}\n";
echo " TRANSACCIONAL: {$result['stats']['transaccional']}\n";
echo " CONFIG: {$result['stats']['config']}\n";Formato de retorno:
php
[
'valid' => bool, // true si todas tienen tipo
'total' => int, // Total de archivos escaneados
'errors' => [ // Errores por archivo
'path/file.php' => 'razón del error'
],
'stats' => [ // Estadísticas por tipo
'base' => int,
'transaccional' => int,
'config' => int,
'without_type' => int
]
]CLI y Comandos
Flag: --validate-types
Propósito: Validar que todas las migraciones tengan tipo definido.
Uso en deployment scripts:
bash
php migrations/migrate-db-command.php --validate-types
# Exit code 0 si todas válidas, 1 si hay erroresSalida de ejemplo:
🔍 Validando tipos de migraciones...
✅ Todas las migraciones tienen tipo definido
📊 Estadísticas:
🏗️ BASE: 117
🔄 TRANSACCIONAL: 6
⚙️ CONFIG: 0
Total: 123 migracionesSalida con errores:
🔍 Validando tipos de migraciones...
❌ Errores de validación:
- migrations/tenancy/20240101000000_create_producto.php:
La migración CreateProducto no tiene método getMigrationType() definido
- migrations/tenancy_p/20240201000000_migrate_cliente.php:
La migración MigrateCliente no tiene método getMigrationType() definido
📊 Estadísticas:
BASE: 115
TRANSACCIONAL: 6
CONFIG: 0
Sin tipo: 2
Total: 123 migracionesFlag: --migration-type=TYPE
Propósito: Ejecutar solo migraciones de un tipo específico.
Valores válidos: base, transaccional, config
Uso en deployment:
bash
# Deployment ordenado (recomendado)
# Paso 1: Ejecutar migraciones de estructura
php migrations/migrate-db-command.php --migrate --migration-type=base
# Paso 2: Ejecutar migraciones de datos
php migrations/migrate-db-command.php --migrate --migration-type=transaccional
# Paso 3: Ejecutar migraciones de configuración
php migrations/migrate-db-command.php --migrate --migration-type=configVentajas del deployment ordenado:
- Seguridad: Estructura lista antes de migrar datos
- Debugging: Fácil identificar en qué stage falló
- Rollback: Rollback selectivo por tipo
- Performance: Separar operaciones pesadas (migraciones de datos)
Ejemplo de script de deployment (deploy-migrations.sh):
bash
#!/bin/bash
set -e # Exit on error
echo "🚀 Deployment de migraciones - Sistema Bautista"
echo "=============================================="
# Paso 1: Validar tipos
echo ""
echo "🔍 Paso 1: Validando tipos de migraciones..."
php migrations/migrate-db-command.php --validate-types
if [ $? -ne 0 ]; then
echo "❌ Validación falló. Abortando deployment."
exit 1
fi
echo "✅ Validación exitosa"
# Paso 2: Ejecutar migraciones BASE
echo ""
echo "🏗️ Paso 2: Ejecutando migraciones BASE (estructura)..."
php migrations/migrate-db-command.php --migrate --migration-type=base --oficial
if [ $? -ne 0 ]; then
echo "❌ Migraciones BASE fallaron. Abortando deployment."
exit 1
fi
echo "✅ Migraciones BASE completadas"
# Paso 3: Ejecutar migraciones TRANSACCIONAL
echo ""
echo "🔄 Paso 3: Ejecutando migraciones TRANSACCIONAL (datos)..."
php migrations/migrate-db-command.php --migrate --migration-type=transaccional --oficial
if [ $? -ne 0 ]; then
echo "❌ Migraciones TRANSACCIONAL fallaron. Abortando deployment."
exit 1
fi
echo "✅ Migraciones TRANSACCIONAL completadas"
# Paso 4: Ejecutar migraciones CONFIG
echo ""
echo "⚙️ Paso 4: Ejecutando migraciones CONFIG (configuración)..."
php migrations/migrate-db-command.php --migrate --migration-type=config --oficial
if [ $? -ne 0 ]; then
echo "❌ Migraciones CONFIG fallaron. Abortando deployment."
exit 1
fi
echo "✅ Migraciones CONFIG completadas"
echo ""
echo "🎉 Deployment de migraciones completado exitosamente"Hacer ejecutable:
bash
chmod +x deploy-migrations.shClasificador Automático
MigrationTypeClassifier
Herramienta CLI para clasificar automáticamente migraciones legacy.
Heurísticas de Clasificación
El clasificador asigna scores basándose en patterns del código fuente:
| Pattern | Tipo Asignado | Score | Explicación | | ----------------------------------------------------------------------------- | ------------- | ------------- | ---------------------------------- | --- | ----------------------------- | | Path contiene /seeds/ | CONFIG | +10 | Directorio de seeds | | Nombre contiene "seed", "config", "inicial", "setup" | CONFIG | +10 | Nomenclatura sugiere configuración | | Contiene CREATE/ALTER/DROP TABLE/INDEX/CONSTRAINT | BASE | +3 por match | Operaciones DDL | | Contiene INSERT/UPDATE/DELETE masivos | TRANSACCIONAL | +2 por match | Operaciones DML | | Usa métodos Phinx: addIndex(), addForeignKey(), addColumn(), create() | BASE | +3 por método | API de estructura Phinx | | Docblock @migration-type base | transaccional | config | Explícito | +20 | Tipo declarado explícitamente |
Determinación de tipo ganador:
- Sumar scores por tipo
- Tipo con mayor score gana
- Si empate o score = 0, default a BASE
- Calcular confianza:
(score_ganador / score_total) * 100
Niveles de confianza:
- High (≥80%): Clasificación muy segura, revisar rápidamente
- Medium (50-79%): Clasificación probable, revisar antes de aplicar
- Low (<50%): Clasificación incierta, revisar manualmente
Comando: --scan
Propósito: Escanear migraciones y sugerir tipos con confianza.
bash
php MigrationTypeClassifier.php --scanSalida de ejemplo:
📊 Clasificación de Migraciones
============================================================
🏗️ ✅ CreateProductoTable → base
• Contiene 5 operaciones DDL (CREATE/ALTER/DROP)
• Usa 3 métodos de Phinx para estructura (addIndex/addForeignKey/addColumn/create)
🔄 ⚠️ MigrateClienteLegacy → transaccional
• Contiene 8 operaciones DML (INSERT/UPDATE/DELETE)
• Usa execute() genérico - requiere revisión manual
⚙️ ✅ SeedProvincias → config
• Archivo en directorio seeds/ o nombre sugiere configuración
• Tipo explícito en docblock: config
🏗️ ❓ AddColumnProductoPrecio → base
• Sin indicadores claros - asignado BASE por defecto
============================================================
📈 Estadísticas:
Por tipo:
🏗️ BASE: 87
🔄 TRANSACCIONAL: 4
⚙️ CONFIG: 2
Por confianza:
✅ Alta: 75
⚠️ Media: 12
❓ Baja: 6
Total: 93 migracionesComando: --scan --output=FILE
Propósito: Generar archivo JSON con clasificaciones para revisión.
bash
php MigrationTypeClassifier.php --scan --output=classification.jsonFormato del JSON generado:
json
{
"generated_at": "2026-02-11 10:30:45",
"total_files": 93,
"migrations": [
{
"file": "/path/to/migrations/tenancy/20240101000000_create_producto_table.php",
"class_name": "CreateProductoTable",
"suggested_type": "base",
"confidence": "high",
"reasons": [
"Contiene 5 operaciones DDL (CREATE/ALTER/DROP)",
"Usa 3 métodos de Phinx para estructura (addIndex/addForeignKey/addColumn/create)",
"Crea tabla usando Phinx table()->create()"
]
},
{
"file": "/path/to/migrations/tenancy/20240201000000_migrate_cliente_legacy.php",
"class_name": "MigrateClienteLegacy",
"suggested_type": "transaccional",
"confidence": "medium",
"reasons": [
"Contiene 8 operaciones DML (INSERT/UPDATE/DELETE)",
"Usa execute() genérico - requiere revisión manual"
]
}
]
}Revisar clasificaciones:
bash
# Ver solo clasificaciones con confianza baja
jq '.migrations[] | select(.confidence == "low")' classification.json
# Ver solo migraciones transaccionales
jq '.migrations[] | select(.suggested_type == "transaccional")' classification.jsonComando: --apply=FILE --dry-run
Propósito: Preview de cambios antes de aplicar clasificación.
bash
php MigrationTypeClassifier.php --apply=classification.json --dry-runSalida de ejemplo:
🔧 Aplicando clasificación (DRY RUN)...
Would update CreateProductoTable (base)
Would update MigrateClienteLegacy (transaccional)
Would update SeedProvincias (config)
Would update AddColumnProductoPrecio (base)
✅ Aplicadas: 93
⏭️ Omitidas: 0Qué hace (sin --dry-run):
- Leer archivo JSON de clasificación
- Para cada migración:
- Verificar archivo existe
- Verificar no tiene ya
getMigrationType() - Agregar
use App\MigrationType;después del namespace - Inyectar método
getMigrationType()después degetDefaultLevels() - Guardar archivo modificado
Código inyectado (ejemplo):
php
protected function getMigrationType(): MigrationType
{
return MigrationType::BASE;
}Comando: --apply=FILE
Propósito: Aplicar clasificación automática a los archivos.
bash
php MigrationTypeClassifier.php --apply=classification.jsonSalida de ejemplo:
🔧 Aplicando clasificación...
✅ Aplicadas: 93
⏭️ Omitidas: 0Post-aplicación:
- Ejecutar
--validate-typespara verificar - Revisar cambios con
git diff - Corregir manualmente clasificaciones de confianza baja
- Commit cambios
Troubleshooting
Error: "No se pudo cargar la clase"
Síntoma:
Fatal error: Class 'App\MigrationType' not foundCausa: El autoloader de Composer no reconoce la clase MigrationType.
Solución:
bash
cd migrations
composer dump-autoloadVerificar carga:
php
// Test en CLI
php -r "require 'vendor/autoload.php'; use App\MigrationType; var_dump(MigrationType::BASE);"Error: "Tipo no definido" en getMigrationType()
Síntoma:
PHP Fatal error: Class contains abstract method getMigrationType() and must therefore be declared abstractCausa: Una clase extiende ConfigurableMigration pero no implementa getMigrationType().
Solución:
- Agregar método en la clase:
php
protected function getMigrationType(): MigrationType
{
return MigrationType::BASE; // O TRANSACCIONAL, CONFIG
}- Si no sabes qué tipo asignar:
- Ejecutar clasificador:
php MigrationTypeClassifier.php --scan - Revisar sugerencia del archivo
- Aplicar manualmente basándose en Decision Tree
- Ejecutar clasificador:
Error: Migraciones Duplicadas en --validate-types
Síntoma:
Migraciones duplicadas encontradas:
- migrations/tenancy/20240101000000_create_producto.php
- migrations/tenancy_p/20240101000000_create_producto.phpCausa: Migraciones para modo "oficial" y "prueba" están duplicadas (diseño intencional del sistema de multi-modo).
Solución: Esto NO es un error. El sistema multi-modo usa:
migrations/tenancy/→ Migraciones para BD oficialmigrations/tenancy_p/→ Wrappers que ejecutan misma lógica en BD de prueba
Configuración correcta:
Archivo en tenancy/:
php
// migrations/tenancy/20240101000000_create_producto.php
final class CreateProducto extends ConfigurableMigration
{
protected function getMigrationType(): MigrationType
{
return MigrationType::BASE;
}
// ... lógica completa
}Wrapper en tenancy_p/:
php
// migrations/tenancy_p/20240101000000_create_producto.php
<?php
require_once __DIR__ . '/../tenancy/20240101000000_create_producto.php';Validación ignora wrappers: El validador detecta archivos que solo tienen require_once y los omite automáticamente.
Error: Clasificador Sugiere Tipo Incorrecto
Síntoma:
🏗️ ❓ MigrateClienteData → base (LOW confidence)
• Sin indicadores claros - asignado BASE por defectoCausa: El clasificador no detectó patterns claros (DML oculto en stored procedures, lógica compleja).
Solución:
- Revisar manualmente el archivo:
bash
cat migrations/tenancy/20240101000000_migrate_cliente_data.php- Corregir tipo basándose en contenido real:
php
protected function getMigrationType(): MigrationType
{
return MigrationType::TRANSACCIONAL; // Correcto si migra datos
}- Agregar docblock para futuras clasificaciones:
php
/**
* Migra datos de cliente desde tabla legacy a nueva estructura.
*
* @migration-type transaccional
*/
final class MigrateClienteData extends ConfigurableMigration- Regenerar patch con tipo corregido:
bash
php MigrationTypeClassifier.php --scan --output=classification-v2.jsonError: --migration-type No Ejecuta Ninguna Migración
Síntoma:
$ php migrate-db-command.php --migrate --migration-type=base
Ejecutando migraciones BASE...
0 migraciones ejecutadasCausa posible 1: Las migraciones BASE ya fueron ejecutadas (están en phinxlog).
Verificar:
bash
php migrate-db-command.php --status
# Buscar migraciones con status "up"Causa posible 2: Filtro de tipo está funcionando pero no hay migraciones pendientes de ese tipo.
Verificar estadísticas:
bash
php migrate-db-command.php --status | grep -c "down"Solución: Ejecutar --status para ver estado real de migraciones por tipo.
Error: Pre-commit Hook No Se Ejecuta
Síntoma: El hook de validación no se ejecuta al hacer git commit.
Causa 1: Hook no tiene permisos de ejecución.
Solución:
bash
chmod +x .git/hooks/pre-commitCausa 2: Hook está en directorio incorrecto.
Verificar:
bash
ls -la .git/hooks/pre-commit
# Debe mostrar: -rwxr-xr-x ... .git/hooks/pre-commitCausa 3: Git config core.hooksPath apunta a otro directorio.
Verificar:
bash
git config core.hooksPath
# Si devuelve algo diferente a vacío o .git/hooks, resetear:
git config --unset core.hooksPathWarning: "Clasificador No Detecta Tipo en Migración Legacy"
Síntoma:
⚠️ Warning: Migración antigua sin getMigrationType() detectadaCausa: Migraciones legacy que extienden CustomMigration directamente (sin ConfigurableMigration).
Solución a largo plazo: Refactorizar migraciones legacy:
- Identificar migraciones legacy:
bash
grep -r "extends CustomMigration" migrations/migrations/- Refactorizar a ConfigurableMigration:
php
// ANTES
final class OldMigration extends CustomMigration
{
// ...
}
// DESPUÉS
final class OldMigration extends ConfigurableMigration
{
protected function getTableName(): string { return 'tabla'; }
protected function getDefaultLevels(): array { return [self::LEVEL_EMPRESA]; }
protected function getMigrationType(): MigrationType { return MigrationType::BASE; }
// ...
}- Probar con dry-run:
bash
php migrate-db-command.php --migrate --dry-runSolución temporal: Si hay muchas migraciones legacy:
- Crear issue para refactorización gradual
- Priorizar migraciones más usadas
- Nuevas migraciones SIEMPRE usan
ConfigurableMigration
Recursos Adicionales
Archivos Principales
| Archivo | Descripción | Líneas |
|---|---|---|
MigrationType.php | Enum con 3 tipos (BASE, TRANSACCIONAL, CONFIG) | 66 |
MigrationTypeAttribute.php | Atributo PHP 8 para metadata | 38 |
MigrationTypeDetector.php | API pública para detección | 160 |
MigrationTypeValidator.php | Validador recursivo | 187 |
MigrationTypeClassifier.php | Clasificador automático con heurísticas | 414 |
ConfigurableMigration.php | Clase base con método abstracto | 120 |
Logger.php | Logger con soporte de tipos | 350+ |
migrate-db-command.php | CLI con flags --validate-types y --migration-type | 800+ |
Ejemplos Reales
Ver migraciones del proyecto para ejemplos completos:
BASE (estructura):
migrations/tenancy/20231201000000_create_producto_table.phpmigrations/tenancy/20231215000000_add_index_producto_codigo.phpmigrations/tenancy/20240101000000_add_fk_factura_cliente.php
TRANSACCIONAL (datos):
migrations/tenancy/20240201000000_migrate_cliente_legacy.phpmigrations/tenancy/20240301000000_update_producto_precio_iva.php
CONFIG (configuración):
migrations/seeds/argentina_provincias.phpmigrations/seeds/permisos_default.php
Documentación Relacionada
- Multi-Tenancy:
docs/architecture/database/multi-tenant.md - Migraciones:
bautista-backend/migrations/CLAUDE.md - ConfigurableMigration:
migrations/migrations/ConfigurableMigration.php(docblocks)
Comandos Útiles
bash
# Ver todas las migraciones con tipo
grep -r "getMigrationType()" migrations/migrations/ | wc -l
# Listar migraciones por tipo
grep -r "MigrationType::BASE" migrations/migrations/
grep -r "MigrationType::TRANSACCIONAL" migrations/migrations/
grep -r "MigrationType::CONFIG" migrations/migrations/
# Validar todas
php migrations/migrate-db-command.php --validate-types
# Clasificar automáticamente
php migrations/MigrationTypeClassifier.php --scan
# Deployment ordenado
bash deployment/deploy-migrations.shÚltima actualización: 2026-02-11 Versión del sistema: 3.14.0 Autor: Sistema de Tipos de Migraciones - Bautista Backend