Files

508 lines
18 KiB
Ruby

# frozen_string_literal: true
# adn/tools/subcomando_plan.rb — Subcomando para seguimiento y gestión del plan ADN
# ==============================================================================
# Este subcomando proporciona herramientas para el seguimiento, actualización y
# reporte del plan integral de integración ADN con dtic-BITACORAs.
#
# Características principales:
# • Parseo y análisis del archivo de plan Markdown
# • Actualización del estado de tareas (pendiente/completado)
# • Generación de reportes de progreso en múltiples formatos
# • Validación de integridad del plan
# • Exportación de datos de progreso
#
# Comandos disponibles:
# • adn plan progreso [OPCIONES] — Mostrar progreso actual del plan
# • adn plan actualizar [TAREAS] — Marcar tareas como completadas
# • adn plan reporte [--formato] — Generar reporte detallado
# • adn plan validar — Validar integridad del plan
# • adn plan exportar [--output] — Exportar datos de progreso
# • adn plan ayuda — Mostrar ayuda completa
#
# Uso:
# ./adn/tools/run plan [SUBCOMANDO] [OPCIONES]
require 'optparse'
require 'date'
require 'time'
require 'json'
require 'yaml'
# Cargar módulo PlanParser
begin
require_relative '../parsers/plan'
rescue LoadError => e
puts "⚠ Advertencia: No se pudo cargar PlanParser: #{e.message}"
puts " Asegúrate de que adn/tools/parsers/plan.rb existe"
end
module ADN
class SubcomandoPlan
require_relative '../core/colores'
# Ruta por defecto del archivo de plan
DEFAULT_PLAN_PATH = File.expand_path('../../docs/plan/260308-1200_Plan_Integral_ADN_BITACORAs.md', __dir__)
# Inicializa el subcomando con argumentos y logger
#
# @param args [Array<String>] Argumentos de línea de comandos
# @param logger [ADNLogger] Instancia de logger para registro
def initialize(args, logger)
@args = args
@logger = logger
@plan_path = DEFAULT_PLAN_PATH
end
# Ejecuta el subcomando según los argumentos proporcionados
def ejecutar
if @args.empty? || @args[0] == 'ayuda' || @args[0] == 'help' || @args[0] == '-h' || @args[0] == '--help'
mostrar_ayuda
return
end
subcomando = @args[0]
sub_args = @args[1..-1] || []
case subcomando
when 'progreso'
comando_progreso(sub_args)
when 'actualizar'
comando_actualizar(sub_args)
when 'reporte'
comando_reporte(sub_args)
when 'validar'
comando_validar(sub_args)
when 'exportar'
comando_exportar(sub_args)
else
@logger.error("Subcomando desconocido: #{subcomando}")
puts "#{Color::RED}✗ Subcomando desconocido: '#{subcomando}'#{Color::RESET}"
puts ""
mostrar_ayuda
exit 1
end
rescue Interrupt
puts "\n#{Color::YELLOW}⚠ Operación interrumpida por el usuario#{Color::RESET}"
@logger.info("Operación de plan interrumpida por usuario")
exit 130
rescue => e
@logger.error("Error en operación de plan: #{e.message}", { backtrace: e.backtrace })
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
puts "#{Color::DIM}#{e.backtrace.first}#{Color::RESET}" if ENV['ADN_DEBUG']
exit 1
end
private
# Comando: progreso - Mostrar progreso actual del plan
def comando_progreso(args)
opts = { detallado: false, formato: 'texto' }
parser = OptionParser.new do |opt|
opt.banner = "Uso: adn plan progreso [OPCIONES]"
opt.on("--detallado", "Mostrar progreso detallado por fase y semana") do
opts[:detallado] = true
end
opt.on("--formato FORMATO", "Formato de salida (texto, json, markdown)") do |f|
opts[:formato] = f.downcase
end
opt.on("--plan RUTA", "Ruta al archivo de plan (default: #{File.basename(@plan_path)})") do |ruta|
@plan_path = File.expand_path(ruta)
end
opt.on("-h", "--help", "Mostrar ayuda") do
puts opt
exit
end
end
begin
parser.parse!(args)
rescue OptionParser::InvalidOption => e
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
puts parser
exit 1
end
puts "#{Color::CYAN}📊 Analizando progreso del plan ADN BITACORAs...#{Color::RESET}"
puts " #{Color::DIM}Archivo: #{@plan_path}#{Color::RESET}"
puts ""
begin
parser_obj = Plan::PlanParser.new(@plan_path, @logger)
parser_obj.parse_file
if opts[:formato] == 'json'
puts parser_obj.progress_report(format: :json)
elsif opts[:formato] == 'markdown'
puts parser_obj.progress_report(format: :markdown)
else
# Formato texto (default)
puts parser_obj.progress_report(format: :text)
if opts[:detallado]
puts ""
puts "#{Color::BOLD}📈 DETALLE POR FASES Y SEMANAS:#{Color::RESET}"
stats = parser_obj.progress_stats
stats[:phases].each do |phase|
puts ""
puts " #{Color::CYAN}FASE #{phase[:number]}: #{phase[:name]}#{Color::RESET}"
puts " Progreso: #{phase[:percentage]}% (#{phase[:completed]}/#{phase[:total]} tareas)"
# Mostrar tareas de esta fase
phase_tasks = parser_obj.tasks_by_phase(phase[:number])
completed_tasks = phase_tasks.select { |t| t[:completed] }
pending_tasks = phase_tasks.reject { |t| t[:completed] }
if completed_tasks.any?
puts " #{Color::GREEN}✓ Completadas:#{Color::RESET}"
completed_tasks.first(3).each do |task|
puts " • #{task[:id]}: #{task[:description]}"
end
if completed_tasks.size > 3
puts " ... y #{completed_tasks.size - 3} más"
end
end
if pending_tasks.any?
puts " #{Color::YELLOW}⏳ Pendientes:#{Color::RESET}"
pending_tasks.first(3).each do |task|
puts " • #{task[:id]}: #{task[:description]}"
end
if pending_tasks.size > 3
puts " ... y #{pending_tasks.size - 3} más"
end
end
end
end
end
rescue => e
puts "#{Color::RED}✗ Error analizando el plan: #{e.message}#{Color::RESET}"
puts " #{Color::DIM}Asegúrate de que el archivo existe y tiene el formato correcto#{Color::RESET}"
exit 1
end
end
# Comando: actualizar - Marcar tareas como completadas
def comando_actualizar(args)
if args.empty?
puts "#{Color::YELLOW}⚠ Se requiere al menos una tarea para actualizar#{Color::RESET}"
puts " Uso: adn plan actualizar TAREA1 [TAREA2 ...] [OPCIONES]"
exit 1
end
opts = { notas: nil, guardar: true }
parser = OptionParser.new do |opt|
opt.banner = "Uso: adn plan actualizar TAREA1 [TAREA2 ...] [OPCIONES]"
opt.on("--notas TEXTO", "Agregar notas a las tareas actualizadas") do |texto|
opts[:notas] = texto
end
opt.on("--no-guardar", "No guardar cambios en el archivo (solo simular)") do
opts[:guardar] = false
end
opt.on("--plan RUTA", "Ruta al archivo de plan") do |ruta|
@plan_path = File.expand_path(ruta)
end
opt.on("-h", "--help", "Mostrar ayuda") do
puts opt
exit
end
end
# Separar tareas de opciones
task_args = []
begin
parser.parse!(args)
task_args = args # Los argumentos restantes son las tareas
rescue OptionParser::InvalidOption => e
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
puts parser
exit 1
end
if task_args.empty?
puts "#{Color::YELLOW}⚠ No se especificaron tareas para actualizar#{Color::RESET}"
puts " Ejemplo: adn plan actualizar 1.1 1.2 2.3"
exit 1
end
puts "#{Color::CYAN}🔄 Actualizando tareas del plan ADN BITACORAs...#{Color::RESET}"
puts " #{Color::DIM}Tareas: #{task_args.join(', ')}#{Color::RESET}"
puts " #{Color::DIM}Notas: #{opts[:notas] || '(ninguna)'}#{Color::RESET}"
puts " #{Color::DIM}Guardar: #{opts[:guardar]}#{Color::RESET}"
puts ""
begin
parser_obj = Plan::PlanParser.new(@plan_path, @logger)
parser_obj.parse_file
resultados = parser_obj.complete_tasks(task_args, opts[:notas])
if resultados[:updated].any?
puts "#{Color::GREEN}✅ Tareas actualizadas exitosamente:#{Color::RESET}"
resultados[:updated].each do |task_id|
task = parser_obj.find_task_by_id(task_id)
puts " • #{task_id}: #{task[:description]}"
end
if opts[:guardar]
if parser_obj.save_changes
puts ""
puts "#{Color::GREEN}✓ Cambios guardados en #{@plan_path}#{Color::RESET}"
else
puts ""
puts "#{Color::YELLOW}⚠ No se realizaron cambios o no se pudieron guardar#{Color::RESET}"
end
else
puts ""
puts "#{Color::YELLOW}⚠ Modo simulación - cambios no guardados#{Color::RESET}"
puts " Usa --guardar para persistir los cambios"
end
end
if resultados[:failed].any?
puts ""
puts "#{Color::YELLOW}⚠ Tareas no encontradas:#{Color::RESET}"
resultados[:failed].each do |task_id|
puts " • #{task_id}"
end
end
# Mostrar progreso actualizado
if resultados[:updated].any?
puts ""
stats = parser_obj.progress_stats
puts " #{Color::BOLD}Progreso actualizado: #{stats[:overall_percentage]}%#{Color::RESET}"
puts " (#{stats[:completed_tasks]}/#{stats[:total_tasks]} tareas completadas)"
end
rescue => e
puts "#{Color::RED}✗ Error actualizando tareas: #{e.message}#{Color::RESET}"
exit 1
end
end
# Comando: reporte - Generar reporte detallado
def comando_reporte(args)
opts = { formato: 'texto', output: nil }
parser = OptionParser.new do |opt|
opt.banner = "Uso: adn plan reporte [OPCIONES]"
opt.on("--formato FORMATO", "Formato del reporte (texto, json, markdown)") do |f|
opts[:formato] = f.downcase
end
opt.on("--output ARCHIVO", "Guardar reporte en archivo (en lugar de mostrar)") do |archivo|
opts[:output] = archivo
end
opt.on("--plan RUTA", "Ruta al archivo de plan") do |ruta|
@plan_path = File.expand_path(ruta)
end
opt.on("-h", "--help", "Mostrar ayuda") do
puts opt
exit
end
end
begin
parser.parse!(args)
rescue OptionParser::InvalidOption => e
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
puts parser
exit 1
end
puts "#{Color::CYAN}📋 Generando reporte del plan ADN BITACORAs...#{Color::RESET}"
puts " #{Color::DIM}Formato: #{opts[:formato]}#{Color::RESET}"
puts " #{Color::DIM}Archivo: #{@plan_path}#{Color::RESET}"
puts ""
begin
parser_obj = Plan::PlanParser.new(@plan_path, @logger)
parser_obj.parse_file
reporte = parser_obj.progress_report(format: opts[:formato].to_sym)
if opts[:output]
File.write(opts[:output], reporte, encoding: 'UTF-8')
puts "#{Color::GREEN}✅ Reporte guardado en: #{opts[:output]}#{Color::RESET}"
puts " #{Color::DIM}Tamaño: #{reporte.bytesize} bytes#{Color::RESET}"
else
puts reporte
end
rescue => e
puts "#{Color::RED}✗ Error generando reporte: #{e.message}#{Color::RESET}"
exit 1
end
end
# Comando: validar - Validar integridad del plan
def comando_validar(args)
opts = { reparar: false }
parser = OptionParser.new do |opt|
opt.banner = "Uso: adn plan validar [OPCIONES]"
opt.on("--reparar", "Intentar reparar problemas automáticamente") do
opts[:reparar] = true
end
opt.on("--plan RUTA", "Ruta al archivo de plan") do |ruta|
@plan_path = File.expand_path(ruta)
end
opt.on("-h", "--help", "Mostrar ayuda") do
puts opt
exit
end
end
begin
parser.parse!(args)
rescue OptionParser::InvalidOption => e
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
puts parser
exit 1
end
puts "#{Color::CYAN}🔍 Validando integridad del plan ADN BITACORAs...#{Color::RESET}"
puts " #{Color::DIM}Archivo: #{@plan_path}#{Color::RESET}"
puts " #{Color::DIM}Reparar: #{opts[:reparar]}#{Color::RESET}"
puts ""
begin
parser_obj = Plan::PlanParser.new(@plan_path, @logger)
parser_obj.parse_file
resultados = parser_obj.validate_plan
if resultados[:valid]
puts "#{Color::GREEN}✅ Plan válido#{Color::RESET}"
puts " • #{resultados[:tasks_count]} tareas detectadas"
puts " • #{resultados[:phases_count]} fases detectadas"
else
puts "#{Color::RED}✗ Plan contiene errores:#{Color::RESET}"
resultados[:errors].each do |error|
puts " • #{error}"
end
end
if resultados[:warnings].any?
puts ""
puts "#{Color::YELLOW}⚠ Advertencias encontradas:#{Color::RESET}"
resultados[:warnings].each do |warning|
puts " • #{warning}"
end
end
if opts[:reparar] && (!resultados[:valid] || resultados[:warnings].any?)
puts ""
puts "#{Color::CYAN}🔄 Intentando reparar automáticamente...#{Color::RESET}"
# Aquí se podrían implementar reparaciones automáticas
puts " #{Color::DIM}(Reparación automática no implementada aún)#{Color::RESET}"
end
rescue => e
puts "#{Color::RED}✗ Error validando el plan: #{e.message}#{Color::RESET}"
exit 1
end
end
# Comando: exportar - Exportar datos de progreso
def comando_exportar(args)
opts = { formato: 'json', output: nil }
parser = OptionParser.new do |opt|
opt.banner = "Uso: adn plan exportar [OPCIONES]"
opt.on("--formato FORMATO", "Formato de exportación (json, yaml)") do |f|
opts[:formato] = f.downcase
end
opt.on("--output ARCHIVO", "Archivo de salida (default: progreso_YYYYMMDD.json)") do |archivo|
opts[:output] = archivo
end
opt.on("--plan RUTA", "Ruta al archivo de plan") do |ruta|
@plan_path = File.expand_path(ruta)
end
opt.on("-h", "--help", "Mostrar ayuda") do
puts opt
exit
end
end
begin
parser.parse!(args)
rescue OptionParser::InvalidOption => e
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
puts parser
exit 1
end
puts "#{Color::CYAN}💾 Exportando datos de progreso del plan...#{Color::RESET}"
puts " #{Color::DIM}Formato: #{opts[:formato]}#{Color::RESET}"
puts " #{Color::DIM}Archivo: #{@plan_path}#{Color::RESET}"
puts ""
begin
parser_obj = Plan::PlanParser.new(@plan_path, @logger)
parser_obj.parse_file
if opts[:formato] == 'yaml'
datos = {
metadata: parser_obj.instance_variable_get(:@metadata),
progress: parser_obj.progress_stats,
tasks: parser_obj.instance_variable_get(:@tasks).map { |t| t.reject { |k| k == :line_number } },
phases: parser_obj.instance_variable_get(:@phases)
}
contenido = YAML.dump(datos)
extension = '.yaml'
else
contenido = parser_obj.export_progress
extension = '.json'
end
output_file = opts[:output] || "progreso_#{Time.now.strftime('%Y%m%d')}#{extension}"
File.write(output_file, contenido, encoding: 'UTF-8')
puts "#{Color::GREEN}✅ Datos exportados exitosamente:#{Color::RESET}"
puts " • Archivo: #{output_file}"
puts " • Tamaño: #{contenido.bytesize} bytes"
puts " • Tareas: #{parser_obj.instance_variable_get(:@tasks).size}"
puts " • Fases: #{parser_obj.instance_variable_get(:@phases).size}"
rescue => e
puts "#{Color::RED}✗ Error exportando datos: #{e.message}#{Color::RESET}"
exit 1
end
end
# Muestra la ayuda general del subcomando
def mostrar_ayuda
puts "#{Color::BOLD}Uso: ./adn/tools/run plan [SUBCOMANDO] [OPCIONES]#{Color::RESET}"
puts ""
puts "#{Color::BOLD}Subcomandos disponibles:#{Color::RESET}"
puts ""
puts " #{Color::CYAN}progreso [OPCIONES]#{Color::RESET} Mostrar progreso actual del plan"
puts " #{Color::CYAN}actualizar TAREAS [OPCIONES]#{Color::RESET} Marcar tareas como completadas"
puts " #{Color::CYAN}reporte [OPCIONES]#{Color::RESET} Generar reporte detallado"
puts " #{Color::CYAN}validar [OPCIONES]#{Color::RESET} Validar integridad del plan"
puts " #{Color::CYAN}exportar [OPCIONES]#{Color::RESET} Exportar datos de progreso"
puts " #{Color::CYAN}ayuda#{Color::RESET} Mostrar esta ayuda"
puts ""
puts "#{Color::BOLD}Opciones comunes:#{Color::RESET}"
puts " #{Color::DIM}--plan RUTA#{Color::RESET} Ruta al archivo de plan"
puts " #{Color::DIM}--formato FORMATO#{Color::RESET} Formato de salida (texto, json, markdown, yaml)"
puts " #{Color::DIM}--output ARCHIVO#{Color::RESET} Guardar salida en archivo"
puts ""
puts "#{Color::BOLD}Ejemplos:#{Color::RESET}"
puts " #{Color::DIM}./adn/tools/run plan progreso --detallado#{Color::RESET}"
puts " #{Color::DIM}./adn/tools/run plan actualizar 1.1 1.2 2.3 --notas \"Implementado\"#{Color::RESET}"
puts " #{Color::DIM}./adn/tools/run plan reporte --formato markdown --output reporte.md#{Color::RESET}"
puts " #{Color::DIM}./adn/tools/run plan validar --reparar#{Color::RESET}"
puts " #{Color::DIM}./adn/tools/run plan exportar --formato json --output progreso.json#{Color::RESET}"
puts ""
puts "#{Color::YELLOW}⚠ Archivo de plan por defecto:#{Color::RESET} #{File.basename(DEFAULT_PLAN_PATH)}"
puts " #{Color::DIM}#{DEFAULT_PLAN_PATH}#{Color::RESET}"
end
end
end