Reestructuración modular integral de servicios, reorganización de gestión en docs/ y limpieza profunda del repositorio

This commit is contained in:
Ricardo Monla
2026-03-06 18:03:07 -03:00
parent 15725e0b3c
commit ac9bdf516b
286 changed files with 36187 additions and 112775 deletions
+540
View File
@@ -0,0 +1,540 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# tools/orquestador/dashboard/sincronizar.rb
# ==========================================
# Herramienta para sincronizar el dashboard local con producción
# Filosofía: "Menos es más" - herramienta pequeña, específica y reutilizable
require 'fileutils'
require 'time'
class DashboardSynchronizer
# Configuración de rutas
CONFIG = {
local: {
dashboard_dir: File.expand_path('../../../../dashboard', __FILE__),
index_file: 'index.html',
backups_dir: 'backups'
},
production: {
dashboard_dir: '/var/www/html/P2601',
index_file: 'index.html',
backups_dir: '/var/www/html/P2601/backups'
}
}.freeze
def initialize(options = {})
@dry_run = options[:dry_run] || false
@verbose = options[:verbose] || false
@backup = options[:backup] || true
@timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
end
def sincronizar_a_produccion
puts "🔄 SINCRONIZANDO DASHBOARD A PRODUCCIÓN"
puts "=" * 60
# Verificar rutas locales
unless verificar_ruta_local
puts "❌ Error: No se puede acceder al dashboard local"
return false
end
# Verificar rutas de producción
unless verificar_ruta_produccion
puts "❌ Error: No se puede acceder al dashboard de producción"
puts " Ruta: #{CONFIG[:production][:dashboard_dir]}"
return false
end
# Crear backup en producción si está habilitado
if @backup && !@dry_run
unless crear_backup_produccion
puts "❌ Error: No se pudo crear backup en producción"
return false
end
end
# Sincronizar archivos
puts "📁 Sincronizando archivos..."
archivos_sincronizados = sincronizar_archivos
# Mostrar resumen
mostrar_resumen_sincronizacion(archivos_sincronizados)
archivos_sincronizados.any?
end
def verificar_estado
puts "🔍 VERIFICANDO ESTADO DE SINCRONIZACIÓN"
puts "=" * 60
estado = {
local: verificar_estado_local,
production: verificar_estado_produccion,
diferencias: comparar_archivos
}
mostrar_estado_verificacion(estado)
estado[:local][:valido] && estado[:production][:valido]
end
def listar_backups
puts "💾 LISTADO DE BACKUPS EN PRODUCCIÓN"
puts "=" * 60
backups_dir = CONFIG[:production][:backups_dir]
unless Dir.exist?(backups_dir)
puts "️ No existe el directorio de backups: #{backups_dir}"
return false
end
backups = Dir.glob(File.join(backups_dir, '*.html')).sort.reverse
if backups.empty?
puts "️ No se encontraron backups"
return true
end
puts "📂 Directorio: #{backups_dir}"
puts "📊 Total de backups: #{backups.size}"
puts
backups.each_with_index do |backup, i|
nombre = File.basename(backup)
tamano = File.size(backup) / 1024
modificacion = File.mtime(backup).strftime('%Y-%m-%d %H:%M:%S')
puts " #{i + 1}. #{nombre}"
puts " • Tamaño: #{tamano} KB"
puts " • Modificado: #{modificacion}"
puts
end
true
end
def restaurar_backup(nombre_backup = nil)
puts "🔄 RESTAURANDO BACKUP DESDE PRODUCCIÓN"
puts "=" * 60
backups_dir = CONFIG[:production][:backups_dir]
unless Dir.exist?(backups_dir)
puts "❌ Error: No existe el directorio de backups: #{backups_dir}"
return false
end
# Si no se especifica backup, listar disponibles
unless nombre_backup
backups = Dir.glob(File.join(backups_dir, '*.html')).sort.reverse
if backups.empty?
puts "❌ Error: No hay backups disponibles para restaurar"
return false
end
puts "📋 Backups disponibles:"
backups.each_with_index do |backup, i|
puts " #{i + 1}. #{File.basename(backup)}"
end
puts "\n️ Use: #{$0} restaurar NOMBRE_BACKUP"
return false
end
# Verificar que el backup existe
ruta_backup = File.join(backups_dir, nombre_backup)
unless File.exist?(ruta_backup)
puts "❌ Error: Backup no encontrado: #{nombre_backup}"
puts " Ruta: #{ruta_backup}"
return false
end
# Crear backup del estado actual local
if @backup && !@dry_run
puts "💾 Creando backup del estado local actual..."
crear_backup_local
end
# Restaurar backup
puts "📥 Restaurando backup: #{nombre_backup}"
archivo_local = File.join(CONFIG[:local][:dashboard_dir], CONFIG[:local][:index_file])
if @dry_run
puts " [DRY RUN] Se restauraría #{ruta_backup} a #{archivo_local}"
puts " [DRY RUN] Tamaño: #{File.size(ruta_backup)} bytes"
else
begin
FileUtils.cp(ruta_backup, archivo_local)
puts "✅ Backup restaurado exitosamente"
puts " • Origen: #{ruta_backup}"
puts " • Destino: #{archivo_local}"
puts " • Tamaño: #{File.size(archivo_local)} bytes"
rescue => e
puts "❌ Error al restaurar backup: #{e.message}"
return false
end
end
true
end
private
def verificar_ruta_local
dir = CONFIG[:local][:dashboard_dir]
archivo = File.join(dir, CONFIG[:local][:index_file])
unless Dir.exist?(dir)
puts "❌ Directorio local no existe: #{dir}"
return false
end
unless File.exist?(archivo)
puts "❌ Archivo local no existe: #{archivo}"
return false
end
true
end
def verificar_ruta_produccion
dir = CONFIG[:production][:dashboard_dir]
unless Dir.exist?(dir)
puts "❌ Directorio de producción no existe: #{dir}"
return false
end
# Verificar permisos de escritura
unless File.writable?(dir)
puts "❌ Sin permisos de escritura en: #{dir}"
return false
end
true
end
def crear_backup_produccion
puts "💾 Creando backup en producción..."
backups_dir = CONFIG[:production][:backups_dir]
archivo_produccion = File.join(CONFIG[:production][:dashboard_dir], CONFIG[:production][:index_file])
# Crear directorio de backups si no existe
unless Dir.exist?(backups_dir)
puts " Creando directorio de backups: #{backups_dir}"
FileUtils.mkdir_p(backups_dir)
end
# Nombre del backup
nombre_backup = "dashboard_prod_#{@timestamp}.html"
ruta_backup = File.join(backups_dir, nombre_backup)
# Crear backup
begin
FileUtils.cp(archivo_produccion, ruta_backup)
puts "✅ Backup creado: #{nombre_backup}"
puts " • Ruta: #{ruta_backup}"
puts " • Tamaño: #{File.size(ruta_backup)} bytes"
true
rescue => e
puts "❌ Error al crear backup: #{e.message}"
false
end
end
def crear_backup_local
backups_dir = File.join(CONFIG[:local][:dashboard_dir], CONFIG[:local][:backups_dir])
archivo_local = File.join(CONFIG[:local][:dashboard_dir], CONFIG[:local][:index_file])
# Crear directorio de backups si no existe
unless Dir.exist?(backups_dir)
FileUtils.mkdir_p(backups_dir)
end
# Nombre del backup
nombre_backup = "dashboard_local_#{@timestamp}.html"
ruta_backup = File.join(backups_dir, nombre_backup)
# Crear backup
FileUtils.cp(archivo_local, ruta_backup)
end
def sincronizar_archivos
archivos_sincronizados = []
# Archivo principal (index.html)
archivo_local = File.join(CONFIG[:local][:dashboard_dir], CONFIG[:local][:index_file])
archivo_produccion = File.join(CONFIG[:production][:dashboard_dir], CONFIG[:production][:index_file])
puts "📄 Sincronizando: #{CONFIG[:local][:index_file]}"
puts " • Local: #{archivo_local}"
puts " • Producción: #{archivo_produccion}"
# Verificar si hay diferencias
if archivos_iguales?(archivo_local, archivo_produccion)
puts " ️ Los archivos son idénticos, no se requiere sincronización"
else
if @dry_run
puts " [DRY RUN] Se copiaría #{archivo_local} a #{archivo_produccion}"
puts " [DRY RUN] Diferencia de tamaño: #{File.size(archivo_local) - File.size(archivo_produccion)} bytes"
else
begin
FileUtils.cp(archivo_local, archivo_produccion)
puts " ✅ Sincronizado exitosamente"
archivos_sincronizados << CONFIG[:local][:index_file]
rescue => e
puts " ❌ Error al sincronizar: #{e.message}"
end
end
end
# Buscar otros archivos en el directorio local
Dir.glob(File.join(CONFIG[:local][:dashboard_dir], '*')).each do |archivo_local|
next if File.directory?(archivo_local)
next if File.basename(archivo_local) == CONFIG[:local][:index_file]
archivo_produccion = File.join(CONFIG[:production][:dashboard_dir], File.basename(archivo_local))
puts "📄 Sincronizando: #{File.basename(archivo_local)}"
if @dry_run
puts " [DRY RUN] Se copiaría #{archivo_local} a #{archivo_produccion}"
else
begin
FileUtils.cp(archivo_local, archivo_produccion)
puts " ✅ Sincronizado exitosamente"
archivos_sincronizados << File.basename(archivo_local)
rescue => e
puts " ❌ Error al sincronizar: #{e.message}"
end
end
end
archivos_sincronizados
end
def archivos_iguales?(archivo1, archivo2)
return false unless File.exist?(archivo1) && File.exist?(archivo2)
return false unless File.size(archivo1) == File.size(archivo2)
# Comparación simple por tamaño y contenido
File.read(archivo1) == File.read(archivo2)
end
def verificar_estado_local
dir = CONFIG[:local][:dashboard_dir]
archivo = File.join(dir, CONFIG[:local][:index_file])
{
valido: File.exist?(archivo),
ruta: dir,
archivo: CONFIG[:local][:index_file],
existe: File.exist?(archivo),
tamano: File.exist?(archivo) ? File.size(archivo) : 0,
modificacion: File.exist?(archivo) ? File.mtime(archivo).strftime('%Y-%m-%d %H:%M:%S') : 'N/A'
}
end
def verificar_estado_produccion
dir = CONFIG[:production][:dashboard_dir]
archivo = File.join(dir, CONFIG[:production][:index_file])
{
valido: File.exist?(archivo),
ruta: dir,
archivo: CONFIG[:production][:index_file],
existe: File.exist?(archivo),
tamano: File.exist?(archivo) ? File.size(archivo) : 0,
modificacion: File.exist?(archivo) ? File.mtime(archivo).strftime('%Y-%m-%d %H:%M:%S') : 'N/A',
escribible: File.writable?(dir)
}
end
def comparar_archivos
archivo_local = File.join(CONFIG[:local][:dashboard_dir], CONFIG[:local][:index_file])
archivo_produccion = File.join(CONFIG[:production][:dashboard_dir], CONFIG[:production][:index_file])
return {} unless File.exist?(archivo_local) && File.exist?(archivo_produccion)
{
iguales: archivos_iguales?(archivo_local, archivo_produccion),
tamano_local: File.size(archivo_local),
tamano_produccion: File.size(archivo_produccion),
diferencia_tamano: File.size(archivo_local) - File.size(archivo_produccion),
modificacion_local: File.mtime(archivo_local),
modificacion_produccion: File.mtime(archivo_produccion),
mas_reciente: File.mtime(archivo_local) > File.mtime(archivo_produccion) ? 'local' : 'produccion'
}
end
def mostrar_resumen_sincronizacion(archivos_sincronizados)
puts "\n📊 RESUMEN DE SINCRONIZACIÓN"
puts "=" * 60
if @dry_run
puts "🔍 MODO SIMULACIÓN (DRY RUN)"
puts " No se realizaron cambios reales"
end
if archivos_sincronizados.empty?
puts "️ No se sincronizaron archivos"
puts " • Los archivos ya están sincronizados"
puts " • O hubo errores en la sincronización"
else
puts "✅ Archivos sincronizados: #{archivos_sincronizados.size}"
archivos_sincronizados.each do |archivo|
puts "#{archivo}"
end
end
# Mostrar URLs
puts "\n🔗 URLs DEL DASHBOARD:"
puts " • Local: file://#{CONFIG[:local][:dashboard_dir]}/#{CONFIG[:local][:index_file]}"
puts " • Producción: https://ns8.frlr.utn.edu.ar/P2601/"
puts " • Bitácoras: https://ns8.frlr.utn.edu.ar/bitacoras/p2601"
puts "\n💡 RECOMENDACIONES:"
puts " 1. Verificar cambios en producción: https://ns8.frlr.utn.edu.ar/P2601/"
puts " 2. Usar 'verificar' para validar la sincronización"
puts " 3. Revisar backups si es necesario restaurar"
end
def mostrar_estado_verificacion(estado)
puts "🏠 ESTADO LOCAL:"
if estado[:local][:valido]
puts " ✅ Dashboard local válido"
puts " • Ruta: #{estado[:local][:ruta]}"
puts " • Archivo: #{estado[:local][:archivo]}"
puts " • Tamaño: #{estado[:local][:tamano]} bytes"
puts " • Modificación: #{estado[:local][:modificacion]}"
else
puts " ❌ Dashboard local no válido"
end
puts "\n🏭 ESTADO PRODUCCIÓN:"
if estado[:production][:valido]
puts " ✅ Dashboard de producción válido"
puts " • Ruta: #{estado[:production][:ruta]}"
puts " • Archivo: #{estado[:production][:archivo]}"
puts " • Tamaño: #{estado[:production][:tamano]} bytes"
puts " • Modificación: #{estado[:production][:modificacion]}"
puts " • Escribible: #{estado[:production][:escribible] ? '✅ Sí' : '❌ No'}"
else
puts " ❌ Dashboard de producción no válido"
end
if estado[:diferencias].any?
puts "\n🔍 COMPARACIÓN DE ARCHIVOS:"
if estado[:diferencias][:iguales]
puts " ✅ Los archivos son idénticos"
else
puts " ⚠️ Los archivos son diferentes"
puts " • Tamaño local: #{estado[:diferencias][:tamano_local]} bytes"
puts " • Tamaño producción: #{estado[:diferencias][:tamano_produccion]} bytes"
puts " • Diferencia: #{estado[:diferencias][:diferencia_tamano]} bytes"
puts " • Más reciente: #{estado[:diferencias][:mas_reciente]}"
end
end
puts "\n💡 RECOMENDACIONES:"
if estado[:local][:valido] && estado[:production][:valido]
if estado[:diferencias][:iguales]
puts " ✅ Los dashboards están sincronizados"
puts " • No se requiere acción"
else
puts " ⚠️ Los dashboards están desincronizados"
puts " • Use 'sincronizar' para actualizar producción"
puts " • Use 'sincronizar --dry-run' para simular"
end
else
puts " ❌ Hay problemas con los dashboards"
puts " • Verifique las rutas y permisos"
end
end
end
# Interfaz de línea de comandos
if __FILE__ == $0
begin
synchronizer = DashboardSynchronizer.new
# Parsear argumentos
command = ARGV[0] || 'verificar'
options = {
dry_run: ARGV.include?('--dry-run') || ARGV.include?('-n'),
verbose: ARGV.include?('--verbose') || ARGV.include?('-v'),
backup: !ARGV.include?('--no-backup')
}
# Extraer argumentos adicionales
extra_args = (ARGV[1..-1] || []).reject { |arg| arg.start_with?('--') || arg == '-n' || arg == '-v' }
case command
when 'sincronizar', 'sync'
synchronizer = DashboardSynchronizer.new(options)
success = synchronizer.sincronizar_a_produccion
when 'verificar', 'check'
success = synchronizer.verificar_estado
when 'backups', 'list-backups'
success = synchronizer.listar_backups
when 'restaurar', 'restore'
nombre_backup = extra_args[0]
synchronizer = DashboardSynchronizer.new(options)
success = synchronizer.restaurar_backup(nombre_backup)
when 'help', '--help', '-h'
puts "📖 USO:"
puts " #{$0} [comando] [argumentos...]"
puts
puts "COMANDOS:"
puts " sincronizar, sync - Sincronizar dashboard local a producción"
puts " verificar, check - Verificar estado de sincronización"
puts " backups, list-backups - Listar backups en producción"
puts " restaurar, restore [NOMBRE] - Restaurar backup desde producción"
puts " help - Mostrar esta ayuda"
puts
puts "OPCIONES:"
puts " --dry-run, -n - Simular sin hacer cambios"
puts " --verbose, -v - Mostrar detalles adicionales"
puts " --no-backup - No crear backups automáticos"
puts
puts "EJEMPLOS:"
puts " #{$0} sincronizar"
puts " #{$0} sincronizar --dry-run"
puts " #{$0} verificar"
puts " #{$0} backups"
puts " #{$0} restaurar dashboard_prod_20240306_114500.html"
puts
puts "🔗 URLs IMPORTANTES:"
puts " • Producción: https://ns8.frlr.utn.edu.ar/P2601/"
puts " • Bitácoras: https://ns8.frlr.utn.edu.ar/bitacoras/p2601"
puts
success = true
else
puts "❌ Comando '#{command}' no reconocido"
puts " Use '#{$0} help' para ver la ayuda"
success = false
end
if success
puts "\n🚀 PRÓXIMOS PASOS:"
puts " 1. Verificar cambios en: https://ns8.frlr.utn.edu.ar/P2601/"
puts " 2. Usar herramientas de orquestador para gestión continua"
puts " 3. Revisar backups periódicamente"
puts
puts "📅 #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
puts "=" * 60
else
exit 1
end
rescue => e
puts "❌ Error: #{e.message}"
puts e.backtrace if ENV['DEBUG']
exit 1
end
end