Refactor(ADN): Transición absoluta a DB-First, extirpación de MDExporter e inferencia de auto-modo
This commit is contained in:
@@ -46,7 +46,7 @@ module BitacorasDB
|
||||
ESTADOS_VALIDOS = ['⏳', '✅', '❌', '⚠️', '⏸️'].freeze
|
||||
|
||||
# Modos válidos para entradas
|
||||
MODOS_VALIDOS = ['P', 'R'].freeze # P=Presencial, R=Remoto
|
||||
MODOS_VALIDOS = ['P', 'R', 'S'].freeze # P=Presencial, R=Remoto, S=Servicio
|
||||
|
||||
# Inicializa la conexión a la base de datos
|
||||
#
|
||||
@@ -281,7 +281,7 @@ module BitacorasDB
|
||||
# @param activo [Boolean] Si el nodo está activo
|
||||
# @return [Hash] Nodo creado con ID
|
||||
def create_nodo(nombre:, ip: nil, tipo: 'servidor', descripcion: nil, activo: true)
|
||||
tipos_validos = ['servidor', 'hipervisor', 'vm', 'pc', 'vmware', 'xenserver']
|
||||
tipos_validos = ['servidor', 'hipervisor', 'vm', 'pc', 'vmware', 'xenserver', 'servicio']
|
||||
|
||||
unless tipos_validos.include?(tipo.downcase)
|
||||
raise ArgumentError, "Tipo de nodo inválido. Válidos: #{tipos_validos.join(', ')}"
|
||||
@@ -365,13 +365,28 @@ module BitacorasDB
|
||||
# @param bitacora_id [Integer, nil] ID de la bitácora
|
||||
# @param nodo_id [Integer, nil] ID del nodo
|
||||
# @return [Hash] Entrada creada con ID
|
||||
def create_entrada(inicio:, descripcion:, fin: nil, estado: '⏳', modo: 'P', es_ia: false,
|
||||
def create_entrada(inicio:, descripcion:, fin: nil, estado: nil, modo: nil, es_ia: false,
|
||||
tema_id: nil, bitacora_id: nil, nodo_id: nil)
|
||||
|
||||
# Validar formato de tiempo
|
||||
inicio_time = parse_time(inicio)
|
||||
fin_time = parse_time(fin) if fin
|
||||
|
||||
# Inferir Estado
|
||||
if estado.nil? || estado.strip.empty?
|
||||
estado = fin_time ? '✅' : '⏳'
|
||||
end
|
||||
|
||||
# Inferir Modo
|
||||
if modo.nil? || modo.strip.empty?
|
||||
if bitacora_id
|
||||
res = @connection.exec_params("SELECT modo FROM #{SCHEMA}.#{TABLES[:entradas]} WHERE bitacora_id = $1 ORDER BY id DESC LIMIT 1", [bitacora_id])
|
||||
modo = res.ntuples > 0 ? res[0]['modo'] : 'P'
|
||||
else
|
||||
modo = 'P'
|
||||
end
|
||||
end
|
||||
|
||||
# Validar estado
|
||||
unless ESTADOS_VALIDOS.include?(estado)
|
||||
raise ArgumentError, "Estado inválido. Válidos: #{ESTADOS_VALIDOS.join(', ')}"
|
||||
@@ -400,6 +415,44 @@ module BitacorasDB
|
||||
result.first
|
||||
end
|
||||
|
||||
# Actualiza una entrada existente
|
||||
def update_entrada(id, params)
|
||||
# Auto-inferir estado si se está mandando una hora de fin y no se fuerza un estado explícito
|
||||
if params.key?(:fin) && !params.key?(:estado)
|
||||
if params[:fin].nil? || params[:fin].to_s.strip.empty?
|
||||
params[:estado] = '⏳'
|
||||
else
|
||||
params[:estado] = '✅'
|
||||
end
|
||||
end
|
||||
|
||||
set_clauses = []
|
||||
values = []
|
||||
i = 1
|
||||
|
||||
params.each do |k, v|
|
||||
if [:fin, :descripcion, :estado, :modo, :es_ia].include?(k)
|
||||
val = (k == :fin) ? parse_time(v) : v
|
||||
set_clauses << "#{k} = $#{i}"
|
||||
values << val
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
return nil if set_clauses.empty?
|
||||
|
||||
query = <<~SQL
|
||||
UPDATE #{SCHEMA}.#{TABLES[:entradas]}
|
||||
SET #{set_clauses.join(', ')}, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $#{i}
|
||||
RETURNING *
|
||||
SQL
|
||||
|
||||
values << id
|
||||
result = @connection.exec_params(query, values)
|
||||
result.first&.to_h
|
||||
end
|
||||
|
||||
# Lista entradas con filtros avanzados
|
||||
#
|
||||
# @param bitacora_id [Integer, nil] Filtrar por bitácora
|
||||
@@ -630,6 +683,107 @@ module BitacorasDB
|
||||
create_bitacora(fecha: Date.today, resumen: resumen)
|
||||
end
|
||||
|
||||
|
||||
# ==========================================
|
||||
# PROYECTOS, FASES E HITOS (Planes Sistemáticos)
|
||||
# ==========================================
|
||||
|
||||
def find_proyecto_by_codigo(codigo)
|
||||
q = "SELECT * FROM bitacoras.proyectos WHERE codigo = $1"
|
||||
res = execute(q, [codigo.upcase])
|
||||
return nil if res.ntuples.zero?
|
||||
res.first.to_h
|
||||
end
|
||||
|
||||
def list_hitos(proyecto_codigo)
|
||||
proyecto = find_proyecto_by_codigo(proyecto_codigo)
|
||||
raise StandardError, "Proyecto no encontrado: #{proyecto_codigo}" unless proyecto
|
||||
|
||||
q = <<~SQL
|
||||
SELECT f.numero as fase_num, f.nombre as fase_nombre,
|
||||
h.id, h.id_hito, h.titulo, h.estado, h.horas_presencial, h.horas_remoto
|
||||
FROM bitacoras.fases f
|
||||
JOIN bitacoras.hitos h ON h.fase_id = f.id
|
||||
WHERE f.proyecto_id = $1
|
||||
ORDER BY f.numero, h.id_hito
|
||||
SQL
|
||||
res = execute(q, [proyecto['id']])
|
||||
res.map(&:to_h)
|
||||
end
|
||||
|
||||
def rename_hito_id(proyecto_codigo, id_actual, id_nuevo)
|
||||
q_busqueda = <<~SQL
|
||||
SELECT h.id, h.titulo
|
||||
FROM bitacoras.hitos h
|
||||
JOIN bitacoras.fases f ON h.fase_id = f.id
|
||||
JOIN bitacoras.proyectos p ON f.proyecto_id = p.id
|
||||
WHERE p.codigo = $1 AND h.id_hito = $2
|
||||
SQL
|
||||
|
||||
res = execute(q_busqueda, [proyecto_codigo.upcase, id_actual])
|
||||
raise StandardError, "Hito #{id_actual} del proyecto #{proyecto_codigo} no encontrado" if res.ntuples.zero?
|
||||
|
||||
hito = res.first
|
||||
|
||||
q_update = "UPDATE bitacoras.hitos SET id_hito = $1 WHERE id = $2 RETURNING titulo"
|
||||
res_update = execute(q_update, [id_nuevo, hito['id']])
|
||||
|
||||
res_update.first
|
||||
end
|
||||
|
||||
def update_hito(proyecto_codigo, id_hito, estado: nil, horas_presencial: nil, horas_remoto: nil, descripcion: nil)
|
||||
q_busqueda = <<~SQL
|
||||
SELECT h.id, h.horas_presencial, h.horas_remoto, h.estado, h.descripcion
|
||||
FROM bitacoras.hitos h
|
||||
JOIN bitacoras.fases f ON h.fase_id = f.id
|
||||
JOIN bitacoras.proyectos p ON f.proyecto_id = p.id
|
||||
WHERE p.codigo = $1 AND h.id_hito = $2
|
||||
SQL
|
||||
|
||||
res = execute(q_busqueda, [proyecto_codigo.upcase, id_hito])
|
||||
raise StandardError, "Hito #{id_hito} del proyecto #{proyecto_codigo} no encontrado" if res.ntuples.zero?
|
||||
|
||||
hito = res.first
|
||||
|
||||
updates = []
|
||||
params = []
|
||||
param_count = 1
|
||||
|
||||
if estado
|
||||
updates << "estado = $#{param_count}"
|
||||
params << estado
|
||||
param_count += 1
|
||||
end
|
||||
|
||||
unless horas_presencial.nil?
|
||||
updates << "horas_presencial = $#{param_count}"
|
||||
params << horas_presencial
|
||||
param_count += 1
|
||||
end
|
||||
|
||||
unless horas_remoto.nil?
|
||||
updates << "horas_remoto = $#{param_count}"
|
||||
params << horas_remoto
|
||||
param_count += 1
|
||||
end
|
||||
|
||||
if descripcion
|
||||
updates << "descripcion = $#{param_count}"
|
||||
params << descripcion
|
||||
param_count += 1
|
||||
end
|
||||
|
||||
return hito if updates.empty?
|
||||
|
||||
updates << "updated_at = NOW()"
|
||||
params << hito['id']
|
||||
|
||||
query = "UPDATE bitacoras.hitos SET #{updates.join(', ')} WHERE id = $#{param_count} RETURNING *"
|
||||
|
||||
resultado = execute(query, params)
|
||||
resultado.first.to_h
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Ruta por defecto al archivo de configuración
|
||||
|
||||
Reference in New Issue
Block a user