Files
dtic-DIIAA/adn/tools/db/migrations/003_create_dron_tables.rb
T
Ricardo MonlaandClaude Opus 4.6 3543e62263 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>
2026-04-07 11:35:13 -03:00

232 lines
8.3 KiB
Ruby

# frozen_string_literal: true
# Migration: 003 - Tablas para sistema de Drones (AtomicDrones)
#
# Usage:
# ruby adn/tools/db/migrations/003_create_dron_tables.rb
#
# Propósito: Crear tablas internas para registro detallado de drones
# - dron_logs: Logs detallados de cada dron
# - dron_avances: Avances paso-a-paso de flujos multi-paso
# - dron_metricas: Métricas agregadas diarias por tipo de dron
require_relative '../core/bitacora_db'
puts "🔧 Running migration 003: Create dron tables"
BitacorasDB::BitacoraDB.with_connection do |db|
# ============================================================================
# Tabla 1: dron_logs
# ============================================================================
puts "\n📝 Creando tabla bitacoras.dron_logs..."
db.execute(<<~SQL)
CREATE TABLE IF NOT EXISTS bitacoras.dron_logs (
id SERIAL PRIMARY KEY,
dron_id VARCHAR(50) NOT NULL,
flujo_id VARCHAR(50),
tipo VARCHAR(20) NOT NULL,
estado VARCHAR(20) NOT NULL,
cmd TEXT,
output TEXT,
exit_code INTEGER,
started_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP,
heartbeat TIMESTAMP DEFAULT NOW(),
reintentos INTEGER DEFAULT 0,
metadata JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
)
SQL
puts "✅ Tabla bitacoras.dron_logs creada"
# Índices para dron_logs
indices_dron_logs = [
["idx_dron_logs_estado", "CREATE INDEX IF NOT EXISTS idx_dron_logs_estado ON bitacoras.dron_logs(estado)"],
["idx_dron_logs_heartbeat", "CREATE INDEX IF NOT EXISTS idx_dron_logs_heartbeat ON bitacoras.dron_logs(heartbeat)"],
["idx_dron_logs_flujo", "CREATE INDEX IF NOT EXISTS idx_dron_logs_flujo ON bitacoras.dron_logs(flujo_id)"],
["idx_dron_logs_tipo", "CREATE INDEX IF NOT EXISTS idx_dron_logs_tipo ON bitacoras.dron_logs(tipo)"],
["idx_dron_logs_started_at", "CREATE INDEX IF NOT EXISTS idx_dron_logs_started_at ON bitacoras.dron_logs(started_at)"]
]
indices_dron_logs.each do |nombre, sql|
db.execute(sql)
puts "📈 Índice creado: #{nombre}"
end
# ============================================================================
# Tabla 2: dron_avances
# ============================================================================
puts "\n📝 Creando tabla bitacoras.dron_avances..."
db.execute(<<~SQL)
CREATE TABLE IF NOT EXISTS bitacoras.dron_avances (
id SERIAL PRIMARY KEY,
dron_id VARCHAR(50) NOT NULL,
flujo_id VARCHAR(50),
paso INTEGER NOT NULL,
descripcion TEXT NOT NULL,
estado VARCHAR(20) NOT NULL,
output TEXT,
started_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP,
metadata JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMP DEFAULT NOW()
)
SQL
puts "✅ Tabla bitacoras.dron_avances creada"
# Índices para dron_avances
indices_dron_avances = [
["idx_dron_avances_dron", "CREATE INDEX IF NOT EXISTS idx_dron_avances_dron ON bitacoras.dron_avances(dron_id)"],
["idx_dron_avances_flujo", "CREATE INDEX IF NOT EXISTS idx_dron_avances_flujo ON bitacoras.dron_avances(flujo_id)"],
["idx_dron_avances_paso", "CREATE INDEX IF NOT EXISTS idx_dron_avances_paso ON bitacoras.dron_avances(paso)"]
]
indices_dron_avances.each do |nombre, sql|
db.execute(sql)
puts "📈 Índice creado: #{nombre}"
end
# ============================================================================
# Tabla 3: dron_metricas
# ============================================================================
puts "\n📝 Creando tabla bitacoras.dron_metricas..."
db.execute(<<~SQL)
CREATE TABLE IF NOT EXISTS bitacoras.dron_metricas (
id SERIAL PRIMARY KEY,
fecha DATE NOT NULL,
dron_tipo VARCHAR(20) NOT NULL,
total_ejecuciones INTEGER DEFAULT 0,
completados INTEGER DEFAULT 0,
fallidos INTEGER DEFAULT 0,
zombies INTEGER DEFAULT 0,
tiempo_promedio INTERVAL DEFAULT '0 seconds',
reintentos_total INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(fecha, dron_tipo)
)
SQL
puts "✅ Tabla bitacoras.dron_metricas creada"
# Índices para dron_metricas
db.execute("CREATE INDEX IF NOT EXISTS idx_dron_metricas_fecha ON bitacoras.dron_metricas(fecha)")
puts "📈 Índice creado: idx_dron_metricas_fecha"
# ============================================================================
# Trigger para actualizar updated_at
# ============================================================================
puts "\n📝 Creando trigger para actualizar updated_at..."
db.execute(<<~SQL)
CREATE OR REPLACE FUNCTION bitacoras.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql
SQL
puts "✅ Función update_updated_at_column creada"
# Triggers para cada tabla
triggers = [
["update_dron_logs_updated_at", "dron_logs"],
["update_dron_metricas_updated_at", "dron_metricas"]
]
triggers.each do |nombre_trigger, tabla|
db.execute("DROP TRIGGER IF EXISTS #{nombre_trigger} ON bitacoras.#{tabla}")
db.execute(<<~SQL)
CREATE TRIGGER #{nombre_trigger}
BEFORE UPDATE ON bitacoras.#{tabla}
FOR EACH ROW
EXECUTE FUNCTION bitacoras.update_updated_at_column()
SQL
puts "📈 Trigger creado: #{nombre_trigger} en #{tabla}"
end
# ============================================================================
# Vistas útiles
# ============================================================================
puts "\n📝 Creando vistas útiles..."
# Vista: Drones activos por estado
db.execute(<<~SQL)
CREATE OR REPLACE VIEW bitacoras.vw_dron_estado AS
SELECT
estado,
tipo,
COUNT(*) as cantidad,
MIN(started_at) as mas_antiguo,
MAX(started_at) as mas_reciente
FROM bitacoras.dron_logs
WHERE estado IN ('running', 'pending')
GROUP BY estado, tipo
SQL
puts "✅ Vista creada: vw_dron_estado"
# Vista: Drones zombies
db.execute(<<~SQL)
CREATE OR REPLACE VIEW bitacoras.vw_dron_zombies AS
SELECT
id,
dron_id,
tipo,
cmd,
started_at,
heartbeat,
EXTRACT(EPOCH FROM (NOW() - heartbeat))/60 as minutos_sin_heartbeat
FROM bitacoras.dron_logs
WHERE estado = 'running'
AND heartbeat < NOW() - INTERVAL '5 minutes'
SQL
puts "✅ Vista creada: vw_dron_zombies"
# Vista: Métricas semanales
db.execute(<<~SQL)
CREATE OR REPLACE VIEW bitacoras.vw_dron_metricas_semanal AS
SELECT
fecha,
dron_tipo,
total_ejecuciones,
completados,
fallidos,
zombies,
tiempo_promedio,
reintentos_total,
ROUND(100.0 * completados / NULLIF(total_ejecuciones, 0), 2) as tasa_exito
FROM bitacoras.dron_metricas
WHERE fecha >= CURRENT_DATE - INTERVAL '7 days'
ORDER BY fecha DESC, dron_tipo
SQL
puts "✅ Vista creada: vw_dron_metricas_semanal"
# ============================================================================
# Comentarios de documentación
# ============================================================================
puts "\n📝 Agregando comentarios de documentación..."
db.execute("COMMENT ON TABLE bitacoras.dron_logs IS 'Logs detallados de cada dron en la colonia (NO visible directamente en Bitácora Web)'")
db.execute("COMMENT ON TABLE bitacoras.dron_avances IS 'Avances paso-a-paso de flujos multi-paso (orquestación)'")
db.execute("COMMENT ON TABLE bitacoras.dron_metricas IS 'Métricas agregadas diarias por tipo de dron (para dashboard y estadísticas)'")
puts "✅ Comentarios agregados"
end
puts "\n🎉 Migration 003 completed successfully!"
puts "\nResumen:"
puts " - bitacoras.dron_logs: Logs detallados"
puts " - bitacoras.dron_avances: Avances paso-a-paso"
puts " - bitacoras.dron_metricas: Métricas agregadas"
puts " - Vistas: vw_dron_estado, vw_dron_zombies, vw_dron_metricas_semanal"
puts "\n🏁 Migration finished."