413 lines
12 KiB
Ruby
Executable File
413 lines
12 KiB
Ruby
Executable File
#!/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
|