341 lines
11 KiB
Ruby
341 lines
11 KiB
Ruby
# 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
|
|
#
|