Appearance
Consolidación Inversa de Schemas (Hijo → Padre)
Módulo: Migrations Tipo: Database Seed Fecha creación: 2026-02-11 Versión: v3.13.1 Archivo: /bautista-backend/migrations/seeds/tenancy/ZZSchemaDataConsolidation.php
Overview
El seed ZZSchemaDataConsolidation implementa consolidación inversa de datos entre schemas PostgreSQL, moviendo tablas desde niveles inferiores (hijo) hacia niveles superiores (padre) cuando cambia la configuración de niveles en configuracion_niveles_tablas.
Este proceso complementa el seed AASchemaDataMigration (padre → hijo), creando un sistema bidireccional completo de redistribución de datos según cambios en la configuración del sistema.
Propósito principal: Cuando una tabla configurada originalmente para nivel 2 (sucursal) o nivel 3 (caja) se reconfigura para nivel 1 (empresa), el sistema automáticamente consolida los datos dispersos en múltiples schemas hijo hacia el schema padre, sincroniza el historial de migraciones (phinxlog), y ejecuta nuevamente las migraciones para completar la transición.
Restricción de portabilidad por tipo de migración: Solo las tablas de tipo BASE 🏗️ (tablas base compartidas) son portables entre schemas. Las tablas TRANSACCIONAL 🔄 (transacciones) y CONFIG ⚙️ (configuraciones) NO son portables porque contienen datos específicos por ubicación. Ver docs/backend/migration-types-system.md para detalles completos sobre tipos de migración.
Problem Statement
Contexto del Problema
En el sistema multi-tenant basado en schemas de PostgreSQL, las tablas pueden existir en diferentes niveles jerárquicos:
- Nivel 1 (EMPRESA):
public- Datos compartidos por toda la empresa - Nivel 2 (SUCURSAL):
suc0001,suc0002, etc. - Datos específicos de sucursal - Nivel 3 (CAJA):
suc0001caja001,suc0001caja002, etc. - Datos específicos de punto de venta
El campo JSONB configuracion_niveles_tablas en la tabla sistema permite personalizar en qué niveles debe existir cada tabla:
json
{
"ordcon": [1], // Solo nivel empresa (clientes compartidos)
"factura": [3], // Solo nivel caja (facturas por punto)
"movimi": [2, 3] // Niveles sucursal y caja
}Escenarios que Requieren Consolidación Inversa
Escenario 1: Cambio de Configuración Inicial
Una empresa inicia con clientes (ordcon) distribuidos por sucursal (nivel 2), pero luego decide centralizar:
json
// ANTES
{"ordcon": [1, 2]}
// DESPUÉS
{"ordcon": [1]}Problema: Existen datos de ordcon en suc0001.ordcon, suc0002.ordcon, etc., pero la nueva configuración requiere que estén únicamente en public.ordcon.
Nota: ordcon es una tabla BASE (portable) - clientes compartidos - por lo que SÍ puede consolidarse. Las tablas TRANSACCIONAL (facturas, movimientos) y CONFIG (configuraciones por sucursal) NO pueden consolidarse porque sus datos están ligados a la ubicación específica.
Escenario 2: Migraciones Skipped
Cuando AASchemaDataMigration detecta que una tabla ya existe en un schema hijo, omite crearla nuevamente. Si luego se cambia la configuración a un nivel superior, esas migraciones quedan "skipped" en el phinxlog del padre, causando errores al ejecutar migraciones futuras.
Escenario 3: Datos Huérfanos
Tablas creadas manualmente o por procesos anteriores que quedaron en schemas hijo y necesitan moverse al padre para centralizar operaciones.
Soluciones Existentes Insuficientes
| Solución | Limitación |
|---|---|
| Migración manual | Propensa a errores, no escala |
| DROP + re-ejecutar migraciones | Pérdida de datos |
AASchemaDataMigration solo | Solo mueve padre → hijo, no inversa |
| Modificar phinxlog manualmente | Riesgo de inconsistencias |
Impacto sin Consolidación Inversa
- Datos fragmentados: Información distribuida en múltiples schemas cuando debería estar centralizada
- Errores en migraciones: Phinx intenta crear estructuras que "ya existen" en schemas hijos
- Inconsistencia de datos: Búsquedas incompletas si las queries solo consultan el schema padre
- Complejidad operativa: Personal técnico debe intervenir manualmente cada vez
Architecture
Principios de Diseño
- Detección Automática: Compara configuración actual vs. ubicación real de tablas
- Validación de Conflictos: Evita consolidaciones cuando hay datos en múltiples schemas hermanos
- Transacciones Independientes: Una tabla fallida no bloquea consolidación de otras
- Sincronización de Migraciones: Marca migraciones como ejecutadas (simula
--fake) para evitar re-ejecuciones - Re-Ejecución Guiada: Crea flag y muestra instrucciones para completar el proceso
Diagrama de Flujo del Proceso
mermaid
flowchart TD
Start[Inicio: ZZSchemaDataConsolidation] --> DetectCandidates[Fase 1: Detección de Candidatos]
DetectCandidates --> ReadConfig[Leer configuracion_niveles_tablas]
ReadConfig --> GetTables[Obtener tablas en schema actual]
GetTables --> CheckConfig{Tabla tiene<br/>configuración<br/>personalizada?}
CheckConfig -->|No| NextTable[Siguiente tabla]
CheckConfig -->|Sí| CheckLevel{Nivel actual<br/>en config?}
CheckLevel -->|Sí| NextTable
CheckLevel -->|No| AddCandidate[Agregar a candidatos]
AddCandidate --> MoreTables{Más tablas?}
MoreTables -->|Sí| GetTables
MoreTables -->|No| HasCandidates{Hay candidatos?}
HasCandidates -->|No| EndNoWork[Fin: Sin tablas para consolidar]
HasCandidates -->|Sí| Validation[Fase 2: Validación]
Validation --> ValidateTable[Para cada candidato]
ValidateTable --> GetSiblings[Obtener schemas hermanos]
GetSiblings --> CountRecords[Contar registros en cada schema]
CountRecords --> CheckConflict{Múltiples schemas<br/>con datos?}
CheckConflict -->|Sí| AddConflict[Registrar conflicto]
CheckConflict -->|No| AddValidated[Agregar a validados]
AddConflict --> MoreCandidates1{Más candidatos?}
AddValidated --> MoreCandidates1
MoreCandidates1 -->|Sí| ValidateTable
MoreCandidates1 -->|No| HasConflicts{Hay conflictos?}
HasConflicts -->|Sí| Abort[Abortar: Mostrar conflictos]
HasConflicts -->|No| Consolidation[Fase 3: Consolidación]
Consolidation --> StartTx[Iniciar transacción]
StartTx --> CreateStructure{Tabla existe<br/>en padre?}
CreateStructure -->|No| CreateTable[Crear estructura con LIKE]
CreateStructure -->|Sí| CheckData{Tiene datos?}
CreateTable --> CheckData
CheckData -->|Sí| CopyData[Copiar datos con mapeo explícito]
CheckData -->|No| SyncLog[Sincronizar phinxlog]
CopyData --> UpdateSeq[Actualizar secuencias]
UpdateSeq --> SyncLog
SyncLog --> FilterMigrations[Filtrar migraciones por tabla]
FilterMigrations --> MarkExecuted[Marcar en phinxlog destino]
MarkExecuted --> DropSource[Eliminar tabla origen]
DropSource --> CommitTx[Commit transacción]
CommitTx --> MoreValidated{Más validados?}
MoreValidated -->|Sí| StartTx
MoreValidated -->|No| FlagCreation[Fase 4: Indicador]
FlagCreation --> CreateFlag[Crear flag en /tmp]
CreateFlag --> ShowMessage[Mostrar instrucción de re-ejecución]
ShowMessage --> EndSuccess[Fin: Consolidación completada]
Abort --> EndConflict[Fin: Conflictos detectados]Relación con AASchemaDataMigration
mermaid
sequenceDiagram
participant Config as configuracion_niveles_tablas
participant AA as AASchemaDataMigration
participant Schema as PostgreSQL Schemas
participant ZZ as ZZSchemaDataConsolidation
participant Phinx as Sistema de Migraciones
Note over Config: Estado inicial: ordcon en [1,2]
Note over Schema: ordcon existe en public y suc0001
Config->>Config: Admin cambia a [1]
Note over AA: Seed AA ejecuta PRIMERO
AA->>Schema: Detecta tablas en parent no en child
AA->>Schema: Migra public → suc0001 (si aplica)
Note over ZZ: Seed ZZ ejecuta DESPUÉS
ZZ->>Config: Lee nueva configuración [1]
ZZ->>Schema: Detecta ordcon en suc0001 pero config dice [1]
ZZ->>Schema: Valida sin conflictos
ZZ->>Schema: Consolida suc0001.ordcon → public.ordcon
ZZ->>Schema: Sincroniza phinxlog
ZZ->>ZZ: Crea flag de re-ejecución
Note over ZZ: Flag creado: /tmp/bautista_consolidation_*.flag
ZZ-->>Phinx: Instrucción: RE-EJECUTAR migraciones
Note over Phinx: Admin ejecuta: php migrate-db-command.php --migrate
Phinx->>Schema: Detecta phinxlog sincronizado
Phinx->>Schema: Ejecuta migraciones pendientes
Phinx->>Schema: Completa estructuras faltantesAlgorithm (4 Phases)
Fase 1: Detección de Candidatos
Objetivo: Identificar tablas que existen en el schema actual pero cuya configuración indica que deberían estar en un nivel superior.
Método: detectConsolidationCandidates()
Lógica:
- Obtener schema actual y su nivel (1, 2, o 3)
- Leer
configuracion_niveles_tablasdesdesistema - Obtener todas las tablas del schema actual (excluyendo
phinxlog,migrations,schema_migrations) - Para cada tabla:
- Si NO tiene configuración personalizada → ignorar (usar defaults)
- Si nivel actual NO está en
configured_levels→ candidata para consolidación - Determinar schema padre (inmediato superior en jerarquía)
- Agregar a lista:
['table', 'from_schema', 'to_schema', 'current_level', 'configured_levels']
Ejemplo:
php
// Schema actual: suc0001 (nivel 2)
// Configuración: {"ordcon": [1]}
// Resultado: ordcon existe en suc0001 pero config dice [1]
// → Candidata: ['table' => 'ordcon', 'from_schema' => 'suc0001', 'to_schema' => 'public', ...]SQL Crítico:
sql
-- Obtener todas las tablas del schema actual
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'suc0001'
AND table_type = 'BASE TABLE'
AND table_name NOT IN ('phinxlog', 'migrations', 'schema_migrations')
ORDER BY table_name;Fase 2: Validación de Conflictos
Objetivo: Evitar consolidaciones cuando múltiples schemas hermanos contienen datos (pérdida de información).
Método: validateTableConsolidation()
Lógica:
- Obtener schemas hermanos del mismo nivel:
- Nivel 2: Otras sucursales (
suc0001,suc0002, etc.) - Nivel 3: Otras cajas de la misma sucursal (
suc0001caja001,suc0001caja002, etc.)
- Nivel 2: Otras sucursales (
- Contar registros en cada schema hermano (incluyendo el actual)
- Validar conflictos:
- 0 schemas con datos: Consolidar (eliminar tablas vacías)
- 1 schema con datos: Consolidar (mover único conjunto de datos)
- 2+ schemas con datos: CONFLICTO (requiere intervención manual)
Método auxiliar: getSiblingSchemas()
SQL para schemas hermanos (Nivel 2 - Sucursales):
sql
-- Buscar otras sucursales (nivel 2)
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name ~ '^suc[0-9]+$' -- Regex: suc + dígitos
AND schema_name != 'suc0001' -- Excluir el actual
ORDER BY schema_name;SQL para schemas hermanos (Nivel 3 - Cajas):
sql
-- Buscar otras cajas de suc0001
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name ~ '^suc0001caja[0-9]+$' -- Regex: suc0001caja + dígitos
AND schema_name != 'suc0001caja001' -- Excluir el actual
ORDER BY schema_name;Ejemplos de Validación:
| Escenario | suc0001 | suc0002 | Resultado |
|---|---|---|---|
| Caso 1 | 100 registros | 0 registros | Consolidar ✅ |
| Caso 2 | 0 registros | 0 registros | Consolidar ✅ (eliminar vacías) |
| Caso 3 | 100 registros | 50 registros | CONFLICTO ❌ |
Fase 3: Consolidación
Objetivo: Mover datos, sincronizar estructura y phinxlog, eliminar origen.
Método: consolidateTableToParent()
Subfases:
A. Verificar/Crear Estructura en Destino
Método: createTableStructureInTarget()
sql
-- Crear tabla en destino copiando estructura completa
CREATE TABLE public.ordcon (
LIKE suc0001.ordcon INCLUDING ALL
);
-- INCLUDING ALL copia: defaults, constraints, indexes, commentsB. Copiar Datos
Método heredado: migrateDataWithExplicitMapping() (de AutoMigrationSeeder)
Lógica:
- Obtener columnas de ambas tablas
- Encontrar columnas comunes (intersección)
- Construir INSERT con mapeo explícito por nombres
sql
-- Mapeo explícito evita problemas de orden de columnas
INSERT INTO public.ordcon (id, razon, cuit, direccion)
SELECT id, razon, cuit, direccion
FROM suc0001.ordcon
ON CONFLICT DO NOTHING;C. Actualizar Secuencias
Método heredado: updateTableSequences() (de AutoMigrationSeeder)
sql
-- Obtener valor máximo actual
SELECT COALESCE(MAX(id), 0) FROM public.ordcon;
-- Resultado: 1250
-- Establecer próximo valor de secuencia
SELECT setval('public.ordcon_id_seq', 1250, true);
-- Próximo nextval() retornará 1251D. Sincronizar phinxlog
Métodos:
detectMissingMigrations()- Detecta migraciones en origen que NO están en destinofilterMigrationsForTable()- Filtra solo las que afectan a la tabla consolidadamarkMigrationsAsExecuted()- Inserta en phinxlog destino (simula--fake)
SQL de Detección:
sql
-- Encontrar migraciones en suc0001 que no están en public
SELECT o.version, o.migration_name, o.start_time, o.end_time
FROM suc0001.phinxlog o
LEFT JOIN public.phinxlog d ON o.version = d.version
WHERE d.version IS NULL
ORDER BY o.version ASC;Filtrado por Tabla:
php
// Leer archivo de migración: migrations/tenancy/20231201120000_create_ordcon_table.php
// Verificar si extiende ConfigurableMigration
if (preg_match('/class\s+\w+\s+extends\s+ConfigurableMigration/', $content)) {
// Extraer getTableName()
preg_match('/protected\s+function\s+getTableName\(\)\s*:\s*string\s*\{\s*return\s+[\'"]([^\'"]+)[\'"]/', $content, $matches);
$migrationTable = $matches[1]; // 'ordcon'
if ($migrationTable === 'ordcon') {
// Esta migración afecta a la tabla consolidada
}
}SQL de Sincronización:
sql
-- Marcar migración como ejecutada en public (simula --fake)
INSERT INTO public.phinxlog
(version, migration_name, start_time, end_time, breakpoint)
VALUES
(20231201120000, 'CreateOrdconTable', NOW(), NOW(), 0)
ON CONFLICT (version) DO NOTHING;E. Eliminar Tabla Origen
sql
-- Eliminar tabla en schema hijo (CASCADE elimina dependencias)
DROP TABLE IF EXISTS suc0001.ordcon CASCADE;Decisión Arquitectónica: No validar Foreign Keys antes de eliminar porque PostgreSQL CASCADE maneja automáticamente las dependencias. Si existen FKs que apuntan a esta tabla, CASCADE las elimina.
Fase 4: Indicador de Re-Ejecución
Objetivo: Notificar al administrador que debe re-ejecutar migraciones para completar el proceso.
Lógica:
- Si al menos 1 tabla se consolidó exitosamente:
- Crear flag en
/tmp/bautista_consolidation_executed_{timestamp}.flag - Mostrar mensaje en consola con instrucciones
- Crear flag en
Output de Consola:
============================================================
🔄 RE-EJECUTAR MIGRACIONES REQUERIDO:
👉 php migrations/migrate-db-command.php --migrate
============================================================Razón: Después de consolidar datos, las migraciones en el schema padre pueden estar "skipped". Re-ejecutarlas asegura que:
- Estructuras faltantes se completen (índices, constraints)
- Migraciones futuras se ejecuten sin errores
- Estado de
phinxlogsea consistente
Key Methods
detectConsolidationCandidates()
Firma:
php
protected function detectConsolidationCandidates(): arrayRetorna: Array de candidatos ['table', 'from_schema', 'to_schema', 'current_level', 'configured_levels']
Algoritmo:
- Obtener schema actual y nivel
- Leer
configuracion_niveles_tablas(JSONB) desdesistema - Obtener todas las tablas del schema actual (excluyendo sistema)
- Para cada tabla:
- Verificar si tiene configuración personalizada
- Si nivel actual NO está en
configured_levels→ candidata - Determinar schema padre usando
getParentSchemaForConsolidation()
- Retornar lista de candidatos
Dependencias:
getSystemData()- Lee tablasistemagetPrincipalConnection()- Obtiene schema actualgetSchemaLevel()- Determina nivel jerárquicogetParentSchemaForConsolidation()- Schema destino
validateTableConsolidation()
Firma:
php
protected function validateTableConsolidation(string $table, string $targetSchema): arrayParámetros:
$table- Nombre de la tabla$targetSchema- Schema destino (padre)
Retorna:
php
[
'can_consolidate' => bool,
'conflict_reason' => string,
'schemas_with_data' => array // ['suc0001' => 100, 'suc0002' => 50]
]Algoritmo:
- Obtener schemas hermanos usando
getSiblingSchemas() - Contar registros en cada schema hermano (incluyendo el actual)
- Validar:
- 0 schemas con datos → consolidar (eliminar vacías)
- 1 schema con datos → consolidar (mover único conjunto)
- 2+ schemas con datos → CONFLICTO
Dependencias:
getSiblingSchemas()- Detecta schemas del mismo nivelcountRecordsInSchema()- Cuenta registros
getSiblingSchemas()
Firma:
php
protected function getSiblingSchemas(): arrayRetorna: Array de schemas hermanos (sin incluir el actual)
Algoritmo por Nivel:
Nivel 2 (Sucursales):
sql
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name ~ '^suc[0-9]+$'
AND schema_name != :current_schema
ORDER BY schema_name;Nivel 3 (Cajas):
php
// Extraer prefijo de sucursal
if (preg_match('/^(suc[0-9]+)caja[0-9]+$/', $currentSchema, $matches)) {
$sucursalPrefix = $matches[1]; // 'suc0001'
// Buscar otras cajas de la misma sucursal
$pattern = '^' . $sucursalPrefix . 'caja[0-9]+$'; // '^suc0001caja[0-9]+$'
}sql
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name ~ :pattern
AND schema_name != :current_schema
ORDER BY schema_name;consolidateTableToParent()
Firma:
php
protected function consolidateTableToParent(string $table, string $sourceSchema, string $targetSchema): boolParámetros:
$table- Nombre de la tabla$sourceSchema- Schema origen (hijo)$targetSchema- Schema destino (padre)
Retorna: true si éxito, false si error
Algoritmo (dentro de transacción):
- Verificar/Crear estructura en destino (LIKE INCLUDING ALL)
- Contar registros en origen
- Si tiene datos:
- Copiar usando
migrateDataWithExplicitMapping() - Actualizar secuencias con
updateTableSequences()
- Copiar usando
- Sincronizar phinxlog:
- Detectar migraciones faltantes con
detectMissingMigrations() - Filtrar por tabla con
filterMigrationsForTable() - Marcar como ejecutadas con
markMigrationsAsExecuted()
- Detectar migraciones faltantes con
- Eliminar tabla origen (DROP CASCADE)
- Commit transacción
Manejo de Errores:
- Rollback automático en caso de excepción
- Logging con
Logger::logDataMigrationError()
filterMigrationsForTable()
Firma:
php
protected function filterMigrationsForTable(array $migrations, string $tableName): arrayParámetros:
$migrations- Lista de migraciones faltantes$tableName- Nombre de la tabla consolidada
Retorna: Migraciones filtradas que afectan a la tabla
Algoritmo:
- Para cada migración:
- Construir path del archivo:
migrations/tenancy/{version}_{name}.php - Leer contenido del archivo
- Verificar si extiende
ConfigurableMigration - Extraer
getTableName()usando regex - Si
getTableName()retorna el$tableNamebuscado → incluir en resultados
- Construir path del archivo:
Regex para Extracción:
php
// Verificar clase
preg_match('/class\s+\w+\s+extends\s+ConfigurableMigration/', $content)
// Extraer table name
preg_match(
'/protected\s+function\s+getTableName\(\)\s*:\s*string\s*\{\s*return\s+[\'"]([^\'"]+)[\'"]/',
$content,
$matches
);
$migrationTable = $matches[1]; // 'ordcon'Decisión Arquitectónica: Solo marcar migraciones específicas de la tabla evita sincronizar TODO el phinxlog (lo cual marcaría migraciones de otras tablas que no deberían ejecutarse).
markMigrationsAsExecuted()
Firma:
php
protected function markMigrationsAsExecuted(\PDO $pdo, string $targetSchema, array $migrations): intParámetros:
$pdo- Conexión PDO$targetSchema- Schema destino (padre)$migrations- Migraciones a marcar
Retorna: Número de migraciones sincronizadas
SQL:
sql
INSERT INTO {$targetSchema}.phinxlog
(version, migration_name, start_time, end_time, breakpoint)
VALUES
(:version, :migration_name, NOW(), NOW(), 0)
ON CONFLICT (version) DO NOTHING;Decisión Arquitectónica: Usar ON CONFLICT DO NOTHING evita errores si la migración ya está marcada (idempotencia).
Critical SQL Queries
1. Detección de Schemas Hermanos (Nivel 2)
sql
-- Buscar todas las sucursales excepto la actual
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name ~ '^suc[0-9]+$' -- Regex: suc seguido de dígitos
AND schema_name != 'suc0001' -- Excluir el actual
ORDER BY schema_name;Resultado Ejemplo:
schema_name
-----------
suc0002
suc0003
suc00102. Detección de Schemas Hermanos (Nivel 3)
sql
-- Buscar todas las cajas de suc0001 excepto la actual
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name ~ '^suc0001caja[0-9]+$' -- Regex específico de sucursal
AND schema_name != 'suc0001caja001' -- Excluir el actual
ORDER BY schema_name;Resultado Ejemplo:
schema_name
--------------
suc0001caja002
suc0001caja0033. Comparación de phinxlog entre Schemas
sql
-- Encontrar migraciones en origen (suc0001) que NO están en destino (public)
SELECT
o.version,
o.migration_name,
o.start_time,
o.end_time
FROM suc0001.phinxlog o
LEFT JOIN public.phinxlog d ON o.version = d.version
WHERE d.version IS NULL
ORDER BY o.version ASC;Resultado Ejemplo:
version | migration_name | start_time | end_time
----------------|------------------------|---------------------|---------------------
20231201120000 | CreateOrdconTable | 2023-12-01 12:00:00 | 2023-12-01 12:00:05
20231201130000 | AddOrdconIndexes | 2023-12-01 13:00:00 | 2023-12-01 13:00:02
20231201140000 | CreateMulctaTable | 2023-12-01 14:00:00 | 2023-12-01 14:00:034. Inserción Selectiva en phinxlog (Simular --fake)
sql
-- Marcar migración como ejecutada sin ejecutarla
INSERT INTO public.phinxlog
(version, migration_name, start_time, end_time, breakpoint)
VALUES
(20231201120000, 'CreateOrdconTable', NOW(), NOW(), 0)
ON CONFLICT (version) DO NOTHING;Notas:
ON CONFLICT DO NOTHING- Idempotentebreakpoint = 0- Sin punto de interrupciónstart_time = end_time = NOW()- Simulación de ejecución instantánea
5. Creación de Estructura con LIKE INCLUDING ALL
sql
-- Copiar estructura completa de tabla (schema + datos + constraints + indexes)
CREATE TABLE public.ordcon (
LIKE suc0001.ordcon INCLUDING ALL
);LIKE INCLUDING ALL incluye:
INCLUDING DEFAULTS- Valores por defecto de columnasINCLUDING CONSTRAINTS- CHECK constraints, NOT NULLINCLUDING INDEXES- Todos los índices (incluyendo PRIMARY KEY)INCLUDING STORAGE- Configuración de almacenamientoINCLUDING COMMENTS- Comentarios de columnas/tablaINCLUDING IDENTITY- Columnas IDENTITY (serial)INCLUDING GENERATED- Columnas generadas
NO incluye:
- Datos (se copian después con INSERT SELECT)
- Foreign Keys (deben recrearse manualmente)
- Triggers (deben recrearse manualmente)
6. Conteo de Registros en Schema Específico
sql
-- Contar registros en tabla de schema específico
SELECT COUNT(*)
FROM suc0001.ordcon;Manejo de Errores (en código PHP):
php
try {
$stmt = $pdo->prepare("SELECT COUNT(*) FROM {$schema}.{$table}");
$stmt->execute();
return (int) $stmt->fetchColumn();
} catch (\Exception $e) {
// Tabla no existe en este schema, retornar 0
return 0;
}7. Obtener Columnas de Tabla
sql
-- Obtener todas las columnas en orden de definición
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'suc0001'
AND table_name = 'ordcon'
ORDER BY ordinal_position;Uso: Encontrar columnas comunes entre origen y destino para mapeo explícito.
8. Actualizar Secuencias
sql
-- Paso 1: Obtener valor máximo actual
SELECT COALESCE(MAX(id), 0) FROM public.ordcon;
-- Resultado: 1250
-- Paso 2: Establecer próximo valor de secuencia
SELECT setval('public.ordcon_id_seq', 1250, true);
-- Parámetros:
-- 'public.ordcon_id_seq' - Nombre de la secuencia
-- 1250 - Último valor usado
-- true - is_called=true (próximo nextval retorna 1251)Si tabla está vacía:
sql
-- Resetear secuencia a 1
SELECT setval('public.ordcon_id_seq', 1, false);
-- false - is_called=false (próximo nextval retorna 1)Use Cases & Examples
Caso de Uso 1: Consolidación Exitosa (Tabla con Datos)
Contexto:
- Schema actual:
suc0001(nivel 2) - Tabla:
ordcon(clientes) - Configuración inicial:
{"ordcon": [1, 2]} - Configuración nueva:
{"ordcon": [1]} - Estado:
suc0001.ordcontiene 150 registros
Flujo:
- Detección:
🔄 Iniciando consolidación inversa de schemas
📍 Schema actual: suc0001 (Nivel: Sucursal)
📋 1 tabla(s) candidata(s) detectada(s)- Validación:
🔍 Validando 'ordcon'...
Schemas hermanos encontrados: suc0002, suc0003
suc0001: 150 registros
suc0002: 0 registros
suc0003: 0 registros
✅ 'ordcon': Sin conflictos- Consolidación:
📦 Procesando 'ordcon'...
🔧 Creando estructura en 'public'...
📦 Copiando 150 registros...
✓ 150 registros migrados
🔢 Secuencias actualizadas: 1
📊 Sincronizando historial de migraciones...
📋 Detectadas 3 migraciones faltantes
✓ 'CreateOrdconTable' afecta 'ordcon'
✓ 'AddOrdconIndexes' afecta 'ordcon'
✓ 2 migraciones sincronizadas para 'ordcon'
🗑️ Eliminando tabla en schema origen...
✅ Consolidada en 1.23s- Indicador:
============================================================
🔄 RE-EJECUTAR MIGRACIONES REQUERIDO:
👉 php migrations/migrate-db-command.php --migrate
============================================================
📊 Resumen: 1 exitosa(s), 0 fallida(s)
🎉 Consolidación completadaCaso de Uso 2: Conflicto (Múltiples Schemas con Datos)
Contexto:
- Schema actual:
suc0001(nivel 2) - Tabla:
factura - Configuración nueva:
{"factura": [1]} - Estado:
suc0001.factura: 500 registrossuc0002.factura: 300 registros
Flujo:
- Detección:
🔄 Iniciando consolidación inversa de schemas
📍 Schema actual: suc0001 (Nivel: Sucursal)
📋 1 tabla(s) candidata(s) detectada(s)- Validación:
🔍 Validando 'factura'...
❌ CONFLICTO en 'factura': Múltiples schemas con datos (2 schemas)
📊 Datos en múltiples schemas:
- suc0001: 500 registros
- suc0002: 300 registros- Abortar:
🛑 Consolidación abortada por conflictos
⚠️ Se detectaron datos en múltiples schemas para una o más tablas
💡 Solución: Consolidar datos manualmente o ajustar configuraciónSolución Manual:
Opción 1 - Consolidar datos de ambas sucursales:
sql
-- Copiar datos de suc0001 a public
INSERT INTO public.factura SELECT * FROM suc0001.factura ON CONFLICT DO NOTHING;
-- Copiar datos de suc0002 a public
INSERT INTO public.factura SELECT * FROM suc0002.factura ON CONFLICT DO NOTHING;
-- Actualizar secuencias
SELECT setval('public.factura_id_seq', (SELECT MAX(id) FROM public.factura), true);
-- Eliminar tablas origen
DROP TABLE suc0001.factura CASCADE;
DROP TABLE suc0002.factura CASCADE;Opción 2 - Revertir configuración:
json
// Mantener factura en nivel 2
{ "factura": [2] }Caso de Uso 3: Tablas Vacías (Solo Sincronizar Estructura)
Contexto:
- Schema actual:
suc0001(nivel 2) - Tabla:
movimi(movimientos) - Configuración nueva:
{"movimi": [1]} - Estado:
suc0001.movimiexiste pero sin datos (0 registros)
Flujo:
📦 Procesando 'movimi'...
🔧 Creando estructura en 'public'...
ℹ️ Tabla vacía, solo sincronizando estructura
📊 Sincronizando historial de migraciones...
✓ 1 migraciones sincronizadas para 'movimi'
🗑️ Eliminando tabla en schema origen...
✅ Consolidada en 0.15sResultado:
public.movimicreada con estructura completasuc0001.movimieliminadapublic.phinxlogsincronizado- Sin copia de datos (tabla vacía)
Caso de Uso 4: Consolidación de Nivel 3 → 2
Contexto:
- Schema actual:
suc0001caja001(nivel 3) - Tabla:
cierre_caja - Configuración nueva:
{"cierre_caja": [2]} - Estado:
suc0001caja001.cierre_cajatiene 30 registros
Flujo:
- Detección:
🔄 Iniciando consolidación inversa de schemas
📍 Schema actual: suc0001caja001 (Nivel: Caja)
📋 1 tabla(s) candidata(s) detectada(s)- Validación de Hermanos:
🔍 Validando 'cierre_caja'...
Schemas hermanos encontrados: suc0001caja002, suc0001caja003
suc0001caja001: 30 registros
suc0001caja002: 0 registros
suc0001caja003: 0 registros
✅ 'cierre_caja': Sin conflictos- Consolidación:
📦 Procesando 'cierre_caja'...
🔧 Creando estructura en 'suc0001'...
📦 Copiando 30 registros...
✓ 30 registros migrados
🔢 Secuencias actualizadas: 1
📊 Sincronizando historial de migraciones...
✓ 1 migraciones sincronizadas para 'cierre_caja'
🗑️ Eliminando tabla en schema origen...
✅ Consolidada en 0.45sNota: Schema destino es suc0001 (padre inmediato), NO public.
Integration with Migration System
Relación con AASchemaDataMigration
| Aspecto | AASchemaDataMigration | ZZSchemaDataConsolidation |
|---|---|---|
| Dirección | Padre → Hijo | Hijo → Padre |
| Trigger | Migración inicial | Cambio de configuración |
| Prefijo | AA (ejecuta primero) | ZZ (ejecuta último) |
| Objetivo | Distribuir datos existentes | Centralizar datos dispersos |
| Validación | Tabla existe en padre | Conflictos entre hermanos |
Flujo de Despliegue Completo
mermaid
sequenceDiagram
participant Admin as Administrador
participant DB as Base de Datos
participant M1 as Migraciones (Phase 1)
participant AA as AASchemaDataMigration
participant ZZ as ZZSchemaDataConsolidation
participant M2 as Migraciones (Phase 2)
Admin->>DB: Cambiar configuracion_niveles_tablas
Note over DB: ordcon: [1,2] → [1]
Admin->>M1: php migrate-db-command.php --migrate
M1->>DB: Ejecutar migraciones pendientes
M1->>DB: Crear estructuras nuevas
Note over M1: Seed AA ejecuta automáticamente
M1->>AA: run()
AA->>DB: Detectar tablas en parent no en child
AA->>DB: Migrar datos parent → child (si aplica)
AA-->>M1: Completado
Note over M1: Seed ZZ ejecuta automáticamente
M1->>ZZ: run()
ZZ->>DB: Detectar candidatos (ordcon en suc0001)
ZZ->>DB: Validar sin conflictos
ZZ->>DB: Consolidar suc0001.ordcon → public.ordcon
ZZ->>DB: Sincronizar phinxlog
ZZ->>ZZ: Crear flag /tmp/bautista_consolidation_*.flag
ZZ-->>Admin: ⚠️ RE-EJECUTAR MIGRACIONES REQUERIDO
Admin->>M2: php migrate-db-command.php --migrate
M2->>DB: Detectar phinxlog sincronizado
M2->>DB: Ejecutar migraciones pendientes
M2->>DB: Completar estructuras faltantes
M2-->>Admin: ✅ Proceso completadoFlag de Re-Ejecución
Path: /tmp/bautista_consolidation_executed_{timestamp}.flag
Propósito:
- Indicador persistente de que se requiere re-ejecutar migraciones
- Timestamp permite rastrear cuándo ocurrió la consolidación
- Puede ser usado por scripts de CI/CD para detectar necesidad de re-ejecución
Ejemplo:
bash
/tmp/bautista_consolidation_executed_1738440123.flagScript de Verificación:
bash
#!/bin/bash
if ls /tmp/bautista_consolidation_*.flag 1> /dev/null 2>&1; then
echo "⚠️ Consolidación detectada, re-ejecutando migraciones..."
php migrations/migrate-db-command.php --migrate
rm /tmp/bautista_consolidation_*.flag
else
echo "✅ Sin consolidaciones pendientes"
fiSincronización de phinxlog
Problema: Cuando AASchemaDataMigration detecta que una tabla ya existe en un schema hijo (por ejemplo, creada por una migración anterior), omite crearla nuevamente. Sin embargo, esto puede dejar el phinxlog del schema padre sin esas migraciones marcadas, causando errores futuros.
Solución de ZZ:
- Detecta migraciones en hijo que NO están en padre
- Filtra solo las relevantes a la tabla consolidada
- Las marca como ejecutadas en el padre (simula
--fake)
Ejemplo:
Estado inicial:
suc0001.phinxlog: [20231201120000_CreateOrdconTable, 20231201130000_AddOrdconIndexes]
public.phinxlog: []
Después de consolidación:
public.phinxlog: [20231201120000_CreateOrdconTable, 20231201130000_AddOrdconIndexes]Beneficio: Al re-ejecutar migraciones, Phinx ve que CreateOrdconTable ya está marcada en public.phinxlog, entonces la omite en lugar de fallar con "table already exists".
Error Handling
Validación de Conflictos
Detección:
php
if ($schemasWithDataCount > 1) {
return [
'can_consolidate' => false,
'conflict_reason' => "Múltiples schemas con datos ({$schemasWithDataCount} schemas)",
'schemas_with_data' => $schemasWithData
];
}Output:
❌ CONFLICTO en 'ordcon': Múltiples schemas con datos (2 schemas)
📊 Datos en múltiples schemas:
- suc0001: 150 registros
- suc0002: 100 registros
🛑 Consolidación abortada por conflictos
⚠️ Se detectaron datos en múltiples schemas para una o más tablas
💡 Solución: Consolidar datos manualmente o ajustar configuraciónDecisión Arquitectónica: Abortar COMPLETAMENTE (no consolidar ninguna tabla) si se detecta al menos un conflicto. Esto evita estados inconsistentes donde algunas tablas se consolidan y otras no.
Transacciones Independientes por Tabla
Implementación:
php
foreach ($validated as $candidate) {
try {
$pdo->beginTransaction();
// A. Crear estructura
// B. Copiar datos
// C. Actualizar secuencias
// D. Sincronizar phinxlog
// E. Eliminar origen
$pdo->commit();
$results['success'][] = $candidate['table'];
} catch (\Exception $e) {
$pdo->rollBack();
$results['failed'][] = $candidate['table'];
$this->output->writeln("❌ Error consolidando '{$candidate['table']}'");
}
}Beneficio: Una tabla fallida NO bloquea la consolidación de otras tablas. El proceso continúa y reporta éxitos/fallos al final.
Ejemplo de Fallo Parcial:
📦 Procesando 'ordcon'...
✅ 'ordcon' consolidada exitosamente
📦 Procesando 'factura'...
❌ Error consolidando 'factura'
📊 Resumen: 1 exitosa(s), 1 fallida(s)Logging con Logger Class
Ejemplos de Logging:
php
// Inicio de consolidación
\App\Logger::logMigrationCandidates($candidates, 'Configuration change detected');
// Validación
\App\Logger::logDataValidation(
$candidate['table'],
$currentSchema,
false,
$validation
);
// Migración exitosa
\App\Logger::logDataMigrationEnd($table, $sourceSchema, $targetSchema, $recordCount, $duration);
// Error
\App\Logger::logDataMigrationError($table, $sourceSchema, $targetSchema, $e->getMessage());Archivo de Log: migrations/logs/migrations.log
Formato:
[2026-02-11 10:15:23] migrations.INFO: Candidatos de migración detectados {"candidates":[{"table":"ordcon","from_schema":"suc0001","to_schema":"public"}],"candidate_count":1,"type":"migration_detection","action":"candidates_detected","reason":"Configuration change detected"}
[2026-02-11 10:15:25] migrations.INFO: Finalizando migración de datos: ordcon {"table":"ordcon","source_schema":"suc0001","target_schema":"public","migrated_count":150,"type":"data_migration","action":"end","duration":"1.23s"}Rollback Automático en Errores
Escenarios de Rollback:
- Error al crear estructura:
php
try {
$sql = "CREATE TABLE {$targetSchema}.{$table} (LIKE {$sourceSchema}.{$table} INCLUDING ALL)";
$pdo->exec($sql);
} catch (\Exception $e) {
throw new \Exception("No se pudo crear la estructura en schema destino");
}
// Rollback automático si falla- Error al copiar datos:
php
// Dentro de transacción
$migratedCount = $this->migrateDataWithExplicitMapping($pdo, $table, $sourceSchema, $targetSchema);
// Si falla, rollback automático- Error al sincronizar phinxlog:
php
$synced = $this->markMigrationsAsExecuted($pdo, $targetSchema, $relevantMigrations);
// Si falla, rollback automático (estructura y datos NO se persisten)Garantía: Todas las operaciones dentro de consolidateTableToParent() están en una transacción, asegurando atomicidad.
Manejo de Tablas Inexistentes
Escenario: Intentar contar registros en un schema donde la tabla no existe.
Implementación:
php
protected function countRecordsInSchema(string $tableName, string $schemaName): int
{
$pdo = $this->getPdoConnection(true);
try {
$stmt = $pdo->prepare("SELECT COUNT(*) FROM {$schemaName}.{$tableName}");
$stmt->execute();
return (int) $stmt->fetchColumn();
} catch (Exception) {
return 0; // Tabla no existe, retornar 0
}
}Beneficio: El proceso de validación continúa incluso si algunos schemas hermanos no tienen la tabla.
Architectural Decisions
1. Por qué prefijo ZZ (ejecuta al final)
Razón: Phinx ejecuta seeds en orden alfabético. El prefijo ZZ asegura que ZZSchemaDataConsolidation ejecute DESPUÉS de AASchemaDataMigration.
Flujo:
AASchemaDataMigration (padre → hijo)
↓
BBOtroSeedSiExiste
↓
...
↓
ZZSchemaDataConsolidation (hijo → padre)Importancia: AA debe ejecutar primero para distribuir datos existentes ANTES de que ZZ consolide datos inversos. Si ZZ ejecutara primero, podría consolidar datos que luego AA intentaría distribuir nuevamente, causando inconsistencias.
2. Por qué filtrar migraciones por tabla
Alternativa rechazada: Marcar TODAS las migraciones faltantes en phinxlog destino.
Problema:
Migraciones en suc0001 pero no en public:
- 20231201120000_CreateOrdconTable ← Afecta ordcon (consolidada)
- 20231201130000_CreateFacturaTable ← Afecta factura (NO consolidada)
- 20231201140000_CreateMovimiTable ← Afecta movimi (NO consolidada)
Si marcamos TODAS: factura y movimi quedan en estado "ejecutada" en public pero no existen.
Resultado: Migraciones futuras fallan o se omiten incorrectamente.Solución: Filtrar usando filterMigrationsForTable() para marcar SOLO las migraciones que afectan a ordcon.
Implementación:
php
$relevantMigrations = $this->filterMigrationsForTable($missingMigrations, 'ordcon');
// Resultado: Solo [20231201120000_CreateOrdconTable]3. Por qué NO validar Foreign Keys antes de eliminar
Alternativa rechazada: Consultar information_schema.table_constraints para verificar FKs antes de DROP TABLE.
Decisión: Usar DROP TABLE ... CASCADE sin validación previa.
Razones:
- PostgreSQL CASCADE maneja automáticamente:
sql
DROP TABLE suc0001.ordcon CASCADE;
-- Elimina automáticamente:
-- - Foreign keys en otras tablas que apuntan a ordcon
-- - Vistas que referencian ordcon
-- - Triggers en ordconValidación es innecesaria:
- Si existen FKs,
CASCADElas elimina sin error - Si NO existen FKs,
CASCADEes no-op (sin efecto) - Validar manualmente agrega complejidad sin beneficio
- Si existen FKs,
Performance:
- Evita queries adicionales a
information_schema - Simplifica código
- Evita queries adicionales a
Riesgo mitigado: Después de consolidar, re-ejecutar migraciones recrea las FKs necesarias en el schema destino.
4. Por qué transacciones independientes por tabla
Alternativa rechazada: Una sola transacción para todas las tablas.
Problema con transacción única:
php
$pdo->beginTransaction();
consolidateTable('ordcon'); // ✅ Éxito
consolidateTable('factura'); // ❌ Error
consolidateTable('movimi'); // Nunca se ejecuta
$pdo->rollBack(); // Rollback TODO (ordcon también)Resultado: Una tabla fallida bloquea todas las demás.
Solución con transacciones independientes:
php
foreach ($tables as $table) {
try {
$pdo->beginTransaction();
consolidateTable($table);
$pdo->commit();
$success[] = $table;
} catch (\Exception $e) {
$pdo->rollBack();
$failed[] = $table;
}
}Resultado: ordcon y movimi se consolidan exitosamente; solo factura falla.
Beneficio: Maximiza el progreso incluso con errores parciales.
5. Por qué usar LIKE INCLUDING ALL
Alternativa rechazada: Copiar estructura manualmente (crear columnas, constraints, indexes individualmente).
Decisión: Usar CREATE TABLE ... (LIKE source INCLUDING ALL).
Ventajas:
| Aspecto | LIKE INCLUDING ALL | Manual |
|---|---|---|
| Simplicidad | 1 línea SQL | 50+ líneas |
| Completitud | Copia TODO (defaults, constraints, indexes) | Propenso a omisiones |
| Mantenimiento | Auto-adapta a cambios en estructura | Requiere actualizar código |
| Performance | PostgreSQL optimizado | Múltiples queries |
Limitaciones conocidas:
- NO copia Foreign Keys (se recrean con re-ejecución de migraciones)
- NO copia Triggers (se recrean con re-ejecución de migraciones)
- NO copia datos (se copian después con INSERT SELECT)
Mitigación: Re-ejecución de migraciones después de consolidación completa FKs y Triggers faltantes.
6. Por qué contar registros en schemas hermanos
Alternativa rechazada: Solo validar schema actual vs. schema padre.
Problema:
Escenario:
suc0001.ordcon: 100 registros
suc0002.ordcon: 50 registros
public.ordcon: No existe
Sin validar hermanos:
✅ Consolidar suc0001 → public (100 registros)
Próxima ejecución en suc0002:
✅ Consolidar suc0002 → public (50 registros)
Resultado: Pérdida de 100 registros de suc0001 (sobrescritos por suc0002)Solución: Validar TODOS los schemas hermanos ANTES de consolidar.
Implementación:
php
$siblingSchemas = $this->getSiblingSchemas(); // [suc0002, suc0003, ...]
foreach ($siblingSchemas as $schema) {
$count = $this->countRecordsInSchema($table, $schema);
if ($count > 0) {
$schemasWithData[$schema] = $count;
}
}
if (count($schemasWithData) > 1) {
// CONFLICTO: Múltiples schemas con datos
}Beneficio: Evita pérdida de datos al detectar conflictos ANTES de consolidar.
7. Por qué crear flag de re-ejecución
Alternativa rechazada: Re-ejecutar migraciones automáticamente dentro del seed.
Problemas:
- Recursión infinita:
ZZConsolidation → migrate-db-command → ZZConsolidation → ...Complejidad de contexto:
- Seeds no tienen acceso directo al sistema de migraciones Phinx
- Ejecutar
php migrate-db-command.phpdesde dentro de un seed es anti-patrón
Control del administrador:
- Admin pierde visibilidad de qué ocurre
- Dificulta troubleshooting
Solución: Crear flag + mostrar instrucciones claras.
Beneficios:
| Beneficio | Descripción |
|---|---|
| Transparencia | Admin sabe exactamente qué hacer |
| Control | Admin decide cuándo re-ejecutar |
| Trazabilidad | Flag permite auditar cuándo ocurrió consolidación |
| CI/CD | Scripts automatizados pueden detectar flag |
Ejemplo de Script CI/CD:
bash
# En pipeline de despliegue
php migrations/migrate-db-command.php --migrate
php migrations/seed-db-command.php --run
if ls /tmp/bautista_consolidation_*.flag 1> /dev/null 2>&1; then
echo "⚠️ Consolidación detectada, re-ejecutando migraciones..."
php migrations/migrate-db-command.php --migrate
rm /tmp/bautista_consolidation_*.flag
fiMaintenance & Operations
Monitoreo de Consolidaciones
Archivo de log: migrations/logs/migrations.log
Queries útiles para análisis:
bash
# Buscar consolidaciones en logs
grep "Candidatos de migración detectados" migrations/logs/migrations.log
# Buscar conflictos detectados
grep "CONFLICTO" migrations/logs/migrations.log
# Buscar consolidaciones exitosas
grep "Finalizando migración de datos" migrations/logs/migrations.log | grep "type.*data_migration"
# Buscar errores en consolidación
grep "Error en migración de datos" migrations/logs/migrations.logMétricas clave:
| Métrica | Query |
|---|---|
| Total consolidaciones | grep -c "Candidatos de migración detectados" migrations.log |
| Conflictos detectados | grep -c "CONFLICTO" migrations.log |
| Tiempo promedio | Parsear "duration" de logs |
Troubleshooting
Problema 1: Consolidación abortada por conflictos
Síntoma:
🛑 Consolidación abortada por conflictos
⚠️ Se detectaron datos en múltiples schemas para una o más tablasDiagnóstico:
sql
-- Verificar distribución de datos
SELECT 'suc0001' AS schema, COUNT(*) FROM suc0001.ordcon
UNION ALL
SELECT 'suc0002' AS schema, COUNT(*) FROM suc0002.ordcon
UNION ALL
SELECT 'suc0003' AS schema, COUNT(*) FROM suc0003.ordcon;Soluciones:
- Consolidación manual de datos:
sql
-- Copiar de todos los schemas
INSERT INTO public.ordcon SELECT * FROM suc0001.ordcon ON CONFLICT DO NOTHING;
INSERT INTO public.ordcon SELECT * FROM suc0002.ordcon ON CONFLICT DO NOTHING;
INSERT INTO public.ordcon SELECT * FROM suc0003.ordcon ON CONFLICT DO NOTHING;
-- Actualizar secuencias
SELECT setval('public.ordcon_id_seq', (SELECT MAX(id) FROM public.ordcon), true);
-- Eliminar orígenes
DROP TABLE suc0001.ordcon CASCADE;
DROP TABLE suc0002.ordcon CASCADE;
DROP TABLE suc0003.ordcon CASCADE;- Revertir configuración (si datos deben permanecer distribuidos):
sql
UPDATE sistema
SET configuracion_niveles_tablas = jsonb_set(
COALESCE(configuracion_niveles_tablas, '{}'::jsonb),
'{ordcon}',
'[2]'::jsonb
)
WHERE bd = 'nombre_bd';Problema 2: Re-ejecución de migraciones falla
Síntoma:
php migrations/migrate-db-command.php --migrate
Error: Table 'ordcon' already existsDiagnóstico:
sql
-- Verificar estado de phinxlog
SELECT version, migration_name
FROM public.phinxlog
WHERE migration_name LIKE '%ordcon%';Causa: Migración no se sincronizó correctamente en phinxlog.
Solución:
sql
-- Marcar migración como ejecutada manualmente
INSERT INTO public.phinxlog
(version, migration_name, start_time, end_time, breakpoint)
VALUES
(20231201120000, 'CreateOrdconTable', NOW(), NOW(), 0)
ON CONFLICT (version) DO NOTHING;Problema 3: Secuencias desincronizadas
Síntoma:
ERROR: duplicate key value violates unique constraint "ordcon_pkey"
DETAIL: Key (id)=(150) already exists.Diagnóstico:
sql
-- Ver valor actual de secuencia
SELECT last_value FROM public.ordcon_id_seq;
-- Ver máximo ID en tabla
SELECT MAX(id) FROM public.ordcon;Solución:
sql
-- Sincronizar secuencia manualmente
SELECT setval('public.ordcon_id_seq', (SELECT MAX(id) FROM public.ordcon), true);Mejores Prácticas
1. Backup antes de cambiar configuración
bash
# Backup de configuración actual
pg_dump -h localhost -U postgres -d sistema_bd \
-t sistema \
--data-only \
-f backup_configuracion_niveles_$(date +%Y%m%d_%H%M%S).sql
# Backup de datos completo (opcional)
pg_dump -h localhost -U postgres -d sistema_bd \
-f backup_full_$(date +%Y%m%d_%H%M%S).sql2. Validar configuración antes de aplicar
sql
-- Ver configuración actual
SELECT bd, configuracion_niveles_tablas
FROM sistema
WHERE bd = 'nombre_bd';
-- Validar JSON antes de actualizar
SELECT jsonb_pretty(configuracion_niveles_tablas)
FROM sistema
WHERE bd = 'nombre_bd';3. Ejecutar en ambiente de prueba primero
bash
# 1. Cambiar configuración en BD prueba
UPDATE sistema
SET configuracion_niveles_tablas = '{"ordcon": [1]}'::jsonb
WHERE bd = 'test_bd';
# 2. Ejecutar migraciones en prueba
php migrations/migrate-db-command.php --migrate --database=test_bd
# 3. Verificar resultados
psql -U postgres -d test_bd -c "SELECT * FROM public.ordcon LIMIT 5;"
# 4. Si éxito, aplicar en producción4. Monitorear logs durante consolidación
bash
# Terminal 1: Ejecutar consolidación
php migrations/seed-db-command.php --run --verbosity
# Terminal 2: Monitorear logs en tiempo real
tail -f migrations/logs/migrations.log | grep -E "(CONFLICTO|ERROR|consolidación)"Comandos Útiles
bash
# Ver candidatos de consolidación (dry-run simulado)
psql -U postgres -d sistema_bd -c "
SELECT
t.table_name,
t.table_schema,
s.configuracion_niveles_tablas->t.table_name AS configured_levels
FROM information_schema.tables t
CROSS JOIN sistema s
WHERE t.table_schema = 'suc0001'
AND t.table_type = 'BASE TABLE'
AND s.configuracion_niveles_tablas ? t.table_name
AND NOT (s.configuracion_niveles_tablas->t.table_name @> '2'::jsonb);
"
# Limpiar flags antiguos
find /tmp -name "bautista_consolidation_*.flag" -mtime +7 -delete
# Verificar schemas hermanos
psql -U postgres -d sistema_bd -c "
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name ~ '^suc[0-9]+$'
ORDER BY schema_name;
"
# Ver estado de phinxlog entre schemas
psql -U postgres -d sistema_bd -c "
SELECT
'suc0001' AS schema,
COUNT(*) AS migrations
FROM suc0001.phinxlog
UNION ALL
SELECT
'public' AS schema,
COUNT(*) AS migrations
FROM public.phinxlog;
"Related Documentation
Conceptual Documentation
docs/architecture/database/index.md- Overview de patrones de base de datosdocs/architecture/database/multi-tenant.md- Multi-tenancy con schemas (ConnectionManager, X-Schema)docs/architecture/database/multi-schema.md- Queries cross-schema (UNION ALL, consolidación)
Technical Implementation
bautista-backend/migrations/CLAUDE.md- Sistema de migraciones Phinx, ConfigurableMigrationbautista-backend/migrations/seeds/tenancy/AASchemaDataMigration.php- Seed complementario (padre → hijo)bautista-backend/migrations/MigrationTrait.php- Métodos compartidos para multi-tenancybautista-backend/migrations/Logger.php- Sistema de logging
Management Tools
bautista-backend/migrations/manage-table-levels.php- Script para administrarconfiguracion_niveles_tablas
Version History
| Versión | Fecha | Cambios |
|---|---|---|
| v3.13.1 | 2026-02-11 | Implementación inicial de ZZSchemaDataConsolidation |
Authors & Maintainers
- Sistema Bautista Development Team
- Archivo:
/bautista-backend/migrations/seeds/tenancy/ZZSchemaDataConsolidation.php - Documentación creada: 2026-02-11