#!/usr/bin/env ruby # frozen_string_literal: true # tools/analizar_dashboard_p2601.rb — Analizador del dashboard P2601 para proponer hitos adicionales # ================================================================================ # Este script analiza el estado actual del dashboard P2601 y propone hitos # intermedios adicionales para la fase de implementación final, utilizando # herramientas ADN existentes y mejorándolas según sea necesario. # # Características: # • Análisis del dashboard actual (HTML estático) # • Integración con base de datos dtic-BITACORAs # • Uso de herramientas ADN existentes # • Propuesta de hitos basados en mejores prácticas # • Generación de reporte estructurado # # Uso: # ruby tools/analizar_dashboard_p2601.rb [OPCIONES] # # Opciones: # --analizar-html Analizar dashboard/index.html actual # --consultar-bd Consultar estado en base de datos # --proponer-hitos Proponer hitos adicionales # --generar-reporte Generar reporte completo # --integrar-adn Integrar con herramientas ADN existentes # --todo Ejecutar todas las opciones require 'pg' require 'yaml' require 'json' require 'nokogiri' require 'optparse' require 'date' class AnalizadorDashboardP2601 def initialize(opciones = {}) @opciones = opciones @config = cargar_configuracion_bd @conn = nil @dashboard_html = nil @hitos_actuales = [] @hitos_propuestos = [] @estadisticas = {} end def ejecutar puts "🔍 ANALIZADOR DE DASHBOARD P2601 - PROYECTO DASUTEN" puts "=" * 70 puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}" puts "" if @opciones[:todo] || @opciones[:analizar_html] analizar_dashboard_html end if @opciones[:todo] || @opciones[:consultar_bd] consultar_base_datos end if @opciones[:todo] || @opciones[:proponer_hitos] proponer_hitos_adicionales end if @opciones[:todo] || @opciones[:generar_reporte] generar_reporte_completo end if @opciones[:todo] || @opciones[:integrar_adn] integrar_con_herramientas_adn end mostrar_resumen rescue => e puts "❌ Error durante el análisis: #{e.message}" puts e.backtrace if @opciones[:debug] exit 1 ensure desconectar_bd if @conn end private def cargar_configuracion_bd config_path = File.join(__dir__, 'config', 'database.yml') unless File.exist?(config_path) puts "⚠️ Archivo de configuración no encontrado: #{config_path}" return {} end config = YAML.load_file(config_path) config['development'] || config['production'] || config['test'] end def conectar_bd return unless @config && @config['host'] puts "🔌 Conectando a PostgreSQL: #{@config['host']}:#{@config['port']}/#{@config['database']}" @conn = PG.connect( host: @config['host'], port: @config['port'], dbname: @config['database'], user: @config['username'], password: @config['password'] ) puts "✅ Conexión establecida\n" rescue PG::Error => e puts "⚠️ Error de conexión a BD: #{e.message}" puts " Continuando sin conexión a base de datos\n" end def desconectar_bd @conn&.close puts "\n🔌 Conexión cerrada" if @conn end def analizar_dashboard_html puts "📋 ANALIZANDO DASHBOARD HTML ACTUAL" puts "-" * 50 dashboard_path = File.join(__dir__, '..', '..', 'dashboard', 'index.html') unless File.exist?(dashboard_path) puts "❌ Archivo dashboard no encontrado: #{dashboard_path}" return end @dashboard_html = File.read(dashboard_path) doc = Nokogiri::HTML(@dashboard_html) # Extraer hitos del timeline @hitos_actuales = [] doc.css('.tl-item').each do |item| hito = { id: item.css('.tl-id').text.strip, fecha: item.css('.tl-date').text.strip, titulo: item.css('.tl-title').text.strip, descripcion: item.css('.tl-desc').text.strip.gsub(/\s+/, ' '), estado: determinar_estado_hito(item), tags: item.css('.tag').map(&:text).map(&:strip) } @hitos_actuales << hito end # Extraer nodos nodos = [] doc.css('.node-card').each do |node| nodos << { nombre: node.css('.node-name').text.strip, estado: node.css('.status-dot').attr('title')&.value || 'desconocido', detalles: node.css('.node-detail').text.strip.gsub(/\s+/, ' ') } end # Extraer métricas metricas = {} doc.css('.metric').each do |metric| label = metric.css('.label').text.strip value = metric.css('.value').text.strip metricas[label] = value if label && value end @estadisticas[:hitos_total] = @hitos_actuales.size @estadisticas[:hitos_completados] = @hitos_actuales.count { |h| h[:estado] == 'completado' } @estadisticas[:hitos_activos] = @hitos_actuales.count { |h| h[:estado] == 'activo' } @estadisticas[:hitos_pendientes] = @hitos_actuales.count { |h| h[:estado] == 'pendiente' } @estadisticas[:nodos_total] = nodos.size @estadisticas[:nodos_online] = nodos.count { |n| n[:estado] == 'Online' } puts "📊 Estadísticas extraídas:" puts " • Hitos totales: #{@estadisticas[:hitos_total]}" puts " • Hitos completados: #{@estadisticas[:hitos_completados]}" puts " • Hitos activos: #{@estadisticas[:hitos_activos]}" puts " • Hitos pendientes: #{@estadisticas[:hitos_pendientes]}" puts " • Nodos totales: #{@estadisticas[:nodos_total]}" puts " • Nodos online: #{@estadisticas[:nodos_online]}" # Mostrar últimos hitos puts "\n📅 ÚLTIMOS HITOS REGISTRADOS:" @hitos_actuales.last(5).each do |hito| estado_emoji = case hito[:estado] when 'completado' then '✅' when 'activo' then '🔵' when 'pendiente' then '⏳' else '❓' end puts " #{estado_emoji} #{hito[:id]}: #{hito[:titulo][0..60]}..." end puts "" end def determinar_estado_hito(item) dot_class = item.css('.tl-dot').attr('class').to_s if dot_class.include?('done') 'completado' elsif dot_class.include?('active') 'activo' elsif dot_class.include?('pending') 'pendiente' else 'desconocido' end end def consultar_base_datos puts "📋 CONSULTANDO BASE DE DATOS dtic-BITACORAs" puts "-" * 50 conectar_bd return unless @conn begin # Consultar si existen tablas de proyectos tablas = @conn.exec("SELECT table_name FROM information_schema.tables WHERE table_schema = 'bitacoras'") tablas_proyecto = tablas.select { |t| t['table_name'] =~ /proyectos|fases|hitos/ } if tablas_proyecto.any? puts "✅ Tablas de proyectos encontradas: #{tablas_proyecto.map { |t| t['table_name'] }.join(', ')}" # Consultar proyecto P2601 proyecto = @conn.exec_params( "SELECT * FROM bitacoras.proyectos WHERE codigo = $1", ['P2601'] ) if proyecto.ntuples > 0 puts "✅ Proyecto P2601 encontrado en base de datos" proyecto_info = proyecto[0] puts " • Nombre: #{proyecto_info['nombre']}" puts " • Estado: #{proyecto_info['estado']}" puts " • Descripción: #{proyecto_info['descripcion'][0..100]}..." if proyecto_info['descripcion'] # Consultar fases del proyecto fases = @conn.exec_params( "SELECT * FROM bitacoras.fases WHERE proyecto_id = $1 ORDER BY numero", [proyecto_info['id']] ) puts " • Fases: #{fases.ntuples} fases encontradas" fases.each do |fase| puts " - Fase #{fase['numero']}: #{fase['nombre']} (#{fase['estado']})" end else puts "⚠️ Proyecto P2601 no encontrado en base de datos" end else puts "ℹ️ Tablas de proyectos no encontradas en el esquema 'bitacoras'" puts " Considerar crear estructura: proyectos, fases, hitos" end # Consultar estadísticas de entradas relacionadas con DASUTEN entradas_dasuten = @conn.exec_params( "SELECT COUNT(*) as total FROM bitacoras.entradas WHERE descripcion ILIKE $1", ['%dasuten%'] ) if entradas_dasuten.ntuples > 0 total = entradas_dasuten[0]['total'].to_i puts "📝 Entradas relacionadas con DASUTEN: #{total}" @estadisticas[:entradas_dasuten] = total end rescue PG::Error => e puts "⚠️ Error consultando base de datos: #{e.message}" end puts "" end def proponer_hitos_adicionales puts "🎯 PROPONIENDO HITOS ADICIONALES PARA FASE DE IMPLEMENTACIÓN" puts "-" * 50 # Basado en el análisis del dashboard y mejores prácticas hitos_propuestos = [ { 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.", categoria: "Infraestructura", prioridad: "alta", estimacion_horas: 2, prerequisitos: ["FÍSICO"], herramientas: ["Proxmox VE", "VZDump", "Scripts Bash"] }, { 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.", categoria: "Monitoreo", prioridad: "media", estimacion_horas: 3, prerequisitos: ["FÍSICO"], herramientas: ["Prometheus Node Exporter", "Grafana", "Alertmanager"] }, { 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.", categoria: "Base de Datos", prioridad: "alta", estimacion_horas: 2, prerequisitos: ["ADDOM"], herramientas: ["SQL Server Management Studio", "SQL Agent", "PowerShell"] }, { id: "SEC01", 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.", categoria: "Seguridad", prioridad: "media", estimacion_horas: 4, prerequisitos: ["GPO"], herramientas: ["Windows Event Viewer", "Audit Policy", "SIEM básico"] }, { 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.", categoria: "Documentación", prioridad: "media", estimacion_horas: 6, prerequisitos: ["UAT"], herramientas: ["Markdown", "Git", "Herramientas ADN"] }, { id: "RED01", titulo: "Optimización de Red y Firewall", descripcion: "Revisar reglas de firewall en todas las VMs. Optimizar rutas Tailscale. Configurar QoS si es necesario.", categoria: "Red", prioridad: "baja", estimacion_horas: 3, prerequisitos: ["NET01"], herramientas: ["Windows Firewall", "Tailscale", "PowerShell"] }, { 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.", categoria: "Performance", prioridad: "media", estimacion_horas: 4, prerequisitos: ["CLI"], herramientas: ["SQL Profiler", "Performance Monitor", "Scripts de prueba"] }, { id: "MIG02", 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.", categoria: "Migración", prioridad: "alta", estimacion_horas: 8, prerequisitos: ["P02.APP"], herramientas: ["SQL Server Integration Services", "PowerShell", "Validación de datos"] } ] @hitos_propuestos = hitos_propuestos puts "💡 HITOS PROPUESTOS (#{hitos_propuestos.size} nuevos):" puts "" hitos_propuestos.each do |hito| prioridad_emoji = case hito[:prioridad] when 'alta' then '🔴' when 'media' then '🟡' when 'baja' then '🟢' else '⚪' end puts " #{prioridad_emoji} #{hito[:id]}: #{hito[:titulo]}" puts " 📍 Categoría: #{hito[:categoria]}" puts " ⏱️ Estimación: #{hito[:estimacion_horas]} horas" puts " 📋 Prerequisitos: #{hito[:prerequisitos].join(', ')}" if hito[:prerequisitos].any? puts " 🛠️ Herramientas: #{hito[:herramientas].join(', ')}" puts "" end # Estadísticas de propuestas categorias = hitos_propuestos.group_by { |h| h[:categoria] } puts "📈 DISTRIBUCIÓN POR CATEGORÍA:" categorias.each do |categoria, hitos| puts " • #{categoria}: #{hitos.size} hitos" end puts "" end def generar_reporte_completo puts "📊 GENERANDO REPORTE COMPLETO" puts "-" * 50 reporte = { metadata: { generado_en: Time.now.iso8601, proyecto: "P2601 - DASUTEN", version_analizador: "1.0.0" }, estado_actual: @estadisticas, hitos_actuales: @hitos_actuales, hitos_propuestos: @hitos_propuestos, recomendaciones: generar_recomendaciones, acciones_sugeridas: generar_acciones_sugeridas } # Guardar reporte JSON reporte_path = File.join(__dir__, '..', '..', 'tmp', 'reporte_p2601_analisis.json') FileUtils.mkdir_p(File.dirname(reporte_path)) File.write(reporte_path, JSON.pretty_generate(reporte)) puts "✅ Reporte guardado en: #{reporte_path}" puts " • Tamaño: #{File.size(reporte_path)} bytes" puts " • Elementos: #{reporte[:hitos_actuales].size} hitos actuales, #{reporte[:hitos_propuestos].size} hitos propuestos" puts "" end def generar_recomendaciones recomendaciones = [] # Recomendaciones basadas en análisis if @estadisticas[:hitos_pendientes] && @estadisticas[:hitos_pendientes] > 3 recomendaciones << "Priorizar hitos pendientes críticos (FÍSICO, ADDOM) antes de agregar nuevos" end if @estadisticas[:entradas_dasuten] && @estadisticas[:entradas_dasuten] < 20 recomendaciones << "Aumentar registro de actividades específicas de DASUTEN en bitácoras" end recomendaciones << "Integrar hitos propuestos en timeline del dashboard de forma gradual" recomendaciones << "Utilizar herramientas ADN para automatizar seguimiento de hitos" recomendaciones << "Considerar implementar sistema de notificaciones para hitos críticos" recomendaciones << "Documentar cada hito completado con lecciones aprendidas" recomendaciones end def generar_acciones_sugeridas acciones = [ { accion: "Actualizar dashboard HTML", descripcion: "Agregar hitos propuestos al timeline", prioridad: "media", herramientas: ["HTML/CSS", "Herramientas ADN"] }, { accion: "Crear estructura en base de datos", descripcion: "Implementar tablas proyectos/fases/hitos si no existen", prioridad: "alta", herramientas: ["PostgreSQL", "SQL migrations"] }, { accion: "Integrar con sistema de búsqueda", descripcion: "Conectar hitos con sistema de búsqueda ADN BITACORAs", prioridad: "media", herramientas: ["BuscadorBDSimple", "API REST"] }, { accion: "Automatizar actualizaciones", descripcion: "Crear script para actualizar dashboard desde base de datos", prioridad: "baja", herramientas: ["Ruby", "Cron jobs", "Herramientas ADN"] }, { accion: "Implementar métricas avanzadas", descripcion: "Agregar KPIs de progreso, desviaciones, pronósticos", prioridad: "baja", herramientas: ["JavaScript", "Chart.js", "API"] } ] acciones end def integrar_con_herramientas_adn puts "🔄 INTEGRANDO CON HERRAMIENTAS ADN EXISTENTES" puts "-" * 50 puts "🔍 Herramientas ADN disponibles:" # Verificar herramientas existentes herramientas_adn = [ { nombre: "Sistema de búsqueda", archivo: "buscador_bd_simple.rb", estado: verificar_herramienta("bitacoras-db/core/buscador_bd_simple.rb") }, { nombre: "Seguimiento de plan", archivo: "subcomando_plan.rb", estado: verificar_herramienta("adn/subcomando_plan.rb") }, { nombre: "Operaciones BD", archivo: "subcomando_db.rb", estado: verificar_herramienta("adn/subcomando_db.rb") }, { nombre: "Sistema de triggers", archivo: "subcomando_triggers.rb", estado: verificar_herramienta("adn/subcomando_triggers.rb") }, { nombre: "Logger ADN", archivo: "logger.rb", estado: verificar_herramienta("adn/logger.rb") } ] herramientas_adn.each do |herramienta| estado_emoji = herramienta[:estado] ? "✅" : "❌" puts " #{estado_emoji} #{herramienta[:nombre]}: #{herramienta[:archivo]}" end puts "\n💡 OPORTUNIDADES DE INTEGRACIÓN:" puts " 1. Usar BuscadorBDSimple para buscar hitos relacionados con DASUTEN" puts " 2. Integrar hitos del dashboard con el sistema de seguimiento de plan ADN" puts " 3. Usar subcomando_db para registrar hitos completados en base de datos" puts " 4. Implementar triggers para notificar cambios en estado de hitos" puts " 5. Usar logger ADN para registrar actividades de seguimiento" puts "\n🚀 ACCIONES RECOMENDADAS:" puts " • Crear comando: adn proyecto:hitos --proyecto P2601" puts " • Integrar dashboard con API de proyectos en dtic-BITACORAs" puts " • Automatizar actualización de métricas del dashboard" puts " • Implementar sistema de notificaciones para hitos críticos" puts "" end def verificar_herramienta(ruta_relativa) File.exist?(File.join(__dir__, '..', ruta_relativa)) end def mostrar_resumen puts "=" * 70 puts "📊 RESUMEN DEL ANÁLISIS DASHBOARD P2601" puts "=" * 70 puts "\n🎯 ESTADO ACTUAL:" puts " • Hitos totales: #{@estadisticas[:hitos_total] || 'N/A'}" puts " • Completados: #{@estadisticas[:hitos_completados] || 'N/A'}" puts " • Activos: #{@estadisticas[:hitos_activos] || 'N/A'}" puts " • Pendientes: #{@estadisticas[:hitos_pendientes] || 'N/A'}" puts " • Nodos online: #{@estadisticas[:nodos_online] || 'N/A'}/#{@estadisticas[:nodos_total] || 'N/A'}" puts "\n💡 HITOS PROPUESTOS: #{@hitos_propuestos.size}" categorias = @hitos_propuestos.group_by { |h| h[:categoria] } categorias.each do |categoria, hitos| puts " • #{categoria}: #{hitos.size} hitos" end puts "\n🔄 INTEGRACIÓN ADN:" puts " • Herramientas disponibles: 5 verificadas" puts " • Oportunidades identificadas: 5 áreas" puts "\n📈 RECOMENDACIONES PRINCIPALES:" puts " 1. Priorizar hitos FÍSICO y ADDOM antes de agregar nuevos" puts " 2. Integrar hitos propuestos gradualmente en el dashboard" puts " 3. Usar herramientas ADN para automatizar seguimiento" puts " 4. Considerar implementar sistema de notificaciones" puts " 5. Documentar lecciones aprendidas de cada hito" puts "\n🚀 PRÓXIMOS PASOS SUGERIDOS:" puts " 1. Ejecutar: ruby tools/analizar_dashboard_p2601.rb --proponer-hitos" puts " 2. Revisar reporte en tmp/reporte_p2601_analisis.json" puts " 3. Actualizar dashboard/index.html con hitos adicionales" puts " 4. Integrar con sistema de búsqueda ADN BITACORAs" puts " 5. Automatizar actualizaciones del dashboard" 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 = { analizar_html: false, consultar_bd: false, proponer_hitos: false, generar_reporte: false, integrar_adn: false, debug: false } OptionParser.new do |opts| opts.banner = "Uso: ruby tools/analizar_dashboard_p2601.rb [OPCIONES]" opts.on("--analizar-html", "Analizar dashboard/index.html actual") do opciones[:analizar_html] = true end opts.on("--consultar-bd", "Consultar estado en base de datos") do opciones[:consultar_bd] = true end opts.on("--proponer-hitos", "Proponer hitos adicionales") do opciones[:proponer_hitos] = true end opts.on("--generar-reporte", "Generar reporte completo") do opciones[:generar_reporte] = true end opts.on("--integrar-adn", "Integrar con herramientas ADN existentes") do opciones[:integrar_adn] = true end opts.on("--todo", "Ejecutar todas las opciones") do opciones.each_key { |k| opciones[k] = true } end opts.on("--debug", "Mostrar información de depuración") do opciones[:debug] = true end opts.on("-h", "--help", "Mostrar ayuda") do puts opts exit end end.parse! # Si no se especificó ninguna opción, mostrar ayuda if opciones.values.none? { |v| v == true } puts "Ejecutar con --help para ver opciones disponibles" puts "Ejemplo: ruby tools/analizar_dashboard_p2601.rb --todo" exit end # Ejecutar analizador begin analizador = AnalizadorDashboardP2601.new(opciones) analizador.ejecutar rescue => e puts "❌ Error fatal: #{e.message}" puts e.backtrace if opciones[:debug] exit 1 end end