# frozen_string_literal: true # Dron::Dispatcher — Dispatcher de subcomandos para AtomicDrones # # Responsabilidad única: Despachar comandos a los módulos atómicos correctos # - Parsea argumentos de línea de comandos # - Invoca el módulo atómico correspondiente # - Muestra ayuda formateada # # Uso: # require_relative 'dron/dispatcher' # Dron::Dispatcher.ejecutar(ARGV) require_relative '../../core/help_formatter' require_relative '../../core/logger' require_relative '../../core/colores' module Dron class Dispatcher def self.ejecutar(args) @logger ||= ADN::Logger.new subcomando = args.shift case subcomando when 'lanzar', 'run', nil EjecutorComando.ejecutar(args) when 'flota', 'list' VigilanteComando.flota(args) when 'salud', 'health' VigilanteComando.salud(args) when 'sanear', 'clean' SanadorComando.sanear(args) when 'limpiar', 'cleanup' SanadorComando.limpiar(args) when 'reintentar', 'retry' SanadorComando.reintentar(args) when 'estado', 'status' VigilanteComando.estado(args) when 'ayuda', 'help', '--help', '-h' mostrar_ayuda else @logger.error("❌ Subcomando desconocido: #{subcomando}") mostrar_ayuda exit 1 end end def self.mostrar_ayuda lines = [] lines << "#{Color::BOLD}🛸 Sistema de Drones ADN (AtomicDrones)#{Color::RESET}" lines << "#{Color::DIM}#{'=' * 50}#{Color::RESET}" lines << "" lines << "#{Color::CYAN}Gestión de drones atómicos especializados para automatización de tareas#{Color::RESET}" lines << "" lines << "Uso: #{Color::YELLOW}./adn/tools/run dron [opciones]#{Color::RESET}" lines << "" lines << "\n#{Color::BOLD}Subcomandos:#{Color::RESET}" lines << " #{Color::GREEN}lanzar [OPTIONS]#{Color::RESET} Lanzar tarea como dron ejecutor" lines << " #{Color::GREEN}flota#{Color::RESET} Dashboard compacto de la flota" lines << " #{Color::GREEN}salud#{Color::RESET} Health check activo (detecta zombies)" lines << " #{Color::GREEN}estado #{Color::RESET} Estado detallado de un dron" lines << " #{Color::GREEN}sanear#{Color::RESET} Sanear flota (reintentar + limpiar)" lines << " #{Color::GREEN}limpiar#{Color::RESET} Limpiar drones antiguos" lines << " #{Color::GREEN}reintentar#{Color::RESET} Re-intentar drones fallidos" lines << "" lines << "\n#{Color::BOLD}Opciones:#{Color::RESET}" lines << " #{Color::GREEN}--nota \"texto\"#{Color::RESET} Nota descriptiva para la bitácora" lines << " #{Color::GREEN}--evento ID#{Color::RESET} Vincular a evento existente" lines << " #{Color::GREEN}--timeout N#{Color::RESET} Timeout en segundos (default: 3600)" lines << " #{Color::GREEN}--dry-run#{Color::RESET} Simulación sin ejecutar" lines << " #{Color::GREEN}--help#{Color::RESET} Mostrar esta ayuda" lines << "" lines << "\n#{Color::BOLD}Ejemplos:#{Color::RESET}" lines << " #{Color::DIM}$ ./adn/tools/run dron lanzar --nota 'Backup SQL' -- vzdump 103#{Color::RESET}" lines << " #{Color::CYAN}Lanzar backup como dron#{Color::RESET}" lines << " #{Color::DIM}$ ./adn/tools/run dron flota#{Color::RESET}" lines << " #{Color::CYAN}Ver estado de la flota#{Color::RESET}" lines << " #{Color::DIM}$ ./adn/tools/run dron salud#{Color::RESET}" lines << " #{Color::CYAN}Health check de la flota#{Color::RESET}" lines << "" puts lines.join("\n") end end # ============================================================================ # Comandos encapsulados para cada módulo atómico # ============================================================================ class EjecutorComando def self.ejecutar(args) require_relative 'ejecutor' # Parsear argumentos opciones = parsear_opciones(args) if opciones[:cmd].nil? puts "❌ Error: Debes especificar un comando a ejecutar" puts "" puts "Uso: ./adn/tools/run dron lanzar --nota \"Nota\" -- " exit 1 end # Lanzar dron ejecutor resultado = Dron::Ejecutor.lanzar( cmd: opciones[:cmd], nota: opciones[:nota], evento_id: opciones[:evento_id], timeout: opciones[:timeout], flujo_id: opciones[:flujo_id] ) # Mostrar resultado puts "\n🛸 Resultado del Dron Ejecutor" puts "═" * 60 puts " Dron ID: #{resultado[:dron_id]}" puts " Estado: #{resultado[:exit_code] == 0 ? '✅ Completado' : '❌ Fallido'}" puts " Exit Code: #{resultado[:exit_code]}" puts " Duración: #{resultado[:duration]}s" puts "═" * 60 exit resultado[:exit_code] end def self.parsear_opciones(args) opciones = { nota: nil, evento_id: nil, timeout: 3600, flujo_id: nil, cmd: nil } # Parsear opciones con nombre while args.first&.start_with?('--') arg = args.shift case arg when '--nota' opciones[:nota] = args.shift when '--evento' opciones[:evento_id] = args.shift.to_i when '--timeout' opciones[:timeout] = args.shift.to_i when '--flujo' opciones[:flujo_id] = args.shift when '--help', '-h' Dispatcher.mostrar_ayuda exit 0 end end # El resto es el comando a ejecutar opciones[:cmd] = args.join(' ') unless args.empty? opciones end end class VigilanteComando def self.flota(args) require_relative 'vigilante' output = Dron::Vigilante.dashboard puts output end def self.salud(args) require_relative 'vigilante' diagnosticos = Dron::Vigilante.salud exit(diagnosticos[:saludable] ? 0 : 1) end def self.estado(args) require_relative 'vigilante' require_relative '../../db/core/dron_db' dron_id = args.first if dron_id.nil? puts "❌ Error: Debes especificar un dron_id" puts "Uso: ./adn/tools/run dron estado " exit 1 end dron = ADN::DB::DronDB.obtener_dron(dron_id) if dron.nil? puts "❌ Dron no encontrado: #{dron_id}" exit 1 end puts "\n📊 Estado del Dron" puts "═" * 60 puts " ID: #{dron['dron_id']}" puts " Tipo: #{dron['tipo']}" puts " Estado: #{dron['estado']}" puts " Comando: #{dron['cmd']}" puts " Inicio: #{dron['started_at']}" puts " Fin: #{dron['completed_at'] || '—'}" puts " Heartbeat: #{dron['heartbeat']}" puts " Reintentos: #{dron['reintentos']}" puts " Exit Code: #{dron['exit_code'] || '—'}" puts "═" * 60 # Mostrar avances si existen require_relative '../../db/core/bitacora_db' sql = 'SELECT * FROM bitacoras.dron_avances WHERE dron_id = $1 ORDER BY paso ASC' avances = BitacorasDB::BitacoraDB.ejecutar(sql, [dron_id]) unless avances.empty? puts "\n📝 Avances:" avances.each do |avance| puts " [#{avance['estado']}] Paso #{avance['paso']}: #{avance['descripcion']}" end end end end class SanadorComando def self.sanear(args) require_relative 'sanador' opciones = parsear_opciones(args) resultado = Dron::Sanador.sanear( dias_limpieza: opciones[:dias], dry_run: opciones[:dry_run] ) exit(0) end def self.limpiar(args) require_relative 'sanador' opciones = parsear_opciones(args) resultado = Dron::Sanador.limpiar_antiguos( dias: opciones[:dias], dry_run: opciones[:dry_run] ) exit(0) end def self.reintentar(args) require_relative 'sanador' opciones = parsear_opciones(args) resultado = Dron::Sanador.reintentar_fallidos( dry_run: opciones[:dry_run] ) exit(0) end def self.parsear_opciones(args) opciones = { dias: 7, dry_run: false } args.each do |arg| case arg when '--dry-run' opciones[:dry_run] = true when '--dias' idx = args.index(arg) opciones[:dias] = args[idx + 1].to_i if args[idx + 1] end end opciones end end end