#!/usr/bin/env ruby # frozen_string_literal: true # tools/orquestador/dashboard/agregar_hitos_db.rb # ============================================== # Herramienta para agregar hitos de fase 5 a la base de datos PostgreSQL # de dtic-BITACORAs (contenedor Docker) # Filosofía: "Menos es más" - herramienta pequeña, específica y reutilizable require 'pg' require 'time' class HitosDBManager # Configuración de conexión a PostgreSQL DB_CONFIG = { host: 'localhost', port: 5433, dbname: 'dtic_bitacoras', user: 'dtic_user', password: 'dtic_pass_2026' }.freeze # Hitos de fase 5 para agregar HITOS_FASE_5 = [ { id_hito: 'BKP02', titulo: '🔒 Backup Automático de VMs en Proxmox', descripcion: 'Configurar backups automáticos VZDump programados para DC y SQL Server. Establecer retención de 7 días, almacenamiento en NAS/SMB compartido.', estado: '⏳', horas_presencial: 2, horas_remoto: 2, nodo_nombre: 'srv-dasu' }, { id_hito: 'DB02', titulo: '🗄️ Backup Automático de Base de Datos sysdasuten', descripcion: 'Configurar jobs de SQL Server Agent para backup diario completo y log cada 15 minutos. Verificar restauración periódica en ambiente de pruebas.', estado: '⏳', horas_presencial: 0, horas_remoto: 2, nodo_nombre: 'sql-dasuten' }, { id_hito: 'MON01', titulo: '📊 Monitoreo de Recursos del Hipervisor', descripcion: 'Implementar sistema de monitoreo básico para CPU, RAM, almacenamiento y red de srv-dasu. Alertas por email/Telegram para recursos críticos.', estado: '⏳', horas_presencial: 0, horas_remoto: 3, nodo_nombre: 'srv-dasu' }, { id_hito: 'DOC01', titulo: '📋 Documentación Operativa del Sistema', descripcion: 'Crear manual de operaciones: procedimientos de backup/restore, acceso remoto, resolución de problemas comunes, contactos de soporte, diagramas de red.', estado: '⏳', horas_presencial: 0, horas_remoto: 6, nodo_nombre: 'srv-ns8' }, { id_hito: 'PERF01', titulo: '⚡ Pruebas de Rendimiento del Sistema', descripcion: 'Realizar pruebas de carga simulada en sistema DASUTEN. Medir tiempos de respuesta, consumo de recursos, identificar cuellos de botella. Generar reporte de performance.', estado: '⏳', horas_presencial: 2, horas_remoto: 2, nodo_nombre: 'pcv-dasu0' } ].freeze def initialize @conn = nil end def conectar_db puts "🔌 Conectando a PostgreSQL..." puts " • Host: #{DB_CONFIG[:host]}" puts " • Puerto: #{DB_CONFIG[:port]}" puts " • Base de datos: #{DB_CONFIG[:dbname]}" begin @conn = PG.connect(DB_CONFIG) puts "✅ Conexión exitosa" true rescue PG::Error => e puts "❌ Error de conexión: #{e.message}" puts " Verifique que:" puts " 1. El contenedor PostgreSQL esté ejecutándose" puts " 2. El puerto 5433 esté accesible" puts " 3. Las credenciales sean correctas" false end end def desconectar_db @conn&.close puts "🔌 Conexión cerrada" end def verificar_estructura puts "🔍 Verificando estructura de la base de datos..." tablas_requeridas = ['proyectos', 'fases', 'hitos', 'nodos'] tablas_encontradas = [] tablas_requeridas.each do |tabla| resultado = @conn.exec("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'bitacoras' AND table_name = $1)", [tabla]) existe = resultado[0]['exists'] == 't' tablas_encontradas << tabla if existe puts " • #{tabla}: #{existe ? '✅' : '❌'}" end if tablas_encontradas.size == tablas_requeridas.size puts "✅ Todas las tablas requeridas existen" true else puts "❌ Faltan tablas requeridas" false end end def obtener_proyecto_p2601 puts "📋 Buscando proyecto P2601..." resultado = @conn.exec("SELECT id, codigo, nombre FROM bitacoras.proyectos WHERE codigo = $1", ['P2601']) if resultado.ntuples.zero? puts "❌ Proyecto P2601 no encontrado" nil else proyecto = resultado[0] puts "✅ Proyecto encontrado: #{proyecto['codigo']} - #{proyecto['nombre']}" proyecto end end def obtener_fase_5(proyecto_id) puts "🔍 Buscando fase 5 del proyecto..." resultado = @conn.exec("SELECT id, numero, nombre FROM bitacoras.fases WHERE proyecto_id = $1 AND numero = 5", [proyecto_id]) if resultado.ntuples.zero? puts "❌ Fase 5 no encontrada" nil else fase = resultado[0] puts "✅ Fase encontrada: #{fase['numero']} - #{fase['nombre']}" fase end end def listar_hitos_existentes(fase_id) puts "📋 Listando hitos existentes en fase 5..." resultado = @conn.exec("SELECT id_hito, titulo, estado FROM bitacoras.hitos WHERE fase_id = $1 ORDER BY id_hito", [fase_id]) if resultado.ntuples.zero? puts " ℹ️ No hay hitos en esta fase" [] else puts " 📊 Hitos encontrados: #{resultado.ntuples}" resultado.each do |hito| puts " • #{hito['id_hito']}: #{hito['titulo']} (#{hito['estado']})" end resultado.to_a end end def agregar_hitos_fase_5 puts "🎯 AGREGANDO HITOS A FASE 5" puts "=" * 60 # Conectar a la base de datos unless conectar_db puts "❌ No se pudo conectar a la base de datos" return false end begin # Verificar estructura unless verificar_estructura puts "❌ La estructura de la base de datos no es válida" return false end # Obtener proyecto P2601 proyecto = obtener_proyecto_p2601 return false unless proyecto # Obtener fase 5 fase = obtener_fase_5(proyecto['id']) return false unless fase # Listar hitos existentes hitos_existentes = listar_hitos_existentes(fase['id']) ids_existentes = hitos_existentes.map { |h| h['id_hito'] } # Filtrar hitos que no existen hitos_a_agregar = HITOS_FASE_5.reject { |h| ids_existentes.include?(h[:id_hito]) } if hitos_a_agregar.empty? puts "ℹ️ Todos los hitos de fase 5 ya existen en la base de datos" return true end puts "📝 Agregando #{hitos_a_agregar.size} hitos nuevos..." # Agregar cada hito hitos_agregados = [] hitos_a_agregar.each do |hito| if agregar_hito(fase['id'], hito) hitos_agregados << hito[:id_hito] end end # Mostrar resultados mostrar_resultados(hitos_agregados, hitos_a_agregar.size) hitos_agregados.any? ensure desconectar_db end end def agregar_hito(fase_id, hito) puts " ➕ Agregando hito: #{hito[:id_hito]}" begin # Obtener ID del nodo si se especifica nodo_id = nil if hito[:nodo_nombre] resultado = @conn.exec("SELECT id FROM bitacoras.nodos WHERE nombre = $1", [hito[:nodo_nombre]]) nodo_id = resultado[0]['id'] if resultado.ntuples > 0 end # Insertar hito @conn.exec( "INSERT INTO bitacoras.hitos (fase_id, id_hito, titulo, descripcion, estado, horas_presencial, horas_remoto, nodo_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", [ fase_id, hito[:id_hito], hito[:titulo], hito[:descripcion], hito[:estado], hito[:horas_presencial], hito[:horas_remoto], nodo_id ] ) puts " ✅ #{hito[:id_hito]} agregado exitosamente" true rescue PG::Error => e puts " ❌ Error al agregar #{hito[:id_hito]}: #{e.message}" false end end def mostrar_resultados(hitos_agregados, total_intentos) puts "\n📊 RESULTADOS DE LA OPERACIÓN" puts "=" * 60 if hitos_agregados.empty? puts "ℹ️ No se agregaron hitos nuevos" else puts "✅ Hitos agregados exitosamente: #{hitos_agregados.size}/#{total_intentos}" hitos_agregados.each do |id_hito| puts " • #{id_hito}" end end # Mostrar hitos de fase 5 puts "\n🎯 HITOS DE FASE 5 COMPLETOS:" HITOS_FASE_5.each do |hito| existe = hitos_agregados.include?(hito[:id_hito]) ? '✅' : '📋' puts " #{existe} #{hito[:id_hito]}: #{hito[:titulo]}" end puts "\n🔗 Dashboard actualizado: https://ns8.frlr.utn.edu.ar/bitacoras/p2601" end def ver_estado_actual puts "🔍 ESTADO ACTUAL DEL DASHBOARD P2601" puts "=" * 60 unless conectar_db puts "❌ No se pudo conectar a la base de datos" return false end begin # Obtener proyecto P2601 proyecto = obtener_proyecto_p2601 return false unless proyecto # Obtener todas las fases resultado = @conn.exec("SELECT id, numero, nombre FROM bitacoras.fases WHERE proyecto_id = $1 ORDER BY numero", [proyecto['id']]) puts "📋 PROYECTO: #{proyecto['codigo']} - #{proyecto['nombre']}" puts "📊 FASES: #{resultado.ntuples}" resultado.each do |fase| puts "\n📁 Fase #{fase['numero']}: #{fase['nombre']}" # Obtener hitos de esta fase hitos = @conn.exec("SELECT id_hito, titulo, estado, horas_presencial, horas_remoto FROM bitacoras.hitos WHERE fase_id = $1 ORDER BY id_hito", [fase['id']]) if hitos.ntuples.zero? puts " ℹ️ Sin hitos" else puts " 📝 Hitos: #{hitos.ntuples}" hitos.each do |hito| estado_emoji = hito['estado'] horas = " (P:#{hito['horas_presencial']}h, R:#{hito['horas_remoto']}h)" puts " • #{estado_emoji} #{hito['id_hito']}: #{hito['titulo']}#{horas}" end end end # Métricas generales puts "\n📈 MÉTRICAS GENERALES:" metricas = @conn.exec(" SELECT COUNT(*) as total_hitos, COUNT(*) FILTER (WHERE h.estado = '✅') as completados, COUNT(*) FILTER (WHERE h.estado != '✅') as pendientes, COALESCE(SUM(h.horas_presencial), 0) as total_presencial, COALESCE(SUM(h.horas_remoto), 0) as total_remoto FROM bitacoras.hitos h JOIN bitacoras.fases f ON h.fase_id = f.id WHERE f.proyecto_id = $1 ", [proyecto['id']]) if metricas.ntuples > 0 m = metricas[0] puts " • Total hitos: #{m['total_hitos']}" puts " • Completados: #{m['completados']}" puts " • Pendientes: #{m['pendientes']}" puts " • Horas presenciales: #{m['total_presencial']}h" puts " • Horas remotas: #{m['total_remoto']}h" puts " • Total horas: #{m['total_presencial'].to_i + m['total_remoto'].to_i}h" end true ensure desconectar_db end end end # Interfaz de línea de comandos if __FILE__ == $0 begin manager = HitosDBManager.new # Parsear argumentos command = ARGV[0] || 'agregar' case command when 'agregar', 'add' success = manager.agregar_hitos_fase_5 when 'estado', 'status' success = manager.ver_estado_actual when 'help', '--help', '-h' puts "📖 USO:" puts " #{$0} [comando]" puts puts "COMANDOS:" puts " agregar, add - Agregar hitos de fase 5 a la base de datos" puts " estado, status - Ver estado actual del dashboard P2601" puts " help - Mostrar esta ayuda" puts puts "DESCRIPCIÓN:" puts " Esta herramienta agrega hitos de fase 5 al dashboard P2601" puts " que se encuentra en: https://ns8.frlr.utn.edu.ar/bitacoras/p2601" puts puts "REQUISITOS:" puts " 1. Contenedor PostgreSQL de dtic-BITACORAs ejecutándose" puts " 2. Puerto 5433 accesible localmente" puts " 3. Credenciales de base de datos configuradas" puts puts "HITOS DE FASE 5:" HitosDBManager::HITOS_FASE_5.each do |hito| puts " • #{hito[:id_hito]}: #{hito[:titulo]}" end puts success = true else puts "❌ Comando '#{command}' no reconocido" puts " Use '#{$0} help' para ver la ayuda" success = false end if success puts "\n🚀 PRÓXIMOS PASOS:" puts " 1. Verificar dashboard: https://ns8.frlr.utn.edu.ar/bitacoras/p2601" puts " 2. Los cambios deberían reflejarse automáticamente" puts " 3. Actualizar estados según progreso real" puts puts "📅 #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}" puts "=" * 60 else exit 1 end rescue => e puts "❌ Error: #{e.message}" puts e.backtrace if ENV['DEBUG'] exit 1 end end