#!/usr/bin/env ruby # frozen_string_literal: true # tools/orquestador/lib/verificacion/manager.rb # ============================================ # Clase para verificación centralizada del dashboard P2601 # Filosofía: "Menos es más" - código reutilizable y modular require_relative '../dashboard_core' module DashboardCore class VerificacionManager < Base # Reglas de verificación REGLAS_VERIFICACION = { estructura: { nivel: 'crítico', descripcion: 'Estructura HTML básica del dashboard', verificar: ->(contenido) { elementos = { 'DOCTYPE html' => contenido.include?(''), 'head' => contenido.include?('') && contenido.include?(''), 'body' => contenido.include?('') && contenido.include?(''), 'title P2601' => contenido.include?('P2601'), 'container' => contenido.include?('class="container"'), 'header' => contenido.include?('class="header"'), 'footer' => contenido.include?('class="footer"'), 'timeline' => contenido.include?('class="timeline"') } elementos } }, hitos: { nivel: 'alto', descripcion: 'Integridad y formato de hitos', verificar: ->(contenido) { hitos = extraer_hitos(contenido) { total_hitos: hitos.size, hitos_validos: hitos.count { |h| h[:id] && h[:titulo] }, ids_validos: hitos.count { |h| HitoIdGenerator.es_valido?(h[:id]) }, duplicados: buscar_hitos_duplicados(contenido).size } } }, estados: { nivel: 'medio', descripcion: 'Estados válidos de hitos', verificar: ->(contenido) { hitos = extraer_hitos(contenido) estados_validos = hitos.count { |h| Validator.validar_estado(h[:estado]) } estados_invalidos = hitos.reject { |h| Validator.validar_estado(h[:estado]) } { estados_validos: estados_validos, estados_invalidos: estados_invalidos.size, estados_presentes: hitos.map { |h| h[:estado] }.uniq } } }, timeline: { nivel: 'alto', descripcion: 'Estructura de la timeline', verificar: ->(contenido) { timeline_presente = contenido.include?('class="timeline"') if timeline_presente timeline_match = contenido.match(/
([\s\S]*?)<\/div>\s*<\/div>\s*<\/div>/m) timeline_content = timeline_match ? timeline_match[1] : '' hitos_en_timeline = timeline_content.scan(/
/).size else hitos_en_timeline = 0 end { presente: timeline_presente, hitos_en_timeline: hitos_en_timeline, estructura_correcta: timeline_presente && hitos_en_timeline > 0 } } }, metricas: { nivel: 'bajo', descripcion: 'Métricas y estadísticas', verificar: ->(contenido) { metricas_encontradas = { progreso: contenido.include?('Progreso General') || contenido.include?('progressPct'), hitos_completados: contenido.include?('Hitos Completados'), hitos_pendientes: contenido.include?('Hitos Pendientes'), esfuerzo_total: contenido.include?('Esfuerzo Total') } valores = extraer_valores_metricas(contenido) { metricas_presentes: metricas_encontradas.values.count(true), valores_encontrados: valores.size, valores: valores } } } }.freeze def initialize(dashboard_path = nil) super(dashboard_path) @resultados_cache = nil end # Métodos principales de verificación def verificar_completo puts "🔍 VERIFICACIÓN COMPLETA DEL DASHBOARD P2601" puts "=" * 60 contenido = leer_dashboard resultados = {} REGLAS_VERIFICACION.each do |nombre, regla| puts "📋 Verificando: #{regla[:descripcion]}" resultados[nombre] = ejecutar_verificacion(contenido, regla) end @resultados_cache = resultados mostrar_resumen_verificacion(resultados) resultados end def verificar_estructura puts "🏗️ VERIFICANDO ESTRUCTURA DEL DASHBOARD" puts "=" * 60 contenido = leer_dashboard regla = REGLAS_VERIFICACION[:estructura] resultado = ejecutar_verificacion(contenido, regla) mostrar_detalle_estructura(resultado) resultado end def verificar_hitos puts "🎯 VERIFICANDO HITOS DEL DASHBOARD" puts "=" * 60 contenido = leer_dashboard regla = REGLAS_VERIFICACION[:hitos] resultado = ejecutar_verificacion(contenido, regla) mostrar_detalle_hitos(resultado, contenido) resultado end def verificar_estados puts "📊 VERIFICANDO ESTADOS DE HITOS" puts "=" * 60 contenido = leer_dashboard regla = REGLAS_VERIFICACION[:estados] resultado = ejecutar_verificacion(contenido, regla) mostrar_detalle_estados(resultado, contenido) resultado end def analizar_hitos_detallado puts "📋 ANÁLISIS DETALLADO DE HITOS" puts "=" * 60 contenido = leer_dashboard hitos = extraer_hitos(contenido) if hitos.empty? puts "ℹ️ No se encontraron hitos en el dashboard" return nil end analisis = { total: hitos.size, por_estado: analizar_por_estado(hitos), por_fase: analizar_por_fase(hitos), por_tag: analizar_por_tag(hitos), duplicados: buscar_hitos_duplicados(contenido), ids_invalidos: hitos.reject { |h| HitoIdGenerator.es_valido?(h[:id]) } } mostrar_analisis_detallado(analisis, hitos) analisis end def generar_reporte_verificacion resultados = @resultados_cache || verificar_completo reporte = "📋 REPORTE DE VERIFICACIÓN - DASHBOARD P2601\n" reporte += "=" * 70 + "\n" reporte += "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}\n" reporte += "Dashboard: #{CONFIG[:production_url]}\n\n" # Resumen ejecutivo reporte += "📊 RESUMEN EJECUTIVO\n" reporte += "-" * 40 + "\n" total_reglas = resultados.size reglas_exitosas = resultados.values.count { |r| r[:exito] } porcentaje_exito = (reglas_exitosas * 100.0 / total_reglas).round(1) reporte += "Reglas verificadas: #{total_reglas}\n" reporte += "Reglas exitosas: #{reglas_exitosas}\n" reporte += "Porcentaje de éxito: #{porcentaje_exito}%\n\n" # Detalle por regla reporte += "🔍 DETALLE POR REGLA DE VERIFICACIÓN\n" reporte += "-" * 40 + "\n" resultados.each do |nombre, resultado| regla = REGLAS_VERIFICACION[nombre] emoji = resultado[:exito] ? '✅' : '❌' reporte += "#{emoji} #{regla[:descripcion]} (#{regla[:nivel].upcase})\n" if resultado[:detalles].is_a?(Hash) resultado[:detalles].each do |detalle, valor| reporte += " • #{detalle}: #{valor}\n" end end unless resultado[:exito] reporte += " ⚠️ Problemas detectados:\n" resultado[:problemas].each do |problema| reporte += " - #{problema}\n" end end reporte += "\n" end # Recomendaciones reporte += "💡 RECOMENDACIONES\n" reporte += "-" * 40 + "\n" recomendaciones = generar_recomendaciones(resultados) if recomendaciones.any? recomendaciones.each do |recomendacion| reporte += "• #{recomendacion}\n" end else reporte += "✅ Todas las verificaciones pasaron exitosamente.\n" end reporte += "\n🔗 Dashboard: #{CONFIG[:production_url]}\n" reporte += "📅 Reporte generado: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}\n" reporte end private def ejecutar_verificacion(contenido, regla) begin detalles = regla[:verificar].call(contenido) problemas = identificar_problemas(detalles, regla[:nivel]) { exito: problemas.empty?, detalles: detalles, problemas: problemas, nivel: regla[:nivel], descripcion: regla[:descripcion] } rescue => e { exito: false, detalles: { error: e.message }, problemas: ["Error durante verificación: #{e.message}"], nivel: regla[:nivel], descripcion: regla[:descripcion] } end end def identificar_problemas(detalles, nivel) problemas = [] case nivel when 'crítico' # Para estructura, todos los elementos deben estar presentes if detalles.is_a?(Hash) && detalles.key?('DOCTYPE html') detalles.each do |elemento, presente| problemas << "Falta elemento: #{elemento}" unless presente end end when 'alto' # Para hitos, no debe haber duplicados y todos deben ser válidos if detalles.is_a?(Hash) if detalles[:duplicados] && detalles[:duplicados] > 0 problemas << "Se encontraron #{detalles[:duplicados]} hitos duplicados" end if detalles[:total_hitos] && detalles[:hitos_validos] if detalles[:total_hitos] > 0 && detalles[:hitos_validos] < detalles[:total_hitos] problemas << "#{detalles[:total_hitos] - detalles[:hitos_validos]} hitos no válidos" end end end when 'medio' # Para estados, todos deben ser válidos if detalles.is_a?(Hash) && detalles[:estados_invalidos] && detalles[:estados_invalidos] > 0 problemas << "Se encontraron #{detalles[:estados_invalidos]} estados inválidos" end end problemas end def extraer_valores_metricas(contenido) valores = {} # Buscar porcentaje de progreso if match = contenido.match(/id="progressPct">([^<]+)]*>([^<]+)]*>([^<]+)]*>([^<]+) 0 puts " • #{nivel.capitalize}: #{exitos_nivel}/#{total_nivel} exitosas" end end # Mostrar problemas críticos problemas_criticos = resultados.select { |_, r| !r[:exito] && r[:nivel] == 'crítico' } if problemas_criticos.any? puts "\n⚠️ PROBLEMAS CRÍTICOS DETECTADOS:" problemas_criticos.each do |nombre, resultado| puts " • #{resultado[:descripcion]}" resultado[:problemas].each do |problema| puts " - #{problema}" end end end if reglas_exitosas == total_reglas puts "\n🎉 ¡Todas las verificaciones pasaron exitosamente!" else puts "\n⚠️ Se encontraron problemas que requieren atención" end end def mostrar_detalle_estructura(resultado) puts "\n🏗️ DETALLE DE ESTRUCTURA:" if resultado[:exito] puts "✅ Estructura HTML básica correcta" else puts "❌ Problemas en la estructura:" resultado[:problemas].each do |problema| puts " • #{problema}" end end puts "\n📋 ELEMENTOS VERIFICADOS:" resultado[:detalles].each do |elemento, presente| puts " • #{elemento}: #{presente ? '✅ Presente' : '❌ Ausente'}" end end def mostrar_detalle_hitos(resultado, contenido) hitos = extraer_hitos(contenido) puts "\n🎯 DETALLE DE HITOS:" puts " • Total de hitos: #{resultado[:detalles][:total_hitos]}" puts " • Hitos válidos: #{resultado[:detalles][:hitos_validos]}" puts " • IDs válidos: #{resultado[:detalles][:ids_validos]}" puts " • Hitos duplicados: #{resultado[:detalles][:duplicados]}" if resultado[:detalles][:duplicados] > 0 duplicados = buscar_hitos_duplicados(contenido) puts " • IDs duplicados: #{duplicados.join(', ')}" end unless resultado[:exito] puts "\n⚠️ PROBLEMAS DETECTADOS:" resultado[:problemas].each do |problema| puts " • #{problema}" end end end def mostrar_detalle_estados(resultado, contenido) hitos = extraer_hitos(contenido) puts "\n📊 DETALLE DE ESTADOS:" puts " • Estados válidos: #{resultado[:detalles][:estados_validos]}" puts " • Estados inválidos: #{resultado[:detalles][:estados_invalidos]}" puts " • Estados presentes: #{resultado[:detalles][:estados_presentes].join(', ')}" unless resultado[:exito] puts "\n⚠️ PROBLEMAS DETECTADOS:" resultado[:problemas].each do |problema| puts " • #{problema}" end end if resultado[:detalles][:estados_invalidos] > 0 puts "\n🔍 ESTADOS INVÁLIDOS ENCONTRADOS:" hitos.each do |hito| unless Validator.validar_estado(hito[:estado]) puts " • #{hito[:id]}: '#{hito[:estado]}'" end end end end def mostrar_analisis_detallado(analisis, hitos) puts "\n📈 ANÁLISIS DETALLADO DE HITOS" puts "=" * 60 puts "📊 ESTADÍSTICAS GENERALES:" puts " • Total de hitos: #{analisis[:total]}" if analisis[:por_estado].any? puts "\n📊 DISTRIBUCIÓN POR ESTADO:" analisis[:por_estado].each do |estado, cantidad| porcentaje = (cantidad * 100.0 / analisis[:total]).round(1) estado_info = ESTADOS_VALIDOS[estado] || ESTADOS_VALIDOS['pending'] puts " • #{estado_info[:nombre]}: #{cantidad} (#{porcentaje}%)" end end if analisis[:por_fase].any? puts "\n📁 DISTRIBUCIÓN POR FASE:" analisis[:por_fase].each do |fase, cantidad| puts " • Fase #{fase}: #{cantidad} hitos" end end if analisis[:por_tag].any? puts "\n🏷️ DISTRIBUCIÓN POR TAG:" analisis[:por_tag].sort_by { |_, count| -count }.first(10).each do |tag, cantidad| puts " • #{tag}: #{cantidad} hitos" end end if analisis[:duplicados].any? puts "\n⚠️ HITOS DUPLICADOS:" puts " • Total: #{analisis[:duplicados].size}" puts " • IDs: #{analisis[:duplicados].join(', ')}" end if analisis[:ids_invalidos].any? puts "\n❌ IDs DE HITOS INVÁLIDOS:" analisis[:ids_invalidos].each do |hito| puts " • #{hito[:id]}: #{hito[:titulo]}" end end # Mostrar hitos por fase y estado puts "\n🎯 HITOS POR FASE Y ESTADO:" hitos.group_by do |hito| info = HitoIdGenerator.parsear_id(hito[:id]) info ? info[:fase] : 'sin_fase' end.each do |fase, hitos_fase| puts " 📁 Fase #{fase}:" hitos_fase.group_by { |h| h[:estado] }.each do |estado, hitos_estado| estado_info = ESTADOS_VALIDOS[estado] || ESTADOS_VALIDOS['pending'] puts " • #{estado_info[:nombre]}: #{hitos_estado.size}" end end end def generar_recomendaciones(resultados) recomendaciones = [] # Recomendaciones basadas en problemas encontrados resultados.each do |nombre, resultado| next if resultado[:exito] case nombre when :estructura recomendaciones << "Corregir estructura HTML básica del dashboard" when :hitos if resultado[:detalles][:duplicados] > 0 recomendaciones << "Eliminar hitos duplicados usando la herramienta 'limpiar'" end if resultado[:detalles][:total_hitos] > 0 && resultado[:detalles][:hitos_validos] < resultado[:detalles][:total_hitos] recomendaciones << "Corregir hitos con estructura inválida" end when :estados if resultado[:detalles][:estados_invalidos] > 0 recomendaciones << "Corregir estados inválidos de hitos" end when :timeline unless resultado[:detalles][:presente] recomendaciones << "Agregar sección timeline al dashboard" end end end # Recomendaciones generales contenido = leer_dashboard hitos = extraer_hitos(contenido) if hitos.empty? recomendaciones << "Agregar hitos al dashboard usando la herramienta 'agregar_hitos'" elsif hitos.size < 5 recomendaciones << "Considerar agregar más hitos para mejor seguimiento del proyecto" end estados = hitos.map { |h| h[:estado] } if estados.count('pending') > hitos.size * 0.7 recomendaciones << "Iniciar más hitos pendientes para avanzar en el proyecto" end if estados.count('blocked') > 0 recomendaciones << "Revisar hitos bloqueados y resolver dependencias" end recomendaciones.uniq end end end