#!/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">([^<]+))
valores['Progreso'] = match[1]
end
# Buscar hitos completados
if match = contenido.match(/Hitos Completados[^<]*<[^>]*>([^<]+))
valores['Hitos Completados'] = match[1]
end
# Buscar hitos pendientes
if match = contenido.match(/Hitos Pendientes[^<]*<[^>]*>([^<]+))
valores['Hitos Pendientes'] = match[1]
end
# Buscar esfuerzo total
if match = contenido.match(/Esfuerzo Total[^<]*<[^>]*>([^<]+))
valores['Esfuerzo Total'] = match[1]
end
valores
end
def analizar_por_estado(hitos)
conteo = Hash.new(0)
hitos.each { |h| conteo[h[:estado]] += 1 }
conteo
end
def analizar_por_fase(hitos)
conteo = Hash.new(0)
hitos.each do |hito|
if info = HitoIdGenerator.parsear_id(hito[:id])
conteo[info[:fase]] += 1
else
conteo['sin_fase'] += 1
end
end
conteo
end
def analizar_por_tag(hitos)
conteo = Hash.new(0)
hitos.each do |hito|
hito[:tags].each { |tag| conteo[tag] += 1 }
end
conteo
end
def mostrar_resumen_verificacion(resultados)
puts "\n📊 RESUMEN DE VERIFICACIÓN"
puts "=" * 60
total_reglas = resultados.size
reglas_exitosas = resultados.values.count { |r| r[:exito] }
reglas_fallidas = total_reglas - reglas_exitosas
puts "Reglas verificadas: #{total_reglas}"
puts "Reglas exitosas: #{reglas_exitosas}"
puts "Reglas fallidas: #{reglas_fallidas}"
puts "Porcentaje de éxito: #{(reglas_exitosas * 100.0 / total_reglas).round(1)}%"
# Mostrar resumen por nivel
puts "\n📈 DISTRIBUCIÓN POR NIVEL DE IMPORTANCIA:"
['crítico', 'alto', 'medio', 'bajo'].each do |nivel|
reglas_nivel = resultados.select { |_, r| r[:nivel] == nivel }
exitos_nivel = reglas_nivel.count { |_, r| r[:exito] }
total_nivel = reglas_nivel.size
if total_nivel > 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