Files
dtic-DIIAA/tools/orquestador/lib/estados/manager.rb
T

441 lines
13 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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