Refactor(ADN): Migración de tools/adn a adn/tools en busca de la Armonía Integral del DIIAA
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# adn/tools/eventos.rb — Bus de eventos interno para el sistema ADN
|
||||
# ==================================================================
|
||||
# Este módulo implementa un bus de eventos que permite la comunicación
|
||||
# desacoplada entre los diferentes componentes del sistema ADN.
|
||||
# Facilita la implementación del Principio de Armonía Integral al
|
||||
# permitir que cambios en un componente disparen acciones automáticas
|
||||
# en otros componentes dependientes.
|
||||
#
|
||||
# 📅 Última actualización: 2026-03-07 (Fase 2: Automatización Inteligente)
|
||||
# 📚 Referencia: docs/plan/260307-1400_Plan_Mejoras_ADN.md
|
||||
# 🔗 Integración: adn/triggers.yml (configuración de triggers)
|
||||
|
||||
require 'singleton'
|
||||
|
||||
module ADN
|
||||
class Eventos
|
||||
include Singleton
|
||||
|
||||
# Eventos predefinidos del sistema ADN
|
||||
# Estos eventos corresponden a los triggers definidos en adn/triggers.yml
|
||||
EVENTOS = {
|
||||
# Bitácoras
|
||||
bitacora_creada: :bitacora_creada,
|
||||
bitacora_modificada: :bitacora_modificada,
|
||||
bitacora_guardada: :bitacora_guardada,
|
||||
nueva_vinyeta_cronologia: :nueva_vinyeta_cronologia,
|
||||
estado_cambiado: :estado_cambiado,
|
||||
metrica_hibrida_registrada: :metrica_hibrida_registrada,
|
||||
error_formato_detectado: :error_formato_detectado,
|
||||
|
||||
# Nodos
|
||||
nodo_creado: :nodo_creado,
|
||||
nodo_modificado: :nodo_modificado,
|
||||
estado_nodo_cambiado: :estado_nodo_cambiado,
|
||||
|
||||
# Proyectos
|
||||
proyecto_creado: :proyecto_creado,
|
||||
proyecto_modificado: :proyecto_modificado,
|
||||
hito_proyecto_actualizado: :hito_proyecto_actualizado,
|
||||
estado_proyecto_cambiado: :estado_proyecto_cambiado,
|
||||
|
||||
# ADN y configuración
|
||||
hebra_adn_modificada: :hebra_adn_modificada,
|
||||
iconografia_actualizada: :iconografia_actualizada,
|
||||
directivas_ia_actualizadas: :directivas_ia_actualizadas,
|
||||
configuracion_actualizada: :configuracion_actualizada,
|
||||
|
||||
# Sistema
|
||||
sistema_iniciado: :sistema_iniciado,
|
||||
validacion_completada: :validacion_completada,
|
||||
error_sistema: :error_sistema,
|
||||
advertencia_sistema: :advertencia_sistema
|
||||
}.freeze
|
||||
|
||||
# Inicializa el bus de eventos
|
||||
def initialize
|
||||
@suscriptores = Hash.new { |hash, key| hash[key] = [] }
|
||||
@logger = ADN.logger
|
||||
@mutex = Mutex.new
|
||||
@logger.debug('Bus de eventos ADN inicializado')
|
||||
end
|
||||
|
||||
# Registra un suscriptor para un tipo de evento
|
||||
#
|
||||
# @param evento [Symbol, String] Tipo de evento al que suscribirse
|
||||
# @param bloque [Proc] Bloque a ejecutar cuando se publique el evento
|
||||
# @param identificador [String] Identificador opcional para el suscriptor
|
||||
# @return [String] Identificador del suscriptor
|
||||
def suscriptor(evento, identificador = nil, &bloque)
|
||||
evento_normalizado = normalizar_evento(evento)
|
||||
|
||||
@mutex.synchronize do
|
||||
id = identificador || "suscriptor_#{@suscriptores[evento_normalizado].length + 1}"
|
||||
@suscriptores[evento_normalizado] << { id: id, bloque: bloque }
|
||||
@logger.debug("Suscriptor registrado: #{id} para evento #{evento_normalizado}")
|
||||
id
|
||||
end
|
||||
end
|
||||
|
||||
# Publica un evento a todos los suscriptores registrados
|
||||
#
|
||||
# @param evento [Symbol, String] Tipo de evento a publicar
|
||||
# @param datos [Hash] Datos asociados al evento
|
||||
# @param opciones [Hash] Opciones de publicación
|
||||
# @option opciones [Boolean] :async Ejecutar suscriptores en hilos separados
|
||||
# @option opciones [Boolean] :silencioso No loggear la publicación
|
||||
# @return [Array<Hash>] Resultados de la ejecución de suscriptores
|
||||
def publicar(evento, datos = {}, opciones = {})
|
||||
evento_normalizado = normalizar_evento(evento)
|
||||
suscriptores = @suscriptores[evento_normalizado].dup
|
||||
|
||||
return [] if suscriptores.empty?
|
||||
|
||||
@logger.debug("Publicando evento #{evento_normalizado} a #{suscriptores.length} suscriptor(es)", datos) unless opciones[:silencioso]
|
||||
|
||||
resultados = []
|
||||
suscriptores.each do |suscriptor|
|
||||
resultado = ejecutar_suscriptor(suscriptor, evento_normalizado, datos, opciones)
|
||||
resultados << resultado if resultado
|
||||
end
|
||||
|
||||
resultados
|
||||
end
|
||||
|
||||
# Elimina un suscriptor específico
|
||||
#
|
||||
# @param evento [Symbol, String] Tipo de evento
|
||||
# @param identificador [String] Identificador del suscriptor a eliminar
|
||||
# @return [Boolean] true si se eliminó, false si no se encontró
|
||||
def eliminar_suscriptor(evento, identificador)
|
||||
evento_normalizado = normalizar_evento(evento)
|
||||
|
||||
@mutex.synchronize do
|
||||
inicial = @suscriptores[evento_normalizado].length
|
||||
@suscriptores[evento_normalizado].reject! { |s| s[:id] == identificador }
|
||||
eliminados = inicial - @suscriptores[evento_normalizado].length
|
||||
|
||||
if eliminados > 0
|
||||
@logger.debug("Suscriptor eliminado: #{identificador} del evento #{evento_normalizado}")
|
||||
true
|
||||
else
|
||||
@logger.advertencia("Suscriptor no encontrado: #{identificador} para evento #{evento_normalizado}")
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Elimina todos los suscriptores de un evento
|
||||
#
|
||||
# @param evento [Symbol, String] Tipo de evento
|
||||
# @return [Integer] Número de suscriptores eliminados
|
||||
def limpiar_suscriptores(evento = nil)
|
||||
@mutex.synchronize do
|
||||
if evento.nil?
|
||||
total = @suscriptores.values.flatten.length
|
||||
@suscriptores.clear
|
||||
@logger.debug("Todos los suscriptores eliminados (#{total} total)")
|
||||
total
|
||||
else
|
||||
evento_normalizado = normalizar_evento(evento)
|
||||
eliminados = @suscriptores[evento_normalizado].length
|
||||
@suscriptores.delete(evento_normalizado)
|
||||
@logger.debug("#{eliminados} suscriptor(es) eliminados del evento #{evento_normalizado}")
|
||||
eliminados
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Lista todos los eventos con suscriptores
|
||||
#
|
||||
# @return [Hash] Mapa de evento -> número de suscriptores
|
||||
def lista_suscriptores
|
||||
resultado = {}
|
||||
@mutex.synchronize do
|
||||
@suscriptores.each do |evento, suscriptores|
|
||||
resultado[evento] = suscriptores.length
|
||||
end
|
||||
end
|
||||
resultado
|
||||
end
|
||||
|
||||
# Verifica si hay suscriptores para un evento
|
||||
#
|
||||
# @param evento [Symbol, String] Tipo de evento
|
||||
# @return [Boolean] true si hay suscriptores
|
||||
def tiene_suscriptores?(evento)
|
||||
evento_normalizado = normalizar_evento(evento)
|
||||
!@suscriptores[evento_normalizado].empty?
|
||||
end
|
||||
|
||||
# Métodos de conveniencia para eventos comunes
|
||||
|
||||
# Publica un evento de archivo modificado
|
||||
#
|
||||
# @param ruta [String] Ruta del archivo modificado
|
||||
# @param tipo [String] Tipo de archivo (bitacora, nodo, proyecto, hebra)
|
||||
# @param accion [String] Acción (creado, modificado, eliminado)
|
||||
# @param datos_extra [Hash] Datos adicionales
|
||||
def archivo_modificado(ruta, tipo, accion, datos_extra = {})
|
||||
datos = {
|
||||
ruta: ruta,
|
||||
tipo: tipo,
|
||||
accion: accion,
|
||||
timestamp: Time.now.utc
|
||||
}.merge(datos_extra)
|
||||
|
||||
evento = case tipo
|
||||
when 'bitacora' then :bitacora_modificada
|
||||
when 'nodo' then :nodo_modificado
|
||||
when 'proyecto' then :proyecto_modificado
|
||||
when 'hebra' then :hebra_adn_modificada
|
||||
else :archivo_modificado
|
||||
end
|
||||
|
||||
publicar(evento, datos)
|
||||
end
|
||||
|
||||
# Publica un evento de estado cambiado
|
||||
#
|
||||
# @param entidad [String] Tipo de entidad (nodo, proyecto, tarea)
|
||||
# @param nombre [String] Nombre de la entidad
|
||||
# @param estado_anterior [String] Estado anterior
|
||||
# @param estado_nuevo [String] Estado nuevo
|
||||
# @param datos_extra [Hash] Datos adicionales
|
||||
def estado_cambiado(entidad, nombre, estado_anterior, estado_nuevo, datos_extra = {})
|
||||
datos = {
|
||||
entidad: entidad,
|
||||
nombre: nombre,
|
||||
estado_anterior: estado_anterior,
|
||||
estado_nuevo: estado_nuevo,
|
||||
timestamp: Time.now.utc
|
||||
}.merge(datos_extra)
|
||||
|
||||
publicar(:estado_cambiado, datos)
|
||||
end
|
||||
|
||||
# Publica un evento de error detectado
|
||||
#
|
||||
# @param tipo [String] Tipo de error (formato, validacion, sistema)
|
||||
# @param ubicacion [String] Ubicación del error (archivo, módulo)
|
||||
# @param mensaje [String] Mensaje descriptivo del error
|
||||
# @param severidad [String] Severidad (baja, media, alta, critica)
|
||||
# @param datos_extra [Hash] Datos adicionales
|
||||
def error_detectado(tipo, ubicacion, mensaje, severidad = 'media', datos_extra = {})
|
||||
datos = {
|
||||
tipo: tipo,
|
||||
ubicacion: ubicacion,
|
||||
mensaje: mensaje,
|
||||
severidad: severidad,
|
||||
timestamp: Time.now.utc
|
||||
}.merge(datos_extra)
|
||||
|
||||
publicar(:error_formato_detectado, datos)
|
||||
end
|
||||
|
||||
# Publica un evento de validación completada
|
||||
#
|
||||
# @param tipo [String] Tipo de validación (bitacoras, nodos, proyectos, completa)
|
||||
# @param resultados [Hash] Resultados de la validación
|
||||
def validacion_completada(tipo, resultados)
|
||||
datos = {
|
||||
tipo: tipo,
|
||||
resultados: resultados,
|
||||
timestamp: Time.now.utc
|
||||
}
|
||||
|
||||
publicar(:validacion_completada, datos)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Normaliza un evento a símbolo
|
||||
#
|
||||
# @param evento [Symbol, String] Evento a normalizar
|
||||
# @return [Symbol] Evento normalizado
|
||||
def normalizar_evento(evento)
|
||||
evento.to_s.to_sym
|
||||
end
|
||||
|
||||
# Ejecuta un suscriptor individual con manejo de errores
|
||||
#
|
||||
# @param suscriptor [Hash] Información del suscriptor
|
||||
# @param evento [Symbol] Evento que se está publicando
|
||||
# @param datos [Hash] Datos del evento
|
||||
# @param opciones [Hash] Opciones de publicación
|
||||
# @return [Hash] Resultado de la ejecución
|
||||
def ejecutar_suscriptor(suscriptor, evento, datos, opciones)
|
||||
id = suscriptor[:id]
|
||||
bloque = suscriptor[:bloque]
|
||||
|
||||
inicio = Time.now
|
||||
begin
|
||||
resultado = bloque.call(datos.dup.freeze)
|
||||
duracion = Time.now - inicio
|
||||
|
||||
{
|
||||
suscriptor: id,
|
||||
evento: evento,
|
||||
exito: true,
|
||||
duracion: duracion,
|
||||
resultado: resultado,
|
||||
timestamp: inicio
|
||||
}
|
||||
rescue => e
|
||||
duracion = Time.now - inicio
|
||||
@logger.error("Error en suscriptor #{id} para evento #{evento}: #{e.message}", {
|
||||
backtrace: e.backtrace.take(5),
|
||||
evento: evento,
|
||||
datos: datos
|
||||
})
|
||||
|
||||
{
|
||||
suscriptor: id,
|
||||
evento: evento,
|
||||
exito: false,
|
||||
duracion: duracion,
|
||||
error: e.message,
|
||||
backtrace: e.backtrace.take(5),
|
||||
timestamp: inicio
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Acceso global al bus de eventos
|
||||
#
|
||||
# @return [ADN::Eventos] Instancia singleton del bus de eventos
|
||||
def self.eventos
|
||||
Eventos.instance
|
||||
end
|
||||
end
|
||||
|
||||
# ─── Notas de uso ──────────────────────────────────────────────────────
|
||||
#
|
||||
# Ejemplo de suscripción:
|
||||
# ADN.eventos.suscriptor(:bitacora_creada) do |datos|
|
||||
# puts "Nueva bitácora creada: #{datos[:ruta]}"
|
||||
# end
|
||||
#
|
||||
# Ejemplo de publicación:
|
||||
# ADN.eventos.publicar(:bitacora_creada, {
|
||||
# ruta: 'bitacoras/2026-03-07.md',
|
||||
# fecha: Date.today
|
||||
# })
|
||||
#
|
||||
# Ejemplo de publicación con método de conveniencia:
|
||||
# ADN.eventos.archivo_modificado(
|
||||
# 'bitacoras/2026-03-07.md',
|
||||
# 'bitacora',
|
||||
# 'creado'
|
||||
# )
|
||||
#
|
||||
# El bus de eventos es especialmente útil para:
|
||||
# - Implementar triggers basados en eventos
|
||||
# - Desacoplar componentes del sistema
|
||||
# - Facilitar la extensibilidad mediante plugins
|
||||
# - Loggear actividades del sistema de forma centralizada
|
||||
#
|
||||
Reference in New Issue
Block a user