Files
dtic-DIIAA/adn/tools/cli/jornada.rb
T

238 lines
8.0 KiB
Ruby

# frozen_string_literal: true
# adn/tools/cli/jornada.rb — Subcomando unificado para gestión de jornada laboral
# ==================================================================================
# Uso:
# ./adn/tools/run jornada iniciar presencial [HH:MM]
# ./adn/tools/run jornada iniciar remoto [HH:MM]
# ./adn/tools/run jornada cerrar [presencial|remoto] [HH:MM]
# ./adn/tools/run jornada estado
require 'date'
require 'time'
require 'optparse'
require_relative '../db/core/bitacora_db'
require_relative '../core/colores'
require_relative '../core/help_formatter'
module ADN
class SubcomandoJornada
NODO_DEFAULT = 'dtic-DIIAA'
def initialize(args, logger)
@args = args
@logger = logger
@fecha = Date.today
@opciones = { nodo: NODO_DEFAULT, fecha: nil }
end
def ejecutar
if @args.empty?
mostrar_ayuda
return
end
# Parsear opciones globales (--nodo, --fecha) antes del subcomando
parser_global = OptionParser.new do |opts|
opts.on('--nodo NOMBRE', "Nodo a registrar (default: #{NODO_DEFAULT})") { |n| @opciones[:nodo] = n }
opts.on('--fecha YYYY-MM-DD', 'Fecha de la bitácora (default: hoy)') { |f| @opciones[:fecha] = f }
end
parser_global.order!(@args)
accion = @args.shift
case accion
when 'iniciar', 'inicio', 'abrir'
ejecutar_inicio
when 'cerrar', 'cierre', 'finalizar'
ejecutar_cierre
when 'estado', 'status'
ejecutar_estado
else
puts "#{Color::RED}✗ Acción desconocida: '#{accion}'#{Color::RESET}"
mostrar_ayuda
exit 1
end
end
private
def mostrar_ayuda
ayuda = ADN::HelpFormatter.generar_help(
titulo: "Gestión de Jornada Laboral",
descripcion: "Registro y control de jornada laboral",
uso: "./adn/tools/run jornada <acción> [modo] [hora] [opciones]",
opciones: [
{ names: ['iniciar [presencial|remoto] [HH:MM]'], desc: "Registrar inicio de jornada" },
{ names: ['cerrar [presencial|remoto] [HH:MM]'], desc: "Registrar cierre de jornada" },
{ names: ['estado'], desc: "Ver estado de la jornada actual" },
{ names: ['--nodo NOMBRE'], desc: "Nodo a registrar (default: #{NODO_DEFAULT})" },
{ names: ['--fecha YYYY-MM-DD'], desc: "Fecha de la bitácora (default: hoy)" }
],
ejemplos: [
{ cmd: "./adn/tools/run jornada iniciar presencial 08:00", desc: "Iniciar jornada presencial a las 08:00" },
{ cmd: "./adn/tools/run jornada cerrar remoto 23:00", desc: "Cerrar jornada remota a las 23:00" },
{ cmd: "./adn/tools/run jornada estado", desc: "Ver estado actual de la jornada" }
]
)
puts ayuda
end
def parsear_modo_y_hora
modo = 'P' # default presencial
hora = Time.now.strftime('%H:%M')
@args.each do |arg|
case arg.downcase
when 'presencial', 'p'
modo = 'P'
when 'remoto', 'remota', 'r'
modo = 'R'
else
# Si parece una hora (HH:MM), usarla
if arg.match?(/^\d{1,2}:\d{2}$/)
hora = arg
end
end
end
[modo, hora]
end
def obtener_fecha
if @opciones[:fecha]
Date.parse(@opciones[:fecha])
else
@fecha
end
end
def ejecutar_inicio
modo, hora = parsear_modo_y_hora
tipo_str = modo == 'P' ? 'Presencial' : 'Remoto'
fecha = obtener_fecha
@logger.info("Iniciando jornada #{tipo_str} a las #{hora}")
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 #{fecha}...")
bitacora = db.create_bitacora(fecha: fecha)
end
# 2. Buscar nodo
nodo = db.find_nodo_by_nombre(@opciones[:nodo])
unless nodo
@logger.error("Nodo '#{@opciones[:nodo]}' no encontrado en la BD.")
puts "#{Color::RED}✗ Nodo '#{@opciones[:nodo]}' no existe. Usa 'db nodo:listar' para ver disponibles.#{Color::RESET}"
return
end
# 3. Crear entrada de inicio
entrada = db.create_entrada(
inicio: hora,
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['id']
)
puts "#{Color::GREEN}✅ Jornada #{tipo_str.downcase} iniciada#{Color::RESET}"
puts " #{Color::BOLD}ID:#{Color::RESET} #{entrada['id']}"
puts " #{Color::BOLD}Hora:#{Color::RESET} #{hora}"
puts " #{Color::BOLD}Nodo:#{Color::RESET} #{@opciones[:nodo]}"
puts " #{Color::BOLD}Fecha:#{Color::RESET} #{fecha}"
@logger.exito("Jornada #{tipo_str} iniciada en Base de Datos.")
end
end
def ejecutar_cierre
modo, hora = parsear_modo_y_hora
tipo_str = modo == 'P' ? 'Presencial' : 'Remoto'
fecha = obtener_fecha
@logger.info("Cerrando jornada #{tipo_str} a las #{hora}")
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 #{fecha}. ¿Iniciaste la jornada?")
puts "#{Color::RED}✗ No hay bitácora para #{fecha}#{Color::RESET}"
return
end
# 2. Buscar nodo
nodo = db.find_nodo_by_nombre(@opciones[:nodo])
unless nodo
@logger.error("Nodo '#{@opciones[:nodo]}' no encontrado.")
puts "#{Color::RED}✗ Nodo '#{@opciones[:nodo]}' no existe.#{Color::RESET}"
return
end
# 3. Crear entrada de cierre
entrada = db.create_entrada(
inicio: hora,
fin: hora,
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['id']
)
puts "#{Color::GREEN}✅ Jornada #{tipo_str.downcase} cerrada#{Color::RESET}"
puts " #{Color::BOLD}ID:#{Color::RESET} #{entrada['id']}"
puts " #{Color::BOLD}Hora:#{Color::RESET} #{hora}"
puts " #{Color::BOLD}Nodo:#{Color::RESET} #{@opciones[:nodo]}"
puts " #{Color::BOLD}Fecha:#{Color::RESET} #{fecha}"
@logger.exito("Jornada #{tipo_str} cerrada en Base de Datos.")
end
end
def ejecutar_estado
fecha = obtener_fecha
puts "#{Color::CYAN}📅 Estado de jornada para #{fecha}#{Color::RESET}"
puts "#{Color::DIM}#{'-' * 50}#{Color::RESET}"
BitacorasDB::BitacoraDB.with_connection do |db|
bitacora = db.find_bitacora_by_fecha(fecha)
unless bitacora
puts "#{Color::YELLOW}⚠ No hay bitácora para #{fecha}#{Color::RESET}"
return
end
entradas = db.list_entradas(bitacora_id: bitacora['id'], limit: 50)
if entradas.empty?
puts "#{Color::YELLOW}⚠ Sin entradas para hoy#{Color::RESET}"
return
end
abiertas = entradas.select { |e| e['estado'] == '⏳' }
cerradas = entradas.select { |e| e['estado'] == '✅' }
puts " #{Color::GREEN}Total entradas:#{Color::RESET} #{entradas.length}"
puts " #{Color::GREEN}Completadas:#{Color::RESET} #{cerradas.length}"
puts " #{Color::YELLOW}Abiertas:#{Color::RESET} #{abiertas.length}"
if abiertas.any?
puts "\n #{Color::YELLOW}⏳ Entradas abiertas:#{Color::RESET}"
abiertas.each do |e|
nodo = e['nodo_nombre'] || '(sin nodo)'
desc = e['descripcion']
desc = desc[0..50] + '...' if desc.length > 53
puts " ID #{e['id']} | #{e['inicio'][0..4]} | #{nodo} | #{desc}"
end
end
puts "#{Color::DIM}#{'-' * 50}#{Color::RESET}"
end
end
end
end