# 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