#!/usr/bin/env ruby # frozen_string_literal: true # tools/actualizar_dashboard_p2601.rb — Herramienta para actualizar dashboard P2601 con hitos adicionales # ================================================================================ # Esta herramienta actualiza el dashboard P2601 (dashboard/index.html) agregando # hitos adicionales propuestos para la fase de implementación final del proyecto DASUTEN. # # Características: # • Lee el dashboard HTML actual # • Agrega hitos adicionales basados en análisis del proyecto # • Mantiene la estructura y estilo existentes # • Integra con herramientas ADN para seguimiento # • Crea backup del archivo original # # Uso: # ruby tools/actualizar_dashboard_p2601.rb [OPCIONES] # # Opciones: # --dry-run Mostrar cambios sin aplicar # --backup Crear backup antes de modificar # --hitos N Agregar solo N hitos (por defecto: todos) # --categoria CAT Filtrar por categoría (infraestructura, monitoreo, etc.) # --help Mostrar ayuda require 'nokogiri' require 'fileutils' require 'optparse' require 'date' class ActualizadorDashboardP2601 VERSION = "1.0.0" # Hitos adicionales propuestos para la fase de implementación HITOS_ADICIONALES = [ { id: "BKP02", fecha: "Pendiente", 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: [ 'Remoto: ~2h', 'Requiere acceso físico' ], estado: "pending", # pending, active, done categoria: "infraestructura", prioridad: "alta", prerequisitos: ["FÍSICO"] }, { id: "MON01", fecha: "Pendiente", 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: [ 'Remoto: ~3h', 'Herramientas ADN' ], estado: "pending", categoria: "monitoreo", prioridad: "media", prerequisitos: ["FÍSICO"] }, { id: "DB02", fecha: "Pendiente", 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: [ 'Remoto: ~2h', 'SQL Agent' ], estado: "pending", categoria: "base_datos", prioridad: "alta", prerequisitos: ["ADDOM"] }, { id: "SEC01", fecha: "Pendiente", titulo: "Auditoría de Seguridad del Dominio", descripcion: "Revisar y configurar políticas de auditoría en DC. Logs de acceso, cambios en AD, intentos fallidos. Centralización de logs en syslog server.", tags: [ 'Remoto: ~4h', 'Windows Event Log' ], estado: "pending", categoria: "seguridad", prioridad: "media", prerequisitos: ["GPO"] }, { id: "DOC01", fecha: "Pendiente", 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: [ 'Remoto: ~6h', 'Herramientas ADN', 'Git' ], estado: "pending", categoria: "documentacion", prioridad: "media", prerequisitos: ["UAT"] }, { id: "RED01", fecha: "Pendiente", titulo: "Optimización de Red y Firewall", descripcion: "Revisar reglas de firewall en todas las VMs. Optimizar rutas Tailscale. Configurar QoS para tráfico crítico. Documentar reglas de firewall.", tags: [ 'Remoto: ~3h', 'Tailscale', 'Windows Firewall' ], estado: "pending", categoria: "red", prioridad: "baja", prerequisitos: ["NET01"] }, { id: "PERF01", fecha: "Pendiente", 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: [ 'Físico: 2h', 'Remoto: ~2h', 'SQL Profiler' ], estado: "pending", categoria: "performance", prioridad: "media", prerequisitos: ["CLI"] }, { id: "MIG02", fecha: "Pendiente", titulo: "Plan de Migración de Datos Final", descripcion: "Desarrollar script/procedimiento para migración incremental de datos desde sistema antiguo. Validar integridad de datos migrados. Crear rollback plan.", tags: [ 'Remoto: ~8h', 'SSIS', 'PowerShell' ], estado: "pending", categoria: "migracion", prioridad: "alta", prerequisitos: ["P02.APP"] } ].freeze def initialize(opciones = {}) @opciones = opciones @dashboard_path = '/home/rmonla/Documentos/GitHub/dtic-srv-NS8/dashboard/index.html' @backup_path = nil @cambios = [] @errores = [] end def ejecutar mostrar_encabezado unless validar_entorno puts "❌ No se puede continuar debido a errores en el entorno." exit 1 end if @opciones[:dry_run] puts "🔍 MODO SIMULACIÓN (dry-run) - No se aplicarán cambios reales" puts "-" * 50 end contenido_original = leer_dashboard return if contenido_original.nil? if @opciones[:backup] && !@opciones[:dry_run] crear_backup(contenido_original) end hitos_filtrados = filtrar_hitos puts "📋 Hitos a agregar: #{hitos_filtrados.size} de #{HITOS_ADICIONALES.size} disponibles" contenido_actualizado = agregar_hitos_al_dashboard(contenido_original, hitos_filtrados) if contenido_actualizado && !@opciones[:dry_run] guardar_dashboard(contenido_actualizado) elsif @opciones[:dry_run] mostrar_cambios_simulados(contenido_actualizado) end mostrar_resumen rescue => e puts "❌ Error fatal: #{e.message}" puts e.backtrace if @opciones[:debug] exit 1 end private def mostrar_encabezado puts "🔄 ACTUALIZADOR DE DASHBOARD P2601 - v#{VERSION}" puts "=" * 70 puts "Proyecto: DASUTEN (P2601)" puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}" puts "Dashboard: #{@dashboard_path}" puts "" end def validar_entorno puts "🔍 Validando entorno..." # Verificar que el dashboard existe unless File.exist?(@dashboard_path) @errores << "Dashboard no encontrado en: #{@dashboard_path}" puts "❌ #{@errores.last}" return false end # Verificar permisos de escritura unless File.writable?(@dashboard_path) @errores << "Sin permisos de escritura en: #{@dashboard_path}" puts "❌ #{@errores.last}" return false end # Verificar que es un archivo HTML válido contenido = File.read(@dashboard_path, 500) unless contenido.include?('') || contenido.include?(' e @errores << "Error leyendo dashboard: #{e.message}" puts "❌ #{@errores.last}" nil end end def crear_backup(contenido) timestamp = Time.now.strftime('%Y%m%d_%H%M%S') @backup_path = "#{@dashboard_path}.backup_#{timestamp}" puts "💾 Creando backup en: #{@backup_path}" begin File.write(@backup_path, contenido) puts "✅ Backup creado exitosamente" rescue => e @errores << "Error creando backup: #{e.message}" puts "⚠️ #{@errores.last}" @backup_path = nil end end def filtrar_hitos hitos = HITOS_ADICIONALES.dup # Filtrar por categoría si se especificó if @opciones[:categoria] categoria = @opciones[:categoria].downcase hitos.select! { |h| h[:categoria] == categoria } puts " • Filtrado por categoría: #{categoria} (#{hitos.size} hitos)" end # Limitar número de hitos si se especificó if @opciones[:hitos] && @opciones[:hitos] > 0 limite = [@opciones[:hitos], hitos.size].min hitos = hitos.first(limite) puts " • Limitado a: #{limite} hitos" end # Ordenar por prioridad (alta -> media -> baja) orden_prioridad = { 'alta' => 1, 'media' => 2, 'baja' => 3 } hitos.sort_by! { |h| orden_prioridad[h[:prioridad]] } hitos end def agregar_hitos_al_dashboard(contenido, hitos) puts "✏️ Agregando hitos al dashboard..." return contenido if hitos.empty? # Buscar el final de la sección de timeline timeline_end_marker = '' unless contenido.include?(timeline_end_marker) timeline_end_marker = '' puts "⚠️ Marcador de timeline no encontrado, usando marcador genérico" end # Generar HTML para los nuevos hitos hitos_html = hitos.map { |hito| generar_html_hito(hito) }.join("\n\n") # Insertar los nuevos hitos antes del marcador de fin nuevo_contenido = contenido.gsub(timeline_end_marker) do |match| "#{hitos_html}\n\n#{match}" end @cambios = hitos.map { |h| h[:id] } puts "✅ #{hitos.size} hitos preparados para agregar: #{@cambios.join(', ')}" nuevo_contenido end def generar_html_hito(hito) clase_dot = case hito[:estado] when 'done' then 'tl-dot done' when 'active' then 'tl-dot active' else 'tl-dot pending' end <<~HTML
#{hito[:id]}#{hito[:fecha]}
#{hito[:titulo]}
#{hito[:descripcion]}
#{hito[:tags].join}
HTML end def guardar_dashboard(contenido) puts "💾 Guardando dashboard actualizado..." begin # Crear backup temporal temp_backup = "#{@dashboard_path}.tmp" File.write(temp_backup, contenido) # Verificar que el HTML es válido doc = Nokogiri::HTML(contenido) if doc.errors.empty? # Reemplazar el archivo original FileUtils.mv(temp_backup, @dashboard_path) puts "✅ Dashboard actualizado exitosamente" puts " • Nuevo tamaño: #{contenido.length} bytes" puts " • Nuevas líneas: #{contenido.lines.count}" else @errores << "HTML inválido generado" puts "❌ #{@errores.last}" FileUtils.rm(temp_backup) if File.exist?(temp_backup) end rescue => e @errores << "Error guardando dashboard: #{e.message}" puts "❌ #{@errores.last}" end end def mostrar_cambios_simulados(contenido) puts "📋 CAMBIOS SIMULADOS (dry-run):" puts "-" * 50 if @cambios.any? puts "✅ Se agregarían #{@cambios.size} hitos:" @cambios.each_with_index do |hito_id, index| hito = HITOS_ADICIONALES.find { |h| h[:id] == hito_id } puts " #{index + 1}. #{hito_id}: #{hito[:titulo]}" puts " Categoría: #{hito[:categoria]}, Prioridad: #{hito[:prioridad]}" puts " Prerequisitos: #{hito[:prerequisitos].join(', ')}" if hito[:prerequisitos].any? end puts "\n📊 Estadísticas del nuevo contenido:" puts " • Tamaño: #{contenido.length} bytes" puts " • Líneas: #{contenido.lines.count}" # Mostrar ejemplo del HTML generado puts "\n📝 Ejemplo de HTML generado:" ejemplo = contenido.lines.grep(/tl-item/).first(3) ejemplo.each { |line| puts " #{line.strip}" } else puts "ℹ️ No se generarían cambios" end end def mostrar_resumen puts "\n" + "=" * 70 puts "📊 RESUMEN DE LA ACTUALIZACIÓN" puts "=" * 70 if @opciones[:dry_run] puts "🔍 MODO SIMULACIÓN - No se aplicaron cambios reales" else puts "🔄 CAMBIOS APLICADOS:" if @cambios.any? puts "✅ #{@cambios.size} hitos agregados exitosamente" @cambios.each { |h| puts " • #{h}" } else puts "ℹ️ No se agregaron hitos nuevos" end if @backup_path puts "💾 Backup creado en: #{@backup_path}" end end if @errores.any? puts "\n⚠️ ERRORES ENCONTRADOS (#{@errores.size}):" @errores.each_with_index do |error, i| puts " #{i + 1}. #{error}" end end puts "\n🚀 PRÓXIMOS PASOS SUGERIDOS:" puts " 1. Verificar dashboard en: https://ns8.frlr.utn.edu.ar/bitacoras/p2601" puts " 2. Revisar que los nuevos hitos se muestren correctamente" puts " 3. Actualizar estado de hitos según progreso real" puts " 4. Integrar con herramientas ADN para seguimiento automático" puts " 5. Documentar cambios en bitácora del proyecto" puts "\n📅 " + Time.now.strftime('%Y-%m-%d %H:%M:%S') puts "=" * 70 end end # Parsear opciones de línea de comandos if __FILE__ == $0 opciones = { dry_run: false, backup: true, hitos: nil, categoria: nil, debug: false } OptionParser.new do |opts| opts.banner = "Uso: ruby tools/actualizar_dashboard_p2601.rb [OPCIONES]" opts.on("--dry-run", "Mostrar cambios sin aplicar") do opciones[:dry_run] = true end opts.on("--no-backup", "No crear backup antes de modificar") do opciones[:backup] = false end opts.on("--hitos N", Integer, "Agregar solo N hitos (por defecto: todos)") do |n| opciones[:hitos] = n end opts.on("--categoria CAT", String, "Filtrar por categoría (infraestructura, monitoreo, etc.)") do |cat| opciones[:categoria] = cat end opts.on("--debug", "Mostrar información de depuración") do opciones[:debug] = true end opts.on("-h", "--help", "Mostrar ayuda") do puts opts puts "\nEjemplos:" puts " ruby tools/actualizar_dashboard_p2601.rb --dry-run" puts " ruby tools/actualizar_dashboard_p2601.rb --hitos 3 --categoria infraestructura" puts " ruby tools/actualizar_dashboard_p2601.rb --no-backup" exit end end.parse! # Ejecutar actualizador begin actualizador = ActualizadorDashboardP2601.new(opciones) actualizador.ejecutar rescue => e puts "❌ Error fatal: #{e.message}" puts e.backtrace if opciones[:debug] exit 1 end end