486 lines
15 KiB
Ruby
486 lines
15 KiB
Ruby
#!/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
|