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
+534
View File
@@ -0,0 +1,534 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# tools/orquestador/dashboard/vista_previa.rb
# ===========================================
# Herramienta para vista previa rápida del dashboard P2601
# Filosofía: "Menos es más" - herramienta pequeña, específica y reutilizable
require 'fileutils'
class DashboardPreview
def initialize(dashboard_path = nil)
@dashboard_path = dashboard_path || default_dashboard_path
end
def default_dashboard_path
File.expand_path('../../../../dashboard/index.html', __FILE__)
end
def mostrar_vista_previa(modo = 'resumen')
puts "👁️ VISTA PREVIA DEL DASHBOARD P2601"
puts "=" * 60
unless File.exist?(@dashboard_path)
puts "❌ Error: No se encuentra el dashboard en #{@dashboard_path}"
return false
end
puts "📖 Analizando dashboard..."
content = File.read(@dashboard_path)
case modo
when 'resumen'
mostrar_resumen(content)
when 'hitos'
mostrar_hitos_detallados(content)
when 'estructura'
mostrar_estructura(content)
when 'estadisticas'
mostrar_estadisticas(content)
else
puts "❌ Modo '#{modo}' no válido"
puts " Modos disponibles: resumen, hitos, estructura, estadisticas"
return false
end
true
end
def mostrar_resumen_rapido
puts "⚡ RESUMEN RÁPIDO DASHBOARD"
puts "=" * 40
unless File.exist?(@dashboard_path)
puts "❌ Dashboard no encontrado"
return false
end
content = File.read(@dashboard_path)
# Información básica
puts "📄 Archivo: #{File.basename(@dashboard_path)}"
puts " • Tamaño: #{content.bytesize / 1024} KB"
puts " • Líneas: #{content.lines.count}"
puts " • Última modificación: #{File.mtime(@dashboard_path).strftime('%Y-%m-%d %H:%M')}"
# Hitos
hitos_count = content.scan(/<div class="tl-item">/).size
hitos_unicos = extraer_hitos_unicos(content).size
puts "🎯 Hitos: #{hitos_count} totales, #{hitos_unicos} únicos"
# Estados
estados = contar_estados_hitos(content)
if estados.any?
puts "📊 Estados:"
estados.each do |estado, count|
puts "#{estado}: #{count}"
end
end
# Timeline
timeline_presente = content.include?('class="timeline"')
puts "📅 Timeline: #{timeline_presente ? '✅ Presente' : '❌ Ausente'}"
# Métricas clave
metricas = extraer_metricas_clave(content)
if metricas.any?
puts "📈 Métricas clave:"
metricas.each do |nombre, valor|
puts "#{nombre}: #{valor}"
end
end
puts "\n🔗 URL: https://ns8.frlr.utn.edu.ar/bitacoras/p2601"
puts "📅 #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
puts "=" * 40
true
end
private
def mostrar_resumen(content)
puts "📋 RESUMEN COMPLETO"
puts "=" * 60
# Información del archivo
puts "📄 INFORMACIÓN DEL ARCHIVO:"
puts " • Ruta: #{@dashboard_path}"
puts " • Tamaño: #{content.bytesize} bytes (#{content.bytesize / 1024} KB)"
puts " • Líneas: #{content.lines.count}"
puts " • Encoding: #{content.encoding.name}"
# Estructura HTML
puts "\n🏗️ ESTRUCTURA HTML:"
elementos = {
'DOCTYPE html' => content.include?('<!DOCTYPE html>'),
'Elemento head' => content.include?('<head>') && content.include?('</head>'),
'Elemento body' => content.include?('<body>') && content.include?('</body>'),
'Título P2601' => content.include?('P2601'),
'Container principal' => content.include?('class="container"'),
'Header' => content.include?('class="header"'),
'Footer' => content.include?('class="footer"')
}
elementos.each do |elemento, presente|
puts "#{elemento}: #{presente ? '✅' : '❌'}"
end
# Hitos
puts "\n🎯 INFORMACIÓN DE HITOS:"
hitos_count = content.scan(/<div class="tl-item">/).size
hitos_unicos = extraer_hitos_unicos(content)
puts " • Total de hitos: #{hitos_count}"
puts " • Hitos únicos: #{hitos_unicos.size}"
if hitos_unicos.any?
puts " • Distribución por estado:"
estados = Hash.new(0)
hitos_unicos.each { |h| estados[h[:estado]] += 1 }
estados.each do |estado, count|
porcentaje = (count * 100.0 / hitos_unicos.size).round(1)
puts " - #{estado}: #{count} (#{porcentaje}%)"
end
end
# Timeline
puts "\n📅 TIMELINE:"
timeline_presente = content.include?('class="timeline"')
puts " • Presente: #{timeline_presente ? '✅ Sí' : '❌ No'}"
if timeline_presente
timeline_match = content.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 Y ESTADÍSTICAS:"
metricas = {
'Progreso General' => content.include?('Progreso General'),
'Hitos Completados' => content.include?('Hitos Completados'),
'Hitos Pendientes' => content.include?('Hitos Pendientes'),
'Esfuerzo Total' => content.include?('Esfuerzo Total')
}
metricas.each do |metrica, presente|
puts "#{metrica}: #{presente ? '✅ Presente' : '❌ Ausente'}"
end
# Extraer valores de métricas si existen
valores_metricas = extraer_valores_metricas(content)
if valores_metricas.any?
puts " • Valores encontrados:"
valores_metricas.each do |nombre, valor|
puts " - #{nombre}: #{valor}"
end
end
puts "\n💡 RECOMENDACIONES:"
if hitos_count > hitos_unicos.size * 2
puts " • Posibles hitos duplicados detectados"
puts " • Considerar usar 'limpiar.rb'"
end
if hitos_unicos.size < 3
puts " • Pocos hitos definidos"
puts " • Considerar agregar más hitos con 'agregar_hitos.rb'"
end
puts "\n✅ Resumen completado"
end
def mostrar_hitos_detallados(content)
puts "📋 HITOS DETALLADOS"
puts "=" * 60
hitos = extraer_hitos_unicos(content)
if hitos.empty?
puts "️ No se encontraron hitos en el dashboard"
return
end
puts "🎯 Total de hitos únicos: #{hitos.size}"
puts
hitos.each_with_index do |hito, i|
puts " #{i + 1}. [#{hito[:id]}] #{hito[:titulo]}"
puts " 📊 Estado: #{hito[:estado]}"
puts " 📅 Fecha: #{hito[:fecha]}"
puts " 🏷️ Tags: #{hito[:tags].join(', ')}" if hito[:tags].any?
# Mostrar descripción truncada
if hito[:descripcion] && hito[:descripcion].length > 100
puts " 📝 Descripción: #{hito[:descripcion][0..100]}..."
elsif hito[:descripcion]
puts " 📝 Descripción: #{hito[:descripcion]}"
end
puts
end
# Resumen de estados
puts "📊 RESUMEN DE ESTADOS:"
estados = Hash.new(0)
hitos.each { |h| estados[h[:estado]] += 1 }
estados.each do |estado, count|
porcentaje = (count * 100.0 / hitos.size).round(1)
puts "#{estado}: #{count} hitos (#{porcentaje}%)"
end
end
def mostrar_estructura(content)
puts "🏗️ ESTRUCTURA DEL DASHBOARD"
puts "=" * 60
# Mostrar 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 = content.include?(seccion[:inicio])
puts "#{seccion[:nombre]}: #{presente ? '✅ Presente' : '❌ Ausente'}"
if presente && seccion[:fin] && content.include?(seccion[:fin])
# Calcular tamaño aproximado de la sección
inicio_idx = content.index(seccion[:inicio])
fin_idx = content.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
# Contar elementos HTML
puts "\n🔢 CONTEO DE ELEMENTOS HTML:"
elementos = {
'Divs' => content.scan(/<div/).size,
'Spans' => content.scan(/<span/).size,
'Headers (h1-h6)' => content.scan(/<h[1-6]/).size,
'Párrafos (p)' => content.scan(/<p>/).size,
'Enlaces (a)' => content.scan(/<a /).size
}
elementos.each do |elemento, count|
puts "#{elemento}: #{count}"
end
# Clases CSS más comunes
puts "\n🎨 CLASES CSS MÁS COMUNES:"
clases = content.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
end
def mostrar_estadisticas(content)
puts "📈 ESTADÍSTICAS DETALLADAS"
puts "=" * 60
# Métricas extraídas del contenido
metricas = extraer_valores_metricas(content)
if metricas.any?
puts "📊 MÉTRICAS ENCONTRADAS:"
metricas.each do |nombre, valor|
puts "#{nombre}: #{valor}"
end
else
puts "️ No se encontraron métricas específicas en el contenido"
end
# Estadísticas de hitos
puts "\n🎯 ESTADÍSTICAS DE HITOS:"
hitos = extraer_hitos_unicos(content)
if hitos.any?
puts " • Total de hitos únicos: #{hitos.size}"
# Por estado
estados = Hash.new(0)
hitos.each { |h| estados[h[:estado]] += 1 }
if estados.any?
puts " • Distribución por estado:"
estados.each do |estado, count|
porcentaje = (count * 100.0 / hitos.size).round(1)
puts " - #{estado}: #{count} (#{porcentaje}%)"
end
end
# Por tags
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|
puts " - #{tag}: #{count} hitos"
end
end
else
puts " • No se encontraron hitos"
end
# Estadísticas de texto
puts "\n📝 ESTADÍSTICAS DE TEXTO:"
texto_sin_html = content.gsub(/<[^>]*>/, ' ').gsub(/\s+/, ' ')
puts " • Caracteres totales: #{content.length}"
puts " • Caracteres sin HTML: #{texto_sin_html.length}"
puts " • Palabras aproximadas: #{texto_sin_html.split.size}"
puts " • Líneas de código: #{content.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
end
def extraer_hitos_unicos(content)
hitos = []
content.scan(/<div class="tl-item">([\s\S]*?)<\/div>\s*<\/div>\s*<\/div>/m) do |hito_html|
hito = hito_html[0]
# Extraer información
id_match = hito.match(/<span class="tl-id">([^<]+)<\/span>/)
titulo_match = hito.match(/<div class="tl-title">([^<]+)<\/div>/)
estado_match = hito.match(/<div class="tl-dot ([^"]+)"><\/div>/)
fecha_match = hito.match(/<span class="tl-date">([^<]+)<\/span>/)
descripcion_match = hito.match(/<div class="tl-desc">([^<]+)<\/div>/)
next unless id_match && titulo_match
hitos << {
id: id_match[1],
titulo: titulo_match[1],
estado: estado_match ? estado_match[1] : 'pending',
fecha: fecha_match ? fecha_match[1] : 'Sin fecha',
descripcion: descripcion_match ? descripcion_match[1] : nil,
tags: hito.scan(/<span class="[^"]*tag[^"]*">([^<]+)<\/span>/).flatten
}
end
# Eliminar duplicados por ID
hitos.uniq { |h| h[:id] }
end
def contar_estados_hitos(content)
estados = Hash.new(0)
content.scan(/<div class="tl-dot ([^"]+)"><\/div>/) do |estado|
estados[estado[0]] += 1
end
estados
end
def extraer_metricas_clave(content)
metricas = {}
# Buscar porcentaje de progreso
if match = content.match(/id="progressPct">([^<]+)</)
metricas['Progreso'] = match[1]
end
# Buscar hitos completados
if match = content.match(/Hitos Completados[^<]*<[^>]*>([^<]+)</)
metricas['Hitos Completados'] = match[1]
end
# Buscar hitos pendientes
if match = content.match(/Hitos Pendientes[^<]*<[^>]*>([^<]+)</)
metricas['Hitos Pendientes'] = match[1]
end
metricas
end
def extraer_valores_metricas(content)
metricas = {}
# Buscar porcentaje de progreso
if match = content.match(/id="progressPct">([^<]+)</)
metricas['Progreso'] = match[1]
end
# Buscar hitos completados
if match = content.match(/Hitos Completados[^<]*<[^>]*>([^<]+)</)
metricas['Hitos Completados'] = match[1]
end
# Buscar hitos pendientes
if match = content.match(/Hitos Pendientes[^<]*<[^>]*>([^<]+)</)
metricas['Hitos Pendientes'] = match[1]
end
# Buscar esfuerzo total
if match = content.match(/Esfuerzo Total[^<]*<[^>]*>([^<]+)</)
metricas['Esfuerzo Total'] = match[1]
end
# Buscar horas físicas
if match = content.match(/Horas Físicas[^<]*<[^>]*>([^<]+)</)
metricas['Horas Físicas'] = match[1]
end
metricas
end
end
# Interfaz de línea de comandos
if __FILE__ == $0
begin
preview = DashboardPreview.new
# Parsear argumentos
modo = 'resumen'
dashboard_path = nil
ARGV.each_with_index do |arg, i|
if arg == '--dashboard' && ARGV[i + 1]
dashboard_path = ARGV[i + 1]
elsif i == 0 && !arg.start_with?('--')
modo = arg
end
end
preview = DashboardPreview.new(dashboard_path)
case modo
when 'resumen', 'summary'
success = preview.mostrar_vista_previa('resumen')
when 'hitos', 'milestones'
success = preview.mostrar_vista_previa('hitos')
when 'estructura', 'structure'
success = preview.mostrar_vista_previa('estructura')
when 'estadisticas', 'stats', 'statistics'
success = preview.mostrar_vista_previa('estadisticas')
when 'rapido', 'quick', 'fast'
success = preview.mostrar_resumen_rapido
when 'help', '--help', '-h'
puts "📖 USO:"
puts " #{$0} [modo] [--dashboard RUTA]"
puts
puts "MODOS:"
puts " resumen, summary - Resumen completo (predeterminado)"
puts " hitos, milestones - Hitos detallados"
puts " estructura, structure - Estructura del dashboard"
puts " estadisticas, stats - Estadísticas detalladas"
puts " rapido, quick - Resumen rápido"
puts " help - Mostrar esta ayuda"
puts
puts "OPCIONES:"
puts " --dashboard RUTA - Ruta personalizada al archivo dashboard"
puts
puts "EJEMPLOS:"
puts " #{$0} resumen"
puts " #{$0} hitos"
puts " #{$0} rapido"
puts " #{$0} --dashboard /ruta/al/dashboard.html estructura"
puts
success = true
else
puts "❌ Modo '#{modo}' no reconocido"
puts " Use '#{$0} help' para ver la ayuda"
success = false
end
if success
puts "\n🚀 HERRAMIENTAS RELACIONADAS:"
puts " 1. limpiar.rb - Limpiar hitos duplicados"
puts " 2. agregar_hitos.rb - Agregar nuevos hitos"
puts " 3. actualizar_estados.rb - Actualizar estados de hitos"
puts " 4. verificar.rb - Verificar estructura del dashboard"
puts
puts "🔗 Dashboard: https://ns8.frlr.utn.edu.ar/bitacoras/p2601"
puts "📅 #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
puts "=" * 60
else
exit 1
end
rescue => e
puts "❌ Error: #{e.message}"
puts e.backtrace if ENV['DEBUG']
exit 1
end
end