654 lines
20 KiB
Ruby
654 lines
20 KiB
Ruby
#!/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
|