Files
dtic-DIIAA/tools/orquestador/lib/vista_previa/manager.rb
T

654 lines
20 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env ruby
# frozen_string_literal: true
# tools/orquestador/lib/vista_previa/manager.rb
# ============================================
# Clase para vista previa centralizada del dashboard P2601
# Filosofía: "Menos es más" - código reutilizable y modular
require_relative '../dashboard_core'
module DashboardCore
class VistaPreviaManager < Base
# Modos de vista previa disponibles
MODOS_VISTA_PREVIA = {
'resumen' => {
descripcion: 'Resumen completo del dashboard',
metodo: :mostrar_resumen_completo
},
'hitos' => {
descripcion: 'Hitos detallados',
metodo: :mostrar_hitos_detallados
},
'estructura' => {
descripcion: 'Estructura del dashboard',
metodo: :mostrar_estructura
},
'estadisticas' => {
descripcion: 'Estadísticas detalladas',
metodo: :mostrar_estadisticas_detalladas
},
'rapido' => {
descripcion: 'Resumen rápido',
metodo: :mostrar_resumen_rapido
},
'metricas' => {
descripcion: 'Métricas clave',
metodo: :mostrar_metricas_clave
},
'estados' => {
descripcion: 'Análisis de estados',
metodo: :mostrar_analisis_estados
},
'fases' => {
descripcion: 'Análisis por fases',
metodo: :mostrar_analisis_fases
}
}.freeze
def initialize(dashboard_path = nil)
super(dashboard_path)
@contenido_cache = nil
@hitos_cache = nil
end
# Métodos principales de vista previa
def mostrar_vista_previa(modo = 'resumen')
unless MODOS_VISTA_PREVIA.key?(modo)
raise "Modo '#{modo}' no válido. Modos disponibles: #{MODOS_VISTA_PREVIA.keys.join(', ')}"
end
modo_info = MODOS_VISTA_PREVIA[modo]
puts "👁️ VISTA PREVIA - #{modo_info[:descripcion].upcase}"
puts "=" * 60
send(modo_info[:metodo])
true
end
def mostrar_resumen_completo
contenido = leer_contenido
hitos = cargar_hitos
# Información del archivo
puts "📄 INFORMACIÓN DEL ARCHIVO:"
puts " • Ruta: #{@dashboard_path}"
puts " • Tamaño: #{contenido.bytesize} bytes (#{contenido.bytesize / 1024} KB)"
puts " • Líneas: #{contenido.lines.count}"
puts " • Última modificación: #{File.mtime(@dashboard_path).strftime('%Y-%m-%d %H:%M')}"
# Estructura básica
puts "\n🏗️ ESTRUCTURA BÁSICA:"
estructura = verificar_estructura_basica(contenido)
estructura[:detalles].each do |elemento, presente|
puts " • #{elemento}: #{presente ? '✅ Presente' : '❌ Ausente'}"
end
# Hitos
puts "\n🎯 INFORMACIÓN DE HITOS:"
puts " • Total de hitos: #{hitos.size}"
if hitos.any?
# Distribución por estado
estados = Hash.new(0)
hitos.each { |h| estados[h[:estado]] += 1 }
puts " • Distribución por estado:"
estados.each do |estado, cantidad|
porcentaje = (cantidad * 100.0 / hitos.size).round(1)
estado_info = ESTADOS_VALIDOS[estado] || ESTADOS_VALIDOS['pending']
puts " - #{estado_info[:nombre]}: #{cantidad} (#{porcentaje}%)"
end
# Distribución por fase
fases = Hash.new(0)
hitos.each do |hito|
if info = HitoIdGenerator.parsear_id(hito[:id])
fases[info[:fase]] += 1
end
end
if fases.any?
puts " • Distribución por fase:"
fases.sort.each do |fase, cantidad|
puts " - Fase #{fase}: #{cantidad} hitos"
end
end
end
# Timeline
puts "\n📅 TIMELINE:"
timeline_presente = contenido.include?('class="timeline"')
puts " • Presente: #{timeline_presente ? '✅ Sí' : '❌ No'}"
if timeline_presente
timeline_match = contenido.match(/<div class="timeline">([\s\S]*?)<\/div>\s*<\/div>\s*<\/div>/m)
if timeline_match
hitos_en_timeline = timeline_match[1].scan(/<div class="tl-item">/).size
puts " • Hitos en timeline: #{hitos_en_timeline}"
end
end
# Métricas
puts "\n📈 MÉTRICAS:"
metricas = extraer_metricas_clave(contenido)
if metricas.any?
metricas.each do |nombre, valor|
puts " • #{nombre}: #{valor}"
end
else
puts " • No se encontraron métricas específicas"
end
# Recomendaciones rápidas
puts "\n💡 RECOMENDACIONES RÁPIDAS:"
generar_recomendaciones_rapidas(contenido, hitos).each do |recomendacion|
puts " • #{recomendacion}"
end
mostrar_pie
end
def mostrar_hitos_detallados
hitos = cargar_hitos
if hitos.empty?
puts "️ No se encontraron hitos en el dashboard"
return
end
puts "📋 HITOS DETALLADOS (#{hitos.size} total)"
puts "=" * 60
hitos.each_with_index do |hito, i|
puts "\n#{i + 1}. [#{hito[:id]}] #{hito[:titulo]}"
puts " 📊 Estado: #{hito[:estado]}"
puts " 📅 Fecha: #{hito[:fecha]}"
if hito[:tags].any?
puts " 🏷️ Tags: #{hito[:tags].join(', ')}"
end
if hito[:descripcion]
if hito[:descripcion].length > 100
puts " 📝 Descripción: #{hito[:descripcion][0..100]}..."
else
puts " 📝 Descripción: #{hito[:descripcion]}"
end
end
end
# Resumen de estados
puts "\n📊 RESUMEN DE ESTADOS:"
estados = Hash.new(0)
hitos.each { |h| estados[h[:estado]] += 1 }
estados.each do |estado, cantidad|
porcentaje = (cantidad * 100.0 / hitos.size).round(1)
estado_info = ESTADOS_VALIDOS[estado] || ESTADOS_VALIDOS['pending']
puts " • #{estado_info[:nombre]}: #{cantidad} (#{porcentaje}%)"
end
mostrar_pie
end
def mostrar_estructura
contenido = leer_contenido
puts "🏗️ ESTRUCTURA DEL DASHBOARD"
puts "=" * 60
# Secciones principales
puts "📂 SECCIONES PRINCIPALES:"
secciones = [
{ nombre: 'Head', inicio: '<head>', fin: '</head>' },
{ nombre: 'Body', inicio: '<body>', fin: '</body>' },
{ nombre: 'Header', inicio: 'class="header"', fin: nil },
{ nombre: 'Timeline', inicio: 'class="timeline"', fin: '</div><!-- Timeline de Hitos -->' },
{ nombre: 'Footer', inicio: 'class="footer"', fin: '</footer>' }
]
secciones.each do |seccion|
presente = contenido.include?(seccion[:inicio])
puts " • #{seccion[:nombre]}: #{presente ? '✅ Presente' : '❌ Ausente'}"
if presente && seccion[:fin] && contenido.include?(seccion[:fin])
inicio_idx = contenido.index(seccion[:inicio])
fin_idx = contenido.index(seccion[:fin], inicio_idx)
if inicio_idx && fin_idx
tamaño = fin_idx - inicio_idx + seccion[:fin].length
puts " Tamaño: ~#{tamaño} bytes"
end
end
end
# Elementos HTML
puts "\n🔢 CONTEO DE ELEMENTOS HTML:"
elementos = {
'Divs' => contenido.scan(/<div/).size,
'Spans' => contenido.scan(/<span/).size,
'Headers (h1-h6)' => contenido.scan(/<h[1-6]/).size,
'Párrafos (p)' => contenido.scan(/<p>/).size,
'Enlaces (a)' => contenido.scan(/<a /).size
}
elementos.each do |elemento, count|
puts " • #{elemento}: #{count}"
end
# Clases CSS
puts "\n🎨 CLASES CSS MÁS COMUNES:"
clases = contenido.scan(/class="([^"]+)"/).flatten
clases_frecuentes = Hash.new(0)
clases.each do |clase_lista|
clase_lista.split(/\s+/).each do |clase|
clases_frecuentes[clase] += 1
end
end
# Mostrar las 10 clases más comunes
clases_frecuentes.sort_by { |_, count| -count }.first(10).each do |clase, count|
puts " • .#{clase}: #{count} ocurrencias"
end
mostrar_pie
end
def mostrar_estadisticas_detalladas
contenido = leer_contenido
hitos = cargar_hitos
puts "📈 ESTADÍSTICAS DETALLADAS"
puts "=" * 60
# Estadísticas del archivo
puts "📄 ESTADÍSTICAS DEL ARCHIVO:"
puts " • Caracteres totales: #{contenido.length}"
texto_sin_html = contenido.gsub(/<[^>]*>/, ' ').gsub(/\s+/, ' ')
puts " • Caracteres sin HTML: #{texto_sin_html.length}"
puts " • Palabras aproximadas: #{texto_sin_html.split.size}"
puts " • Líneas de código: #{contenido.lines.count}"
# Densidad de información
if hitos.any?
palabras_por_hito = texto_sin_html.split.size / hitos.size.to_f
puts " • Palabras por hito: #{palabras_por_hito.round(1)}"
end
# Estadísticas de hitos
puts "\n🎯 ESTADÍSTICAS DE HITOS:"
puts " • Total de hitos: #{hitos.size}"
if hitos.any?
# Por estado
estados = Hash.new(0)
hitos.each { |h| estados[h[:estado]] += 1 }
puts " • Distribución por estado:"
estados.each do |estado, cantidad|
porcentaje = (cantidad * 100.0 / hitos.size).round(1)
estado_info = ESTADOS_VALIDOS[estado] || ESTADOS_VALIDOS['pending']
puts " - #{estado_info[:nombre]}: #{cantidad} (#{porcentaje}%)"
end
# Por fase
fases = Hash.new(0)
hitos.each do |hito|
if info = HitoIdGenerator.parsear_id(hito[:id])
fases[info[:fase]] += 1
end
end
if fases.any?
puts " • Distribución por fase:"
fases.sort.each do |fase, cantidad|
porcentaje = (cantidad * 100.0 / hitos.size).round(1)
puts " - Fase #{fase}: #{cantidad} (#{porcentaje}%)"
end
end
# Por tag
todos_tags = hitos.flat_map { |h| h[:tags] }
if todos_tags.any?
tags_frecuentes = Hash.new(0)
todos_tags.each { |tag| tags_frecuentes[tag] += 1 }
puts " • Tags más comunes:"
tags_frecuentes.sort_by { |_, count| -count }.first(5).each do |tag, count|
porcentaje = (count * 100.0 / hitos.size).round(1)
puts " - #{tag}: #{count} hitos (#{porcentaje}%)"
end
end
end
# Métricas extraídas
puts "\n📊 MÉTRICAS EXTRAÍDAS:"
metricas = extraer_todas_metricas(contenido)
if metricas.any?
metricas.each do |nombre, valor|
puts " • #{nombre}: #{valor}"
end
else
puts " • No se encontraron métricas específicas"
end
mostrar_pie
end
def mostrar_resumen_rapido
contenido = leer_contenido
hitos = cargar_hitos
puts "⚡ RESUMEN RÁPIDO DASHBOARD"
puts "=" * 40
# Información básica
puts "📄 Archivo: #{File.basename(@dashboard_path)}"
puts " • Tamaño: #{contenido.bytesize / 1024} KB"
puts " • Líneas: #{contenido.lines.count}"
puts " • Modificado: #{File.mtime(@dashboard_path).strftime('%Y-%m-%d %H:%M')}"
# Hitos
puts "🎯 Hitos: #{hitos.size} totales"
# Estados
if hitos.any?
estados = Hash.new(0)
hitos.each { |h| estados[h[:estado]] += 1 }
puts "📊 Estados:"
estados.each do |estado, count|
estado_info = ESTADOS_VALIDOS[estado] || ESTADOS_VALIDOS['pending']
puts " • #{estado_info[:nombre]}: #{count}"
end
end
# Timeline
timeline_presente = contenido.include?('class="timeline"')
puts "📅 Timeline: #{timeline_presente ? '✅ Presente' : '❌ Ausente'}"
# Métricas clave
metricas = extraer_metricas_clave(contenido)
if metricas.any?
puts "📈 Métricas clave:"
metricas.each do |nombre, valor|
puts " • #{nombre}: #{valor}"
end
end
puts "\n🔗 URL: #{CONFIG[:production_url]}"
puts "📅 #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
puts "=" * 40
end
def mostrar_metricas_clave
contenido = leer_contenido
puts "📊 MÉTRICAS CLAVE DEL DASHBOARD"
puts "=" * 60
metricas = extraer_todas_metricas(contenido)
if metricas.empty?
puts "️ No se encontraron métricas específicas en el dashboard"
return
end
puts "📈 MÉTRICAS ENCONTRADAS:"
metricas.each do |nombre, valor|
puts " • #{nombre}: #{valor}"
end
# Análisis de progreso si hay métricas relevantes
if metricas['Progreso']
progreso = metricas['Progreso'].to_i rescue 0
puts "\n🎯 ANÁLISIS DE PROGRESO:"
puts " • Progreso general: #{progreso}%"
if progreso >= 80
puts " • Estado: 🎉 Avanzado"
elsif progreso >= 50
puts " • Estado: ⚡ En progreso"
elsif progreso >= 20
puts " • Estado: 🚀 Iniciando"
else
puts " • Estado: 📋 Pendiente"
end
end
mostrar_pie
end
def mostrar_analisis_estados
hitos = cargar_hitos
puts "📊 ANÁLISIS DE ESTADOS DE HITOS"
puts "=" * 60
if hitos.empty?
puts "️ No se encontraron hitos para analizar"
return
end
# Distribución por estado
estados = Hash.new(0)
hitos.each { |h| estados[h[:estado]] += 1 }
puts "📈 DISTRIBUCIÓN POR ESTADO:"
estados.each do |estado, cantidad|
porcentaje = (cantidad * 100.0 / hitos.size).round(1)
estado_info = ESTADOS_VALIDOS[estado] || ESTADOS_VALIDOS['pending']
puts " • #{estado_info[:nombre]}: #{cantidad} (#{porcentaje}%)"
end
# Análisis de progreso
completados = estados['completed'] || 0
en_progreso = estados['in-progress'] || 0
total = hitos.size
if total > 0
progreso_total = ((completados + (en_progreso * 0.5)) / total * 100).round(1)
puts "\n🎯 ANÁLISIS DE PROGRESO:"
puts " • Progreso estimado: #{progreso_total}%"
puts " • Hitos completados: #{completados}/#{total}"
puts " • Hitos en progreso: #{en_progreso}/#{total}"
puts " • Hitos pendientes: #{estados['pending'] || 0}/#{total}"
end
# Recomendaciones basadas en estados
puts "\n💡 RECOMENDACIONES:"
if (estados['pending'] || 0) > total * 0.7
puts " • Muchos hitos pendientes (#{estados['pending']}/#{total})"
puts " • Considerar iniciar más hitos"
end
if (estados['blocked'] || 0) > 0
puts " • Hay hitos bloqueados (#{estados['blocked']})"
puts " • Revisar dependencias y obstáculos"
end
if (estados['on-hold'] || 0) > 0
puts " • Hay hitos en espera (#{estados['on-hold']})"
puts " • Evaluar prioridades y recursos"
end
mostrar_pie
end
def mostrar_analisis_fases
hitos = cargar_hitos
puts "📁 ANÁLISIS POR FASES"
puts "=" * 60
if hitos.empty?
puts "️ No se encontraron hitos para analizar"
return
end
# Agrupar hitos por fase
hitos_por_fase = Hash.new { |h, k| h[k] = [] }
hitos.each do |hito|
if info = HitoIdGenerator.parsear_id(hito[:id])
hitos_por_fase[info[:fase]] << hito
else
hitos_por_fase['sin_fase'] << hito
end
end
puts "📊 DISTRIBUCIÓN POR FASE:"
hitos_por_fase.sort.each do |fase, hitos_fase|
puts " • Fase #{fase}: #{hitos_fase.size} hitos"
end
# Análisis detallado por fase
puts "\n📈 ANÁLISIS DETALLADO POR FASE:"
hitos_por_fase.sort.each do |fase, hitos_fase|
next if hitos_fase.empty?
puts "\n 📁 FASE #{fase}:"
puts " • Total hitos: #{hitos_fase.size}"
# Estados en esta fase
estados_fase = Hash.new(0)
hitos_fase.each { |h| estados_fase[h[:estado]] += 1 }
if estados_fase.any?
puts " • Distribución por estado:"
estados_fase.each do |estado, cantidad|
porcentaje = (cantidad * 100.0 / hitos_fase.size).round(1)
estado_info = ESTADOS_VALIDOS[estado] || ESTADOS_VALIDOS['pending']
puts " - #{estado_info[:nombre]}: #{cantidad} (#{porcentaje}%)"
end
end
# Progreso de la fase
completados = estados_fase['completed'] || 0
en_progreso = estados_fase['in-progress'] || 0
progreso_fase = hitos_fase.size > 0 ? ((completados + (en_progreso * 0.5)) / hitos_fase.size * 100).round(1) : 0
puts " • Progreso de la fase: #{progreso_fase}%"
end
# Hitos sin fase
if hitos_por_fase['sin_fase'] && !hitos_por_fase['sin_fase'].empty?
puts "\n⚠️ HITOS SIN FASE ASIGNADA:"
hitos_por_fase['sin_fase'].each do |hito|
puts " • #{hito[:id]}: #{hito[:titulo]}"
end
end
mostrar_pie
end
private
def leer_contenido
@contenido_cache ||= leer_dashboard
end
def cargar_hitos
@hitos_cache ||= begin
contenido = leer_contenido
extraer_hitos(contenido)
end
end
def extraer_metricas_clave(contenido)
metricas = {}
# Buscar porcentaje de progreso
if match = contenido.match(/id="progressPct">([^<]+)</)
metricas['Progreso'] = match[1]
end
# Buscar hitos completados
if match = contenido.match(/Hitos Completados[^<]*<[^>]*>([^<]+)</)
metricas['Hitos Completados'] = match[1]
end
# Buscar hitos pendientes
if match = contenido.match(/Hitos Pendientes[^<]*<[^>]*>([^<]+)</)
metricas['Hitos Pendientes'] = match[1]
end
metricas
end
def extraer_todas_metricas(contenido)
metricas = {}
# Buscar porcentaje de progreso
if match = contenido.match(/id="progressPct">([^<]+)</)
metricas['Progreso'] = match[1]
end
# Buscar hitos completados
if match = contenido.match(/Hitos Completados[^<]*<[^>]*>([^<]+)</)
metricas['Hitos Completados'] = match[1]
end
# Buscar hitos pendientes
if match = contenido.match(/Hitos Pendientes[^<]*<[^>]*>([^<]+)</)
metricas['Hitos Pendientes'] = match[1]
end
# Buscar esfuerzo total
if match = contenido.match(/Esfuerzo Total[^<]*<[^>]*>([^<]+)</)
metricas['Esfuerzo Total'] = match[1]
end
# Buscar horas físicas
if match = contenido.match(/Horas Físicas[^<]*<[^>]*>([^<]+)</)
metricas['Horas Físicas'] = match[1]
end
metricas
end
def generar_recomendaciones_rapidas(contenido, hitos)
recomendaciones = []
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
# Verificar duplicados
duplicados = buscar_hitos_duplicados(contenido)
if duplicados.any?
recomendaciones << "Eliminar hitos duplicados usando la herramienta 'limpiar'"
end
# Verificar estructura
estructura = verificar_estructura_basica(contenido)
unless estructura[:exito]
recomendaciones << "Corregir estructura HTML básica del dashboard"
end
# Verificar timeline
unless contenido.include?('class="timeline"')
recomendaciones << "Agregar sección timeline al dashboard"
end
# Verificar estados
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