Reestructuración modular integral de servicios, reorganización de gestión en docs/ y limpieza profunda del repositorio

This commit is contained in:
Ricardo Monla
2026-03-06 18:03:07 -03:00
parent 15725e0b3c
commit ac9bdf516b
286 changed files with 36187 additions and 112775 deletions
+485
View File
@@ -0,0 +1,485 @@
#!/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