feat(dron): Migración a AtomicDrones con arquitectura DB-First
Implementación del sistema AtomicDrones basado en "Menos es Más": - Refactorización de dron.rb a dispatcher + módulos atómicos - Nuevos módulos: ejecutor, vigilante, sanador, bitacora - DronDB para acceso a datos en PostgreSQL - Tablas: dron_logs, dron_avances, dron_metricas - Views: vw_dron_estado, vw_dron_zombies, vw_dron_metricas_semanal - Health check con detección de zombies (heartbeat > 5min) - Dashboard compacto de la flota - Saneamiento con reintentos (backoff exponencial) y limpieza Comandos soportados: - dron lanzar: Ejecutar comando como dron - dron flota: Dashboard de la flota - dron salud: Health check activo - dron estado <ID>: Estado detallado - dron sanear: Saneamiento completo - dron limpiar: Limpieza de antiguos - dron reintentar: Re-intentar fallidos Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
b5e89144e3
commit
3543e62263
@@ -0,0 +1,197 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Dron::Bitacora — Módulo atómico para registro en bitácora humana
|
||||
#
|
||||
# Responsabilidad única: Gestionar registro de eventos en bitácora
|
||||
# - Crea eventos ⏳ al iniciar dron
|
||||
# - Actualiza eventos ✅ o ❌ al finalizar
|
||||
# - Soporta modo AUTO (crea y cierra automáticamente)
|
||||
#
|
||||
# Uso:
|
||||
# require_relative 'dron/bitacora'
|
||||
# Dron::Bitacora.iniciar(nota: 'Backup SQL')
|
||||
# Dron::Bitacora.finalizar(evento_id, exit_code: 0)
|
||||
|
||||
require_relative '../../db/core/bitacora_db'
|
||||
require_relative '../../core/constants'
|
||||
require_relative '../../core/logger'
|
||||
|
||||
module Dron
|
||||
class Bitacora
|
||||
class << self
|
||||
# Registrar inicio de actividad en bitácora
|
||||
#
|
||||
# @param nota [String] Nota descriptiva de la actividad
|
||||
# @param nodo_id [Integer, nil] ID del nodo (default: 1 = srv-ns8)
|
||||
# @param modo [String] Modo de ejecución (P=presencial, R=remoto, A=automático)
|
||||
# @return [Integer, nil] ID del evento creado o nil si falló
|
||||
def iniciar(nota:, nodo_id: 1, modo: 'A')
|
||||
@logger = ADN::Logger.new
|
||||
|
||||
descripcion = "🛸 dron: #{nota}"
|
||||
hora_inicio = Time.now.strftime('%H:%M')
|
||||
|
||||
@logger.info("📝 Bitácora: Iniciando evento - #{nota}")
|
||||
|
||||
sql = <<-SQL
|
||||
INSERT INTO bitacoras.events (nodo_id, descripcion, estado, modo, inicio)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id
|
||||
SQL
|
||||
|
||||
resultado = BitacorasDB::BitacoraDB.ejecutar(sql, [nodo_id, descripcion, '⏳', modo, hora_inicio])
|
||||
|
||||
evento_id = resultado.first['id']
|
||||
@logger.info("📝 Evento creado: ##{evento_id}")
|
||||
|
||||
evento_id
|
||||
rescue StandardError => e
|
||||
@logger.error("📝 Error creando evento: #{e.message}")
|
||||
nil
|
||||
end
|
||||
|
||||
# Registrar finalización de actividad en bitácora
|
||||
#
|
||||
# @param evento_id [Integer] ID del evento a actualizar
|
||||
# @param exit_code [Integer] Código de salida (0=éxito, >0=fallo)
|
||||
# @param nota [String, nil] Nota adicional para el resumen
|
||||
# @return [Boolean] true si se actualizó correctamente
|
||||
def finalizar(evento_id, exit_code:, nota: nil)
|
||||
@logger = ADN::Logger.new
|
||||
|
||||
estado_icono = exit_code == 0 ? '✅' : '❌'
|
||||
hora_fin = Time.now.strftime('%H:%M')
|
||||
|
||||
# Obtener evento actual para construir descripción
|
||||
evento = obtener_evento(evento_id)
|
||||
return false unless evento
|
||||
|
||||
# Construir nueva descripción
|
||||
descripcion_original = evento['descripcion']
|
||||
nota_adicional = nota ? " — #{nota}" : ""
|
||||
|
||||
# Extraer nota original si existe
|
||||
nota_original = descripcion_original.gsub('🛸 dron: ', '').split(' — ').first
|
||||
descripcion = "🛸 dron: #{nota_original} — #{estado_icono}#{nota_adicional}"
|
||||
|
||||
@logger.info("📝 Bitácora: Finalizando evento ##{evento_id} - #{estado_icono}")
|
||||
|
||||
sql = <<-SQL
|
||||
UPDATE bitacoras.events
|
||||
SET descripcion = $1,
|
||||
estado = $2,
|
||||
fin = $3,
|
||||
updated_at = NOW()
|
||||
WHERE id = $4
|
||||
SQL
|
||||
|
||||
BitacorasDB::BitacoraDB.ejecutar(sql, [descripcion, estado_icono, hora_fin, evento_id])
|
||||
|
||||
@logger.info("📝 Evento ##{evento_id} actualizado")
|
||||
true
|
||||
rescue StandardError => e
|
||||
@logger.error("📝 Error actualizando evento: #{e.message}")
|
||||
false
|
||||
end
|
||||
|
||||
# Registrar inicio y finalización automática (modo AUTO)
|
||||
# Útil para comandos rápidos que no requieren seguimiento
|
||||
#
|
||||
# @param nota [String] Nota descriptiva
|
||||
# @param bloque [Proc] Bloque de código a ejecutar
|
||||
# @return [Hash] Resultado de la ejecución
|
||||
def auto(nota:, &bloque)
|
||||
@logger = ADN::Logger.new
|
||||
|
||||
# Registrar inicio
|
||||
evento_id = iniciar(nota: nota)
|
||||
|
||||
# Ejecutar bloque
|
||||
inicio = Time.now
|
||||
resultado = nil
|
||||
exit_code = 0
|
||||
|
||||
begin
|
||||
resultado = yield
|
||||
exit_code = 0
|
||||
rescue StandardError => e
|
||||
@logger.error("📝 Error en ejecución AUTO: #{e.message}")
|
||||
exit_code = 1
|
||||
end
|
||||
|
||||
duracion = (Time.now - inicio).round(2)
|
||||
|
||||
# Registrar finalización
|
||||
finalizar(evento_id, exit_code: exit_code, nota: "#{duracion}s") if evento_id
|
||||
|
||||
{
|
||||
evento_id: evento_id,
|
||||
exit_code: exit_code,
|
||||
resultado: resultado,
|
||||
duracion: duracion
|
||||
}
|
||||
end
|
||||
|
||||
# Actualizar descripción de un evento
|
||||
#
|
||||
# @param evento_id [Integer] ID del evento
|
||||
# @param descripcion [String] Nueva descripción
|
||||
# @return [Boolean] true si se actualizó correctamente
|
||||
def actualizar(evento_id, descripcion:)
|
||||
@logger = ADN::Logger.new
|
||||
|
||||
sql = <<-SQL
|
||||
UPDATE bitacoras.events
|
||||
SET descripcion = $1, updated_at = NOW()
|
||||
WHERE id = $2
|
||||
SQL
|
||||
|
||||
BitacorasDB::BitacoraDB.ejecutar(sql, [descripcion, evento_id])
|
||||
@logger.info("📝 Evento ##{evento_id} actualizado")
|
||||
true
|
||||
rescue StandardError => e
|
||||
@logger.error("📝 Error actualizando evento: #{e.message}")
|
||||
false
|
||||
end
|
||||
|
||||
# Obtener información de un evento
|
||||
#
|
||||
# @param evento_id [Integer] ID del evento
|
||||
# @return [Hash, nil] Datos del evento o nil si no existe
|
||||
def obtener_evento(evento_id)
|
||||
sql = 'SELECT * FROM bitacoras.events WHERE id = $1'
|
||||
resultado = BitacorasDB::BitacoraDB.ejecutar(sql, [evento_id])
|
||||
resultado.first
|
||||
end
|
||||
|
||||
# Vincular un evento con un flujo de drones
|
||||
#
|
||||
# @param evento_id [Integer] ID del evento
|
||||
# @param flujo_id [String] ID del flujo
|
||||
# @return [Boolean] true si se vinculó correctamente
|
||||
def vincular_flujo(evento_id, flujo_id)
|
||||
@logger = ADN::Logger.new
|
||||
|
||||
evento = obtener_evento(evento_id)
|
||||
return false unless evento
|
||||
|
||||
metadata = evento['metadata'] || '{}'
|
||||
metadata_hash = JSON.parse(metadata) rescue {}
|
||||
metadata_hash['flujo_id'] = flujo_id
|
||||
|
||||
sql = <<-SQL
|
||||
UPDATE bitacoras.events
|
||||
SET metadata = $1
|
||||
WHERE id = $2
|
||||
SQL
|
||||
|
||||
BitacorasDB::BitacoraDB.ejecutar(sql, [metadata_hash.to_json, evento_id])
|
||||
@logger.info("📝 Evento ##{evento_id} vinculado al flujo #{flujo_id}")
|
||||
true
|
||||
rescue StandardError => e
|
||||
@logger.error("📝 Error vinculando flujo: #{e.message}")
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user