- P2606: Plan v1.1 EN EJECUCIÓN. Fases 1-3 completadas. - Ficha nodo srvv-nginx-rm.md. P2606 en adn/07_proyectos.md. - Incluye cambios acumulados de sesiones anteriores.
197 lines
6.3 KiB
Ruby
197 lines
6.3 KiB
Ruby
# 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
|