Files
dtic-DIIAA/tools/actualizar_dashboard_p2601.rb
T

486 lines
15 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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: [
'<span class="tag tag-remote">Remoto: ~2h</span>',
'<span class="tag tag-phys">Requiere acceso físico</span>'
],
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: [
'<span class="tag tag-remote">Remoto: ~3h</span>',
'<span class="tag">Herramientas ADN</span>'
],
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: [
'<span class="tag tag-remote">Remoto: ~2h</span>',
'<span class="tag">SQL Agent</span>'
],
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: [
'<span class="tag tag-remote">Remoto: ~4h</span>',
'<span class="tag">Windows Event Log</span>'
],
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: [
'<span class="tag tag-remote">Remoto: ~6h</span>',
'<span class="tag">Herramientas ADN</span>',
'<span class="tag">Git</span>'
],
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: [
'<span class="tag tag-remote">Remoto: ~3h</span>',
'<span class="tag">Tailscale</span>',
'<span class="tag">Windows Firewall</span>'
],
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: [
'<span class="tag tag-phys">Físico: 2h</span>',
'<span class="tag tag-remote">Remoto: ~2h</span>',
'<span class="tag">SQL Profiler</span>'
],
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: [
'<span class="tag tag-remote">Remoto: ~8h</span>',
'<span class="tag">SSIS</span>',
'<span class="tag">PowerShell</span>'
],
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?('<!DOCTYPE html>') || contenido.include?('<html')
@errores << "El archivo no parece ser un HTML válido"
puts "⚠️ #{@errores.last}"
end
puts "✅ Entorno validado correctamente"
true
end
def leer_dashboard
puts "📖 Leyendo dashboard actual..."
begin
contenido = File.read(@dashboard_path)
puts "✅ Dashboard leído (#{contenido.length} bytes, #{contenido.lines.count} líneas)"
contenido
rescue => 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 = '</div><!-- Timeline de Hitos -->'
unless contenido.include?(timeline_end_marker)
timeline_end_marker = '</div>'
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
<div class="tl-item">
<div class="#{clase_dot}"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">#{hito[:id]}</span><span class="tl-date">#{hito[:fecha]}</span></div>
<div class="tl-title">#{hito[:titulo]}</div>
<div class="tl-desc">#{hito[:descripcion]}</div>
<div class="tl-tags">#{hito[:tags].join}</div>
</div>
</div>
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