Reestructuración modular integral de servicios, reorganización de gestión en docs/ y limpieza profunda del repositorio
This commit is contained in:
Executable
+534
@@ -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
|
||||
Reference in New Issue
Block a user