Refactor(ADN): Transición absoluta a DB-First, extirpación de MDExporter e inferencia de auto-modo
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# tools/adn/comando/cierre.rb — Subcomando para cierre de jornada laboral (DB-First)
|
||||
# ==============================================================================
|
||||
|
||||
require 'date'
|
||||
require 'time'
|
||||
require_relative '../../bitacoras-db/core/bitacora_db'
|
||||
|
||||
module ADN
|
||||
class SubcomandoCierre
|
||||
module Color
|
||||
RESET = "\e[0m"
|
||||
BOLD = "\e[1m"
|
||||
GREEN = "\e[32m"
|
||||
CYAN = "\e[36m"
|
||||
YELLOW = "\e[33m"
|
||||
RED = "\e[31m"
|
||||
DIM = "\e[2m"
|
||||
end
|
||||
|
||||
def initialize(args, logger)
|
||||
@args = args
|
||||
@logger = logger
|
||||
@fecha = Date.today
|
||||
end
|
||||
|
||||
def ejecutar
|
||||
if @args.empty? || !(@args.include?('jornada'))
|
||||
puts "#{Color::YELLOW}Uso: ./tools/adn/run cierre de jornada [presencial|remoto]#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
modo = @args.include?('presencial') ? 'P' : 'R'
|
||||
tipo_str = modo == 'P' ? 'Presencial' : 'Remoto'
|
||||
hora_fin = Time.now.strftime('%H:%M')
|
||||
|
||||
@logger.info("Iniciando cierre de jornada #{tipo_str} (Modo DB-First)")
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
# 1. Buscar la bitácora
|
||||
bitacora = db.find_bitacora_by_fecha(@fecha)
|
||||
unless bitacora
|
||||
@logger.error("No existe bitácora en DB para hoy (#{@fecha}). ¿Iniciaste la jornada?")
|
||||
return
|
||||
end
|
||||
|
||||
# 2. Obtener nodo srv-ns8
|
||||
nodo = db.find_nodo_by_nombre('srv-ns8')
|
||||
|
||||
# 3. Crear entrada de cierre
|
||||
db.create_entrada(
|
||||
inicio: hora_fin,
|
||||
fin: hora_fin,
|
||||
descripcion: "Cierre de jornada #{tipo_str.downcase}. Sincronización final y validación de backups.",
|
||||
estado: '✅',
|
||||
modo: modo,
|
||||
bitacora_id: bitacora['id'],
|
||||
nodo_id: nodo ? nodo['id'] : nil
|
||||
)
|
||||
|
||||
@logger.exito("Cierre de jornada registrado en Base de Datos.")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+253
-150
@@ -28,6 +28,7 @@ require 'time'
|
||||
begin
|
||||
require_relative '../../bitacoras-db/core/bitacora_db'
|
||||
require_relative '../../bitacoras-db/importers/md_importer'
|
||||
require_relative '../../bitacoras-db/exporters/md_exporter'
|
||||
rescue LoadError => e
|
||||
# Fallback para desarrollo
|
||||
puts "⚠ Advertencia: No se pudo cargar BitacoraDB o MDImporter: #{e.message}"
|
||||
@@ -67,12 +68,20 @@ module ADN
|
||||
comando_evento_crear(sub_args)
|
||||
when 'evento:listar'
|
||||
comando_evento_listar(sub_args)
|
||||
when 'evento:actualizar'
|
||||
comando_evento_actualizar(sub_args)
|
||||
when 'nodo:crear'
|
||||
comando_nodo_crear(sub_args)
|
||||
when 'nodo:listar'
|
||||
comando_nodo_listar(sub_args)
|
||||
when 'migrar:md'
|
||||
comando_migrar_md(sub_args)
|
||||
when 'hito:listar'
|
||||
comando_hito_listar(sub_args)
|
||||
when 'hito:actualizar'
|
||||
comando_hito_actualizar(sub_args)
|
||||
when 'hito:renombrar'
|
||||
comando_hito_renombrar(sub_args)
|
||||
when 'hito:ordenar'
|
||||
comando_hito_ordenar(sub_args)
|
||||
when 'estadisticas'
|
||||
comando_estadisticas
|
||||
when 'backup'
|
||||
@@ -266,8 +275,8 @@ module ADN
|
||||
inicio: nil,
|
||||
descripcion: nil,
|
||||
fin: nil,
|
||||
estado: '⏳',
|
||||
modo: 'P',
|
||||
estado: nil,
|
||||
modo: nil,
|
||||
es_ia: false,
|
||||
nodo: nil,
|
||||
tema: nil
|
||||
@@ -370,6 +379,59 @@ module ADN
|
||||
end
|
||||
end
|
||||
|
||||
# Comando: evento:actualizar <ID> [OPCIONES] - Actualizar una entrada existente
|
||||
def comando_evento_actualizar(args)
|
||||
if args.length < 1 || args[0].start_with?('-')
|
||||
puts "#{Color::YELLOW}Uso: adn db evento:actualizar <ID> [OPCIONES]#{Color::RESET}"
|
||||
puts "Opciones soportadas: --fin \"HH:MM\", --inicio \"HH:MM\", --estado \"✅\", --descripcion \"Texto\""
|
||||
return
|
||||
end
|
||||
|
||||
id_evento = args.shift.to_i
|
||||
opts = {}
|
||||
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.banner = "Opciones:"
|
||||
opt.on("--inicio HH:MM", "Nueva hora de inicio") { |i| opts[:inicio] = i }
|
||||
opt.on("--fin HH:MM", "Nueva hora de fin") { |f| opts[:fin] = f }
|
||||
opt.on("--descripcion TEXTO", "Nueva descripción") { |d| opts[:descripcion] = d }
|
||||
opt.on("--estado ESTADO", "Nuevo estado (⏳, ✅, ❌, ⚠️, ⏸️)") { |e| opts[:estado] = e }
|
||||
opt.on("-h", "--help", "Mostrar ayuda") do
|
||||
puts opt
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
parser.parse!(args)
|
||||
rescue OptionParser::InvalidOption => e
|
||||
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
if opts.empty?
|
||||
puts "#{Color::YELLOW}⚠ No se especificó nada para actualizar. Use --fin, --inicio, --descripcion o --estado#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
puts "#{Color::CYAN}🔄 Actualizando evento #{id_evento}...#{Color::RESET}"
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
begin
|
||||
res = db.update_entrada(id_evento, opts)
|
||||
if res
|
||||
puts "#{Color::GREEN}✅ Evento #{id_evento} actualizado exitosamente.#{Color::RESET}"
|
||||
puts " #{Color::BOLD}Inicio:#{Color::RESET} #{res['inicio']}"
|
||||
puts " #{Color::BOLD}Fin:#{Color::RESET} #{res['fin'] || '(sin fin)'}"
|
||||
else
|
||||
puts "#{Color::YELLOW}⚠ No se pudo actualizar el evento. ¿Existe el ID #{id_evento}?#{Color::RESET}"
|
||||
end
|
||||
rescue => e
|
||||
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Comando: evento:listar [OPCIONES] - Listar entradas con filtros
|
||||
def comando_evento_listar(args)
|
||||
opts = { limit: 20, offset: 0, nodo: nil, estado: nil, buscar: nil, desde: nil, hasta: nil }
|
||||
@@ -598,90 +660,216 @@ module ADN
|
||||
end
|
||||
end
|
||||
|
||||
# Comando: migrar:md [FECHA] - Migrar bitácora .md a base de datos
|
||||
def comando_migrar_md(args)
|
||||
opts = { sobrescribir: false, crear_nodos: true, todo: false }
|
||||
# Comando: hito:listar [PROYECTO]
|
||||
def comando_hito_listar(args)
|
||||
if args.empty?
|
||||
puts "#{Color::YELLOW}Uso: adn db hito:listar <CODIGO_PROYECTO> (ej: P2605)#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
codigo = args[0]
|
||||
puts "#{Color::CYAN}📋 Listando hitos del proyecto #{codigo.upcase}...#{Color::RESET}"
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
begin
|
||||
hitos = db.list_hitos(codigo)
|
||||
if hitos.empty?
|
||||
puts "#{Color::YELLOW}⚠ No se encontraron hitos o el proyecto no existe.#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
puts ""
|
||||
puts "#{Color::BOLD}#{"Hito".ljust(8)} #{"Fase".ljust(6)} #{"Estado".ljust(8)} #{"Hrs P/R".ljust(10)} #{"Título"}#{Color::RESET}"
|
||||
puts "#{"─" * 8} #{"─" * 6} #{"─" * 8} #{"─" * 10} #{"─" * 40}"
|
||||
|
||||
hitos.each do |h|
|
||||
hrs = "#{h['horas_presencial']}/#{h['horas_remoto']}"
|
||||
puts "#{h['id_hito'].ljust(8)} #{h['fase_num'].to_s.ljust(6)} #{h['estado'].ljust(8)} #{hrs.ljust(10)} #{h['titulo']}"
|
||||
end
|
||||
puts "\nTotal: #{hitos.size} hitos"
|
||||
rescue => e
|
||||
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Comando: hito:ordenar <PROYECTO> [--aplicar FILE]
|
||||
def comando_hito_ordenar(args)
|
||||
codigo = args.shift
|
||||
if !codigo
|
||||
puts "#{Color::YELLOW}Uso: adn db hito:ordenar <PROYECTO> [--aplicar /ruta/al/archivo.txt]#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
aplicar_path = nil
|
||||
if args[0] == '--aplicar' && args[1]
|
||||
aplicar_path = args[1]
|
||||
end
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
begin
|
||||
hitos = db.list_hitos(codigo)
|
||||
if hitos.empty?
|
||||
puts "#{Color::YELLOW}⚠ No se encontraron hitos para #{codigo.upcase}.#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if aplicar_path
|
||||
unless File.exist?(aplicar_path)
|
||||
puts "#{Color::RED}✗ No se encontró el archivo: #{aplicar_path}#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
# Procesar el archivo y aplicarrenombramientos lógicos "Fase.Grupo.Paso"
|
||||
puts "#{Color::CYAN}📋 Aplicando ordenamiento desde #{aplicar_path}...#{Color::RESET}"
|
||||
lineas = File.readlines(aplicar_path).map(&:strip).reject { |l| l.empty? || l.start_with?('#') }
|
||||
|
||||
# Agrupar por fase según el orden de las líneas
|
||||
hitos_por_fase = Hash.new { |h, k| h[k] = [] }
|
||||
lineas.each do |linea|
|
||||
if linea =~ /^(\d+):\s*\[.+?\]\s*(.+)$/
|
||||
id_db = $1.to_i
|
||||
hito = hitos.find { |h| h['id'] == id_db }
|
||||
hitos_por_fase[hito['fase_num']] << hito if hito
|
||||
end
|
||||
end
|
||||
|
||||
# Iniciar renombrado secuencial
|
||||
hitos_por_fase.each do |fase, hitos_fase|
|
||||
grupo = 1
|
||||
letra = 65 # 'A'
|
||||
|
||||
hitos_fase.each do |h|
|
||||
nuevo_id = "#{fase}.#{grupo}.#{letra.chr}"
|
||||
|
||||
if h['id_hito'] != nuevo_id
|
||||
db.rename_hito_id(codigo, h['id_hito'], nuevo_id)
|
||||
puts " #{Color::GREEN}✔ #{h['id_hito'].ljust(8)} → #{nuevo_id.ljust(8)}#{Color::RESET} | #{h['titulo']}"
|
||||
else
|
||||
puts " #{Color::DIM}• #{nuevo_id.ljust(8)} (Sin cambios) | #{h['titulo']}#{Color::RESET}"
|
||||
end
|
||||
|
||||
letra += 1
|
||||
if letra > 67 # 'C'
|
||||
letra = 65
|
||||
grupo += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
puts "#{Color::GREEN}✅ Ordenamiento aplicado exitosamente a #{codigo.upcase}.#{Color::RESET}"
|
||||
|
||||
else
|
||||
# Generar archivo temporal para reordenamiento (Swarm-Ready)
|
||||
tmp_file = "/tmp/adn_ordenar_#{codigo.downcase}.txt"
|
||||
File.open(tmp_file, 'w') do |f|
|
||||
f.puts "# ==========================================================================="
|
||||
f.puts "# MODO ORDENAMIENTO DE HITOS - PROYECTO #{codigo.upcase}"
|
||||
f.puts "# ==========================================================================="
|
||||
f.puts "# Cambia el orden de las líneas (Cortar y Pegar) dentro de la misma FASE"
|
||||
f.puts "# para definir la secuencia lógica de ejecución."
|
||||
f.puts "# NO MODIFIQUES el número inicial ni los corchetes (ej: '42: [F3]')."
|
||||
f.puts "# Cuando termines de editar, guarda el archivo y ejecuta en la terminal:"
|
||||
f.puts "# adn db hito:ordenar #{codigo.upcase} --aplicar #{tmp_file}"
|
||||
f.puts "# ===========================================================================\n\n"
|
||||
|
||||
fases = hitos.group_by { |h| h['fase_num'] }.sort_by { |k, v| k }
|
||||
fases.each do |num_fase, hitos_fase|
|
||||
f.puts "# --- FASE #{num_fase} ---"
|
||||
hitos_fase.each do |h|
|
||||
f.puts "#{h['id']}: [F#{num_fase}] #{h['titulo']}"
|
||||
end
|
||||
f.puts "\n"
|
||||
end
|
||||
end
|
||||
|
||||
puts "#{Color::CYAN}✅ Archivo de configuración generado en: #{tmp_file}#{Color::RESET}"
|
||||
puts "1. Abre el archivo y reordena las líneas como necesites."
|
||||
puts "2. Ejecuta: #{Color::BOLD}adn db hito:ordenar #{codigo.upcase} --aplicar #{tmp_file}#{Color::RESET}"
|
||||
end
|
||||
rescue => e
|
||||
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Comando: hito:renombrar <PROYECTO> <ID_HITO_ACTUAL> <NUEVO_ID_HITO>
|
||||
def comando_hito_renombrar(args)
|
||||
if args.length < 3
|
||||
puts "#{Color::YELLOW}Uso: adn db hito:renombrar <PROYECTO> <ID_HITO_ACTUAL> <NUEVO_ID_HITO>#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
proyecto = args[0]
|
||||
id_actual = args[1]
|
||||
id_nuevo = args[2]
|
||||
|
||||
puts "#{Color::CYAN}🔄 Renombrando hito #{id_actual} → #{id_nuevo} (Proyecto #{proyecto.upcase})...#{Color::RESET}"
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
begin
|
||||
res = db.rename_hito_id(proyecto, id_actual, id_nuevo)
|
||||
puts "#{Color::GREEN}✅ Hito renombrado exitosamente. Título: #{res['titulo']}#{Color::RESET}"
|
||||
rescue => e
|
||||
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Comando: hito:actualizar <PROYECTO> <ID_HITO> [OPCIONES]
|
||||
def comando_hito_actualizar(args)
|
||||
if args.length < 2
|
||||
puts "#{Color::YELLOW}Uso: adn db hito:actualizar <PROYECTO> <ID_HITO> [OPCIONES]#{Color::RESET}"
|
||||
puts "Opciones soportadas: --estado=EJ, --horas-p=1.5, --horas-r=0, --desc=\"Texto\""
|
||||
return
|
||||
end
|
||||
|
||||
proyecto = args.shift
|
||||
id_hito = args.shift
|
||||
opts = { estado: nil, horas_presencial: nil, horas_remoto: nil, descripcion: nil }
|
||||
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.banner = "Uso: adn db migrar:md [FECHA] [OPCIONES]"
|
||||
opt.on("--sobrescribir", "Sobrescribir bitácora si ya existe") do
|
||||
opts[:sobrescribir] = true
|
||||
end
|
||||
opt.on("--no-crear-nodos", "No crear nodos automáticamente") do
|
||||
opts[:crear_nodos] = false
|
||||
end
|
||||
opt.on("--todo", "Importar todas las bitácoras del directorio") do
|
||||
opts[:todo] = true
|
||||
end
|
||||
opt.on("-h", "--help", "Mostrar ayuda") do
|
||||
puts opt
|
||||
exit
|
||||
end
|
||||
opt.banner = "Opciones:"
|
||||
opt.on("--estado ESTADO", "Nuevo estado (ej: ✅, ⏳, 📋)") { |e| opts[:estado] = e }
|
||||
opt.on("--horas-p HORAS", "Horas presenciales") { |h| opts[:horas_presencial] = h.to_f }
|
||||
opt.on("--horas-r HORAS", "Horas remotas") { |h| opts[:horas_remoto] = h.to_f }
|
||||
opt.on("--desc DESC", "Descripción o detalle para el hito") { |d| opts[:descripcion] = d }
|
||||
end
|
||||
|
||||
begin
|
||||
parser.parse!(args)
|
||||
rescue OptionParser::InvalidOption => e
|
||||
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
|
||||
puts parser
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Si se especificó --todo, importar todas las bitácoras
|
||||
if opts[:todo]
|
||||
importar_todas_bitacoras(opts)
|
||||
if opts.values.all?(&:nil?)
|
||||
puts "#{Color::YELLOW}⚠ No se especificó nada para actualizar. Use --estado, --horas-p, --horas-r o --desc#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
# Si no quedan argumentos, mostrar ayuda
|
||||
if args.empty?
|
||||
puts "#{Color::YELLOW}⚠ Se requiere fecha o --todo#{Color::RESET}"
|
||||
puts parser
|
||||
exit 1
|
||||
end
|
||||
|
||||
fecha_arg = args[0]
|
||||
|
||||
begin
|
||||
fecha = Date.parse(fecha_arg)
|
||||
rescue Date::Error
|
||||
puts "#{Color::RED}✗ Formato de fecha inválido: #{fecha_arg}#{Color::RESET}"
|
||||
puts " Usa formato YYYY-MM-DD (ej: 2026-03-07)"
|
||||
exit 1
|
||||
end
|
||||
|
||||
archivo_md = File.join(ADN::BITACORAS_DIR, "#{fecha}.md")
|
||||
|
||||
unless File.exist?(archivo_md)
|
||||
puts "#{Color::RED}✗ Archivo de bitácora no encontrado: #{archivo_md}#{Color::RESET}"
|
||||
puts " Verifica que la bitácora .md existe en #{ADN::BITACORAS_DIR}/"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "#{Color::CYAN}🔄 Migrando bitácora #{fecha} de .md a base de datos...#{Color::RESET}"
|
||||
puts " #{Color::DIM}Opciones: sobrescribir=#{opts[:sobrescribir]}, crear_nodos=#{opts[:crear_nodos]}#{Color::RESET}"
|
||||
puts "#{Color::CYAN}🔄 Actualizando hito #{id_hito} del proyecto #{proyecto.upcase}...#{Color::RESET}"
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
begin
|
||||
# Crear instancia del importador
|
||||
importer = BitacorasDB::Importers::MDImporter.new(db, @logger)
|
||||
|
||||
# Importar bitácora
|
||||
resultado = importer.importar_bitacora(
|
||||
archivo_md,
|
||||
sobrescribir: opts[:sobrescribir],
|
||||
crear_nodos: opts[:crear_nodos]
|
||||
res = db.update_hito(
|
||||
proyecto, id_hito,
|
||||
estado: opts[:estado],
|
||||
horas_presencial: opts[:horas_presencial],
|
||||
horas_remoto: opts[:horas_remoto],
|
||||
descripcion: opts[:descripcion]
|
||||
)
|
||||
|
||||
mostrar_resultado_migracion(resultado, fecha)
|
||||
puts "#{Color::GREEN}✅ Hito actualizado exitosamente.#{Color::RESET}"
|
||||
hrs = "#{res['horas_presencial'].to_f}P / #{res['horas_remoto'].to_f}R"
|
||||
puts " Estado actual : #{res['estado']}"
|
||||
puts " Horas imputadas : #{hrs}"
|
||||
rescue => e
|
||||
puts "#{Color::RED}✗ Error durante migración: #{e.message}#{Color::RESET}"
|
||||
puts "#{Color::DIM}#{e.backtrace.first}#{Color::RESET}" if ENV['ADN_DEBUG']
|
||||
puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Comando: estadisticas - Mostrar estadísticas del sistema
|
||||
|
||||
|
||||
def comando_estadisticas
|
||||
puts "#{Color::CYAN}📊 Estadísticas del sistema dtic-BITACORAs#{Color::RESET}"
|
||||
|
||||
@@ -817,91 +1005,7 @@ module ADN
|
||||
|
||||
end
|
||||
|
||||
# Importa todas las bitácoras del directorio bitacoras/
|
||||
|
||||
def importar_todas_bitacoras(opciones)
|
||||
puts "#{Color::CYAN}🔄 Migrando todas las bitácoras .md a base de datos...#{Color::RESET}"
|
||||
puts " #{Color::DIM}Opciones: sobrescribir=#{opciones[:sobrescribir]}, crear_nodos=#{opciones[:crear_nodos]}#{Color::RESET}"
|
||||
puts ""
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
# Crear instancia del importador
|
||||
importer = BitacorasDB::Importers::MDImporter.new(db, @logger)
|
||||
|
||||
# Importar directorio completo
|
||||
resultado = importer.importar_directorio(
|
||||
ADN::BITACORAS_DIR,
|
||||
sobrescribir: opciones[:sobrescribir],
|
||||
crear_nodos: opciones[:crear_nodos]
|
||||
)
|
||||
|
||||
# Mostrar resultados consolidados
|
||||
totales = resultado[:totales]
|
||||
estadisticas = resultado[:estadisticas]
|
||||
|
||||
puts ""
|
||||
puts "#{Color::BOLD}📊 Resultado de migración masiva:#{Color::RESET}"
|
||||
puts " #{Color::DIM}Archivos procesados:#{Color::RESET} #{totales[:archivos]}"
|
||||
puts " #{Color::DIM}Migraciones exitosas:#{Color::RESET} #{totales[:exitosos]}"
|
||||
puts " #{Color::DIM}Omitidos (ya existían):#{Color::RESET} #{totales[:omitidos]}"
|
||||
puts " #{Color::DIM}Errores:#{Color::RESET} #{totales[:errores]}"
|
||||
|
||||
if estadisticas[:errores].any?
|
||||
puts ""
|
||||
puts "#{Color::YELLOW}⚠ Errores encontrados:#{Color::RESET}"
|
||||
estadisticas[:errores].each_with_index do |error, idx|
|
||||
puts " #{idx + 1}. #{error}"
|
||||
end
|
||||
end
|
||||
|
||||
puts ""
|
||||
puts "#{Color::BOLD}📈 Estadísticas acumuladas:#{Color::RESET}"
|
||||
puts " #{Color::DIM}Bitácoras creadas:#{Color::RESET} #{estadisticas[:bitacoras_creadas]}"
|
||||
puts " #{Color::DIM}Nodos creados:#{Color::RESET} #{estadisticas[:nodos_creados]}"
|
||||
puts " #{Color::DIM}Temas creados:#{Color::RESET} #{estadisticas[:temas_creados]}"
|
||||
puts " #{Color::DIM}Entradas creadas:#{Color::RESET} #{estadisticas[:entradas_creadas]}"
|
||||
|
||||
if totales[:errores] > 0
|
||||
puts ""
|
||||
puts "#{Color::YELLOW}⚠ Algunas bitácoras no se migraron correctamente#{Color::RESET}"
|
||||
puts " Revisa los errores arriba y corrige los problemas"
|
||||
elsif totales[:exitosos] > 0
|
||||
puts ""
|
||||
puts "#{Color::GREEN}✅ Migración masiva completada exitosamente#{Color::RESET}"
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
puts "#{Color::RED}✗ Error durante migración masiva: #{e.message}#{Color::RESET}"
|
||||
puts "#{Color::DIM}#{e.backtrace.first}#{Color::RESET}" if ENV['ADN_DEBUG']
|
||||
end
|
||||
|
||||
# Muestra el resultado de una migración individual
|
||||
def mostrar_resultado_migracion(resultado, fecha)
|
||||
if resultado[:exitoso]
|
||||
puts "#{Color::GREEN}✅ Bitácora migrada exitosamente#{Color::RESET}"
|
||||
puts ""
|
||||
puts " #{Color::BOLD}Fecha:#{Color::RESET} #{fecha}"
|
||||
puts " #{Color::BOLD}Bitácora ID:#{Color::RESET} #{resultado[:bitacora_id]}" if resultado[:bitacora_id]
|
||||
puts " #{Color::BOLD}Estadísticas:#{Color::RESET}"
|
||||
puts " • Nodos creados: #{resultado.dig(:estadisticas, :nodos_creados) || 0}"
|
||||
puts " • Temas creados: #{resultado.dig(:estadisticas, :temas_creados) || 0}"
|
||||
puts " • Entradas creadas: #{resultado.dig(:estadisticas, :entradas_creadas) || 0}"
|
||||
elsif resultado[:skip]
|
||||
puts "#{Color::YELLOW}⚠ Bitácora ya existe, omitiendo#{Color::RESET}"
|
||||
puts " Usa --sobrescribir para forzar migración"
|
||||
puts " Bitácora ID: #{resultado[:bitacora_id]}" if resultado[:bitacora_id]
|
||||
else
|
||||
puts "#{Color::RED}✗ Error migrando bitácora#{Color::RESET}"
|
||||
if resultado[:error]
|
||||
puts " #{resultado[:error]}"
|
||||
end
|
||||
if resultado[:errores]&.any?
|
||||
resultado[:errores].each do |error|
|
||||
puts " • #{error}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ─── MÉTODOS AUXILIARES ──────────────────────────────────────────────
|
||||
|
||||
@@ -932,7 +1036,6 @@ module ADN
|
||||
puts " #{Color::CYAN}evento:listar [OPCIONES]#{Color::RESET} Listar entradas con filtros"
|
||||
puts " #{Color::CYAN}nodo:crear [OPCIONES]#{Color::RESET} Crear nuevo nodo (servidor/VM/PC)"
|
||||
puts " #{Color::CYAN}nodo:listar [OPCIONES]#{Color::RESET} Listar nodos del sistema"
|
||||
puts " #{Color::CYAN}migrar:md [FECHA]#{Color::RESET} Migrar bitácora .md a base de datos"
|
||||
puts " #{Color::CYAN}estadisticas#{Color::RESET} Mostrar estadísticas del sistema"
|
||||
puts ""
|
||||
puts "#{Color::BOLD}Ejemplos:#{Color::RESET}"
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# tools/adn/comando/inicio.rb — Subcomando para inicio de jornada laboral (DB-First)
|
||||
# ==============================================================================
|
||||
|
||||
require 'date'
|
||||
require 'time'
|
||||
require_relative '../../bitacoras-db/core/bitacora_db'
|
||||
|
||||
module ADN
|
||||
class SubcomandoInicio
|
||||
module Color
|
||||
RESET = "\e[0m"
|
||||
BOLD = "\e[1m"
|
||||
GREEN = "\e[32m"
|
||||
CYAN = "\e[36m"
|
||||
YELLOW = "\e[33m"
|
||||
RED = "\e[31m"
|
||||
DIM = "\e[2m"
|
||||
end
|
||||
|
||||
def initialize(args, logger)
|
||||
@args = args
|
||||
@logger = logger
|
||||
@fecha = Date.today
|
||||
end
|
||||
|
||||
def ejecutar
|
||||
if @args.empty? || !(@args.include?('jornada'))
|
||||
puts "#{Color::YELLOW}Uso: ./tools/adn/run inicio de jornada [presencial|remoto]#{Color::RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
modo = @args.include?('presencial') ? 'P' : 'R'
|
||||
tipo_str = modo == 'P' ? 'Presencial' : 'Remoto'
|
||||
hora_ini = Time.now.strftime('%H:%M')
|
||||
|
||||
@logger.info("Iniciando jornada #{tipo_str} (Modo DB-First)")
|
||||
|
||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||
# 1. Asegurar que existe la bitácora
|
||||
bitacora = db.find_bitacora_by_fecha(@fecha)
|
||||
unless bitacora
|
||||
@logger.info("Creando bitácora en DB para hoy...")
|
||||
bitacora = db.create_bitacora(fecha: @fecha)
|
||||
end
|
||||
|
||||
# 2. Obtener nodo srv-ns8
|
||||
nodo = db.find_nodo_by_nombre('srv-ns8')
|
||||
unless nodo
|
||||
@logger.warn("Nodo 'srv-ns8' no encontrado, usando nodo general...")
|
||||
# Podríamos crearlo o buscar uno por defecto
|
||||
end
|
||||
|
||||
# 3. Crear entrada de inicio
|
||||
db.create_entrada(
|
||||
inicio: hora_ini,
|
||||
descripcion: "Inicio de jornada #{tipo_str.downcase}. Sincronización de contexto y revisión de tareas.",
|
||||
estado: '👁️',
|
||||
modo: modo,
|
||||
bitacora_id: bitacora['id'],
|
||||
nodo_id: nodo ? nodo['id'] : nil
|
||||
)
|
||||
|
||||
@logger.exito("Entrada de inicio creada en Base de Datos.")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user