Files
dtic-DIIAA/tools/orquestador/dashboard/agregar_hitos.rb
T

355 lines
11 KiB
Ruby
Executable File

#!/usr/bin/env ruby
# frozen_string_literal: true
# tools/orquestador/dashboard/agregar_hitos.rb
# ============================================
# Herramienta para agregar hitos al dashboard de forma controlada y estructurada
# Filosofía: "Menos es más" - herramienta pequeña, específica y reutilizable
require 'fileutils'
require 'json'
class HitosManager
# Definición de hitos por fase
HITOS_POR_FASE = {
'fase5' => [
{
id: 'BKP02',
titulo: '🔒 Backup Automático de VMs en Proxmox',
descripcion: 'Configurar backups automáticos VZDump programados para DC y SQL Server. Establecer retención de 7 días, almacenamiento en NAS/SMB compartido.',
tags: ['tag-remote', 'tag-phys'],
esfuerzo: 'Remoto: ~2h',
estado: 'pending'
},
{
id: 'DB02',
titulo: '🗄️ Backup Automático de Base de Datos sysdasuten',
descripcion: 'Configurar jobs de SQL Server Agent para backup diario completo y log cada 15 minutos. Verificar restauración periódica en ambiente de pruebas.',
tags: ['tag-remote', 'SQL Agent'],
esfuerzo: 'Remoto: ~2h',
estado: 'pending'
},
{
id: 'MON01',
titulo: '📊 Monitoreo de Recursos del Hipervisor',
descripcion: 'Implementar sistema de monitoreo básico para CPU, RAM, almacenamiento y red de srv-dasu. Alertas por email/Telegram para recursos críticos.',
tags: ['tag-remote', 'Herramientas ADN'],
esfuerzo: 'Remoto: ~3h',
estado: 'pending'
},
{
id: 'DOC01',
titulo: '📋 Documentación Operativa del Sistema',
descripcion: 'Crear manual de operaciones: procedimientos de backup/restore, acceso remoto, resolución de problemas comunes, contactos de soporte, diagramas de red.',
tags: ['tag-remote', 'Herramientas ADN', 'Git'],
esfuerzo: 'Remoto: ~6h',
estado: 'pending'
},
{
id: 'PERF01',
titulo: '⚡ Pruebas de Rendimiento del Sistema',
descripcion: 'Realizar pruebas de carga simulada en sistema DASUTEN. Medir tiempos de respuesta, consumo de recursos, identificar cuellos de botella. Generar reporte de performance.',
tags: ['tag-phys', 'tag-remote', 'SQL Profiler'],
esfuerzo: 'Físico: 2h, Remoto: ~2h',
estado: 'pending'
}
]
}.freeze
def initialize(dashboard_path = nil)
@dashboard_path = dashboard_path || default_dashboard_path
@backup_dir = File.join(File.dirname(@dashboard_path), 'backups')
end
def default_dashboard_path
File.expand_path('../../../../dashboard/index.html', __FILE__)
end
def agregar_hitos(fase = 'fase5', modo = 'append')
puts "🎯 AGREGANDO HITOS - Fase: #{fase.upcase}"
puts "=" * 50
# Validar fase
unless HITOS_POR_FASE.key?(fase)
puts "❌ Error: Fase '#{fase}' no definida"
puts " Fases disponibles: #{HITOS_POR_FASE.keys.join(', ')}"
return false
end
# Verificar dashboard
unless File.exist?(@dashboard_path)
puts "❌ Error: No se encuentra el dashboard en #{@dashboard_path}"
return false
end
# Crear backup
create_backup
# Leer contenido
puts "📖 Leyendo dashboard..."
content = File.read(@dashboard_path)
original_lines = content.lines.count
# Generar HTML de hitos
hitos_html = generar_hitos_html(fase)
# Insertar hitos según el modo
case modo
when 'append'
puts "📝 Modo: Agregar al final de la timeline"
new_content = insertar_al_final_timeline(content, hitos_html)
when 'replace'
puts "🔄 Modo: Reemplazar hitos existentes"
new_content = reemplazar_hitos_existentes(content, hitos_html, fase)
when 'insert'
puts "📍 Modo: Insertar después de hitos específicos"
new_content = insertar_despues_de_marcador(content, hitos_html)
else
puts "❌ Error: Modo '#{modo}' no válido"
puts " Modos disponibles: append, replace, insert"
return false
end
# Verificar cambios
if new_content == content
puts "⚠️ No se realizaron cambios (posiblemente los hitos ya existen)"
return true
end
# Guardar cambios
puts "💾 Guardando dashboard actualizado..."
File.write(@dashboard_path, new_content)
# Mostrar resultados
show_results(content, new_content, fase, modo)
true
end
def listar_hitos(fase = nil)
puts "📋 LISTADO DE HITOS DISPONIBLES"
puts "=" * 50
if fase
unless HITOS_POR_FASE.key?(fase)
puts "❌ Fase '#{fase}' no encontrada"
return false
end
puts "Fase: #{fase.upcase}"
HITOS_POR_FASE[fase].each_with_index do |hito, i|
puts " #{i + 1}. [#{hito[:id]}] #{hito[:titulo]}"
puts " 📝 #{hito[:descripcion][0..80]}..."
puts " 🏷️ #{hito[:tags].join(', ')}"
puts " ⏱️ #{hito[:esfuerzo]}"
puts
end
else
HITOS_POR_FASE.each do |fase_nombre, hitos|
puts "📁 #{fase_nombre.upcase} (#{hitos.size} hitos)"
hitos.each do |hito|
puts " • [#{hito[:id]}] #{hito[:titulo]}"
end
puts
end
end
true
end
private
def create_backup
FileUtils.mkdir_p(@backup_dir) unless Dir.exist?(@backup_dir)
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
backup_file = File.join(@backup_dir, "dashboard_hitos_#{timestamp}.html")
puts "💾 Creando backup en: #{backup_file}"
FileUtils.cp(@dashboard_path, backup_file)
puts "✅ Backup creado"
end
def generar_hitos_html(fase)
hitos = HITOS_POR_FASE[fase]
timestamp = Time.now.strftime('%Y-%m-%d %H:%M')
html = <<~HTML
<!-- Hitos de #{fase.upcase} agregados automáticamente - #{timestamp} -->
HTML
hitos.each do |hito|
tags_html = hito[:tags].map do |tag|
if tag.start_with?('tag-')
"<span class=\"#{tag}\">#{tag.gsub('tag-', '').capitalize}</span>"
else
"<span class=\"tag\">#{tag}</span>"
end
end.join
html += <<~HTML
<div class="tl-item">
<div class="tl-dot #{hito[:estado]}"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">#{hito[:id]}</span><span class="tl-date">#{hito[:estado].capitalize}</span></div>
<div class="tl-title">#{hito[:titulo]}</div>
<div class="tl-desc">#{hito[:descripcion]}</div>
<div class="tl-tags">#{tags_html}</div>
</div>
</div>
HTML
end
html
end
def insertar_al_final_timeline(content, hitos_html)
# Buscar el cierre de la sección timeline
timeline_end_pattern = /<\/div>\s*<!-- Timeline de Hitos -->/
if content.match?(timeline_end_pattern)
# Insertar antes del marcador de cierre
content.gsub(timeline_end_pattern) do |match|
"#{hitos_html}\n #{match}"
end
else
# Buscar el último </div> antes del footer
footer_start = '<div class="footer">'
if content.include?(footer_start)
content.gsub(footer_start) do |match|
"#{hitos_html}\n #{match}"
end
else
puts "⚠️ No se encontró lugar para insertar hitos, agregando al final del body"
body_end = '</body>'
content.gsub(body_end) do |match|
"#{hitos_html}\n #{match}"
end
end
end
end
def reemplazar_hitos_existentes(content, hitos_html, fase)
# Buscar y reemplazar hitos existentes de la fase específica
fase_pattern = /<!-- Hitos de #{fase.upcase} agregados automáticamente - .*?-->[\s\S]*?(?=<!--|<\/div>\s*<div class="footer">)/m
if content.match?(fase_pattern)
# Reemplazar hitos existentes
content.gsub(fase_pattern, hitos_html)
else
# Si no hay hitos de esta fase, agregarlos al final
insertar_al_final_timeline(content, hitos_html)
end
end
def insertar_despues_de_marcador(content, hitos_html)
# Buscar un marcador específico (último hito existente)
last_hito_pattern = /<div class="tl-item">[\s\S]*?<\/div>\s*<\/div>\s*<\/div>\s*(?=\s*<\/div>)/m
if content.match?(last_hito_pattern)
# Insertar después del último hito
content.gsub(last_hito_pattern) do |match|
"#{match}\n\n#{hitos_html}"
end
else
# Si no se encuentra marcador, usar método por defecto
insertar_al_final_timeline(content, hitos_html)
end
end
def show_results(original, new_content, fase, modo)
puts "\n📊 RESULTADOS:"
puts " • Fase: #{fase}"
puts " • Modo: #{modo}"
puts " • Líneas originales: #{original.lines.count}"
puts " • Líneas nuevas: #{new_content.lines.count}"
puts " • Líneas agregadas: #{new_content.lines.count - original.lines.count}"
# Contar hitos agregados
hitos_agregados = HITOS_POR_FASE[fase].size
puts " • Hitos agregados: #{hitos_agregados}"
# Mostrar hitos agregados
puts "\n🎯 HITOS AGREGADOS:"
HITOS_POR_FASE[fase].each_with_index do |hito, i|
puts " #{i + 1}. [#{hito[:id]}] #{hito[:titulo]}"
end
puts "\n✅ Hitos agregados exitosamente"
end
end
# Interfaz de línea de comandos
if __FILE__ == $0
begin
# Parsear argumentos
dashboard_path = nil
command = 'agregar'
fase = 'fase5'
modo = 'append'
ARGV.each_with_index do |arg, i|
if arg == '--dashboard' && ARGV[i + 1]
dashboard_path = ARGV[i + 1]
elsif i == 0 && !arg.start_with?('--')
command = arg
elsif i == 1 && !arg.start_with?('--') && command != 'help'
fase = arg
elsif i == 2 && !arg.start_with?('--') && command != 'help'
modo = arg
end
end
manager = HitosManager.new(dashboard_path)
case command
when 'agregar', 'add'
success = manager.agregar_hitos(fase, modo)
when 'listar', 'list', 'ls'
success = manager.listar_hitos(fase)
when 'help', '--help', '-h'
puts "📖 USO:"
puts " #{$0} [comando] [fase] [modo] [--dashboard RUTA]"
puts
puts "COMANDOS:"
puts " agregar, add - Agregar hitos al dashboard (predeterminado)"
puts " listar, list - Listar hitos disponibles"
puts " help - Mostrar esta ayuda"
puts
puts "FASES:"
puts " fase5 - Hitos de la fase 5 (predeterminado)"
puts
puts "MODOS (solo para comando 'agregar'):"
puts " append - Agregar al final (predeterminado)"
puts " replace - Reemplazar hitos existentes"
puts " insert - Insertar después de marcador"
puts
puts "OPCIONES:"
puts " --dashboard RUTA - Ruta personalizada al archivo dashboard"
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 dashboard en: https://ns8.frlr.utn.edu.ar/bitacoras/p2601"
puts " 2. Revisar backups en: dashboard/backups/"
puts " 3. Actualizar estado de hitos según progreso real"
puts "\n📅 #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
puts "=" * 50
else
exit 1
end
rescue => e
puts "❌ Error: #{e.message}"
puts e.backtrace if ENV['DEBUG']
exit 1
end
end