Some checks are pending
CI Pipeline / changes (push) Waiting to run
CI Pipeline / core (push) Blocked by required conditions
CI Pipeline / trading-backend (push) Blocked by required conditions
CI Pipeline / trading-data-service (push) Blocked by required conditions
CI Pipeline / trading-frontend (push) Blocked by required conditions
CI Pipeline / erp-core (push) Blocked by required conditions
CI Pipeline / erp-mecanicas (push) Blocked by required conditions
CI Pipeline / gamilit-backend (push) Blocked by required conditions
CI Pipeline / gamilit-frontend (push) Blocked by required conditions
Backend: - Fix email verification and password recovery services - Fix exercise submission and student progress services Frontend: - Update missions, password, and profile API services - Fix ExerciseContentRenderer component Docs & Scripts: - Add SSL/Certbot deployment guide - Add quick deployment guide - Database scripts for testing and validations - Migration and homologation reports - Functions inventory documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
248 lines
11 KiB
Python
248 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Script para comparar archivos DDL entre origen y destino
|
|
"""
|
|
import os
|
|
import hashlib
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
from typing import Dict, List, Tuple, Set
|
|
|
|
ORIGEN = Path("/home/isem/workspace/projects/gamilit/apps/database/ddl/schemas")
|
|
DESTINO = Path("/home/isem/workspace-old/wsl-ubuntu/workspace/workspace-gamilit/gamilit/projects/gamilit/apps/database/ddl/schemas")
|
|
|
|
def get_md5(file_path: Path) -> str:
|
|
"""Calcula el MD5 de un archivo"""
|
|
try:
|
|
with open(file_path, 'rb') as f:
|
|
return hashlib.md5(f.read()).hexdigest()
|
|
except Exception as e:
|
|
return f"ERROR: {e}"
|
|
|
|
def get_sql_files(base_path: Path) -> Dict[str, Path]:
|
|
"""Retorna un diccionario de archivos SQL con ruta relativa como clave"""
|
|
files = {}
|
|
for sql_file in base_path.rglob("*.sql"):
|
|
rel_path = sql_file.relative_to(base_path)
|
|
files[str(rel_path)] = sql_file
|
|
return files
|
|
|
|
def compare_directories():
|
|
"""Compara los directorios y retorna diferencias"""
|
|
print("Recopilando archivos de ORIGEN...")
|
|
origen_files = get_sql_files(ORIGEN)
|
|
print(f"Encontrados {len(origen_files)} archivos en ORIGEN")
|
|
|
|
print("Recopilando archivos de DESTINO...")
|
|
destino_files = get_sql_files(DESTINO)
|
|
print(f"Encontrados {len(destino_files)} archivos en DESTINO")
|
|
|
|
# Identificar archivos nuevos, eliminados y modificados
|
|
nuevos = []
|
|
eliminados = []
|
|
modificados = []
|
|
identicos = []
|
|
|
|
# Archivos en origen
|
|
for rel_path, origen_file in origen_files.items():
|
|
if rel_path not in destino_files:
|
|
nuevos.append((rel_path, origen_file))
|
|
else:
|
|
# Comparar checksums
|
|
origen_md5 = get_md5(origen_file)
|
|
destino_md5 = get_md5(destino_files[rel_path])
|
|
|
|
if origen_md5 != destino_md5:
|
|
modificados.append((rel_path, origen_file, destino_files[rel_path], origen_md5, destino_md5))
|
|
else:
|
|
identicos.append(rel_path)
|
|
|
|
# Archivos solo en destino
|
|
for rel_path, destino_file in destino_files.items():
|
|
if rel_path not in origen_files:
|
|
eliminados.append((rel_path, destino_file))
|
|
|
|
return nuevos, eliminados, modificados, identicos
|
|
|
|
def group_by_schema(files: List[Tuple]) -> Dict[str, List]:
|
|
"""Agrupa archivos por schema"""
|
|
grouped = defaultdict(list)
|
|
for item in files:
|
|
rel_path = item[0]
|
|
schema = rel_path.split('/')[0] if '/' in rel_path else 'root'
|
|
grouped[schema].append(item)
|
|
return dict(grouped)
|
|
|
|
def generate_report(nuevos, eliminados, modificados, identicos):
|
|
"""Genera el reporte en formato Markdown"""
|
|
output_file = "/home/isem/workspace/projects/gamilit/orchestration/analisis-homologacion-database-2025-12-18/REPORTE-DDL-DIFERENCIAS.md"
|
|
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
f.write("# REPORTE DE DIFERENCIAS DDL - ORIGEN vs DESTINO\n\n")
|
|
f.write("**Fecha:** 2025-12-18\n\n")
|
|
f.write("**Proyecto:** Gamilit - Homologación de Base de Datos\n\n")
|
|
|
|
# Resumen ejecutivo
|
|
f.write("## RESUMEN EJECUTIVO\n\n")
|
|
f.write(f"- **Archivos idénticos:** {len(identicos)}\n")
|
|
f.write(f"- **Archivos NUEVOS (en origen, no en destino):** {len(nuevos)}\n")
|
|
f.write(f"- **Archivos ELIMINADOS (en destino, no en origen):** {len(eliminados)}\n")
|
|
f.write(f"- **Archivos MODIFICADOS:** {len(modificados)}\n")
|
|
f.write(f"- **Total archivos analizados:** {len(identicos) + len(nuevos) + len(eliminados) + len(modificados)}\n\n")
|
|
|
|
# Directorios
|
|
f.write("## DIRECTORIOS ANALIZADOS\n\n")
|
|
f.write(f"- **ORIGEN:** `{ORIGEN}`\n")
|
|
f.write(f"- **DESTINO:** `{DESTINO}`\n\n")
|
|
|
|
# Archivos NUEVOS
|
|
if nuevos:
|
|
f.write(f"## ARCHIVOS NUEVOS ({len(nuevos)})\n\n")
|
|
f.write("Archivos que existen en ORIGEN pero NO en DESTINO.\n\n")
|
|
|
|
grouped = group_by_schema(nuevos)
|
|
for schema in sorted(grouped.keys()):
|
|
files = grouped[schema]
|
|
f.write(f"### Schema: `{schema}` ({len(files)} archivos)\n\n")
|
|
for rel_path, full_path in sorted(files):
|
|
f.write(f"- `{rel_path}`\n")
|
|
f.write("\n")
|
|
else:
|
|
f.write("## ARCHIVOS NUEVOS\n\n")
|
|
f.write("No se encontraron archivos nuevos.\n\n")
|
|
|
|
# Archivos ELIMINADOS
|
|
if eliminados:
|
|
f.write(f"## ARCHIVOS ELIMINADOS ({len(eliminados)})\n\n")
|
|
f.write("Archivos que existen en DESTINO pero NO en ORIGEN.\n\n")
|
|
|
|
grouped = group_by_schema(eliminados)
|
|
for schema in sorted(grouped.keys()):
|
|
files = grouped[schema]
|
|
f.write(f"### Schema: `{schema}` ({len(files)} archivos)\n\n")
|
|
for rel_path, full_path in sorted(files):
|
|
f.write(f"- `{rel_path}`\n")
|
|
f.write("\n")
|
|
else:
|
|
f.write("## ARCHIVOS ELIMINADOS\n\n")
|
|
f.write("No se encontraron archivos eliminados.\n\n")
|
|
|
|
# Archivos MODIFICADOS
|
|
if modificados:
|
|
f.write(f"## ARCHIVOS MODIFICADOS ({len(modificados)})\n\n")
|
|
f.write("Archivos con contenido diferente entre ORIGEN y DESTINO.\n\n")
|
|
|
|
grouped_mod = defaultdict(list)
|
|
for item in modificados:
|
|
rel_path = item[0]
|
|
schema = rel_path.split('/')[0] if '/' in rel_path else 'root'
|
|
grouped_mod[schema].append(item)
|
|
|
|
for schema in sorted(grouped_mod.keys()):
|
|
files = grouped_mod[schema]
|
|
f.write(f"### Schema: `{schema}` ({len(files)} archivos)\n\n")
|
|
for rel_path, origen_file, destino_file, md5_origen, md5_destino in sorted(files):
|
|
f.write(f"#### `{rel_path}`\n\n")
|
|
f.write(f"- **MD5 Origen:** `{md5_origen}`\n")
|
|
f.write(f"- **MD5 Destino:** `{md5_destino}`\n")
|
|
f.write(f"- **Ruta Origen:** `{origen_file}`\n")
|
|
f.write(f"- **Ruta Destino:** `{destino_file}`\n\n")
|
|
f.write("\n")
|
|
else:
|
|
f.write("## ARCHIVOS MODIFICADOS\n\n")
|
|
f.write("No se encontraron archivos modificados.\n\n")
|
|
|
|
# Distribución por schema
|
|
f.write("## DISTRIBUCIÓN POR SCHEMA\n\n")
|
|
f.write("| Schema | Idénticos | Nuevos | Eliminados | Modificados | Total |\n")
|
|
f.write("|--------|-----------|--------|------------|-------------|-------|\n")
|
|
|
|
# Recopilar estadísticas por schema
|
|
schemas_stats = defaultdict(lambda: {'identicos': 0, 'nuevos': 0, 'eliminados': 0, 'modificados': 0})
|
|
|
|
for rel_path in identicos:
|
|
schema = rel_path.split('/')[0] if '/' in rel_path else 'root'
|
|
schemas_stats[schema]['identicos'] += 1
|
|
|
|
for rel_path, _ in nuevos:
|
|
schema = rel_path.split('/')[0] if '/' in rel_path else 'root'
|
|
schemas_stats[schema]['nuevos'] += 1
|
|
|
|
for rel_path, _ in eliminados:
|
|
schema = rel_path.split('/')[0] if '/' in rel_path else 'root'
|
|
schemas_stats[schema]['eliminados'] += 1
|
|
|
|
for rel_path, *_ in modificados:
|
|
schema = rel_path.split('/')[0] if '/' in rel_path else 'root'
|
|
schemas_stats[schema]['modificados'] += 1
|
|
|
|
for schema in sorted(schemas_stats.keys()):
|
|
stats = schemas_stats[schema]
|
|
total = stats['identicos'] + stats['nuevos'] + stats['eliminados'] + stats['modificados']
|
|
f.write(f"| `{schema}` | {stats['identicos']} | {stats['nuevos']} | {stats['eliminados']} | {stats['modificados']} | {total} |\n")
|
|
|
|
# Totales
|
|
f.write(f"| **TOTAL** | **{len(identicos)}** | **{len(nuevos)}** | **{len(eliminados)}** | **{len(modificados)}** | **{len(identicos) + len(nuevos) + len(eliminados) + len(modificados)}** |\n\n")
|
|
|
|
# Recomendaciones
|
|
f.write("## RECOMENDACIONES\n\n")
|
|
|
|
if nuevos:
|
|
f.write(f"### Archivos NUEVOS ({len(nuevos)})\n\n")
|
|
f.write("**Acción:** Estos archivos deben ser añadidos al destino si son necesarios para la homologación.\n\n")
|
|
f.write("**Consideraciones:**\n")
|
|
f.write("- Validar que sean cambios intencionales y no archivos de desarrollo/testing\n")
|
|
f.write("- Revisar dependencias con otros esquemas\n")
|
|
f.write("- Ejecutar en orden correcto (schemas, tables, functions, triggers, views, indexes, policies)\n\n")
|
|
|
|
if eliminados:
|
|
f.write(f"### Archivos ELIMINADOS ({len(eliminados)})\n\n")
|
|
f.write("**Acción:** Estos archivos existen en destino pero no en origen. Determinar si deben ser eliminados o conservados.\n\n")
|
|
f.write("**Consideraciones:**\n")
|
|
f.write("- Verificar si fueron eliminados intencionalmente en origen\n")
|
|
f.write("- Validar que no sean archivos legacy que aún se usan en producción\n")
|
|
f.write("- Documentar razón de eliminación\n\n")
|
|
|
|
if modificados:
|
|
f.write(f"### Archivos MODIFICADOS ({len(modificados)})\n\n")
|
|
f.write("**Acción:** Revisar diferencias específicas para determinar cambios necesarios.\n\n")
|
|
f.write("**Consideraciones:**\n")
|
|
f.write("- Usar `diff` para ver diferencias línea por línea\n")
|
|
f.write("- Validar que los cambios sean compatibles con datos existentes\n")
|
|
f.write("- Crear scripts de migración si es necesario\n")
|
|
f.write("- Probar en ambiente de desarrollo antes de aplicar en producción\n\n")
|
|
|
|
f.write("## PRÓXIMOS PASOS\n\n")
|
|
f.write("1. **Análisis detallado:** Revisar cada archivo modificado con `diff` para entender cambios específicos\n")
|
|
f.write("2. **Clasificación:** Categorizar cambios por tipo (schema, tables, functions, etc.)\n")
|
|
f.write("3. **Plan de migración:** Crear secuencia de ejecución ordenada\n")
|
|
f.write("4. **Testing:** Validar cambios en ambiente de desarrollo\n")
|
|
f.write("5. **Documentación:** Documentar cada cambio y su justificación\n")
|
|
f.write("6. **Deployment:** Aplicar cambios en producción con backup previo\n\n")
|
|
|
|
f.write("---\n\n")
|
|
f.write("*Reporte generado automáticamente - 2025-12-18*\n")
|
|
|
|
print(f"\nReporte generado: {output_file}")
|
|
return output_file
|
|
|
|
if __name__ == "__main__":
|
|
print("=" * 80)
|
|
print("ANÁLISIS DE DIFERENCIAS DDL - ORIGEN vs DESTINO")
|
|
print("=" * 80)
|
|
print()
|
|
|
|
nuevos, eliminados, modificados, identicos = compare_directories()
|
|
|
|
print("\n" + "=" * 80)
|
|
print("RESUMEN")
|
|
print("=" * 80)
|
|
print(f"Archivos idénticos: {len(identicos)}")
|
|
print(f"Archivos NUEVOS: {len(nuevos)}")
|
|
print(f"Archivos ELIMINADOS: {len(eliminados)}")
|
|
print(f"Archivos MODIFICADOS: {len(modificados)}")
|
|
print()
|
|
|
|
output_file = generate_report(nuevos, eliminados, modificados, identicos)
|
|
print(f"\nReporte completo guardado en: {output_file}")
|