module ADN module SubcomandoDBHito # Comando: hito:listar [PROYECTO] def comando_hito_listar(args) if args.empty? puts "#{Color::YELLOW}Uso: adn db hito:listar (ej: P2605)#{Color::RESET}" return end codigo = args[0] puts "#{Color::CYAN}📋 Listando hitos del proyecto #{codigo.upcase}...#{Color::RESET}" BitacorasDB::BitacoraDB.with_connection do |db| begin hitos = db.list_hitos(codigo) if hitos.empty? puts "#{Color::YELLOW}⚠ No se encontraron hitos o el proyecto no existe.#{Color::RESET}" return end puts "" puts "#{Color::BOLD}#{"Hito".ljust(8)} #{"Fase".ljust(6)} #{"Estado".ljust(8)} #{"Hrs P/R".ljust(10)} #{"Título"}#{Color::RESET}" puts "#{"─" * 8} #{"─" * 6} #{"─" * 8} #{"─" * 10} #{"─" * 40}" hitos.each do |h| hrs = "#{h['horas_presencial']}/#{h['horas_remoto']}" puts "#{h['id_hito'].ljust(8)} #{h['fase_num'].to_s.ljust(6)} #{h['estado'].ljust(8)} #{hrs.ljust(10)} #{h['titulo']}" end puts "\nTotal: #{hitos.size} hitos" rescue => e puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}" end end end # Comando: hito:ordenar [--aplicar FILE] def comando_hito_ordenar(args) codigo = args.shift if !codigo puts "#{Color::YELLOW}Uso: adn db hito:ordenar [--aplicar /ruta/al/archivo.txt]#{Color::RESET}" return end aplicar_path = nil if args[0] == '--aplicar' && args[1] aplicar_path = args[1] end BitacorasDB::BitacoraDB.with_connection do |db| begin hitos = db.list_hitos(codigo) if hitos.empty? puts "#{Color::YELLOW}⚠ No se encontraron hitos para #{codigo.upcase}.#{Color::RESET}" return end if aplicar_path unless File.exist?(aplicar_path) puts "#{Color::RED}✗ No se encontró el archivo: #{aplicar_path}#{Color::RESET}" return end # Procesar el archivo y aplicarrenombramientos lógicos "Fase.Grupo.Paso" puts "#{Color::CYAN}📋 Aplicando ordenamiento desde #{aplicar_path}...#{Color::RESET}" lineas = File.readlines(aplicar_path).map(&:strip).reject { |l| l.empty? || l.start_with?('#') } # Agrupar por fase según el orden de las líneas hitos_por_fase = Hash.new { |h, k| h[k] = [] } lineas.each do |linea| if linea =~ /^(\d+):\s*\[.+?\]\s*(.+)$/ id_db = $1.to_i hito = hitos.find { |h| h['id'] == id_db } hitos_por_fase[hito['fase_num']] << hito if hito end end # Iniciar renombrado secuencial hitos_por_fase.each do |fase, hitos_fase| grupo = 1 letra = 65 # 'A' hitos_fase.each do |h| nuevo_id = "#{fase}.#{grupo}.#{letra.chr}" if h['id_hito'] != nuevo_id db.rename_hito_id(codigo, h['id_hito'], nuevo_id) puts " #{Color::GREEN}✔ #{h['id_hito'].ljust(8)} → #{nuevo_id.ljust(8)}#{Color::RESET} | #{h['titulo']}" else puts " #{Color::DIM}• #{nuevo_id.ljust(8)} (Sin cambios) | #{h['titulo']}#{Color::RESET}" end letra += 1 if letra > 67 # 'C' letra = 65 grupo += 1 end end end puts "#{Color::GREEN}✅ Ordenamiento aplicado exitosamente a #{codigo.upcase}.#{Color::RESET}" else # Generar archivo temporal para reordenamiento (Swarm-Ready) tmp_file = "/tmp/adn_ordenar_#{codigo.downcase}.txt" File.open(tmp_file, 'w') do |f| f.puts "# ===========================================================================" f.puts "# MODO ORDENAMIENTO DE HITOS - PROYECTO #{codigo.upcase}" f.puts "# ===========================================================================" f.puts "# Cambia el orden de las líneas (Cortar y Pegar) dentro de la misma FASE" f.puts "# para definir la secuencia lógica de ejecución." f.puts "# NO MODIFIQUES el número inicial ni los corchetes (ej: '42: [F3]')." f.puts "# Cuando termines de editar, guarda el archivo y ejecuta en la terminal:" f.puts "# adn db hito:ordenar #{codigo.upcase} --aplicar #{tmp_file}" f.puts "# ===========================================================================\n\n" fases = hitos.group_by { |h| h['fase_num'] }.sort_by { |k, v| k } fases.each do |num_fase, hitos_fase| f.puts "# --- FASE #{num_fase} ---" hitos_fase.each do |h| f.puts "#{h['id']}: [F#{num_fase}] #{h['titulo']}" end f.puts "\n" end end puts "#{Color::CYAN}✅ Archivo de configuración generado en: #{tmp_file}#{Color::RESET}" puts "1. Abre el archivo y reordena las líneas como necesites." puts "2. Ejecuta: #{Color::BOLD}adn db hito:ordenar #{codigo.upcase} --aplicar #{tmp_file}#{Color::RESET}" end rescue => e puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}" end end end # Comando: hito:renombrar def comando_hito_renombrar(args) if args.length < 3 puts "#{Color::YELLOW}Uso: adn db hito:renombrar #{Color::RESET}" return end proyecto = args[0] id_actual = args[1] id_nuevo = args[2] puts "#{Color::CYAN}🔄 Renombrando hito #{id_actual} → #{id_nuevo} (Proyecto #{proyecto.upcase})...#{Color::RESET}" BitacorasDB::BitacoraDB.with_connection do |db| begin res = db.rename_hito_id(proyecto, id_actual, id_nuevo) puts "#{Color::GREEN}✅ Hito renombrado exitosamente. Título: #{res['titulo']}#{Color::RESET}" rescue => e puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}" end end end # Comando: hito:actualizar [OPCIONES] def comando_hito_actualizar(args) if args.length < 2 puts "#{Color::YELLOW}Uso: adn db hito:actualizar [OPCIONES]#{Color::RESET}" puts "Opciones soportadas: --estado=EJ, --horas-p=1.5, --horas-r=0, --desc=\"Texto\"" return end proyecto = args.shift id_hito = args.shift opts = { estado: nil, horas_presencial: nil, horas_remoto: nil, descripcion: nil } parser = OptionParser.new do |opt| opt.banner = "Opciones:" opt.on("--estado ESTADO", "Nuevo estado (ej: ✅, ⏳, 📋)") { |e| opts[:estado] = e } opt.on("--horas-p HORAS", "Horas presenciales") { |h| opts[:horas_presencial] = h.to_f } opt.on("--horas-r HORAS", "Horas remotas") { |h| opts[:horas_remoto] = h.to_f } opt.on("--desc DESC", "Descripción o detalle para el hito") { |d| opts[:descripcion] = d } end begin parser.parse!(args) rescue OptionParser::InvalidOption => e puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}" exit 1 end if opts.values.all?(&:nil?) puts "#{Color::YELLOW}⚠ No se especificó nada para actualizar. Use --estado, --horas-p, --horas-r o --desc#{Color::RESET}" return end puts "#{Color::CYAN}🔄 Actualizando hito #{id_hito} del proyecto #{proyecto.upcase}...#{Color::RESET}" BitacorasDB::BitacoraDB.with_connection do |db| begin res = db.update_hito( proyecto, id_hito, estado: opts[:estado], horas_presencial: opts[:horas_presencial], horas_remoto: opts[:horas_remoto], descripcion: opts[:descripcion] ) puts "#{Color::GREEN}✅ Hito actualizado exitosamente.#{Color::RESET}" hrs = "#{res['horas_presencial'].to_f}P / #{res['horas_remoto'].to_f}R" puts " Estado actual : #{res['estado']}" puts " Horas imputadas : #{hrs}" rescue => e puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}" end end end end end