Reestructuración modular integral de servicios, reorganización de gestión en docs/ y limpieza profunda del repositorio
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user