535 lines
16 KiB
Ruby
Executable File
535 lines
16 KiB
Ruby
Executable File
#!/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
|