# 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."