366 lines
11 KiB
Ruby
Executable File
366 lines
11 KiB
Ruby
Executable File
#!/usr/bin/env ruby
|
||
# frozen_string_literal: true
|
||
|
||
# tools/orquestador/dashboard/actualizar_estados.rb
|
||
# ================================================
|
||
# Herramienta para actualizar el estado de hitos en el dashboard P2601
|
||
# Filosofía: "Menos es más" - herramienta pequeña, específica y reutilizable
|
||
|
||
require 'fileutils'
|
||
|
||
class EstadoHitosManager
|
||
ESTADOS_VALIDOS = {
|
||
'pending' => { nombre: 'Pendiente', clase: 'pending', color: '#eab308' },
|
||
'in-progress' => { nombre: 'En Progreso', clase: 'in-progress', color: '#3b82f6' },
|
||
'completed' => { nombre: 'Completado', clase: 'completed', color: '#22c55e' },
|
||
'blocked' => { nombre: 'Bloqueado', clase: 'blocked', color: '#ef4444' },
|
||
'on-hold' => { nombre: 'En Espera', clase: 'on-hold', color: '#8b5cf6' }
|
||
}.freeze
|
||
|
||
def initialize(dashboard_path = nil)
|
||
@dashboard_path = dashboard_path || default_dashboard_path
|
||
@backup_dir = File.join(File.dirname(@dashboard_path), 'backups')
|
||
end
|
||
|
||
def default_dashboard_path
|
||
File.expand_path('../../../../dashboard/index.html', __FILE__)
|
||
end
|
||
|
||
def listar_hitos
|
||
puts "📋 LISTADO DE HITOS DISPONIBLES"
|
||
puts "=" * 50
|
||
|
||
unless File.exist?(@dashboard_path)
|
||
puts "❌ Error: No se encuentra el dashboard en #{@dashboard_path}"
|
||
return false
|
||
end
|
||
|
||
content = File.read(@dashboard_path)
|
||
hitos = extraer_hitos(content)
|
||
|
||
if hitos.empty?
|
||
puts "ℹ️ No se encontraron hitos en el dashboard"
|
||
return true
|
||
end
|
||
|
||
puts "🎯 Total de hitos encontrados: #{hitos.size}"
|
||
puts
|
||
|
||
hitos.each_with_index do |hito, i|
|
||
estado_info = ESTADOS_VALIDOS[hito[:estado]] || ESTADOS_VALIDOS['pending']
|
||
puts " #{i + 1}. [#{hito[:id]}] #{hito[:titulo]}"
|
||
puts " 📊 Estado actual: #{estado_info[:nombre]} (#{hito[:estado]})"
|
||
puts " 📅 Fecha: #{hito[:fecha]}"
|
||
puts " 🏷️ Tags: #{hito[:tags].join(', ')}" if hito[:tags].any?
|
||
puts
|
||
end
|
||
|
||
mostrar_resumen_estados(hitos)
|
||
true
|
||
end
|
||
|
||
def actualizar_estado(hito_id, nuevo_estado, fecha = nil)
|
||
puts "🔄 ACTUALIZANDO ESTADO DE HITO"
|
||
puts "=" * 50
|
||
|
||
# Validar estado
|
||
unless ESTADOS_VALIDOS.key?(nuevo_estado)
|
||
puts "❌ Error: Estado '#{nuevo_estado}' no válido"
|
||
puts " Estados válidos: #{ESTADOS_VALIDOS.keys.join(', ')}"
|
||
return false
|
||
end
|
||
|
||
unless File.exist?(@dashboard_path)
|
||
puts "❌ Error: No se encuentra el dashboard"
|
||
return false
|
||
end
|
||
|
||
# Crear backup
|
||
create_backup
|
||
|
||
# Leer contenido
|
||
puts "📖 Leyendo dashboard..."
|
||
content = File.read(@dashboard_path)
|
||
|
||
# Buscar y actualizar hito
|
||
puts "🔍 Buscando hito: #{hito_id}"
|
||
nuevo_contenido, hito_encontrado = actualizar_hito_en_contenido(content, hito_id, nuevo_estado, fecha)
|
||
|
||
unless hito_encontrado
|
||
puts "❌ Error: No se encontró el hito con ID '#{hito_id}'"
|
||
puts " Use 'listar' para ver los hitos disponibles"
|
||
return false
|
||
end
|
||
|
||
# Guardar cambios
|
||
puts "💾 Guardando dashboard actualizado..."
|
||
File.write(@dashboard_path, nuevo_contenido)
|
||
|
||
# Mostrar resultados
|
||
estado_info = ESTADOS_VALIDOS[nuevo_estado]
|
||
puts "\n✅ Estado actualizado exitosamente"
|
||
puts " • Hito: #{hito_id}"
|
||
puts " • Nuevo estado: #{estado_info[:nombre]}"
|
||
puts " • Fecha: #{fecha || 'Actualizada automáticamente'}"
|
||
|
||
true
|
||
end
|
||
|
||
def actualizar_multiples(hitos_estados)
|
||
puts "🔄 ACTUALIZANDO MÚLTIPLES HITOS"
|
||
puts "=" * 50
|
||
|
||
unless File.exist?(@dashboard_path)
|
||
puts "❌ Error: No se encuentra el dashboard"
|
||
return false
|
||
end
|
||
|
||
# Validar todos los estados primero
|
||
hitos_estados.each do |hito_id, estado|
|
||
unless ESTADOS_VALIDOS.key?(estado)
|
||
puts "❌ Error: Estado '#{estado}' no válido para hito '#{hito_id}'"
|
||
return false
|
||
end
|
||
end
|
||
|
||
# Crear backup
|
||
create_backup
|
||
|
||
# Leer contenido
|
||
puts "📖 Leyendo dashboard..."
|
||
content = File.read(@dashboard_path)
|
||
nuevo_contenido = content.dup
|
||
|
||
# Actualizar cada hito
|
||
resultados = {}
|
||
hitos_estados.each do |hito_id, estado|
|
||
puts "🔍 Actualizando hito: #{hito_id} -> #{estado}"
|
||
nuevo_contenido, encontrado = actualizar_hito_en_contenido(nuevo_contenido, hito_id, estado)
|
||
resultados[hito_id] = encontrado
|
||
end
|
||
|
||
# Guardar cambios
|
||
puts "💾 Guardando dashboard actualizado..."
|
||
File.write(@dashboard_path, nuevo_contenido)
|
||
|
||
# Mostrar resultados
|
||
puts "\n📊 RESULTADOS DE ACTUALIZACIÓN:"
|
||
exitos = resultados.values.count(true)
|
||
fallos = resultados.values.count(false)
|
||
|
||
resultados.each do |hito_id, exitoso|
|
||
emoji = exitoso ? '✅' : '❌'
|
||
puts " #{emoji} #{hito_id}: #{exitoso ? 'Actualizado' : 'No encontrado'}"
|
||
end
|
||
|
||
puts "\n📈 RESUMEN:"
|
||
puts " • Hitós actualizados exitosamente: #{exitos}"
|
||
puts " • Hitós no encontrados: #{fallos}"
|
||
|
||
exitos > 0
|
||
end
|
||
|
||
def mostrar_estados_disponibles
|
||
puts "🎨 ESTADOS DISPONIBLES"
|
||
puts "=" * 50
|
||
|
||
ESTADOS_VALIDOS.each do |codigo, info|
|
||
puts " • #{codigo}: #{info[:nombre]}"
|
||
puts " Clase CSS: #{info[:clase]}"
|
||
puts " Color: #{info[:color]}"
|
||
puts
|
||
end
|
||
|
||
true
|
||
end
|
||
|
||
private
|
||
|
||
def create_backup
|
||
FileUtils.mkdir_p(@backup_dir) unless Dir.exist?(@backup_dir)
|
||
|
||
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
||
backup_file = File.join(@backup_dir, "dashboard_estados_#{timestamp}.html")
|
||
|
||
puts "💾 Creando backup en: #{backup_file}"
|
||
FileUtils.cp(@dashboard_path, backup_file)
|
||
puts "✅ Backup creado"
|
||
end
|
||
|
||
def extraer_hitos(content)
|
||
hitos = []
|
||
|
||
# Buscar todos los 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>/)
|
||
|
||
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',
|
||
tags: hito.scan(/<span class="[^"]*tag[^"]*">([^<]+)<\/span>/).flatten,
|
||
html_completo: hito_html[0]
|
||
}
|
||
end
|
||
|
||
hitos.uniq { |h| h[:id] }
|
||
end
|
||
|
||
def actualizar_hito_en_contenido(content, hito_id, nuevo_estado, fecha = nil)
|
||
fecha_actual = fecha || Time.now.strftime('%Y-%m-%d')
|
||
estado_info = ESTADOS_VALIDOS[nuevo_estado]
|
||
|
||
# Patrón para encontrar el hito específico
|
||
patron_hito = /(<div class="tl-item">[\s\S]*?<span class="tl-id">#{Regexp.escape(hito_id)}<\/span>[\s\S]*?<\/div>\s*<\/div>\s*<\/div>)/
|
||
|
||
hito_encontrado = false
|
||
nuevo_contenido = content.gsub(patron_hito) do |hito_completo|
|
||
hito_encontrado = true
|
||
|
||
# Actualizar estado (clase del dot)
|
||
hito_actualizado = hito_completo.gsub(
|
||
/<div class="tl-dot ([^"]+)"><\/div>/,
|
||
"<div class=\"tl-dot #{nuevo_estado}\"></div>"
|
||
)
|
||
|
||
# Actualizar fecha
|
||
hito_actualizado = hito_actualizado.gsub(
|
||
/<span class="tl-date">([^<]+)<\/span>/,
|
||
"<span class=\"tl-date\">#{estado_info[:nombre]} - #{fecha_actual}</span>"
|
||
)
|
||
|
||
hito_actualizado
|
||
end
|
||
|
||
[nuevo_contenido, hito_encontrado]
|
||
end
|
||
|
||
def mostrar_resumen_estados(hitos)
|
||
return if hitos.empty?
|
||
|
||
puts "📊 RESUMEN DE ESTADOS"
|
||
puts "=" * 50
|
||
|
||
conteo_estados = Hash.new(0)
|
||
hitos.each { |h| conteo_estados[h[:estado]] += 1 }
|
||
|
||
total = hitos.size
|
||
conteo_estados.each do |estado, cantidad|
|
||
porcentaje = (cantidad * 100.0 / total).round(1)
|
||
estado_info = ESTADOS_VALIDOS[estado] || ESTADOS_VALIDOS['pending']
|
||
puts " • #{estado_info[:nombre]}: #{cantidad} (#{porcentaje}%)"
|
||
end
|
||
|
||
puts "\n💡 RECOMENDACIONES:"
|
||
|
||
if conteo_estados['pending'] && conteo_estados['pending'] > total * 0.7
|
||
puts " • Muchos hitos pendientes (#{conteo_estados['pending']}/#{total})"
|
||
puts " • Considerar avanzar algunos a 'en-progreso'"
|
||
end
|
||
|
||
if conteo_estados['completed'] && conteo_estados['completed'] < total * 0.2
|
||
puts " • Pocos hitos completados (#{conteo_estados['completed'] || 0}/#{total})"
|
||
puts " • Revisar progreso real del proyecto"
|
||
end
|
||
|
||
puts
|
||
end
|
||
end
|
||
|
||
# Interfaz de línea de comandos
|
||
if __FILE__ == $0
|
||
begin
|
||
manager = EstadoHitosManager.new
|
||
|
||
# Parsear argumentos
|
||
command = ARGV[0] || 'listar'
|
||
|
||
case command
|
||
when 'listar', 'list', 'ls'
|
||
success = manager.listar_hitos
|
||
when 'actualizar', 'update'
|
||
if ARGV.length < 3
|
||
puts "❌ Error: Se requieren ID del hito y nuevo estado"
|
||
puts " Uso: #{$0} actualizar ID_HITO NUEVO_ESTADO [FECHA]"
|
||
puts " Ejemplo: #{$0} actualizar BKP02 in-progress 2024-03-06"
|
||
success = false
|
||
else
|
||
hito_id = ARGV[1]
|
||
nuevo_estado = ARGV[2]
|
||
fecha = ARGV[3] if ARGV.length >= 4
|
||
success = manager.actualizar_estado(hito_id, nuevo_estado, fecha)
|
||
end
|
||
when 'multiples', 'batch'
|
||
if ARGV.length < 2 || ARGV[1..-1].length.odd?
|
||
puts "❌ Error: Se requieren pares ID-ESTADO"
|
||
puts " Uso: #{$0} multiples ID1 ESTADO1 ID2 ESTADO2 ..."
|
||
puts " Ejemplo: #{$0} multiples BKP02 in-progress DB02 completed"
|
||
success = false
|
||
else
|
||
hitos_estados = {}
|
||
args = ARGV[1..-1]
|
||
args.each_slice(2) do |hito_id, estado|
|
||
hitos_estados[hito_id] = estado
|
||
end
|
||
success = manager.actualizar_multiples(hitos_estados)
|
||
end
|
||
when 'estados', 'states'
|
||
success = manager.mostrar_estados_disponibles
|
||
when 'help', '--help', '-h'
|
||
puts "📖 USO:"
|
||
puts " #{$0} [comando] [argumentos...]"
|
||
puts
|
||
puts "COMANDOS:"
|
||
puts " listar, list - Listar hitos disponibles con sus estados (predeterminado)"
|
||
puts " actualizar, update ID ESTADO [FECHA] - Actualizar estado de un hito"
|
||
puts " multiples, batch ID1 ESTADO1 ID2 ESTADO2 ... - Actualizar múltiples hitos"
|
||
puts " estados, states - Mostrar estados disponibles"
|
||
puts " help - Mostrar esta ayuda"
|
||
puts
|
||
puts "ESTADOS DISPONIBLES:"
|
||
puts " pending - Pendiente (amarillo)"
|
||
puts " in-progress - En Progreso (azul)"
|
||
puts " completed - Completado (verde)"
|
||
puts " blocked - Bloqueado (rojo)"
|
||
puts " on-hold - En Espera (púrpura)"
|
||
puts
|
||
puts "EJEMPLOS:"
|
||
puts " #{$0} listar"
|
||
puts " #{$0} actualizar BKP02 in-progress"
|
||
puts " #{$0} actualizar DB02 completed 2024-03-06"
|
||
puts " #{$0} multiples BKP02 in-progress MON01 completed"
|
||
puts
|
||
success = true
|
||
else
|
||
puts "❌ Comando '#{command}' no reconocido"
|
||
puts " Use '#{$0} help' para ver la ayuda"
|
||
success = false
|
||
end
|
||
|
||
if success
|
||
puts "\n🚀 PRÓXIMOS PASOS SUGERIDOS:"
|
||
puts " 1. Revisar dashboard actualizado"
|
||
puts " 2. Verificar que los estados se muestren correctamente"
|
||
puts " 3. Actualizar bitácora del proyecto con los cambios"
|
||
|
||
puts "\n📅 #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||
puts "=" * 50
|
||
else
|
||
exit 1
|
||
end
|
||
rescue => e
|
||
puts "❌ Error: #{e.message}"
|
||
puts e.backtrace if ENV['DEBUG']
|
||
exit 1
|
||
end
|
||
end
|