Reestructuración modular integral de servicios, reorganización de gestión en docs/ y limpieza profunda del repositorio

This commit is contained in:
Ricardo Monla
2026-03-06 18:03:07 -03:00
parent 15725e0b3c
commit ac9bdf516b
286 changed files with 36187 additions and 112775 deletions
@@ -0,0 +1,653 @@
#!/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