#!/usr/bin/env ruby # frozen_string_literal: true # tools/orquestador/lib/estados/manager.rb # ======================================== # Clase para manejo centralizado de estados de hitos del dashboard P2601 # Filosofía: "Menos es más" - código reutilizable y modular require_relative '../dashboard_core' module DashboardCore class EstadosManager < Base attr_reader :estados_cache def initialize(dashboard_path = nil) super(dashboard_path) @estados_cache = nil end # Métodos principales para gestión de estados def cargar_estados(force_reload = false) return @estados_cache if @estados_cache && !force_reload hitos = cargar_hitos @estados_cache = analizar_estados(hitos) @estados_cache end def listar_estados_disponibles ESTADOS_VALIDOS.map do |codigo, info| { codigo: codigo, nombre: info[:nombre], clase: info[:clase], color: info[:color], descripcion: descripcion_estado(codigo) } end end def actualizar_estado_hito(id_hito, nuevo_estado, fecha = nil) # Validar estado unless Validator.validar_estado(nuevo_estado) raise "Estado '#{nuevo_estado}' no válido. Estados disponibles: #{ESTADOS_VALIDOS.keys.join(', ')}" end # Verificar que el hito exista hito = buscar_hito_por_id(id_hito) unless hito raise "Hito #{id_hito} no encontrado" end # No hacer nada si el estado ya es el mismo if hito[:estado] == nuevo_estado puts "ℹ️ El hito #{id_hito} ya tiene estado '#{nuevo_estado}'" return true end # Crear backup crear_backup('actualizar_estado') # Leer contenido actual contenido = leer_dashboard # Actualizar estado en el contenido fecha_actual = fecha || Time.now.strftime('%Y-%m-%d') estado_info = ESTADOS_VALIDOS[nuevo_estado] nuevo_contenido, encontrado = actualizar_hito_en_contenido( contenido, id_hito, nuevo_estado, fecha_actual ) unless encontrado raise "No se pudo encontrar el hito #{id_hito} para actualizar estado" end # Guardar cambios guardar_dashboard(nuevo_contenido) # Actualizar cache @estados_cache = nil cargar_estados(true) # Registrar cambio registrar_cambio_estado(id_hito, hito[:estado], nuevo_estado, fecha_actual) puts "✅ Estado del hito #{id_hito} actualizado: #{hito[:estado]} → #{nuevo_estado}" true end def actualizar_estados_multiples(cambios) # Validar todos los cambios primero cambios.each do |id_hito, nuevo_estado| unless Validator.validar_estado(nuevo_estado) raise "Estado '#{nuevo_estado}' no válido para hito #{id_hito}" end end # Crear backup crear_backup('actualizar_estados_multiples') # Leer contenido actual contenido = leer_dashboard nuevo_contenido = contenido.dup # Aplicar cada cambio resultados = {} cambios.each do |id_hito, nuevo_estado| puts "🔍 Actualizando hito: #{id_hito} → #{nuevo_estado}" # Verificar que el hito exista hito = buscar_hito_por_id(id_hito) if hito # Actualizar en el contenido fecha_actual = Time.now.strftime('%Y-%m-%d') nuevo_contenido, encontrado = actualizar_hito_en_contenido( nuevo_contenido, id_hito, nuevo_estado, fecha_actual ) if encontrado && hito[:estado] != nuevo_estado resultados[id_hito] = { exito: true, anterior: hito[:estado], nuevo: nuevo_estado } registrar_cambio_estado(id_hito, hito[:estado], nuevo_estado, fecha_actual) else resultados[id_hito] = { exito: false, error: 'No encontrado o estado igual' } end else resultados[id_hito] = { exito: false, error: 'Hito no encontrado' } end end # Guardar cambios si hubo actualizaciones exitosas exitos = resultados.values.count { |r| r[:exito] } if exitos > 0 guardar_dashboard(nuevo_contenido) @estados_cache = nil cargar_estados(true) end # Mostrar resultados mostrar_resultados_actualizacion(resultados) exitos > 0 end def avanzar_estado_hito(id_hito) # Obtener hito actual hito = buscar_hito_por_id(id_hito) unless hito raise "Hito #{id_hito} no encontrado" end # Determinar siguiente estado según flujo de trabajo siguiente_estado = siguiente_estado_en_flujo(hito[:estado]) if siguiente_estado actualizar_estado_hito(id_hito, siguiente_estado) else puts "ℹ️ El hito #{id_hito} ya está en el estado final del flujo" false end end def retroceder_estado_hito(id_hito) # Obtener hito actual hito = buscar_hito_por_id(id_hito) unless hito raise "Hito #{id_hito} no encontrado" end # Determinar estado anterior según flujo de trabajo estado_anterior = estado_anterior_en_flujo(hito[:estado]) if estado_anterior actualizar_estado_hito(id_hito, estado_anterior) else puts "ℹ️ El hito #{id_hito} ya está en el estado inicial del flujo" false end end def reiniciar_estado_hito(id_hito) actualizar_estado_hito(id_hito, 'pending') end def completar_hito(id_hito, fecha = nil) actualizar_estado_hito(id_hito, 'completed', fecha) end def bloquear_hito(id_hito, fecha = nil) actualizar_estado_hito(id_hito, 'blocked', fecha) end def poner_en_espera_hito(id_hito, fecha = nil) actualizar_estado_hito(id_hito, 'on-hold', fecha) end def iniciar_hito(id_hito, fecha = nil) actualizar_estado_hito(id_hito, 'in-progress', fecha) end # Métodos de consulta y análisis def estadisticas_estados estados = cargar_estados { total_hitos: estados[:total_hitos], distribucion: estados[:distribucion], porcentajes: estados[:porcentajes], tendencia: calcular_tendencia(estados), ultima_actualizacion: Time.now.strftime('%Y-%m-%d %H:%M:%S') } end def reporte_estados estados = cargar_estados reporte = "📊 REPORTE DE ESTADOS DE HITOS\n" reporte += "=" * 60 + "\n" reporte += "Total de hitos: #{estados[:total_hitos]}\n\n" reporte += "📈 DISTRIBUCIÓN POR ESTADO:\n" estados[:distribucion].each do |estado, cantidad| porcentaje = estados[:porcentajes][estado] estado_info = ESTADOS_VALIDOS[estado] || ESTADOS_VALIDOS['pending'] reporte += " • #{estado_info[:nombre]}: #{cantidad} (#{porcentaje}%)\n" end # Análisis de progreso completados = estados[:distribucion]['completed'] || 0 en_progreso = estados[:distribucion]['in-progress'] || 0 total = estados[:total_hitos] if total > 0 progreso_total = ((completados + (en_progreso * 0.5)) / total * 100).round(1) reporte += "\n🎯 PROGRESO GENERAL DEL PROYECTO:\n" reporte += " • Progreso estimado: #{progreso_total}%\n" reporte += " • Hitos completados: #{completados}/#{total}\n" reporte += " • Hitos en progreso: #{en_progreso}/#{total}\n" end # Recomendaciones reporte += "\n💡 RECOMENDACIONES:\n" if (estados[:distribucion]['pending'] || 0) > total * 0.7 reporte += " • Muchos hitos pendientes (#{estados[:distribucion]['pending']}/#{total})\n" reporte += " • Considerar iniciar más hitos\n" end if (estados[:distribucion]['blocked'] || 0) > 0 reporte += " • Hay hitos bloqueados (#{estados[:distribucion]['blocked']})\n" reporte += " • Revisar dependencias y obstáculos\n" end if (estados[:distribucion]['on-hold'] || 0) > 0 reporte += " • Hay hitos en espera (#{estados[:distribucion]['on-hold']})\n" reporte += " • Evaluar prioridades y recursos\n" end reporte end def hitos_por_estado(estado) hitos = cargar_hitos hitos.select { |h| h[:estado] == estado } end def hitos_pendientes hitos_por_estado('pending') end def hitos_en_progreso hitos_por_estado('in-progress') end def hitos_completados hitos_por_estado('completed') end def hitos_bloqueados hitos_por_estado('blocked') end def hitos_en_espera hitos_por_estado('on-hold') end def progreso_por_fase hitos = cargar_hitos progreso_por_fase = {} hitos.group_by do |hito| info = HitoIdGenerator.parsear_id(hito[:id]) info ? info[:fase] : 'sin_fase' end.each do |fase, hitos_fase| total = hitos_fase.size completados = hitos_fase.count { |h| h[:estado] == 'completed' } en_progreso = hitos_fase.count { |h| h[:estado] == 'in-progress' } progreso = total > 0 ? ((completados + (en_progreso * 0.5)) / total * 100).round(1) : 0 progreso_por_fase[fase] = { total: total, completados: completados, en_progreso: en_progreso, progreso: progreso } end progreso_por_fase end private def cargar_hitos contenido = leer_dashboard extraer_hitos(contenido) end def buscar_hito_por_id(id_hito) hitos = cargar_hitos hitos.find { |h| h[:id] == id_hito } end def analizar_estados(hitos) total_hitos = hitos.size distribucion = Hash.new(0) hitos.each { |h| distribucion[h[:estado]] += 1 } porcentajes = {} distribucion.each do |estado, cantidad| porcentajes[estado] = total_hitos > 0 ? (cantidad * 100.0 / total_hitos).round(1) : 0 end { total_hitos: total_hitos, distribucion: distribucion, porcentajes: porcentajes, hitos: hitos } end def descripcion_estado(codigo_estado) case codigo_estado when 'pending' "Hito pendiente de iniciar. Color amarillo." when 'in-progress' "Hito en ejecución activa. Color azul." when 'completed' "Hito finalizado exitosamente. Color verde." when 'blocked' "Hito bloqueado por dependencias u obstáculos. Color rojo." when 'on-hold' "Hito en espera por priorización o recursos. Color púrpura." else "Estado desconocido." end end def siguiente_estado_en_flujo(estado_actual) flujo = { 'pending' => 'in-progress', 'in-progress' => 'completed', 'completed' => nil, # Estado final 'blocked' => 'in-progress', # Desbloqueado 'on-hold' => 'in-progress' # Reanudado } flujo[estado_actual] end def estado_anterior_en_flujo(estado_actual) flujo_inverso = { 'in-progress' => 'pending', 'completed' => 'in-progress', 'blocked' => 'in-progress', 'on-hold' => 'in-progress' } flujo_inverso[estado_actual] end def registrar_cambio_estado(id_hito, estado_anterior, estado_nuevo, fecha) # Podría guardarse en un log o base de datos log_entry = { id_hito: id_hito, estado_anterior: estado_anterior, estado_nuevo: estado_nuevo, fecha: fecha, timestamp: Time.now.to_i } # Por ahora solo imprimimos para debug debug("Cambio de estado registrado: #{log_entry}") end def mostrar_resultados_actualizacion(resultados) puts "\n📊 RESULTADOS DE ACTUALIZACIÓN DE ESTADOS" puts "=" * 60 exitos = resultados.values.count { |r| r[:exito] } fallos = resultados.values.count { |r| !r[:exito] } resultados.each do |id_hito, resultado| if resultado[:exito] puts "✅ #{id_hito}: #{resultado[:anterior]} → #{resultado[:nuevo]}" else puts "❌ #{id_hito}: #{resultado[:error]}" end end puts "\n📈 RESUMEN:" puts " • Hitós actualizados exitosamente: #{exitos}" puts " • Hitós con errores: #{fallos}" end def calcular_tendencia(estados) # Simple análisis de tendencia (podría mejorarse con histórico) completados = estados[:distribucion]['completed'] || 0 en_progreso = estados[:distribucion]['in-progress'] || 0 total = estados[:total_hitos] if total == 0 return 'sin_datos' end porcentaje_completados = (completados * 100.0 / total).round(1) if porcentaje_completados > 70 'avanzado' elsif porcentaje_completados > 30 'en_progreso' elsif en_progreso > 0 'iniciando' else 'pendiente' end end end end