Files
dtic-DIIAA/adn/tools/core/eventos.rb
T

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
#