fix(dron): Ejecutor funcional + tabla events

Cambios:
- ejecutor.rb: Corregir lectura de output con Open3
- bitacora_db.rb: Agregar métodos crear_evento/actualizar_evento (public)
- dron_db.rb: Corregir conversión de PG::Result a Hash (.first.to_h)
- vigilante.rb: Corregir formato de fecha en dashboard
- migrations/004: Crear tabla bitacoras.events para Bitácora Web

Pruebas:
- dron lanzar --evento AUTO --nota "Test" -- echo "hola" 
- dron flota 
- dron salud 

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ricardo Monla
2026-04-07 12:34:20 -03:00
co-authored by Claude Opus 4.6
parent 3d2a988951
commit be3a78c50a
5 changed files with 156 additions and 48 deletions
+68 -29
View File
@@ -984,38 +984,77 @@ module BitacorasDB
end
end
# Convierte un resultado PG a Hash
def result_to_hash(result)
return {} if result.ntuples.zero?
result.first.to_h
end
# Convierte un resultado PG a Hash
def result_to_hash(result)
return {} if result.ntuples.zero?
result.first.to_h
end
# Ejecuta hooks personalizados después de operaciones de entrada
# Esto implementa el concepto de DB Hooks para migrar triggers.yml a capa de datos
#
# @param entrada [Hash] La entrada que fue creada o actualizada
# @param accion [Symbol] :crear o :actualizar
def ejecutar_hooks_entrada(entrada, accion)
# Hook para proyectos con etiqueta [P2604]
if entrada[:descripcion]&.match?(/\[P2604\]/)
proyecto_codigo = entrada[:descripcion].match(/\[P2604\.(\d+)\.(\d+)\.(\d+)\]/)&.to_s
if proyecto_codigo
# Normalizar formato: P2604.05.01 -> P2604
proyecto_base = proyecto_codigo.split('.')[0..1].join('.')
# Actualizar el manifiesto del proyecto en la base de datos
proyecto = find_proyecto_by_codigo(proyecto_base)
if proyecto
# Aquí podríamos actualizar algún campo del proyecto basado en la entrada
# Por ahora, solo registramos que se debería actualizar
@logger&.info("Hook ejecutado: Entrada #{accion} para proyecto #{proyecto_base} detectada")
# En una implementación completa, aquí actualizaríamos el manifiesto del proyecto
end
# Ejecuta hooks personalizados después de operaciones de entrada
def ejecutar_hooks_entrada(entrada, accion)
if entrada[:descripcion]&.match?(/\[P2604\]/)
proyecto_codigo = entrada[:descripcion].match(/\[P2604\.(\d+)\.(\d+)\.(\d+)\]/)&.to_s
if proyecto_codigo
proyecto_base = proyecto_codigo.split('.')[0..1].join('.')
proyecto = find_proyecto_by_codigo(proyecto_base)
if proyecto
@logger&.info("Hook ejecutado: Entrada #{accion} para proyecto #{proyecto_base} detectada")
end
end
# Otros hooks podrían ir aquí...
# Por ejemplo, hooks para sincronización con nodos, etc.
end
end
public
# Crear un evento en la tabla events (visible en Bitácora Web)
def crear_evento(nodo_id:, descripcion:, estado: '⏳', inicio: nil, fin: nil, modo: 'A', metadata: nil)
inicio ||= Time.now.strftime('%H:%M')
query = <<~SQL
INSERT INTO bitacoras.events (nodo_id, descripcion, estado, inicio, fin, modo, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id
SQL
result = @connection.exec_params(query, [nodo_id, descripcion, estado, inicio, fin, modo, metadata&.to_json])
result.first.to_h
end
# Actualizar un evento existente
def actualizar_evento(id, descripcion: nil, estado: nil, fin: nil)
set_clauses = []
values = []
i = 1
if descripcion
set_clauses << "descripcion = $#{i}"
values << descripcion
i += 1
end
if estado
set_clauses << "estado = $#{i}"
values << estado
i += 1
end
if fin
set_clauses << "fin = $#{i}"
values << fin
i += 1
end
return nil if set_clauses.empty?
set_clauses << "updated_at = NOW()"
query = <<~SQL
UPDATE bitacoras.events
SET #{set_clauses.join(', ')}
WHERE id = $#{i}
RETURNING *
SQL
values << id
result = @connection.exec_params(query, values)
result.first.to_h
end
end
end
+5 -5
View File
@@ -56,7 +56,7 @@ module ADN
dron_id = generar_dron_id
params = [dron_id, tipo, ESTADOS[:running], cmd, flujo_id, metadata.to_json]
resultado = BitacorasDB::BitacoraDB.with_connection { |db| db.execute(sql, params) }
resultado = BitacorasDB::BitacoraDB.with_connection { |db| db.execute(sql, params) }.first.to_h
# Actualizar heartbeat inicial
actualizar_heartbeat(dron_id)
@@ -88,7 +88,7 @@ module ADN
RETURNING *
SQL
resultado = BitacorasDB::BitacoraDB.with_connection { |db| db.execute(sql, [estado, exit_code, output, dron_id]) }
resultado = BitacorasDB::BitacoraDB.with_connection { |db| db.execute(sql, [estado, exit_code, output, dron_id]) }.first.to_h
# Actualizar métricas diarias
actualizar_metricas(dron_id, estado)
@@ -118,7 +118,7 @@ module ADN
# @return [Hash, nil] Datos del dron o nil si no existe
def obtener_dron(dron_id)
sql = 'SELECT * FROM bitacoras.dron_logs WHERE dron_id = $1'
resultado = BitacorasDB::BitacoraDB.with_connection { |db| db.execute(sql, [dron_id]) }
resultado = BitacorasDB::BitacoraDB.with_connection { |db| db.execute(sql, [dron_id]) }.first.to_h
resultado.first
end
@@ -176,7 +176,7 @@ module ADN
RETURNING reintentos
SQL
resultado = BitacorasDB::BitacoraDB.with_connection { |db| db.execute(sql, [dron_id]) }
resultado = BitacorasDB::BitacoraDB.with_connection { |db| db.execute(sql, [dron_id]) }.first.to_h
resultado.first['reintentos'].to_i
end
@@ -229,7 +229,7 @@ module ADN
# @return [Hash, nil] Métricas o nil si no existen
def obtener_metricas(fecha:, tipo:)
sql = 'SELECT * FROM bitacoras.dron_metricas WHERE fecha = $1 AND dron_tipo = $2'
resultado = BitacorasDB::BitacoraDB.with_connection { |db| db.execute(sql, [fecha, tipo]) }
resultado = BitacorasDB::BitacoraDB.with_connection { |db| db.execute(sql, [fecha, tipo]) }.first.to_h
resultado.first
end