Reestructuración modular integral de servicios, reorganización de gestión en docs/ y limpieza profunda del repositorio
This commit is contained in:
Executable
+354
@@ -0,0 +1,354 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user