#!/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(/
([\s\S]*?)<\/div>\s*<\/div>\s*<\/div>/m) do |hito_html| hito = hito_html[0] # Extraer información id_match = hito.match(/([^<]+)<\/span>/) titulo_match = hito.match(/
([^<]+)<\/div>/) estado_match = hito.match(/
<\/div>/) fecha_match = hito.match(/([^<]+)<\/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>/).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 = /(
[\s\S]*?#{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>/, "
" ) # Actualizar fecha hito_actualizado = hito_actualizado.gsub( /([^<]+)<\/span>/, "#{estado_info[:nombre]} - #{fecha_actual}" ) 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