#!/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 HTML hitos.each do |hito| tags_html = hito[:tags].map do |tag| if tag.start_with?('tag-') "#{tag.gsub('tag-', '').capitalize}" else "#{tag}" end end.join html += <<~HTML
#{hito[:id]}#{hito[:estado].capitalize}
#{hito[:titulo]}
#{hito[:descripcion]}
#{tags_html}
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*/ 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 antes del footer footer_start = '