Reestructuración modular integral de servicios, reorganización de gestión en docs/ y limpieza profunda del repositorio
This commit is contained in:
+412
@@ -0,0 +1,412 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user