Refactor(ADN): Migración de tools/bitacoras-db y tools/config/database.yml hacia tools/adn/db para afianzar la arquitectura modular y autosuficiencia del ADN
This commit is contained in:
@@ -185,7 +185,7 @@ module ADN
|
|||||||
end
|
end
|
||||||
|
|
||||||
def registrar_inicio_bitacora(nodo, datos)
|
def registrar_inicio_bitacora(nodo, datos)
|
||||||
require_relative '../../bitacoras-db/core/bitacora_db'
|
require_relative '../db/core/bitacora_db'
|
||||||
|
|
||||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||||
# Asegurar bitácora
|
# Asegurar bitácora
|
||||||
@@ -216,7 +216,7 @@ module ADN
|
|||||||
def registrar_task_id_bitacora(nodo, task_id)
|
def registrar_task_id_bitacora(nodo, task_id)
|
||||||
return unless @backup_entrada_id
|
return unless @backup_entrada_id
|
||||||
|
|
||||||
require_relative '../../bitacoras-db/core/bitacora_db'
|
require_relative '../db/core/bitacora_db'
|
||||||
|
|
||||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||||
db.update_entrada(@backup_entrada_id, {
|
db.update_entrada(@backup_entrada_id, {
|
||||||
@@ -230,8 +230,8 @@ module ADN
|
|||||||
def registrar_fin_bitacora(nodo, exito, task_id = nil)
|
def registrar_fin_bitacora(nodo, exito, task_id = nil)
|
||||||
return unless @backup_entrada_id
|
return unless @backup_entrada_id
|
||||||
|
|
||||||
require_relative '../../bitacoras-db/core/bitacora_db'
|
require_relative '../db/core/bitacora_db'
|
||||||
require_relative '../../bitacoras-db/exporters/md_exporter'
|
require_relative '../db/exporters/md_exporter'
|
||||||
|
|
||||||
hora_fin = Time.now.strftime('%H:%M')
|
hora_fin = Time.now.strftime('%H:%M')
|
||||||
estado = exito ? '✅' : '⚠️'
|
estado = exito ? '✅' : '⚠️'
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
require 'date'
|
require 'date'
|
||||||
require 'time'
|
require 'time'
|
||||||
require_relative '../../bitacoras-db/core/bitacora_db'
|
require_relative '../db/core/bitacora_db'
|
||||||
|
|
||||||
module ADN
|
module ADN
|
||||||
class SubcomandoCierre
|
class SubcomandoCierre
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ require 'time'
|
|||||||
|
|
||||||
# Cargar módulo BitacorasDB
|
# Cargar módulo BitacorasDB
|
||||||
begin
|
begin
|
||||||
require_relative '../../bitacoras-db/core/bitacora_db'
|
require_relative '../db/core/bitacora_db'
|
||||||
require_relative '../../bitacoras-db/importers/md_importer'
|
require_relative '../db/importers/md_importer'
|
||||||
require_relative '../../bitacoras-db/exporters/md_exporter'
|
require_relative '../db/exporters/md_exporter'
|
||||||
rescue LoadError => e
|
rescue LoadError => e
|
||||||
# Fallback para desarrollo
|
# Fallback para desarrollo
|
||||||
puts "⚠ Advertencia: No se pudo cargar BitacoraDB o MDImporter: #{e.message}"
|
puts "⚠ Advertencia: No se pudo cargar BitacoraDB o MDImporter: #{e.message}"
|
||||||
puts " Asegúrate de que los archivos en tools/bitacoras-db/ existen"
|
puts " Asegúrate de que los archivos en tools/adn/db/ existen"
|
||||||
end
|
end
|
||||||
|
|
||||||
module ADN
|
module ADN
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
require 'date'
|
require 'date'
|
||||||
require 'time'
|
require 'time'
|
||||||
require_relative '../../bitacoras-db/core/bitacora_db'
|
require_relative '../db/core/bitacora_db'
|
||||||
|
|
||||||
module ADN
|
module ADN
|
||||||
class SubcomandoInicio
|
class SubcomandoInicio
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# tools/bitacoras-db/cli/exportar_todo.rb
|
# tools/adn/db/cli/exportar_todo.rb
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# Exporta TODAS las bitácoras de la DB a archivos .md
|
# Exporta TODAS las bitácoras de la DB a archivos .md
|
||||||
# Uso: ruby tools/bitacoras-db/cli/exportar_todo.rb [--dir DIRECTORIO]
|
# Uso: ruby tools/adn/db/cli/exportar_todo.rb [--dir DIRECTORIO]
|
||||||
# — o vía enjambre —
|
# — o vía enjambre —
|
||||||
# ./tools/adn/run db exportar:md --todo
|
# ./tools/adn/run db exportar:md --todo
|
||||||
|
|
||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# tools/bitacoras-db/cli/verificar_migracion.rb
|
# tools/adn/db/cli/verificar_migracion.rb
|
||||||
# ================================================
|
# ================================================
|
||||||
# Verifica que todas las bitácoras MD existentes estén correctamente
|
# Verifica que todas las bitácoras MD existentes estén correctamente
|
||||||
# migradas en la base de datos. Reporta faltantes y discrepancias.
|
# migradas en la base de datos. Reporta faltantes y discrepancias.
|
||||||
#
|
#
|
||||||
# Uso:
|
# Uso:
|
||||||
# ruby tools/bitacoras-db/cli/verificar_migracion.rb
|
# ruby tools/adn/db/cli/verificar_migracion.rb
|
||||||
# — o vía enjambre —
|
# — o vía enjambre —
|
||||||
# ./tools/adn/run db verificar
|
# ./tools/adn/run db verificar
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# tools/bitacoras-db/core/bitacora_db.rb — Clase principal para conexión a PostgreSQL
|
# tools/adn/db/core/bitacora_db.rb — Clase principal para conexión a PostgreSQL
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# Esta clase proporciona una interfaz Ruby para interactuar con la base de datos
|
# Esta clase proporciona una interfaz Ruby para interactuar con la base de datos
|
||||||
# dtic-BITACORAs. Maneja conexiones, operaciones CRUD y validaciones para las
|
# dtic-BITACORAs. Maneja conexiones, operaciones CRUD y validaciones para las
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# tools/bitacoras-db/core/buscador_bd.rb — Sistema de búsqueda y filtrado para dtic-BITACORAs
|
# tools/adn/db/core/buscador_bd.rb — Sistema de búsqueda y filtrado para dtic-BITACORAs
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# Esta clase proporciona capacidades avanzadas de búsqueda y filtrado para la
|
# Esta clase proporciona capacidades avanzadas de búsqueda y filtrado para la
|
||||||
# base de datos dtic-BITACORAs. Implementa:
|
# base de datos dtic-BITACORAs. Implementa:
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# tools/bitacoras-db/core/buscador_bd_simple.rb — Versión simplificada del sistema de búsqueda
|
# tools/adn/db/core/buscador_bd_simple.rb — Versión simplificada del sistema de búsqueda
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# Versión simplificada y funcional del sistema de búsqueda para dtic-BITACORAs
|
# Versión simplificada y funcional del sistema de búsqueda para dtic-BITACORAs
|
||||||
# Esta versión evita problemas complejos de parámetros y se enfoca en funcionalidad básica
|
# Esta versión evita problemas complejos de parámetros y se enfoca en funcionalidad básica
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# tools/bitacoras-db/importers/md_importer.rb — Importador de bitácoras .md a PostgreSQL
|
# tools/adn/db/importers/md_importer.rb — Importador de bitácoras .md a PostgreSQL
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# Esta clase convierte bitácoras en formato Markdown al esquema de base de datos
|
# Esta clase convierte bitácoras en formato Markdown al esquema de base de datos
|
||||||
# dtic-BITACORAs. Procesa la estructura jerárquica:
|
# dtic-BITACORAs. Procesa la estructura jerárquica:
|
||||||
@@ -480,7 +480,7 @@ class AnalizadorDashboardP2601
|
|||||||
|
|
||||||
# Verificar herramientas existentes
|
# Verificar herramientas existentes
|
||||||
herramientas_adn = [
|
herramientas_adn = [
|
||||||
{ nombre: "Sistema de búsqueda", archivo: "buscador_bd_simple.rb", estado: verificar_herramienta("bitacoras-db/core/buscador_bd_simple.rb") },
|
{ nombre: "Sistema de búsqueda", archivo: "buscador_bd_simple.rb", estado: verificar_herramienta("adn/db/core/buscador_bd_simple.rb") },
|
||||||
{ nombre: "Seguimiento de plan", archivo: "subcomando_plan.rb", estado: verificar_herramienta("adn/subcomando_plan.rb") },
|
{ nombre: "Seguimiento de plan", archivo: "subcomando_plan.rb", estado: verificar_herramienta("adn/subcomando_plan.rb") },
|
||||||
{ nombre: "Operaciones BD", archivo: "subcomando_db.rb", estado: verificar_herramienta("adn/subcomando_db.rb") },
|
{ nombre: "Operaciones BD", archivo: "subcomando_db.rb", estado: verificar_herramienta("adn/subcomando_db.rb") },
|
||||||
{ nombre: "Sistema de triggers", archivo: "subcomando_triggers.rb", estado: verificar_herramienta("adn/subcomando_triggers.rb") },
|
{ nombre: "Sistema de triggers", archivo: "subcomando_triggers.rb", estado: verificar_herramienta("adn/subcomando_triggers.rb") },
|
||||||
|
|||||||
@@ -1,558 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# tools/bitacoras-db/importers/md_importer.rb — Importador de bitácoras .md a PostgreSQL
|
|
||||||
# ================================================================================
|
|
||||||
# Esta clase convierte bitácoras en formato Markdown al esquema de base de datos
|
|
||||||
# dtic-BITACORAs. Procesa la estructura jerárquica:
|
|
||||||
# Archivo .md → Bitácora → Nodos → Temas → Entradas (I-F-D-E)
|
|
||||||
#
|
|
||||||
# Formato de entrada (.md):
|
|
||||||
# # Bitácora 2026-02-28
|
|
||||||
# ## Control de Gestión
|
|
||||||
# ### 📝 Resumen de Actividades
|
|
||||||
# | NODO | RESUMEN INTEGRAL |
|
|
||||||
# | :--- | :--- |
|
|
||||||
# | srv-ns8 | Resumen del día... |
|
|
||||||
#
|
|
||||||
# ## 📂 Actividades Detalladas
|
|
||||||
# ### srv-ns8
|
|
||||||
# #### ✅ - SEC01 - Refactorización ns8-candados
|
|
||||||
# | I | F | Descripción | E |
|
|
||||||
# | :--- | :--- | :--- | :--- |
|
|
||||||
# | 08:00 | 08:34 | Refactorización ns8-candados... | [P] ✅ |
|
|
||||||
#
|
|
||||||
# Uso:
|
|
||||||
# importer = MDImporter.new(db_connection)
|
|
||||||
# resultado = importer.importar_bitacora('bitacoras/2026-02-28.md')
|
|
||||||
# resultado = importer.importar_todo_directorio('bitacoras/')
|
|
||||||
#
|
|
||||||
# Dependencias: Clase BitacoraDB del módulo BitacorasDB
|
|
||||||
|
|
||||||
require 'date'
|
|
||||||
require 'time'
|
|
||||||
require 'yaml'
|
|
||||||
|
|
||||||
module BitacorasDB
|
|
||||||
module Importers
|
|
||||||
class MDImporter
|
|
||||||
# Estados posibles en archivos .md y su mapeo a BD
|
|
||||||
ESTADOS_MD = {
|
|
||||||
'✅' => '✅',
|
|
||||||
'❌' => '❌',
|
|
||||||
'⚠️' => '⚠️',
|
|
||||||
'⏸️' => '⏸️',
|
|
||||||
'⏳' => '⏳',
|
|
||||||
'👁️' => '⏳' # Sincronización mapea a ⏳
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
# Modos posibles en archivos .md
|
|
||||||
MODOS_MD = {
|
|
||||||
'[P]' => 'P', # Presencial
|
|
||||||
'[R]' => 'R', # Remoto
|
|
||||||
'[P/R]' => 'P' # Presencial/Remoto -> Presencial por defecto
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
# Tipos de nodo inferidos del nombre
|
|
||||||
TIPOS_NODO = {
|
|
||||||
/^srv-/ => 'servidor',
|
|
||||||
/^dc-/ => 'vm',
|
|
||||||
/^sql-/ => 'vm',
|
|
||||||
/^pcv-/ => 'vm',
|
|
||||||
/^pc-/ => 'pc',
|
|
||||||
/^srvv-/ => 'vm',
|
|
||||||
/-pmox\d+$/ => 'hipervisor',
|
|
||||||
/-xen\d+$/ => 'hipervisor'
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
# Inicializa el importador con conexión a base de datos
|
|
||||||
#
|
|
||||||
# @param db [BitacoraDB] Instancia de BitacoraDB para operaciones
|
|
||||||
# @param logger [Object] Logger para registro (opcional)
|
|
||||||
def initialize(db, logger = nil)
|
|
||||||
@db = db
|
|
||||||
@logger = logger || default_logger
|
|
||||||
@estadisticas = {
|
|
||||||
archivos_procesados: 0,
|
|
||||||
bitacoras_creadas: 0,
|
|
||||||
nodos_creados: 0,
|
|
||||||
temas_creados: 0,
|
|
||||||
entradas_creadas: 0,
|
|
||||||
errores: []
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Importa una bitácora específica desde archivo .md
|
|
||||||
#
|
|
||||||
# @param ruta_md [String] Ruta al archivo .md
|
|
||||||
# @param opciones [Hash] Opciones de importación
|
|
||||||
# :sobrescribir [Boolean] Sobrescribir bitácora si ya existe (default: false)
|
|
||||||
# :crear_nodos [Boolean] Crear nodos automáticamente si no existen (default: true)
|
|
||||||
# @return [Hash] Resultado de la importación con estadísticas
|
|
||||||
def importar_bitacora(ruta_md, opciones = {})
|
|
||||||
defaults = { sobrescribir: false, crear_nodos: true }
|
|
||||||
opciones = defaults.merge(opciones)
|
|
||||||
|
|
||||||
@estadisticas[:archivos_procesados] += 1
|
|
||||||
|
|
||||||
begin
|
|
||||||
# Leer y parsear archivo
|
|
||||||
contenido = File.read(ruta_md, encoding: 'UTF-8')
|
|
||||||
fecha = extraer_fecha_desde_ruta(ruta_md) || extraer_fecha_desde_contenido(contenido)
|
|
||||||
|
|
||||||
if fecha.nil?
|
|
||||||
error = "No se pudo determinar fecha del archivo: #{ruta_md}"
|
|
||||||
@logger.error(error)
|
|
||||||
@estadisticas[:errores] << error
|
|
||||||
return { error: error }
|
|
||||||
end
|
|
||||||
|
|
||||||
@logger.info("Importando bitácora #{fecha} desde #{ruta_md}")
|
|
||||||
|
|
||||||
# Verificar si ya existe bitácora
|
|
||||||
bitacora_existente = @db.find_bitacora_by_fecha(fecha)
|
|
||||||
if bitacora_existente && !opciones[:sobrescribir]
|
|
||||||
@logger.warn("Bitácora #{fecha} ya existe, omitiendo (use --sobrescribir)")
|
|
||||||
return {
|
|
||||||
skip: true,
|
|
||||||
mensaje: "Bitácora ya existe",
|
|
||||||
bitacora_id: bitacora_existente['id'],
|
|
||||||
estadisticas: @estadisticas
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parsear contenido
|
|
||||||
estructura = parsear_contenido(contenido, fecha)
|
|
||||||
|
|
||||||
# Importar a base de datos
|
|
||||||
resultado = importar_estructura(estructura, opciones)
|
|
||||||
|
|
||||||
resultado.merge(
|
|
||||||
exitoso: resultado[:errores].empty?,
|
|
||||||
fecha: fecha,
|
|
||||||
estadisticas: @estadisticas.dup
|
|
||||||
)
|
|
||||||
|
|
||||||
rescue => e
|
|
||||||
error = "Error importando #{ruta_md}: #{e.message}"
|
|
||||||
@logger.error(error)
|
|
||||||
@estadisticas[:errores] << error
|
|
||||||
{
|
|
||||||
error: error,
|
|
||||||
backtrace: e.backtrace,
|
|
||||||
estadisticas: @estadisticas
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Importa todas las bitácoras de un directorio
|
|
||||||
#
|
|
||||||
# @param directorio [String] Ruta al directorio con archivos .md
|
|
||||||
# @param opciones [Hash] Opciones de importación
|
|
||||||
# @return [Hash] Resultado consolidado
|
|
||||||
def importar_directorio(directorio, opciones = {})
|
|
||||||
archivos = Dir.glob(File.join(directorio, '*.md')).sort
|
|
||||||
resultados = []
|
|
||||||
|
|
||||||
@logger.info("Importando #{archivos.length} archivos desde #{directorio}")
|
|
||||||
|
|
||||||
archivos.each do |archivo|
|
|
||||||
@logger.info("Procesando: #{File.basename(archivo)}")
|
|
||||||
resultado = importar_bitacora(archivo, opciones)
|
|
||||||
resultados << resultado.merge(archivo: archivo)
|
|
||||||
end
|
|
||||||
|
|
||||||
consolidar_resultados(resultados)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Obtiene estadísticas de importación
|
|
||||||
#
|
|
||||||
# @return [Hash] Estadísticas acumuladas
|
|
||||||
def estadisticas
|
|
||||||
@estadisticas
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Extrae fecha desde la ruta del archivo (ej: bitacoras/2026-02-28.md)
|
|
||||||
def extraer_fecha_desde_ruta(ruta)
|
|
||||||
nombre = File.basename(ruta, '.md')
|
|
||||||
begin
|
|
||||||
Date.parse(nombre)
|
|
||||||
rescue Date::Error
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extrae fecha desde el contenido del archivo
|
|
||||||
def extraer_fecha_desde_contenido(contenido)
|
|
||||||
# Buscar patrón: # Bitácora 2026-02-28
|
|
||||||
match = contenido.match(/# Bitácora (\d{4}-\d{2}-\d{2})/)
|
|
||||||
return Date.parse(match[1]) if match
|
|
||||||
|
|
||||||
# Buscar patrón: # Bitácora de Operaciones - 28/02/2026
|
|
||||||
match = contenido.match(/# Bitácora de Operaciones - (\d{2}\/\d{2}\/\d{4})/)
|
|
||||||
if match
|
|
||||||
fecha_str = match[1]
|
|
||||||
dia, mes, anio = fecha_str.split('/')
|
|
||||||
return Date.parse("#{anio}-#{mes}-#{dia}")
|
|
||||||
end
|
|
||||||
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parsea el contenido del archivo .md a estructura jerárquica
|
|
||||||
def parsear_contenido(contenido, fecha)
|
|
||||||
lineas = contenido.lines.map(&:chomp)
|
|
||||||
|
|
||||||
estructura = {
|
|
||||||
fecha: fecha,
|
|
||||||
resumen: extraer_resumen(contenido),
|
|
||||||
nodos: {},
|
|
||||||
secciones: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodo_actual = nil
|
|
||||||
tema_actual = nil
|
|
||||||
en_actividades_detalladas = false
|
|
||||||
en_tabla_entradas = false
|
|
||||||
encabezados_tabla = []
|
|
||||||
entradas_tabla = []
|
|
||||||
|
|
||||||
lineas.each_with_index do |linea, idx|
|
|
||||||
# Detectar sección de actividades detalladas
|
|
||||||
if linea.match?(/^## 📂 Actividades Detalladas/)
|
|
||||||
en_actividades_detalladas = true
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
# Detectar fin de sección (siguiente ##)
|
|
||||||
if en_actividades_detalladas && linea.start_with?('## ') && !linea.include?('Actividades Detalladas')
|
|
||||||
en_actividades_detalladas = false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Solo procesar dentro de actividades detalladas
|
|
||||||
next unless en_actividades_detalladas
|
|
||||||
|
|
||||||
# Detectar nodo (### srv-ns8)
|
|
||||||
if linea.start_with?('### ') && !linea.start_with?('####')
|
|
||||||
nodo_nombre = linea.sub('### ', '').strip
|
|
||||||
nodo_actual = nodo_nombre
|
|
||||||
estructura[:nodos][nodo_actual] ||= {
|
|
||||||
nombre: nodo_nombre,
|
|
||||||
temas: {}
|
|
||||||
}
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
# Detectar tema (#### ✅ - SEC01 - Refactorización ns8-candados)
|
|
||||||
if linea.start_with?('#### ')
|
|
||||||
tema_linea = linea.sub('#### ', '').strip
|
|
||||||
tema_info = parsear_tema(tema_linea)
|
|
||||||
|
|
||||||
if tema_info && nodo_actual
|
|
||||||
tema_actual = tema_info[:id_tema]
|
|
||||||
estructura[:nodos][nodo_actual][:temas][tema_actual] = {
|
|
||||||
id_tema: tema_info[:id_tema],
|
|
||||||
titulo: tema_info[:titulo],
|
|
||||||
estado: tema_info[:estado],
|
|
||||||
descripcion: tema_info[:descripcion],
|
|
||||||
entradas: []
|
|
||||||
}
|
|
||||||
end
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
# Detectar inicio de tabla de entradas
|
|
||||||
if linea.match?(/^\| I \| F \| Descripción \| E \|$/)
|
|
||||||
en_tabla_entradas = true
|
|
||||||
encabezados_tabla = linea.split('|').map(&:strip).reject(&:empty?)
|
|
||||||
entradas_tabla = []
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
# Detectar fin de tabla
|
|
||||||
if en_tabla_entradas && (linea.empty? || linea.start_with?('#') || linea.start_with?('####'))
|
|
||||||
# Procesar entradas acumuladas
|
|
||||||
if tema_actual && nodo_actual && !entradas_tabla.empty?
|
|
||||||
entradas_parseadas = parsear_entradas_tabla(entradas_tabla, encabezados_tabla)
|
|
||||||
estructura[:nodos][nodo_actual][:temas][tema_actual][:entradas] = entradas_parseadas
|
|
||||||
end
|
|
||||||
|
|
||||||
en_tabla_entradas = false
|
|
||||||
encabezados_tabla = []
|
|
||||||
entradas_tabla = []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Acumular líneas de tabla
|
|
||||||
if en_tabla_entradas && linea.start_with?('|')
|
|
||||||
entradas_tabla << linea
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Procesar última tabla si estaba activa al final del archivo
|
|
||||||
if en_tabla_entradas && tema_actual && nodo_actual && !entradas_tabla.empty?
|
|
||||||
entradas_parseadas = parsear_entradas_tabla(entradas_tabla, encabezados_tabla)
|
|
||||||
estructura[:nodos][nodo_actual][:temas][tema_actual][:entradas] = entradas_parseadas
|
|
||||||
end
|
|
||||||
|
|
||||||
estructura
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extrae resumen general del contenido
|
|
||||||
def extraer_resumen(contenido)
|
|
||||||
# Buscar en sección "Resumen de Actividades"
|
|
||||||
if contenido.include?('### 📝 Resumen de Actividades')
|
|
||||||
seccion = contenido.split('### 📝 Resumen de Actividades')[1]
|
|
||||||
if seccion
|
|
||||||
# Tomar las primeras líneas después de la tabla
|
|
||||||
lineas = seccion.lines.map(&:chomp)
|
|
||||||
resumen_lineas = []
|
|
||||||
en_tabla = false
|
|
||||||
|
|
||||||
lineas.each do |linea|
|
|
||||||
if linea.match?(/^\|.*\|$/)
|
|
||||||
en_tabla = true
|
|
||||||
elsif en_tabla && !linea.match?(/^\|.*\|$/)
|
|
||||||
# Fin de tabla, empezar a capturar resumen
|
|
||||||
if linea.strip.length > 10 # Ignorar líneas muy cortas
|
|
||||||
resumen_lineas << linea.strip
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return resumen_lineas.join(' ') if resumen_lineas.any?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parsea línea de tema (ej: "✅ - SEC01 - Refactorización ns8-candados")
|
|
||||||
def parsear_tema(linea)
|
|
||||||
# Patrón: estado - id - título
|
|
||||||
match = linea.match(/^([✅❌⚠️⏸️⏳👁️]+)\s*-\s*([A-Z0-9]+)\s*-\s*(.+)$/)
|
|
||||||
return nil unless match
|
|
||||||
|
|
||||||
estado = match[1]
|
|
||||||
id_tema = match[2]
|
|
||||||
titulo = match[3].strip
|
|
||||||
|
|
||||||
{
|
|
||||||
id_tema: id_tema,
|
|
||||||
titulo: titulo,
|
|
||||||
estado: ESTADOS_MD[estado] || '⏳',
|
|
||||||
descripcion: titulo # Por defecto, usar título como descripción
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parsea líneas de tabla de entradas
|
|
||||||
def parsear_entradas_tabla(lineas_tabla, encabezados)
|
|
||||||
entradas = []
|
|
||||||
|
|
||||||
# Mapeo de columnas basado en encabezados
|
|
||||||
columnas = {}
|
|
||||||
encabezados.each_with_index do |encabezado, idx|
|
|
||||||
case encabezado.downcase
|
|
||||||
when 'i' then columnas[:inicio] = idx
|
|
||||||
when 'f' then columnas[:fin] = idx
|
|
||||||
when 'descripción' then columnas[:descripcion] = idx
|
|
||||||
when 'e' then columnas[:estado] = idx
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
lineas_tabla.each do |linea|
|
|
||||||
next if linea.match?(/^[\|\s-]+$/) # Saltar separadores de tabla
|
|
||||||
|
|
||||||
celdas = linea.split('|').map(&:strip).reject(&:empty?)
|
|
||||||
next if celdas.length < 3 # Mínimo: inicio, descripción, estado
|
|
||||||
|
|
||||||
fin_valor = nil
|
|
||||||
if columnas[:fin] && celdas[columnas[:fin]] != '-'
|
|
||||||
fin_valor = celdas[columnas[:fin]]
|
|
||||||
end
|
|
||||||
|
|
||||||
entrada = {
|
|
||||||
inicio: celdas[columnas[:inicio]],
|
|
||||||
fin: fin_valor,
|
|
||||||
descripcion: celdas[columnas[:descripcion]],
|
|
||||||
estado_modo: celdas[columnas[:estado]]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parsear estado y modo (ej: "[P] ✅")
|
|
||||||
if entrada[:estado_modo]
|
|
||||||
estado_modo = parsear_estado_modo(entrada[:estado_modo])
|
|
||||||
entrada[:estado] = estado_modo[:estado]
|
|
||||||
entrada[:modo] = estado_modo[:modo]
|
|
||||||
end
|
|
||||||
|
|
||||||
entradas << entrada
|
|
||||||
end
|
|
||||||
|
|
||||||
entradas
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parsea campo estado/modo (ej: "[P] ✅")
|
|
||||||
def parsear_estado_modo(campo)
|
|
||||||
estado = '⏳'
|
|
||||||
modo = 'P'
|
|
||||||
|
|
||||||
# Buscar estado en el campo
|
|
||||||
ESTADOS_MD.each do |md_estado, bd_estado|
|
|
||||||
if campo.include?(md_estado)
|
|
||||||
estado = bd_estado
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Buscar modo en el campo
|
|
||||||
MODOS_MD.each do |md_modo, bd_modo|
|
|
||||||
if campo.include?(md_modo)
|
|
||||||
modo = bd_modo
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
{ estado: estado, modo: modo }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Importa estructura parseada a base de datos
|
|
||||||
def importar_estructura(estructura, opciones)
|
|
||||||
errores = []
|
|
||||||
|
|
||||||
begin
|
|
||||||
# 1. Crear o actualizar bitácora
|
|
||||||
bitacora = @db.find_bitacora_by_fecha(estructura[:fecha])
|
|
||||||
|
|
||||||
if bitacora
|
|
||||||
@logger.info("Bitácora #{estructura[:fecha]} ya existe (ID: #{bitacora['id']})")
|
|
||||||
else
|
|
||||||
bitacora = @db.create_bitacora(
|
|
||||||
fecha: estructura[:fecha],
|
|
||||||
resumen: estructura[:resumen]
|
|
||||||
)
|
|
||||||
@estadisticas[:bitacoras_creadas] += 1
|
|
||||||
@logger.info("Bitácora creada: #{estructura[:fecha]} (ID: #{bitacora['id']})")
|
|
||||||
end
|
|
||||||
|
|
||||||
bitacora_id = bitacora['id']
|
|
||||||
|
|
||||||
# 2. Procesar cada nodo
|
|
||||||
estructura[:nodos].each do |nombre_nodo, datos_nodo|
|
|
||||||
# Buscar o crear nodo
|
|
||||||
nodo = @db.find_nodo_by_nombre(nombre_nodo)
|
|
||||||
|
|
||||||
if nodo.nil? && opciones[:crear_nodos]
|
|
||||||
tipo = inferir_tipo_nodo(nombre_nodo)
|
|
||||||
nodo = @db.create_nodo(
|
|
||||||
nombre: nombre_nodo,
|
|
||||||
tipo: tipo,
|
|
||||||
descripcion: "Importado desde bitácora #{estructura[:fecha]}"
|
|
||||||
)
|
|
||||||
@estadisticas[:nodos_creados] += 1
|
|
||||||
@logger.info("Nodo creado: #{nombre_nodo} (ID: #{nodo['id']})")
|
|
||||||
elsif nodo.nil?
|
|
||||||
@logger.warn("Nodo #{nombre_nodo} no existe y crear_nodos=false, omitiendo")
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
nodo_id = nodo['id']
|
|
||||||
|
|
||||||
# 3. Procesar temas del nodo
|
|
||||||
datos_nodo[:temas].each do |id_tema, datos_tema|
|
|
||||||
# Buscar tema por id_tema y bitácora
|
|
||||||
temas = @db.list_temas(bitacora_id: bitacora_id, nodo_id: nodo_id, limit: 100)
|
|
||||||
tema_existente = temas.find { |t| t['id_tema'] == id_tema }
|
|
||||||
|
|
||||||
if tema_existente
|
|
||||||
@logger.info("Tema #{id_tema} ya existe (ID: #{tema_existente['id']})")
|
|
||||||
tema_id = tema_existente['id']
|
|
||||||
else
|
|
||||||
tema = @db.create_tema(
|
|
||||||
id_tema: id_tema,
|
|
||||||
titulo: datos_tema[:titulo],
|
|
||||||
estado: datos_tema[:estado],
|
|
||||||
descripcion: datos_tema[:descripcion],
|
|
||||||
bitacora_id: bitacora_id,
|
|
||||||
nodo_id: nodo_id
|
|
||||||
)
|
|
||||||
tema_id = tema['id']
|
|
||||||
@estadisticas[:temas_creados] += 1
|
|
||||||
@logger.info("Tema creado: #{id_tema} (ID: #{tema_id})")
|
|
||||||
end
|
|
||||||
|
|
||||||
# 4. Procesar entradas del tema
|
|
||||||
datos_tema[:entradas].each do |entrada_md|
|
|
||||||
# Validar campos requeridos
|
|
||||||
next unless entrada_md[:inicio] && entrada_md[:descripcion]
|
|
||||||
|
|
||||||
# Crear entrada en BD
|
|
||||||
@db.create_entrada(
|
|
||||||
inicio: entrada_md[:inicio],
|
|
||||||
fin: entrada_md[:fin],
|
|
||||||
descripcion: entrada_md[:descripcion],
|
|
||||||
estado: entrada_md[:estado] || '⏳',
|
|
||||||
modo: entrada_md[:modo] || 'P',
|
|
||||||
es_ia: false, # Las importadas de .md no son IA
|
|
||||||
tema_id: tema_id,
|
|
||||||
bitacora_id: bitacora_id,
|
|
||||||
nodo_id: nodo_id
|
|
||||||
)
|
|
||||||
@estadisticas[:entradas_creadas] += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
rescue => e
|
|
||||||
error = "Error importando estructura: #{e.message}"
|
|
||||||
@logger.error(error)
|
|
||||||
errores << error
|
|
||||||
end
|
|
||||||
|
|
||||||
{ errores: errores }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Infiere tipo de nodo basado en su nombre
|
|
||||||
def inferir_tipo_nodo(nombre)
|
|
||||||
TIPOS_NODO.each do |patron, tipo|
|
|
||||||
return tipo if nombre.match?(patron)
|
|
||||||
end
|
|
||||||
'servidor' # Tipo por defecto
|
|
||||||
end
|
|
||||||
|
|
||||||
# Consolida resultados de múltiples importaciones
|
|
||||||
def consolidar_resultados(resultados)
|
|
||||||
totales = {
|
|
||||||
archivos: resultados.length,
|
|
||||||
exitosos: resultados.count { |r| r[:exitoso] == true },
|
|
||||||
omitidos: resultados.count { |r| r[:skip] == true },
|
|
||||||
errores: resultados.count { |r| r[:error] }
|
|
||||||
}
|
|
||||||
|
|
||||||
estadisticas_consolidadas = {
|
|
||||||
archivos_procesados: resultados.length,
|
|
||||||
bitacoras_creadas: resultados.sum { |r| r.dig(:estadisticas, :bitacoras_creadas) || 0 },
|
|
||||||
nodos_creados: resultados.sum { |r| r.dig(:estadisticas, :nodos_creados) || 0 },
|
|
||||||
temas_creados: resultados.sum { |r| r.dig(:estadisticas, :temas_creados) || 0 },
|
|
||||||
entradas_creadas: resultados.sum { |r| r.dig(:estadisticas, :entradas_creadas) || 0 },
|
|
||||||
errores: resultados.flat_map { |r| r.dig(:estadisticas, :errores) || [] }.compact
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
totales: totales,
|
|
||||||
estadisticas: estadisticas_consolidadas,
|
|
||||||
detalles: resultados
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Logger por defecto
|
|
||||||
def default_logger
|
|
||||||
Class.new do
|
|
||||||
def info(msg); puts "[INFO] #{msg}"; end
|
|
||||||
def warn(msg); puts "[WARN] #{msg}"; end
|
|
||||||
def error(msg); puts "[ERROR] #{msg}"; end
|
|
||||||
end.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```Ahora necesito actualizar el subcomando `migrar:md` en `subcomando_db.rb` para usar el importador MD. Primero debo leer el contenido actual del subcomando `migrar:md` y luego modificarlo. Voy a leer el archivo.<|end▁of▁thinking|>
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
# tools/probar_buscador.rb — Script de prueba para sistema de búsqueda
|
# tools/probar_buscador.rb — Script de prueba para sistema de búsqueda
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# Este script prueba la funcionalidad del sistema de búsqueda y filtrado
|
# Este script prueba la funcionalidad del sistema de búsqueda y filtrado
|
||||||
# implementado en tools/bitacoras-db/core/buscador_bd.rb
|
# implementado en tools/adn/db/core/buscador_bd.rb
|
||||||
#
|
#
|
||||||
# Uso:
|
# Uso:
|
||||||
# ruby tools/probar_buscador.rb [OPCIONES]
|
# ruby tools/probar_buscador.rb [OPCIONES]
|
||||||
@@ -32,7 +32,7 @@ require 'optparse'
|
|||||||
require 'json'
|
require 'json'
|
||||||
|
|
||||||
# Cargar módulos locales
|
# Cargar módulos locales
|
||||||
$LOAD_PATH.unshift(File.join(__dir__, 'bitacoras-db', 'core'))
|
$LOAD_PATH.unshift(File.join(__dir__, 'adn', 'db', 'core'))
|
||||||
require 'buscador_bd'
|
require 'buscador_bd'
|
||||||
|
|
||||||
class ProbadorBuscador
|
class ProbadorBuscador
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ require 'yaml'
|
|||||||
require 'json'
|
require 'json'
|
||||||
|
|
||||||
# Cargar módulos locales
|
# Cargar módulos locales
|
||||||
$LOAD_PATH.unshift(File.join(__dir__, 'bitacoras-db', 'core'))
|
$LOAD_PATH.unshift(File.join(__dir__, 'adn', 'db', 'core'))
|
||||||
require 'buscador_bd_simple'
|
require 'buscador_bd_simple'
|
||||||
|
|
||||||
class ProbadorBuscadorSimple
|
class ProbadorBuscadorSimple
|
||||||
|
|||||||
Reference in New Issue
Block a user