[A04.P005] DASUTEN: Pipeline refactorizado + Documentación + Informes
Refactorización del pipeline de backups DASUTEN: 1. Flujo simplificado (un solo destino): - Backup directo a X:\ (share srv-ns8) - Eliminado paso intermedio E:\BK_SQL\sysdasuten\ - Reducido de 3 ubicaciones a 2 (X: → /var/tmp/) 2. Módulo común DasuExecutor (lib/dasu_executor.rb): - Centraliza ejecución PowerShell en dasu-sql4 - Centraliza verificación de archivos en fenix - Elimina código repetido en 6 drones (-52% líneas) 3. Drones refactorizados: - dasuten_exportar_srvv-fenix.rb (FULL → X:) - dasuten_exportar_diferencial_srvv-fenix.rb (DIF → X:) - dasuten_transferir_srvv-fenix-srv-ns8.rb (simplificado) - dasuten_restaurar_dasu-sql4.rb (usa DasuExecutor) - dasuten_restaurar_diferencial_dasu-sql4.rb (usa DasuExecutor) - dasuten_download_drive-dasu-sql4.rb (usa DasuExecutor) - dasuten_verificar-integridad_dasu-sql4.rb (usa DasuExecutor) 4. Integración BKPs mantenida: - T8-T15: Tareas individuales - C7: dasuten_full (pipeline semanal) - C8: dasuten_diferencial (pipeline diario) 5. Documentación actualizada: - A04.P006_Backups-DASUTEN.md: flujo simplificado + módulo común - Lecciones aprendidas agregadas 6. Nuevos archivos: - Informes técnicos DASUTEN (docs/informes/2026-04_DASUTEN-migracion/) - A01.P011/P012: Documentación de drones y pipeline - proc_linux.rb, ops.rb: Utilitarios del ecosistema 7. Scripts legacy (para referencia): - orquestador_pipeline.rb, orquestador_pipeline_diferencial.rb - dasuten_upload_srv-ns8-drive.rb
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ==========================================================
|
||||
# adn/tools/cli/drones/dasuten_upload_srv-ns8-drive.rb
|
||||
# ==========================================================
|
||||
# Dron: Upload a Google Drive desde srv-ns8
|
||||
# Flujo: /var/tmp/sysdasuten_compressed_ADN.bak → Google Drive
|
||||
# Entrada: Backup ya descargado en /var/tmp/
|
||||
# Salida: URL de Google Drive
|
||||
# ==========================================================
|
||||
|
||||
require 'time'
|
||||
require 'json'
|
||||
require 'shellwords'
|
||||
|
||||
REMOTE_DRIVE = "rmonla-GDrive:drive_bkps-dasu"
|
||||
|
||||
# Leer ruta del backup desde el output de la transferencia
|
||||
TRANSFER_OUTPUT = '/tmp/dron_transfer_output.json'
|
||||
if File.exist?(TRANSFER_OUTPUT)
|
||||
output = JSON.parse(File.read(TRANSFER_OUTPUT))
|
||||
BACKUP_LOCAL = output['backup_local']
|
||||
BACKUP_NAME = File.basename(BACKUP_LOCAL)
|
||||
puts "[*] Dron: Upload a Google Drive (DIFERENCIAL)"
|
||||
else
|
||||
BACKUP_LOCAL = "/var/tmp/sysdasuten_compressed_ADN.bak"
|
||||
BACKUP_NAME = "sysdasuten_compressed_ADN_#{Time.now.strftime('%Y%m%d_%H%M%S')}.bak"
|
||||
puts "[*] Dron: Upload a Google Drive (COMPLETO)"
|
||||
end
|
||||
|
||||
puts " Nodo: srv-ns8"
|
||||
puts " Origen: #{BACKUP_LOCAL}"
|
||||
puts " Destino: #{REMOTE_DRIVE}/#{BACKUP_NAME}"
|
||||
|
||||
# Verificar archivo local
|
||||
unless File.exist?(BACKUP_LOCAL)
|
||||
puts "[!] Backup no encontrado en #{BACKUP_LOCAL}"
|
||||
puts " Ejecutar primero: dasuten_transferir_srvv-fenix-srv-ns8.rb"
|
||||
exit 1
|
||||
end
|
||||
|
||||
tamaño_mb = (File.size(BACKUP_LOCAL) / 1024.0 / 1024.0).round(2)
|
||||
puts "[+] Archivo local: #{tamaño_mb} MB"
|
||||
|
||||
begin
|
||||
# Upload a Google Drive
|
||||
puts "\n[1/2] Subiendo a Google Drive..."
|
||||
start = Time.now
|
||||
|
||||
cmd_upload = "rclone copy '#{BACKUP_LOCAL}' '#{REMOTE_DRIVE}/#{BACKUP_NAME}' --progress -v 2>&1"
|
||||
system(cmd_upload)
|
||||
|
||||
if $?.success?
|
||||
duracion = (Time.now - start).round(2)
|
||||
puts "[+] Upload completado en #{duracion} segundos"
|
||||
else
|
||||
puts "[!] Error en upload (código: #{$?.exitstatus})"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Verificar en Drive
|
||||
puts "\n[2/2] Verificando archivo en Drive..."
|
||||
cmd_verify = "rclone size '#{REMOTE_DRIVE}/#{BACKUP_NAME}' 2>/dev/null"
|
||||
output = `#{cmd_verify}`.strip
|
||||
|
||||
puts " Tamaño en Drive: #{output}"
|
||||
|
||||
# Output para orquestador
|
||||
resultado = {
|
||||
paso: 'upload_complete',
|
||||
drive_ruta: "#{REMOTE_DRIVE}/#{BACKUP_NAME}",
|
||||
drive_url: "https://drive.google.com/drive/folders/1Tgrn2QYyWf0v0eyY1bSGCLlhiqhtIA2n",
|
||||
nodo: 'srv-ns8',
|
||||
duracion: duracion,
|
||||
timestamp: Time.now.iso8601,
|
||||
siguiente_paso: 'download_drive'
|
||||
}
|
||||
|
||||
puts "\n✅ Upload completado"
|
||||
puts " Output: #{resultado.to_json}"
|
||||
|
||||
File.write('/tmp/dron_upload_output.json', resultado.to_json)
|
||||
|
||||
rescue => e
|
||||
puts "[!] Error fatal: #{e.message}"
|
||||
exit 1
|
||||
end
|
||||
Executable
+207
@@ -0,0 +1,207 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ==========================================================
|
||||
# adn/tools/cli/drones/orquestador_pipeline.rb
|
||||
# ==========================================================
|
||||
# Orquestador de Pipeline DASUTEN — Intercomunicación vía JSON
|
||||
# ==========================================================
|
||||
# Flujo: exportar → transferir SMB → upload → download → restaurar → verificar
|
||||
# Cada dron escribe su output en JSON y el orquestador lo lee
|
||||
# para decidir el siguiente paso.
|
||||
# ==========================================================
|
||||
|
||||
require 'optparse'
|
||||
require 'json'
|
||||
require 'time'
|
||||
require 'shellwords'
|
||||
|
||||
CLI_DIR = File.expand_path(__dir__)
|
||||
ADN_RUN = File.join(File.dirname(File.dirname(File.dirname(CLI_DIR))), 'tools', 'run')
|
||||
|
||||
# Configuración
|
||||
opciones = {
|
||||
paso_inicial: 'exportar',
|
||||
dry_run: false,
|
||||
timeout: 1800, # 30 min por dron
|
||||
nodo_bitacora: 'dasuten-pipeline'
|
||||
}
|
||||
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Uso: #{ADN_RUN} dron lanzar --nota 'Pipeline DASUTEN' -- ruby adn/tools/cli/drones/orquestador_pipeline.rb [opciones]"
|
||||
opts.on("--paso PASO", %w[exportar transferir upload download restaurar verificar], "Comenzar desde este paso") { |p| opciones[:paso_inicial] = p }
|
||||
opts.on("--dry-run", "Simulación sin ejecutar drones") { opciones[:dry_run] = true }
|
||||
opts.on("--timeout N", Integer, "Timeout por dron en segundos") { |t| opciones[:timeout] = t }
|
||||
end.parse!(ARGV)
|
||||
|
||||
# Estados del pipeline (6 drones atómicos)
|
||||
ESTADOS = {
|
||||
exportar: {
|
||||
script: 'dasuten_exportar_srvv-fenix.rb',
|
||||
nodo: 'srvv-fenix',
|
||||
nota: '📤 Exportar backup DASUTEN',
|
||||
output_file: '/tmp/dron_export_output.json',
|
||||
siguiente: :transferir
|
||||
},
|
||||
transferir: {
|
||||
script: 'dasuten_transferir_srvv-fenix-srv-ns8.rb',
|
||||
nodo: 'srv-ns8',
|
||||
nota: '🔄 Transferir SMB (fenix → ns8)',
|
||||
output_file: '/tmp/dron_transfer_output.json',
|
||||
siguiente: :upload
|
||||
},
|
||||
upload: {
|
||||
script: 'dasuten_upload_srv-ns8-drive.rb',
|
||||
nodo: 'srv-ns8',
|
||||
nota: '☁️ Upload a Google Drive',
|
||||
output_file: '/tmp/dron_upload_output.json',
|
||||
siguiente: :download
|
||||
},
|
||||
download: {
|
||||
script: 'dasuten_download_drive-dasu-sql4.rb',
|
||||
nodo: 'dasu-sql4',
|
||||
nota: '📥 Download desde Google Drive',
|
||||
output_file: '/tmp/dron_download_output.json',
|
||||
siguiente: :restaurar
|
||||
},
|
||||
restaurar: {
|
||||
script: 'dasuten_restaurar_dasu-sql4.rb',
|
||||
nodo: 'dasu-sql4',
|
||||
nota: '💾 Restaurar backup',
|
||||
output_file: '/tmp/dron_restore_output.json',
|
||||
siguiente: :verificar
|
||||
},
|
||||
verificar: {
|
||||
script: 'dasuten_verificar-integridad_dasu-sql4.rb',
|
||||
nodo: 'dasu-sql4',
|
||||
nota: '✅ Verificar integridad BD',
|
||||
output_file: '/tmp/dron_verify_output.json',
|
||||
siguiente: nil # Final
|
||||
}
|
||||
}.freeze
|
||||
|
||||
puts "\n" + "="*70
|
||||
puts "🛸 ORQUESTADOR PIPELINE DASUTEN (Fase 8b — 6 drones atómicos)"
|
||||
puts "="*70
|
||||
puts " Paso inicial: #{opciones[:paso_inicial]}"
|
||||
puts " Dry-run: #{opciones[:dry_run]}"
|
||||
puts " Timeout: #{opciones[:timeout]}s por dron"
|
||||
puts " Drones: #{ESTADOS.keys.join(' → ')}"
|
||||
puts "="*70
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Función para lanzar un dron y esperar su resultado
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
def lanzar_dron(script, nodo, nota, timeout)
|
||||
cmd = "ruby #{File.join(File.dirname(__FILE__), script)}"
|
||||
|
||||
puts "\n" + "-"*70
|
||||
puts "🛸 Lanzando dron: #{nota}"
|
||||
puts " Nodo: #{nodo}"
|
||||
puts " Script: #{script}"
|
||||
puts "-"*70
|
||||
|
||||
# Lanzar dron ejecutor
|
||||
dron_cmd = "#{ADN_RUN} dron lanzar --nota #{Shellwords.escape(nota)} --nodo #{Shellwords.escape(nodo)} --timeout #{timeout} -- #{cmd}"
|
||||
|
||||
puts " Comando: #{dron_cmd}"
|
||||
|
||||
if opciones[:dry_run]
|
||||
puts " [DRY-RUN] No ejecutado"
|
||||
return true, {}
|
||||
end
|
||||
|
||||
exito = system(dron_cmd)
|
||||
codigo = $?.exitstatus
|
||||
|
||||
if exito
|
||||
puts "\n✅ Dron completado exitosamente"
|
||||
|
||||
# Leer output del dron
|
||||
output_file = ESTADOS[nodo.to_sym][:output_file]
|
||||
if File.exist?(output_file)
|
||||
resultado = JSON.parse(File.read(output_file))
|
||||
puts " Output: #{resultado.inspect}"
|
||||
return true, resultado
|
||||
else
|
||||
puts " [!] No se encontró archivo de output"
|
||||
return true, {}
|
||||
end
|
||||
else
|
||||
puts "\n❌ Dron fallido (código: #{codigo})"
|
||||
return false, {}
|
||||
end
|
||||
end
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Ejecutar pipeline
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
paso_actual = opciones[:paso_inicial].to_sym
|
||||
contexto = {} # Para pasar datos entre drones
|
||||
duracioniones = []
|
||||
|
||||
begin
|
||||
loop do
|
||||
break unless ESTADOS[paso_actual]
|
||||
|
||||
config = ESTADOS[paso_actual]
|
||||
|
||||
# Inyectar contexto del paso anterior si es necesario
|
||||
case paso_actual
|
||||
when :upload
|
||||
# El dron de upload ya sabe dónde está el archivo (hardcodeado en /var/tmp/)
|
||||
when :download
|
||||
# El dron de download usa file_id o test_file
|
||||
when :restaurar
|
||||
# El dron de restaurar usa la ruta hardcodeada F:\BACKUP\
|
||||
end
|
||||
|
||||
# Lanzar dron
|
||||
exito, resultado = lanzar_dron(
|
||||
config[:script],
|
||||
config[:nodo],
|
||||
config[:nota],
|
||||
opciones[:timeout]
|
||||
)
|
||||
|
||||
unless exito
|
||||
puts "\n❌ Pipeline fallido en paso: #{paso_actual}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Guardar contexto para siguiente paso
|
||||
contexto.merge!(resultado) if resultado
|
||||
duraciones << resultado[:duracion] if resultado[:duracion]
|
||||
|
||||
# Decidir siguiente paso
|
||||
if config[:siguiente]
|
||||
puts "\n➡️ Pasando a: #{config[:siguiente]}"
|
||||
paso_actual = config[:siguiente]
|
||||
else
|
||||
puts "\n✅ Pipeline completado exitosamente!"
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Resumen final
|
||||
puts "\n" + "="*70
|
||||
puts "📊 RESUMEN DEL PIPELINE"
|
||||
puts "="*70
|
||||
|
||||
if contexto[:pipeline_completo] || contexto[:estado_integridad] == 'OK'
|
||||
puts " Estado: ✅ COMPLETADO"
|
||||
puts " Integridad BD: #{contexto[:estado_integridad] || 'N/A'}"
|
||||
else
|
||||
puts " Estado: ⏳ EN PROGRESO"
|
||||
end
|
||||
|
||||
puts " Backup Drive: #{contexto[:drive_ruta] || contexto[:drive_origen] || 'N/A'}"
|
||||
puts " Duración total: #{duraciones.sum.round(2)}s (#{duraciones.count} drones)"
|
||||
puts "="*70
|
||||
|
||||
rescue => e
|
||||
puts "\n❌ Error en orquestador: #{e.message}"
|
||||
puts e.backtrace.first(5).join("\n")
|
||||
exit 1
|
||||
end
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ==========================================================
|
||||
# adn/tools/cli/drones/orquestador_pipeline_diferencial.rb
|
||||
# ==========================================================
|
||||
# Orquestador de Pipeline DIFERENCIAL DASUTEN
|
||||
# ==========================================================
|
||||
# Flujo: exportar_dif → transferir → upload → download → restaurar_dif → verificar
|
||||
# Para refrescos diarios (solo cambios desde último backup completo)
|
||||
# ==========================================================
|
||||
|
||||
require 'optparse'
|
||||
require 'json'
|
||||
require 'time'
|
||||
require 'shellwords'
|
||||
|
||||
CLI_DIR = File.expand_path(__dir__)
|
||||
ADN_RUN = File.join(File.dirname(File.dirname(File.dirname(CLI_DIR))), 'tools', 'run')
|
||||
|
||||
# Configuración
|
||||
opciones = {
|
||||
paso_inicial: 'exportar_dif',
|
||||
dry_run: false,
|
||||
timeout: 900, # 15 min por dron (suficiente para diferencial)
|
||||
nodo_bitacora: 'dasuten-pipeline-dif'
|
||||
}
|
||||
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Uso: #{ADN_RUN} dron lanzar --nota 'Pipeline Diferencial DASUTEN' -- ruby adn/tools/cli/drones/orquestador_pipeline_diferencial.rb [opciones]"
|
||||
opts.on("--paso PASO", %w[exportar_dif transferir upload download restaurar_dif verificar], "Comenzar desde este paso") { |p| opciones[:paso_inicial] = p }
|
||||
opts.on("--dry-run", "Simulación sin ejecutar drones") { opciones[:dry_run] = true }
|
||||
opts.on("--timeout N", Integer, "Timeout por dron en segundos") { |t| opciones[:timeout] = t }
|
||||
end.parse!(ARGV)
|
||||
|
||||
# Estados del pipeline diferencial (6 drones atómicos)
|
||||
ESTADOS = {
|
||||
exportar_dif: {
|
||||
script: 'dasuten_exportar_diferencial_srvv-fenix.rb',
|
||||
nodo: 'srvv-fenix',
|
||||
nota: '📤 Exportar backup DIFERENCIAL DASUTEN',
|
||||
output_file: '/tmp/dron_export_dif_output.json',
|
||||
siguiente: :transferir
|
||||
},
|
||||
transferir: {
|
||||
script: 'dasuten_transferir_srvv-fenix-srv-ns8.rb',
|
||||
nodo: 'srv-ns8',
|
||||
nota: '🔄 Transferir SMB (fenix → ns8)',
|
||||
output_file: '/tmp/dron_transfer_output.json',
|
||||
siguiente: :upload
|
||||
},
|
||||
upload: {
|
||||
script: 'dasuten_upload_srv-ns8-drive.rb',
|
||||
nodo: 'srv-ns8',
|
||||
nota: '☁️ Upload a Google Drive',
|
||||
output_file: '/tmp/dron_upload_output.json',
|
||||
siguiente: :download
|
||||
},
|
||||
download: {
|
||||
script: 'dasuten_download_drive-dasu-sql4.rb',
|
||||
nodo: 'dasu-sql4',
|
||||
nota: '📥 Download desde Google Drive',
|
||||
output_file: '/tmp/dron_download_output.json',
|
||||
siguiente: :restaurar_dif
|
||||
},
|
||||
restaurar_dif: {
|
||||
script: 'dasuten_restaurar_diferencial_dasu-sql4.rb',
|
||||
nodo: 'dasu-sql4',
|
||||
nota: '💾 Restaurar backup DIFERENCIAL',
|
||||
output_file: '/tmp/dron_restore_dif_output.json',
|
||||
siguiente: :verificar
|
||||
},
|
||||
verificar: {
|
||||
script: 'dasuten_verificar-integridad_dasu-sql4.rb',
|
||||
nodo: 'dasu-sql4',
|
||||
nota: '✅ Verificar integridad BD',
|
||||
output_file: '/tmp/dron_verify_output.json',
|
||||
siguiente: nil # Final
|
||||
}
|
||||
}.freeze
|
||||
|
||||
puts "\n" + "="*70
|
||||
puts "🛸 ORQUESTADOR PIPELINE DIFERENCIAL DASUTEN (Refresco diario)"
|
||||
puts "="*70
|
||||
puts " Paso inicial: #{opciones[:paso_inicial]}"
|
||||
puts " Dry-run: #{opciones[:dry_run]}"
|
||||
puts " Timeout: #{opciones[:timeout]}s por dron"
|
||||
puts " Drones: #{ESTADOS.keys.join(' → ')}"
|
||||
puts "="*70
|
||||
puts ""
|
||||
puts " ℹ️ Este pipeline usa backup DIFERENCIAL (solo cambios)"
|
||||
puts " Esperado: ~5-10 min total (vs ~35 min del completo)"
|
||||
puts "="*70
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Función para lanzar un dron y esperar su resultado
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
def lanzar_dron(script, nodo, nota, timeout, dry_run)
|
||||
cmd = "ruby #{File.join(File.dirname(__FILE__), script)}"
|
||||
|
||||
puts "\n" + "-"*70
|
||||
puts "🛸 Lanzando dron: #{nota}"
|
||||
puts " Nodo: #{nodo}"
|
||||
puts " Script: #{script}"
|
||||
puts "-"*70
|
||||
|
||||
# Lanzar dron ejecutor
|
||||
dron_cmd = "#{ADN_RUN} dron lanzar --nota #{Shellwords.escape(nota)} --nodo #{Shellwords.escape(nodo)} --timeout #{timeout} -- #{cmd}"
|
||||
|
||||
puts " Comando: #{dron_cmd}"
|
||||
|
||||
if dry_run
|
||||
puts " [DRY-RUN] No ejecutado"
|
||||
return true, {}
|
||||
end
|
||||
|
||||
exito = system(dron_cmd)
|
||||
codigo = $?.exitstatus
|
||||
|
||||
if exito
|
||||
puts "\n✅ Dron completado exitosamente"
|
||||
|
||||
# Leer output del dron (buscar en todos los archivos posibles)
|
||||
output_files = ['/tmp/dron_export_dif_output.json', '/tmp/dron_transfer_output.json',
|
||||
'/tmp/dron_upload_output.json', '/tmp/dron_download_output.json',
|
||||
'/tmp/dron_restore_dif_output.json', '/tmp/dron_verify_output.json']
|
||||
output_file = output_files.find { |f| File.exist?(f) }
|
||||
|
||||
if output_file && File.exist?(output_file)
|
||||
resultado = JSON.parse(File.read(output_file))
|
||||
puts " Output: #{resultado.inspect}"
|
||||
return true, resultado
|
||||
else
|
||||
puts " [!] No se encontró archivo de output"
|
||||
return true, {}
|
||||
end
|
||||
else
|
||||
puts "\n❌ Dron fallido (código: #{codigo})"
|
||||
return false, {}
|
||||
end
|
||||
end
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Ejecutar pipeline
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
paso_actual = opciones[:paso_inicial].to_sym
|
||||
contexto = {} # Para pasar datos entre drones
|
||||
duraciones = []
|
||||
|
||||
begin
|
||||
loop do
|
||||
break unless ESTADOS[paso_actual]
|
||||
|
||||
config = ESTADOS[paso_actual]
|
||||
|
||||
# Lanzar dron
|
||||
exito, resultado = lanzar_dron(
|
||||
config[:script],
|
||||
config[:nodo],
|
||||
config[:nota],
|
||||
opciones[:timeout],
|
||||
opciones[:dry_run]
|
||||
)
|
||||
|
||||
unless exito
|
||||
puts "\n❌ Pipeline fallido en paso: #{paso_actual}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Guardar contexto para siguiente paso
|
||||
contexto.merge!(resultado) if resultado
|
||||
duraciones << resultado[:duracion] if resultado[:duracion]
|
||||
|
||||
# Decidir siguiente paso
|
||||
if config[:siguiente]
|
||||
puts "\n➡️ Pasando a: #{config[:siguiente]}"
|
||||
paso_actual = config[:siguiente]
|
||||
else
|
||||
puts "\n✅ Pipeline completado exitosamente!"
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Resumen final
|
||||
puts "\n" + "="*70
|
||||
puts "📊 RESUMEN DEL PIPELINE DIFERENCIAL"
|
||||
puts "="*70
|
||||
|
||||
if contexto[:pipeline_completo] || contexto[:estado_integridad] == 'OK'
|
||||
puts " Estado: ✅ COMPLETADO"
|
||||
puts " Integridad BD: #{contexto[:estado_integridad] || 'N/A'}"
|
||||
else
|
||||
puts " Estado: ⏳ EN PROGRESO"
|
||||
end
|
||||
|
||||
puts " Backup Drive: #{contexto[:drive_ruta] || contexto[:drive_origen] || 'N/A'}"
|
||||
puts " Duración total: #{duraciones.sum.round(2)}s (#{duraciones.count} drones)"
|
||||
puts " Tipo: #{contexto[:backup_tipo] || 'differential'}"
|
||||
puts "="*70
|
||||
|
||||
rescue => e
|
||||
puts "\n❌ Error en orquestador: #{e.message}"
|
||||
puts e.backtrace.first(5).join("\n")
|
||||
exit 1
|
||||
end
|
||||
Reference in New Issue
Block a user