#!/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(/
([\s\S]*?)<\/div>\s*<\/div>\s*<\/div>/m) if timeline_match hitos_en_timeline = timeline_match[1].scan(/
/).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: '', fin: '' }, { nombre: 'Body', inicio: '', fin: '' }, { nombre: 'Header', inicio: 'class="header"', fin: nil }, { nombre: 'Timeline', inicio: 'class="timeline"', fin: '
' }, { nombre: 'Footer', inicio: 'class="footer"', fin: '' } ] 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(/
contenido.scan(/ contenido.scan(/ contenido.scan(/

/).size, 'Enlaces (a)' => contenido.scan(/]*>/, ' ').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">([^<]+)]*>([^<]+)]*>([^<]+)([^<]+)]*>([^<]+)]*>([^<]+)]*>([^<]+)]*>([^<]+) 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