# frozen_string_literal: true require 'optparse' require 'json' require_relative '../core/colores' require_relative '../db/core/mejoras_sensor' module ADN class SubcomandoSalud def initialize(args, logger) @args = args @logger = logger @opciones = { dashboard: false, json: false, mejoras: false } end def ejecutar opts = { dashboard: false, json: false, mejoras: false, ambito: nil } if @args.include?('-h') || @args.include?('--help') || @args.empty? require_relative '../core/help_formatter' puts ADN::HelpFormatter.generar_help( titulo: "Salud del Sistema ADN", descripcion: "Muestra métricas y estado general del ecosistema (Archivos MD + DB)", uso: "./adn/tools/run salud [opciones]", opciones: [ { names: ['--json'], desc: "Salida en formato JSON" }, { names: ['--mejoras'], desc: "Sugerencias de mejora continua" }, { names: ['--ambito AMBITO'], desc: "Filtrar estadísticas de DB por ámbito" } ], ejemplos: [ { cmd: "./adn/tools/run salud", desc: "Reporte general" }, { cmd: "./adn/tools/run salud --ambito dtic-DASUTEN", desc: "Reporte con foco en el ámbito" } ] ) exit 0 end parser = OptionParser.new do |opt| opt.on("--json") { @opciones[:json] = true } opt.on("--mejoras") { @opciones[:mejoras] = true } opt.on("--dashboard") { @opciones[:dashboard] = true } opt.on("--ambito NOMBRE") { |a| @opciones[:ambito] = a } end begin parser.parse!(@args) rescue OptionParser::InvalidOption => e @logger.error("Opción inválida: #{e.message}") puts "#{Color::YELLOW}⚠ Error: #{e.message}#{Color::RESET}" exit 1 end mostrar_salud end private def mostrar_salud @logger.info("Generando reporte de salud del ADN") datos = { timestamp: Time.now.utc.iso8601, hebras_adn: contar_hebras_adn, bitacoras: contar_bitacoras, nodos: contar_nodos, proyectos: contar_proyectos, errores_recientes: buscar_errores_recientes } # Add improvement suggestions if requested if @opciones[:mejoras] datos[:sugerencias_mejora] = obtener_sugerencias_mejora end datos[:db] = obtener_estadisticas_db(@opciones[:ambito]) if @opciones[:json] puts JSON.pretty_generate(datos) return end if @opciones[:dashboard] puts "#{Color::YELLOW}⚠ El dashboard interactivo estructurado será implementado en Fase 3#{Color::RESET}" end # Mostrar en formato legible puts "#{Color::CYAN}🧬 Salud del Sistema ADN#{Color::RESET}" puts "#{Color::DIM}#{'-' * 60}#{Color::RESET}" puts " #{Color::GREEN}Hebras ADN:#{Color::RESET} #{datos[:hebras_adn]} / 7" puts " #{Color::GREEN}Bitácoras MD:#{Color::RESET} #{datos[:bitacoras]}" puts " #{Color::GREEN}Nodos MD:#{Color::RESET} #{datos[:nodos]}" puts " #{Color::GREEN}Proyectos MD:#{Color::RESET} #{datos[:proyectos]}" if datos[:db] puts "" db_stats = datos[:db] ambito_str = @opciones[:ambito] ? " (Ámbito: #{@opciones[:ambito]})" : " (Global)" puts " #{Color::CYAN}Estadísticas Base de Datos#{ambito_str}:#{Color::RESET}" puts " - Ámbitos Totales: #{db_stats[:ambitos]}" unless @opciones[:ambito] puts " - Nodos (DB): #{db_stats[:nodos]}" puts " - Eventos (DB): #{db_stats[:eventos]}" end if datos[:errores_recientes] > 0 puts " #{Color::RED}Errores recientes:#{Color::RESET} #{datos[:errores_recientes]}" else puts " #{Color::GREEN}Errores recientes:#{Color::RESET} 0" end puts "#{Color::DIM}#{'-' * 60}#{Color::RESET}" puts "#{Color::DIM}Para más detalles: ./adn/tools/run salud --json#{Color::RESET}" if @opciones[:mejoras] && datos[:sugerencias_mejora] && !datos[:sugerencias_mejora].empty? puts puts "#{Color::CYAN}💡 Sugerencias de Mejora Continua#{Color::RESET}" puts "#{Color::DIM}#{'-' * 60}#{Color::RESET}" datos[:sugerencias_mejora].each do |sug| prioridad_emoji = { alta: '🔴', media: '🟡', baja: '🟢' }[sug[:prioridad]] || '⚪' puts " #{prioridad_emoji} #{sug[:tipo].to_s.tr('_', ' ')}" puts " #{sug[:descripcion]}" puts " → #{sug[:accion_sugerida]}" puts end end @logger.exito("Reporte de salud generado") end # Obtener sugerencias de mejora continua def obtener_sugerencias_mejora # Activar el sensor temporalmente para obtener sugerencias ::ADN::MejorasSensor.activo = true sugerencias = ::ADN::MejorasSensor.generar_sugerencias ::ADN::MejorasSensor.activo = false # Desactivar nuevamente para no afectar rendimiento sugerencias end def contar_hebras_adn Dir.glob(File.join(ADN::PROJECT_ROOT, 'adn', "*.md")).count end def contar_bitacoras Dir.glob(File.join(ADN::BITACORAS_DIR, "*.md")).count end def contar_nodos Dir.glob(File.join(ADN::NODOS_DIR, "*.md")).count end def obtener_estadisticas_db(ambito_nombre = nil) require_relative '../db/core/bitacora_db' stats = { ambitos: 0, nodos: 0, eventos: 0 } begin BitacorasDB::BitacoraDB.with_connection do |db| ambito_id = nil if ambito_nombre ambito = db.get_ambito(ambito_nombre) return nil unless ambito ambito_id = ambito['id'] else stats[:ambitos] = db.list_ambitos.length end stats[:nodos] = db.list_nodos(ambito_id: ambito_id, limit: 1000).length stats[:eventos] = db.list_entradas(ambito_id: ambito_id, limit: 10000).length end rescue StandardError => e @logger.error("Error obteniendo stats DB: #{e.message}") return nil end stats end def contar_proyectos Dir.glob(File.join(ADN::PROYECTOS_DIR, "*.md")).count end def buscar_errores_recientes # Buscar en logs de hoy log_hoy = File.join(ADN::LOGS_DIR, "#{Date.today}.log") return 0 unless File.exist?(log_hoy) errores = 0 File.foreach(log_hoy) do |linea| begin entrada = JSON.parse(linea) errores += 1 if entrada['nivel'] == 'ERROR' rescue JSON::ParserError next end end errores end end end