Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec4864c0cc | ||
|
|
6bb1c89742 | ||
|
|
1989ec5753 | ||
|
|
17b69d080a | ||
|
|
d4ea8c0bcd | ||
|
|
a6ed2ca771 | ||
|
|
f9b9d55edb | ||
|
|
24bc996ff4 | ||
|
|
89dcdbb37e | ||
|
|
65dd92b769 |
@@ -16,6 +16,11 @@ module ADN
|
|||||||
'cli', 'drones'
|
'cli', 'drones'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Nodo y ámbito de REGISTRO en bitácora (lógico, no operacional)
|
||||||
|
# El nodo operacional (srvv-fenix, srv-ns8, etc.) es interno al dron
|
||||||
|
NODO_BITACORA = 'dtic-BKPs'.freeze
|
||||||
|
AMBITO_BITACORA = 'dtic-DIIAA'.freeze
|
||||||
|
|
||||||
SCRIPTS = {
|
SCRIPTS = {
|
||||||
'dasuten_export_full' => 'dasuten_exportar_srvv-fenix.rb',
|
'dasuten_export_full' => 'dasuten_exportar_srvv-fenix.rb',
|
||||||
'dasuten_export_dif' => 'dasuten_exportar_diferencial_srvv-fenix.rb',
|
'dasuten_export_dif' => 'dasuten_exportar_diferencial_srvv-fenix.rb',
|
||||||
@@ -48,11 +53,13 @@ module ADN
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
nodo = tarea[:nodo] || detectar_nodo(tarea[:id])
|
|
||||||
nota = tarea[:texto] || "DASUTEN: #{tarea[:id]}"
|
nota = tarea[:texto] || "DASUTEN: #{tarea[:id]}"
|
||||||
|
nodo_bitacora = NODO_BITACORA # Registro lógico en bitácora
|
||||||
|
ambito_bitacora = AMBITO_BITACORA # Ámbito de registro en bitácora
|
||||||
|
|
||||||
# Lanzar dron con bitácora automática
|
# Lanzar dron con bitácora automática
|
||||||
cmd = "#{@dron_runner} dron lanzar --nota #{Shellwords.escape(nota)} --nodo #{Shellwords.escape(nodo)} -- ruby #{script_path}"
|
# --nodo y --ambito son para REGISTRO en bitácora, no el nodo operacional
|
||||||
|
cmd = "#{@dron_runner} dron lanzar --nota #{Shellwords.escape(nota)} --nodo #{Shellwords.escape(nodo_bitacora)} --ambito #{Shellwords.escape(ambito_bitacora)} -- ruby #{script_path}"
|
||||||
|
|
||||||
@logger.info("Ejecutando dron: #{nota}")
|
@logger.info("Ejecutando dron: #{nota}")
|
||||||
@logger.debug("Comando: #{cmd}")
|
@logger.debug("Comando: #{cmd}")
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# Configurar zona horaria Argentina en dasu-sql4
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== CONFIGURAR ZONA HORARIA - ARGENTINA (UTC-3) ==="
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$JSON_PATH = "C:\\temp\\timezone_config_output.json"
|
||||||
|
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path "C:\\temp"
|
||||||
|
|
||||||
|
Write-Host "[1/4] Zona horaria ACTUAL:"
|
||||||
|
$tz = Get-TimeZone
|
||||||
|
Write-Host " ID: $($tz.Id)"
|
||||||
|
Write-Host " DisplayName: $($tz.DisplayName)"
|
||||||
|
Write-Host " BaseUtcOffset: $($tz.BaseUtcOffset)"
|
||||||
|
|
||||||
|
Write-Host "`n[2/4] Configurando zona horaria ARGENTINA (UTC-3)..."
|
||||||
|
|
||||||
|
# Argentina Standard Time es el ID correcto para UTC-3
|
||||||
|
try {
|
||||||
|
Set-TimeZone -Id "Argentina Standard Time"
|
||||||
|
Write-Host " ✅ Zona horaria configurada correctamente"
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR: $_"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[3/4] Verificando nueva configuración:"
|
||||||
|
$tz = Get-TimeZone
|
||||||
|
Write-Host " ID: $($tz.Id)"
|
||||||
|
Write-Host " DisplayName: $($tz.DisplayName)"
|
||||||
|
Write-Host " BaseUtcOffset: $($tz.BaseUtcOffset)"
|
||||||
|
|
||||||
|
Write-Host "`n[4/4] Fecha/hora actual:"
|
||||||
|
Write-Host " Get-Date: $(Get-Date)"
|
||||||
|
Write-Host " Get-Date -Format 'o': $(Get-Date -Format 'o')"
|
||||||
|
Write-Host " UTC: $(Get-Date -AsUtc)"
|
||||||
|
|
||||||
|
# Verificar si SQL Server necesita reinicio
|
||||||
|
Write-Host "`n[5/5] Verificando SQL Server:"
|
||||||
|
$connString = "Server=localhost;Database=master;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT GETDATE() as fecha_sql, SYSUTCDATETIME() as utc_sql", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$row = $ds.Tables[0].Rows[0]
|
||||||
|
Write-Host " GETDATE(): $($row.fecha_sql)"
|
||||||
|
Write-Host " SYSUTCDATETIME(): $($row.utc_sql)"
|
||||||
|
Write-Host " Diferencia con UTC: $([DateTime]::Now - [DateTime]::UtcNow)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
|
||||||
|
$resultado = @{
|
||||||
|
paso = "timezone_configurado"
|
||||||
|
timezone_id = $tz.Id
|
||||||
|
timezone_display = $tz.DisplayName
|
||||||
|
utc_offset = $tz.BaseUtcOffset.ToString()
|
||||||
|
timestamp = (Get-Date -Format "o")
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultado | ConvertTo-Json | Out-File -FilePath $JSON_PATH -Encoding utf8
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
puts "\n[1/2] Configurando zona horaria..."
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script, output_path: 'C:\temp\timezone_config_output.json')
|
||||||
|
|
||||||
|
puts "\n[2/2] Leyendo resultado..."
|
||||||
|
begin
|
||||||
|
resultado = DasuExecutor.read_json_from_dasu('C:\temp\timezone_config_output.json')
|
||||||
|
puts "\n=== RESULTADO ==="
|
||||||
|
puts " Timezone ID: #{resultado['timezone_id']}"
|
||||||
|
puts " Display: #{resultado['timezone_display']}"
|
||||||
|
puts " UTC Offset: #{resultado['utc_offset']}"
|
||||||
|
rescue => e
|
||||||
|
puts "[!] Error leyendo resultado: #{e.message}"
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "\n✅ Configuración completada"
|
||||||
|
puts "\n⚠️ NOTA: SQL Server puede necesitar reinicio para aplicar completamente la nueva zona horaria."
|
||||||
@@ -114,7 +114,8 @@ module Dron
|
|||||||
evento_id: opciones[:evento_id],
|
evento_id: opciones[:evento_id],
|
||||||
timeout: opciones[:timeout],
|
timeout: opciones[:timeout],
|
||||||
flujo_id: opciones[:flujo_id],
|
flujo_id: opciones[:flujo_id],
|
||||||
nodo: opciones[:nodo]
|
nodo: opciones[:nodo],
|
||||||
|
ambito: opciones[:ambito]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mostrar resultado
|
# Mostrar resultado
|
||||||
@@ -136,6 +137,7 @@ module Dron
|
|||||||
timeout: 3600,
|
timeout: 3600,
|
||||||
flujo_id: nil,
|
flujo_id: nil,
|
||||||
nodo: nil,
|
nodo: nil,
|
||||||
|
ambito: nil,
|
||||||
cmd: nil
|
cmd: nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +155,8 @@ module Dron
|
|||||||
opciones[:flujo_id] = args.shift
|
opciones[:flujo_id] = args.shift
|
||||||
when '--nodo'
|
when '--nodo'
|
||||||
opciones[:nodo] = args.shift
|
opciones[:nodo] = args.shift
|
||||||
|
when '--ambito'
|
||||||
|
opciones[:ambito] = args.shift
|
||||||
when '--help', '-h'
|
when '--help', '-h'
|
||||||
Dispatcher.mostrar_ayuda
|
Dispatcher.mostrar_ayuda
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ module Dron
|
|||||||
HEARTBEAT_INTERVAL = 30
|
HEARTBEAT_INTERVAL = 30
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def lanzar(cmd:, nota: nil, evento_id: nil, timeout: DEFAULT_TIMEOUT, flujo_id: nil, nodo: nil)
|
def lanzar(cmd:, nota: nil, evento_id: nil, timeout: DEFAULT_TIMEOUT, flujo_id: nil, nodo: nil, ambito: nil)
|
||||||
Base.logger.info("🛠️ Dron Ejecutor iniciado: #{Process.pid}")
|
Base.logger.info("🛠️ Dron Ejecutor iniciado: #{Process.pid}")
|
||||||
inicio = Time.now
|
inicio = Time.now
|
||||||
|
|
||||||
# 1. Registrar en tablas internas (dron_logs)
|
# 1. Registrar en tablas internas (dron_logs)
|
||||||
resultado_inicio = ADN::DB::DronDB.registrar_inicio(
|
resultado_inicio = ADN::DB::DronDB.registrar_inicio(
|
||||||
tipo: 'ejecutor', cmd: cmd, flujo_id: flujo_id,
|
tipo: 'ejecutor', cmd: cmd, flujo_id: flujo_id,
|
||||||
metadata: { nota: nota, evento_id: evento_id, timeout: timeout, nodo: nodo }
|
metadata: { nota: nota, evento_id: evento_id, timeout: timeout, nodo: nodo, ambito: ambito }
|
||||||
)
|
)
|
||||||
dron_id = resultado_inicio[:dron_id]
|
dron_id = resultado_inicio[:dron_id]
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ module Dron
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 2. Crear/vincular evento en Bitácora Web (visible)
|
# 2. Crear/vincular evento en Bitácora Web (visible)
|
||||||
evento_id = crear_o_vincular_evento_web(dron_id, nota, evento_id, cmd, nodo)
|
evento_id = crear_o_vincular_evento_web(dron_id, nota, evento_id, cmd, nodo, ambito)
|
||||||
|
|
||||||
# 3. Ejecutar comando con heartbeat que actualiza AMBAS capas
|
# 3. Ejecutar comando con heartbeat que actualiza AMBAS capas
|
||||||
resultado = ejecutar_comando(cmd, dron_id, timeout, evento_id, nota, inicio)
|
resultado = ejecutar_comando(cmd, dron_id, timeout, evento_id, nota, inicio)
|
||||||
@@ -161,10 +161,11 @@ module Dron
|
|||||||
|
|
||||||
# ─── Bitácora Web: Crear/Vincular evento ─────────────────────
|
# ─── Bitácora Web: Crear/Vincular evento ─────────────────────
|
||||||
|
|
||||||
def crear_o_vincular_evento_web(dron_id, nota, evento_id, cmd, nodo)
|
def crear_o_vincular_evento_web(dron_id, nota, evento_id, cmd, nodo, ambito = nil)
|
||||||
return nil unless nota # Sin nota = sin registro en web
|
return nil unless nota # Sin nota = sin registro en web
|
||||||
|
|
||||||
nodo_id = detectar_nodo_id(nodo)
|
nodo_id = detectar_nodo_id(nodo)
|
||||||
|
ambito_id = detectar_ambito_id(ambito)
|
||||||
|
|
||||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||||
if evento_id.to_i > 0
|
if evento_id.to_i > 0
|
||||||
@@ -196,12 +197,14 @@ module Dron
|
|||||||
estado: '⏳',
|
estado: '⏳',
|
||||||
modo: 'P',
|
modo: 'P',
|
||||||
bitacora_id: bitacora_hoy['id'],
|
bitacora_id: bitacora_hoy['id'],
|
||||||
nodo_id: nodo_id
|
nodo_id: nodo_id,
|
||||||
|
ambito_id: ambito_id
|
||||||
)
|
)
|
||||||
entrada['id']
|
entrada['id']
|
||||||
else
|
else
|
||||||
evento = db.crear_evento(
|
evento = db.crear_evento(
|
||||||
nodo_id: nodo_id,
|
nodo_id: nodo_id,
|
||||||
|
ambito_id: ambito_id,
|
||||||
descripcion: descripcion,
|
descripcion: descripcion,
|
||||||
inicio: Time.now.strftime('%H:%M'),
|
inicio: Time.now.strftime('%H:%M'),
|
||||||
estado: '⏳',
|
estado: '⏳',
|
||||||
@@ -292,13 +295,23 @@ module Dron
|
|||||||
return nodo if nodo.is_a?(Integer)
|
return nodo if nodo.is_a?(Integer)
|
||||||
|
|
||||||
require 'socket'
|
require 'socket'
|
||||||
hostname = Socket.gethostname.downcase
|
|
||||||
|
# Usar el nodo pasado como parámetro; fallback al hostname local si es nil/empty
|
||||||
|
nombre_buscar = nodo.to_s.strip.downcase
|
||||||
|
if nombre_buscar.empty?
|
||||||
|
nombre_buscar = Socket.gethostname.downcase
|
||||||
|
end
|
||||||
|
|
||||||
resultado = nil
|
resultado = nil
|
||||||
BitacorasDB::BitacoraDB.with_connection do |db|
|
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||||
resultado = db.execute('SELECT id FROM nodos WHERE LOWER(nombre) = $1 LIMIT 1', [hostname])
|
resultado = db.execute('SELECT id FROM nodos WHERE LOWER(nombre) = $1 LIMIT 1', [nombre_buscar])
|
||||||
if resultado.nil? || resultado.ntuples == 0
|
if resultado.nil? || resultado.ntuples == 0
|
||||||
resultado = db.execute('SELECT id FROM nodos WHERE LOWER(nombre) LIKE $1 LIMIT 1', ["#{hostname}%"])
|
resultado = db.execute('SELECT id FROM nodos WHERE LOWER(nombre) LIKE $1 LIMIT 1', ["#{nombre_buscar}%"])
|
||||||
|
end
|
||||||
|
# Último fallback: hostname local si el nodo pasado no existe en DB
|
||||||
|
if (resultado.nil? || resultado.ntuples == 0) && nombre_buscar != Socket.gethostname.downcase
|
||||||
|
hostname = Socket.gethostname.downcase
|
||||||
|
resultado = db.execute('SELECT id FROM nodos WHERE LOWER(nombre) = $1 LIMIT 1', [hostname])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -307,6 +320,21 @@ module Dron
|
|||||||
Base.logger.warn("⚠️ Error detectando nodo: #{e.message}, usando default (1)")
|
Base.logger.warn("⚠️ Error detectando nodo: #{e.message}, usando default (1)")
|
||||||
1
|
1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def detectar_ambito_id(ambito)
|
||||||
|
return ambito if ambito.is_a?(Integer)
|
||||||
|
return nil if ambito.nil? || ambito.empty?
|
||||||
|
|
||||||
|
resultado = nil
|
||||||
|
BitacorasDB::BitacoraDB.with_connection do |db|
|
||||||
|
resultado = db.execute('SELECT id FROM bitacoras.ambitos WHERE LOWER(nombre) = $1 LIMIT 1', [ambito.downcase])
|
||||||
|
end
|
||||||
|
|
||||||
|
resultado&.any? ? resultado.first['id'] : nil
|
||||||
|
rescue StandardError => e
|
||||||
|
Base.logger.warn("⚠️ Error detectando ámbito: #{e.message}, usando default (nil)")
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
# adn/tools/cli/drones/dasuten_download_drive-dasu-sql4.rb
|
# adn/tools/cli/drones/dasuten_download_drive-dasu-sql4.rb
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Dron: Download desde Google Drive a dasu-sql4
|
# Dron: Download desde Google Drive a dasu-sql4
|
||||||
# Flujo: Google Drive → F:\BACKUP\sysdasuten_compressed_ADN.bak
|
# Flujo: Google Drive → F:\BACKUP\sysdasuten_FULL_YYYYMMDD_HHMMSS.bak
|
||||||
# Usa: Invoke-WebRequest (sin rclone)
|
# Usa: rclone copy (mantiene nombre original del backup)
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
require 'time'
|
require 'time'
|
||||||
@@ -27,9 +27,12 @@ end.parse!(ARGV)
|
|||||||
|
|
||||||
BACKUP_DIR_WINDOWS = "F:\\BACKUP"
|
BACKUP_DIR_WINDOWS = "F:\\BACKUP"
|
||||||
BACKUP_NAME_DEFAULT = "sysdasuten_compressed_ADN.bak"
|
BACKUP_NAME_DEFAULT = "sysdasuten_compressed_ADN.bak"
|
||||||
JSON_PATH = "C:\temp\dron_download_output.json"
|
JSON_PATH = "C:\\temp\\dron_download_output.json"
|
||||||
|
|
||||||
# Determinar archivo a descargar
|
# Determinar archivo a descargar
|
||||||
|
backup_name = nil
|
||||||
|
file_id = nil
|
||||||
|
|
||||||
if opciones[:test_file]
|
if opciones[:test_file]
|
||||||
puts "[*] Dron: Download desde Google Drive"
|
puts "[*] Dron: Download desde Google Drive"
|
||||||
puts " Nodo: dasu-sql4"
|
puts " Nodo: dasu-sql4"
|
||||||
@@ -52,10 +55,20 @@ else
|
|||||||
if File.exist?(UPLOAD_OUTPUT)
|
if File.exist?(UPLOAD_OUTPUT)
|
||||||
upload_data = JSON.parse(File.read(UPLOAD_OUTPUT))
|
upload_data = JSON.parse(File.read(UPLOAD_OUTPUT))
|
||||||
drive_ruta = upload_data['drive_ruta']
|
drive_ruta = upload_data['drive_ruta']
|
||||||
file_id = File.basename(drive_ruta)
|
# Extraer el nombre EXACTO del archivo desde drive_ruta (ej: "rmonla-GDrive:drive_bkps-dasu/sysdasuten_FULL_20260415_121943.bak")
|
||||||
backup_name = File.basename(drive_ruta)
|
backup_name = File.basename(drive_ruta)
|
||||||
|
|
||||||
|
puts "[*] Dron: Buscando Google Drive ID real (rclone)..."
|
||||||
|
raw_json = `rclone lsjson '#{drive_ruta}' 2>/dev/null`.strip
|
||||||
|
begin
|
||||||
|
parsed = JSON.parse(raw_json).first
|
||||||
|
file_id = parsed['ID']
|
||||||
|
rescue
|
||||||
|
puts "[!] Error al parsear ID de Google Drive. rclone regresó vacio."
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
download_url = "https://drive.google.com/uc?export=download&id=#{file_id}"
|
download_url = "https://drive.google.com/uc?export=download&id=#{file_id}"
|
||||||
puts "[*] Dron: Download desde Google Drive"
|
|
||||||
puts " Nodo: dasu-sql4"
|
puts " Nodo: dasu-sql4"
|
||||||
puts " Drive ruta: #{drive_ruta}"
|
puts " Drive ruta: #{drive_ruta}"
|
||||||
puts " File ID: #{file_id}"
|
puts " File ID: #{file_id}"
|
||||||
@@ -66,54 +79,43 @@ else
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Validar que backup_name tenga el formato correcto (FULL o DIF con fecha)
|
||||||
|
unless backup_name =~ /sysdasuten_(FULL|DIF)_\d{8}_\d{6}\.bak/
|
||||||
|
puts "[!] Advertencia: El nombre del backup '#{backup_name}' no sigue el formato esperado (sysdasuten_FULL_YYYYMMDD_HHMMSS.bak)"
|
||||||
|
end
|
||||||
|
|
||||||
# PowerShell script para download
|
# PowerShell script para download
|
||||||
ps_script = <<~PSCMD.strip
|
ps_script = <<~PSCMD.strip
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
$BACKUP_DIR = "F:\\BACKUP"
|
$BACKUP_DIR = "F:\\BACKUP"
|
||||||
$DOWNLOAD_URL = "#{download_url}"
|
|
||||||
$BACKUP_NAME = "#{backup_name}"
|
$BACKUP_NAME = "#{backup_name}"
|
||||||
$LOCAL_BACKUP = "$BACKUP_DIR\\$BACKUP_NAME"
|
$LOCAL_BACKUP = "$BACKUP_DIR\\$BACKUP_NAME"
|
||||||
$JSON_PATH = "C:\\temp\\dron_download_output.json"
|
$JSON_PATH = "C:\\temp\\dron_download_output.json"
|
||||||
|
$REMOTE_RCLONE_PATH = "#{drive_ruta}"
|
||||||
|
|
||||||
$null = New-Item -ItemType Directory -Force -Path "C:\\temp"
|
$null = New-Item -ItemType Directory -Force -Path "C:\\temp"
|
||||||
$null = New-Item -ItemType Directory -Force -Path $BACKUP_DIR
|
$null = New-Item -ItemType Directory -Force -Path $BACKUP_DIR
|
||||||
|
|
||||||
Write-Host "[1/3] Iniciando descarga..."
|
Write-Host "[1/3] Iniciando descarga con Rclone Nativo..."
|
||||||
Write-Host " URL: $DOWNLOAD_URL"
|
Write-Host " Origen: $REMOTE_RCLONE_PATH"
|
||||||
|
Write-Host " Destino: $LOCAL_BACKUP"
|
||||||
|
|
||||||
Write-Host "[2/3] Descargando..."
|
Write-Host "[2/3] Descargando..."
|
||||||
$start = Get-Date
|
$start = Get-Date
|
||||||
try {
|
|
||||||
$response = Invoke-WebRequest -Uri $DOWNLOAD_URL -UseBasicParsing -Headers @{
|
# Limpiamos el ultimo archivo varado (por si el anterior dejo su esqueleto de 0 bytes)
|
||||||
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
if (Test-Path $LOCAL_BACKUP) { Remove-Item $LOCAL_BACKUP -Force }
|
||||||
|
|
||||||
|
# Llamamos al rclone binario local apuntando al config
|
||||||
|
& C:\\rclone\\rclone.exe copy "$REMOTE_RCLONE_PATH" "$BACKUP_DIR\\" --config "C:\\rclone\\rclone.conf" --stats-one-line --stats 1s
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "[!] Error: Falló la descarga de rclone (Código exit: $LASTEXITCODE)"
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
$html = $response.Content
|
|
||||||
$fileId = ""
|
|
||||||
$uuid = ""
|
|
||||||
$confirm = "t"
|
|
||||||
if ($html -match 'name="id" value="([^"]+)"') { $fileId = $matches[1] }
|
|
||||||
if ($html -match 'name="uuid" value="([^"]+)"') { $uuid = $matches[1] }
|
|
||||||
if ($html -match 'name="confirm" value="([^"]+)"') { $confirm = $matches[1] }
|
|
||||||
|
|
||||||
Write-Host " File ID: $fileId"
|
|
||||||
Write-Host " UUID: $uuid"
|
|
||||||
|
|
||||||
$downloadConfirmUrl = "https://drive.usercontent.google.com/download?id=$fileId&export=download&confirm=$confirm&uuid=$uuid"
|
|
||||||
Write-Host " URL confirmada: $downloadConfirmUrl"
|
|
||||||
|
|
||||||
$finalResponse = Invoke-WebRequest -Uri $downloadConfirmUrl -UseBasicParsing -Headers @{
|
|
||||||
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
||||||
}
|
|
||||||
|
|
||||||
[System.IO.File]::WriteAllBytes($LOCAL_BACKUP, $finalResponse.Content)
|
|
||||||
|
|
||||||
$dur = (New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
|
$dur = (New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
|
||||||
Write-Host "[+] Descarga en $([math]::Round($dur, 2))s"
|
Write-Host "[+] Descarga en $([math]::Round($dur, 2))s"
|
||||||
} catch {
|
|
||||||
Write-Host "[!] Error: $_"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "[3/3] Verificando..."
|
Write-Host "[3/3] Verificando..."
|
||||||
if (Test-Path $LOCAL_BACKUP) {
|
if (Test-Path $LOCAL_BACKUP) {
|
||||||
@@ -131,6 +133,7 @@ ps_script = <<~PSCMD.strip
|
|||||||
$res = @{
|
$res = @{
|
||||||
paso = "download_complete"
|
paso = "download_complete"
|
||||||
backup_local = $LOCAL_BACKUP
|
backup_local = $LOCAL_BACKUP
|
||||||
|
backup_name = $BACKUP_NAME
|
||||||
drive_origen = "Google Drive"
|
drive_origen = "Google Drive"
|
||||||
nodo = "dasu-sql4"
|
nodo = "dasu-sql4"
|
||||||
duracion = [math]::Round($dur, 2)
|
duracion = [math]::Round($dur, 2)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
require 'tiny_tds'
|
require 'tiny_tds'
|
||||||
require 'time'
|
require 'time'
|
||||||
require 'json'
|
require 'json'
|
||||||
|
require 'base64'
|
||||||
require_relative 'lib/dasu_executor'
|
require_relative 'lib/dasu_executor'
|
||||||
|
|
||||||
# Nombre del archivo con timestamp
|
# Nombre del archivo con timestamp
|
||||||
@@ -33,6 +34,10 @@ begin
|
|||||||
|
|
||||||
sql_user = 'UTNlarioja\\monlaricardo'
|
sql_user = 'UTNlarioja\\monlaricardo'
|
||||||
sql_host = '10.0.10.200'
|
sql_host = '10.0.10.200'
|
||||||
|
smb_user = 'rmonla'
|
||||||
|
smb_server = 'srv-ns8'
|
||||||
|
smb_share = 'bkps-dasuten'
|
||||||
|
smb_path = "\\\\#{smb_server}\\#{smb_share}"
|
||||||
|
|
||||||
if sql_pass.empty?
|
if sql_pass.empty?
|
||||||
puts "[!] Error: No se pudo obtener credencial de candados"
|
puts "[!] Error: No se pudo obtener credencial de candados"
|
||||||
@@ -47,8 +52,24 @@ begin
|
|||||||
)
|
)
|
||||||
puts "[+] Conectado a SQL Server en #{sql_host} (auth: Windows)"
|
puts "[+] Conectado a SQL Server en #{sql_host} (auth: Windows)"
|
||||||
|
|
||||||
|
# Mapear share SMB con credenciales usando PowerShell
|
||||||
|
puts "\n[0/3] Mapeando share SMB #{smb_path}..."
|
||||||
|
# Escapar password para PowerShell
|
||||||
|
sql_pass_escaped = sql_pass.gsub("'", "''")
|
||||||
|
ps_map = "net use X: #{smb_path} #{sql_pass_escaped} /user:#{smb_user} /persistent:no"
|
||||||
|
map_result = client.execute("EXEC xp_cmdshell '#{ps_map}'")
|
||||||
|
map_output = []
|
||||||
|
map_result.each { |r| map_output << r['output'] if r['output'] && !r['output'].strip.empty? }
|
||||||
|
puts " #{map_output.join("\n ")}"
|
||||||
|
puts "[+] Share mapeado en X: -> #{smb_path}"
|
||||||
|
|
||||||
|
# Crear directorio en share si no existe
|
||||||
|
puts "\n[1/3] Verificando directorio en share..."
|
||||||
|
client.execute("EXEC master.sys.xp_create_subdir 'X:\\'").each { |r| }
|
||||||
|
puts "[+] Directorio share asegurado: X:\\"
|
||||||
|
|
||||||
# Generar backup diferencial directamente en share X:
|
# Generar backup diferencial directamente en share X:
|
||||||
puts "\n[0/2] Generando backup diferencial en share X:..."
|
puts "[2/3] Generando backup diferencial en share X:..."
|
||||||
start = Time.now
|
start = Time.now
|
||||||
|
|
||||||
sql = <<~SQL.strip
|
sql = <<~SQL.strip
|
||||||
@@ -65,19 +86,22 @@ begin
|
|||||||
duracion = (Time.now - start).round(2)
|
duracion = (Time.now - start).round(2)
|
||||||
puts "[+] Backup diferencial generado en #{duracion} segundos"
|
puts "[+] Backup diferencial generado en #{duracion} segundos"
|
||||||
|
|
||||||
# Verificar archivo en share usando módulo común
|
# Verificar archivo en share directamente en srv-ns8 vía SSH
|
||||||
puts "\n[1/2] Verificando archivo en share..."
|
puts "\n[3/3] Verificando archivo en share..."
|
||||||
file_info = DasuExecutor.get_file_info_on_fenix(client, BACKUP_FILE)
|
backup_unix = BACKUP_FILE.gsub('X:\\', '/mnt/ns8Disco2/bkps-dasuten/')
|
||||||
|
ls_result = `ssh rmonla@10.0.10.8 "ls -l '#{backup_unix}' 2>&1"`.strip
|
||||||
if file_info[:exists]
|
if $?.success? && ls_result.include?(File.basename(backup_unix))
|
||||||
puts "[+] Archivo verificado: #{BACKUP_FILE} (#{file_info[:size_mb]} MB)"
|
# Extraer tamaño del output de ls -l
|
||||||
|
file_size = ls_result.split[4].to_i
|
||||||
|
puts "[+] Archivo verificado: #{backup_unix} (#{file_size.to_i / 1024 / 1024} MB)"
|
||||||
else
|
else
|
||||||
puts "[!] Error: No existe el archivo en #{BACKUP_FILE}"
|
puts "[!] Error: No existe el archivo en #{backup_unix}"
|
||||||
|
puts " SSH output: #{ls_result}"
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
# Tamaño aproximado de la BD base
|
# Tamaño aproximado de la BD base
|
||||||
puts "\n[2/2] Información de tamaño..."
|
puts "\n[4/3] Información de tamaño..."
|
||||||
begin
|
begin
|
||||||
size_info = client.execute("SELECT size_bytes = (SELECT size * 8192 FROM sys.database_files WHERE name = 'sysdasuten')")
|
size_info = client.execute("SELECT size_bytes = (SELECT size * 8192 FROM sys.database_files WHERE name = 'sysdasuten')")
|
||||||
puts " Tamaño BD base: #{size_info.to_a.first['size_bytes'].to_i / 1024 / 1024} MB"
|
puts " Tamaño BD base: #{size_info.to_a.first['size_bytes'].to_i / 1024 / 1024} MB"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
require 'tiny_tds'
|
require 'tiny_tds'
|
||||||
require 'time'
|
require 'time'
|
||||||
require 'json'
|
require 'json'
|
||||||
|
require 'base64'
|
||||||
require_relative 'lib/dasu_executor'
|
require_relative 'lib/dasu_executor'
|
||||||
|
|
||||||
# Nombre del archivo con timestamp
|
# Nombre del archivo con timestamp
|
||||||
@@ -34,6 +35,10 @@ begin
|
|||||||
|
|
||||||
sql_user = 'UTNlarioja\\monlaricardo'
|
sql_user = 'UTNlarioja\\monlaricardo'
|
||||||
sql_host = '10.0.10.200'
|
sql_host = '10.0.10.200'
|
||||||
|
smb_user = 'rmonla'
|
||||||
|
smb_server = 'srv-ns8'
|
||||||
|
smb_share = 'bkps-dasuten'
|
||||||
|
smb_path = "\\\\#{smb_server}\\#{smb_share}"
|
||||||
|
|
||||||
if sql_pass.empty?
|
if sql_pass.empty?
|
||||||
puts "[!] Error: No se pudo obtener credencial de candados"
|
puts "[!] Error: No se pudo obtener credencial de candados"
|
||||||
@@ -48,13 +53,23 @@ begin
|
|||||||
)
|
)
|
||||||
puts "[+] Conectado a SQL Server en #{sql_host} (auth: Windows)"
|
puts "[+] Conectado a SQL Server en #{sql_host} (auth: Windows)"
|
||||||
|
|
||||||
|
# Mapear share SMB con credenciales usando net use
|
||||||
|
puts "\n[0/3] Mapeando share SMB #{smb_path}..."
|
||||||
|
sql_pass_escaped = sql_pass.gsub("'", "''")
|
||||||
|
ps_map = "net use X: #{smb_path} #{sql_pass_escaped} /user:#{smb_user} /persistent:no"
|
||||||
|
map_result = client.execute("EXEC xp_cmdshell '#{ps_map}'")
|
||||||
|
map_output = []
|
||||||
|
map_result.each { |r| map_output << r['output'] if r['output'] && !r['output'].strip.empty? }
|
||||||
|
puts " #{map_output.join("\n ")}"
|
||||||
|
puts "[+] Share mapeado en X: -> #{smb_path}"
|
||||||
|
|
||||||
# Crear directorio en share si no existe
|
# Crear directorio en share si no existe
|
||||||
puts "\n[0/2] Verificando directorio en share..."
|
puts "\n[1/3] Verificando directorio en share..."
|
||||||
client.execute("EXEC master.sys.xp_create_subdir 'X:\\'")
|
client.execute("EXEC master.sys.xp_create_subdir 'X:\\'").each { |r| }
|
||||||
puts "[+] Directorio share asegurado: X:\\"
|
puts "[+] Directorio share asegurado: X:\\"
|
||||||
|
|
||||||
# Generar backup comprimido directamente en share X:
|
# Generar backup comprimido directamente en share X:
|
||||||
puts "[1/2] Generando backup en share X: (srv-ns8)..."
|
puts "[2/3] Generando backup en share X: (srv-ns8)..."
|
||||||
start = Time.now
|
start = Time.now
|
||||||
|
|
||||||
result = client.execute("BACKUP DATABASE sysdasuten TO DISK = '#{BACKUP_NS8}' WITH INIT, COMPRESSION, STATS=10")
|
result = client.execute("BACKUP DATABASE sysdasuten TO DISK = '#{BACKUP_NS8}' WITH INIT, COMPRESSION, STATS=10")
|
||||||
@@ -63,14 +78,17 @@ begin
|
|||||||
duracion = (Time.now - start).round(2)
|
duracion = (Time.now - start).round(2)
|
||||||
puts "[+] Backup generado en #{duracion} segundos"
|
puts "[+] Backup generado en #{duracion} segundos"
|
||||||
|
|
||||||
# Verificar archivo en share usando módulo común
|
# Verificar archivo en share directamente en srv-ns8 vía SSH
|
||||||
puts "[2/2] Verificando archivo en share..."
|
puts "[3/3] Verificando archivo en share..."
|
||||||
file_info = DasuExecutor.get_file_info_on_fenix(client, BACKUP_NS8)
|
backup_unix = BACKUP_NS8.gsub('X:\\', '/mnt/ns8Disco2/bkps-dasuten/')
|
||||||
|
ls_result = `ssh rmonla@10.0.10.8 "ls -l '#{backup_unix}' 2>&1"`.strip
|
||||||
if file_info[:exists]
|
if $?.success? && ls_result.include?(File.basename(backup_unix))
|
||||||
puts "[+] Archivo verificado: #{BACKUP_NS8} (#{file_info[:size_mb]} MB)"
|
# Extraer tamaño del output de ls -l
|
||||||
|
file_size = ls_result.split[4].to_i
|
||||||
|
puts "[+] Archivo verificado: #{backup_unix} (#{file_size.to_i / 1024 / 1024} MB)"
|
||||||
else
|
else
|
||||||
puts "[!] Error: No existe el archivo en #{BACKUP_NS8}"
|
puts "[!] Error: No existe el archivo en #{backup_unix}"
|
||||||
|
puts " SSH output: #{ls_result}"
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -22,47 +22,59 @@ JSON_PATH = 'C:\temp\dron_restore_output.json'
|
|||||||
if File.exist?(DOWNLOAD_OUTPUT)
|
if File.exist?(DOWNLOAD_OUTPUT)
|
||||||
output = JSON.parse(File.read(DOWNLOAD_OUTPUT))
|
output = JSON.parse(File.read(DOWNLOAD_OUTPUT))
|
||||||
backup_local = output['backup_local']
|
backup_local = output['backup_local']
|
||||||
backup_name = File.basename(backup_local)
|
backup_name = output['backup_name'] || File.basename(backup_local)
|
||||||
LOCAL_BACKUP = "#{BACKUP_DIR_WINDOWS}\\#{backup_name}"
|
LOCAL_BACKUP = backup_local # Usar la ruta EXACTA que viene del download
|
||||||
puts "[*] Dron: Restaurar backup DASUTEN"
|
puts "[*] Dron: Restaurar backup DASUTEN"
|
||||||
|
puts " Nodo: dasu-sql4"
|
||||||
|
puts " Backup: #{LOCAL_BACKUP} (desde download)"
|
||||||
|
puts " Nombre: #{backup_name}"
|
||||||
else
|
else
|
||||||
LOCAL_BACKUP = "#{BACKUP_DIR_WINDOWS}\\sysdasuten_FULL_*.bak"
|
LOCAL_BACKUP = "#{BACKUP_DIR_WINDOWS}\\sysdasuten_FULL_*.bak"
|
||||||
puts "[*] Dron: Restaurar backup DASUTEN (buscar más reciente)"
|
puts "[*] Dron: Restaurar backup DASUTEN (buscar más reciente)"
|
||||||
|
puts " Nodo: dasu-sql4"
|
||||||
|
puts " Backup: #{LOCAL_BACKUP} (búsqueda por patrón)"
|
||||||
end
|
end
|
||||||
|
|
||||||
puts " Nodo: dasu-sql4"
|
|
||||||
puts " Backup: #{LOCAL_BACKUP}"
|
|
||||||
|
|
||||||
# PowerShell script para restore
|
# PowerShell script para restore
|
||||||
ps_script = <<~'PSCMD'.strip
|
ps_script = <<~PSCMD.strip
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
$BACKUP_DIR = "F:\BACKUP"
|
$BACKUP_DIR = "F:\\BACKUP"
|
||||||
|
$BACKUP_NAME = "#{backup_name}"
|
||||||
|
$LOCAL_BACKUP = "$BACKUP_DIR\\$BACKUP_NAME"
|
||||||
|
$JSON_PATH = "C:\\temp\\dron_restore_output.json"
|
||||||
|
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path "C:\\temp"
|
||||||
|
|
||||||
|
Write-Host "[0/1] Verificando backup específico..."
|
||||||
|
Write-Host " Archivo esperado: $LOCAL_BACKUP"
|
||||||
|
|
||||||
|
if (-not (Test-Path $LOCAL_BACKUP)) {
|
||||||
|
Write-Host "[!] No se encontró backup en $LOCAL_BACKUP"
|
||||||
|
Write-Host " Buscando backup más reciente como fallback..."
|
||||||
$BACKUP_PATTERN = "sysdasuten_FULL_*.bak"
|
$BACKUP_PATTERN = "sysdasuten_FULL_*.bak"
|
||||||
$JSON_PATH = "C:\temp\dron_restore_output.json"
|
$backupFile = Get-ChildItem "$BACKUP_DIR\\$BACKUP_PATTERN" | Sort-Object Name -Descending | Select-Object -First 1
|
||||||
|
|
||||||
$null = New-Item -ItemType Directory -Force -Path "C:\temp"
|
|
||||||
|
|
||||||
Write-Host "[0/1] Buscando backup completo más reciente..."
|
|
||||||
$backupFile = Get-ChildItem "$BACKUP_DIR\$BACKUP_PATTERN" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
|
||||||
if (-not $backupFile) {
|
if (-not $backupFile) {
|
||||||
Write-Host "[!] No se encontró backup completo en $BACKUP_DIR"
|
Write-Host "[!] No se encontró ningún backup en $BACKUP_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
$LOCAL_BACKUP = $backupFile.FullName
|
$LOCAL_BACKUP = $backupFile.FullName
|
||||||
Write-Host " Archivo: $LOCAL_BACKUP"
|
Write-Host " Fallback: $LOCAL_BACKUP"
|
||||||
|
}
|
||||||
|
|
||||||
|
$backupFile = Get-Item $LOCAL_BACKUP
|
||||||
Write-Host " Tamaño: $([math]::Round($backupFile.Length / 1MB, 2)) MB"
|
Write-Host " Tamaño: $([math]::Round($backupFile.Length / 1MB, 2)) MB"
|
||||||
|
|
||||||
Write-Host "[1/1] Ejecutando RESTORE DATABASE..."
|
Write-Host "[1/1] Ejecutando RESTORE DATABASE..."
|
||||||
$start = Get-Date
|
$start = Get-Date
|
||||||
|
|
||||||
$sqlcmd = "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\sqlcmd.exe"
|
$sqlcmd = "C:\\Program Files\\Microsoft SQL Server\\Client SDK\\ODBC\\170\\Tools\\Binn\\sqlcmd.exe"
|
||||||
|
|
||||||
$restoreSql = @"
|
$restoreSql = @"
|
||||||
RESTORE DATABASE sysdasuten
|
RESTORE DATABASE sysdasuten
|
||||||
FROM DISK = '$LOCAL_BACKUP'
|
FROM DISK = '$LOCAL_BACKUP'
|
||||||
WITH REPLACE,
|
WITH REPLACE,
|
||||||
MOVE 'sysdasuten' TO 'F:\DATA\sysdasuten.mdf',
|
MOVE 'sysdasuten' TO 'F:\\DATA\\sysdasuten.mdf',
|
||||||
MOVE 'sysdasuten_log' TO 'F:\LOG\sysdasuten_log.ldf'
|
MOVE 'sysdasuten_log' TO 'F:\\LOG\\sysdasuten_log.ldf'
|
||||||
"@
|
"@
|
||||||
|
|
||||||
Write-Host " SQL: $restoreSql"
|
Write-Host " SQL: $restoreSql"
|
||||||
@@ -82,6 +94,7 @@ ps_script = <<~'PSCMD'.strip
|
|||||||
$res = @{
|
$res = @{
|
||||||
paso = "restore_complete"
|
paso = "restore_complete"
|
||||||
backup_ruta = $LOCAL_BACKUP
|
backup_ruta = $LOCAL_BACKUP
|
||||||
|
backup_name = $BACKUP_NAME
|
||||||
nodo = "dasu-sql4"
|
nodo = "dasu-sql4"
|
||||||
duracion_total = $duracionRestore
|
duracion_total = $duracionRestore
|
||||||
duracion_restore = $duracionRestore
|
duracion_restore = $duracionRestore
|
||||||
|
|||||||
@@ -16,17 +16,28 @@ require 'shellwords'
|
|||||||
|
|
||||||
REMOTE_DRIVE = "rmonla-GDrive:drive_bkps-dasu"
|
REMOTE_DRIVE = "rmonla-GDrive:drive_bkps-dasu"
|
||||||
|
|
||||||
# Leer ruta del backup desde el output de la transferencia
|
# Archivos de output de los pasos anteriores
|
||||||
TRANSFER_OUTPUT = '/tmp/dron_transfer_output.json'
|
TRANSFER_OUTPUT = '/tmp/dron_transfer_output.json'
|
||||||
if File.exist?(TRANSFER_OUTPUT)
|
EXPORT_OUTPUT = '/tmp/dron_export_output.json'
|
||||||
|
|
||||||
|
# Determinar cuál es el más reciente (si existen)
|
||||||
|
archivos_output = [TRANSFER_OUTPUT, EXPORT_OUTPUT].select { |f| File.exist?(f) }
|
||||||
|
output_reciente = archivos_output.max_by { |f| File.mtime(f) }
|
||||||
|
|
||||||
|
if output_reciente == TRANSFER_OUTPUT
|
||||||
output = JSON.parse(File.read(TRANSFER_OUTPUT))
|
output = JSON.parse(File.read(TRANSFER_OUTPUT))
|
||||||
BACKUP_LOCAL = output['backup_local']
|
BACKUP_LOCAL = output['backup_local'] || output['backup_ruta_unix']
|
||||||
BACKUP_NAME = File.basename(BACKUP_LOCAL)
|
BACKUP_NAME = File.basename(BACKUP_LOCAL)
|
||||||
puts "[*] Dron: Upload a Google Drive (DIFERENCIAL)"
|
puts "[*] Dron: Upload a Google Drive (Desde TRANSFERENCIA)"
|
||||||
|
elsif output_reciente == EXPORT_OUTPUT
|
||||||
|
output = JSON.parse(File.read(EXPORT_OUTPUT))
|
||||||
|
BACKUP_LOCAL = output['backup_ruta_unix']
|
||||||
|
BACKUP_NAME = File.basename(BACKUP_LOCAL)
|
||||||
|
puts "[*] Dron: Upload a Google Drive (Desde EXPORTACIÓN DIRECTA)"
|
||||||
else
|
else
|
||||||
BACKUP_LOCAL = "/var/tmp/sysdasuten_compressed_ADN.bak"
|
BACKUP_LOCAL = "/var/tmp/sysdasuten_compressed_ADN.bak"
|
||||||
BACKUP_NAME = "sysdasuten_compressed_ADN_#{Time.now.strftime('%Y%m%d_%H%M%S')}.bak"
|
BACKUP_NAME = "sysdasuten_compressed_ADN_#{Time.now.strftime('%Y%m%d_%H%M%S')}.bak"
|
||||||
puts "[*] Dron: Upload a Google Drive (COMPLETO)"
|
puts "[*] Dron: Upload a Google Drive (COMPLETO FALLBACK)"
|
||||||
end
|
end
|
||||||
|
|
||||||
puts " Nodo: srv-ns8"
|
puts " Nodo: srv-ns8"
|
||||||
|
|||||||
@@ -108,8 +108,15 @@ module DasuExecutor
|
|||||||
|
|
||||||
# Ejecutar script
|
# Ejecutar script
|
||||||
ssh_cmd = "ssh srv-dasu 'bash #{remote_path}'"
|
ssh_cmd = "ssh srv-dasu 'bash #{remote_path}'"
|
||||||
output = `#{ssh_cmd}`
|
output_buffer = ""
|
||||||
output = output.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') if output
|
IO.popen(ssh_cmd, err: [:child, :out]) do |io|
|
||||||
|
io.each_line do |line|
|
||||||
|
safe_line = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
||||||
|
print safe_line
|
||||||
|
output_buffer += safe_line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
output = output_buffer
|
||||||
|
|
||||||
# Limpieza
|
# Limpieza
|
||||||
bash_file.unlink
|
bash_file.unlink
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== LIMPIEZA DE BACKUPS EN DASU-SQL4 ==="
|
||||||
|
puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$JSON_PATH = "C:\\temp\\limpieza_output.json"
|
||||||
|
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path "C:\\temp"
|
||||||
|
|
||||||
|
# Listar todos los backups
|
||||||
|
$backups = Get-ChildItem "F:\\BACKUP\\sysdasuten*.bak" | Sort-Object LastWriteTime -Descending
|
||||||
|
|
||||||
|
Write-Host "=== ANTES DE LIMPIEZA ==="
|
||||||
|
Write-Host "Total archivos: $($backups.Count)"
|
||||||
|
$totalSize = ($backups | Measure-Object -Property Length -Sum).Sum / 1MB
|
||||||
|
Write-Host "Total tamaño: $([math]::Round($totalSize, 2)) MB"
|
||||||
|
|
||||||
|
Write-Host "`nArchivos:"
|
||||||
|
$backups | ForEach-Object {
|
||||||
|
$sizeMB = [math]::Round($_.Length / 1MB, 2)
|
||||||
|
Write-Host " $($_.Name) - ${sizeMB} MB - $($_.LastWriteTime)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mantener solo el más reciente
|
||||||
|
if ($backups.Count -gt 1) {
|
||||||
|
$aEliminar = $backups | Select-Object -Skip 1
|
||||||
|
|
||||||
|
Write-Host "`n=== ELIMINANDO $($aEliminar.Count) ARCHIVOS ==="
|
||||||
|
$aEliminar | ForEach-Object {
|
||||||
|
Write-Host " Eliminando: $($_.Name)..."
|
||||||
|
Remove-Item $_.FullName -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verificar resultado
|
||||||
|
$restantes = Get-ChildItem "F:\\BACKUP\\sysdasuten*.bak" | Sort-Object LastWriteTime -Descending
|
||||||
|
Write-Host "`n=== DESPUÉS DE LIMPIEZA ==="
|
||||||
|
Write-Host "Total archivos: $($restantes.Count)"
|
||||||
|
$totalSize = ($restantes | Measure-Object -Property Length -Sum).Sum / 1MB
|
||||||
|
Write-Host "Total tamaño: $([math]::Round($totalSize, 2)) MB"
|
||||||
|
|
||||||
|
$restantes | ForEach-Object {
|
||||||
|
$sizeMB = [math]::Round($_.Length / 1MB, 2)
|
||||||
|
Write-Host " $($_.Name) - ${sizeMB} MB - $($_.LastWriteTime)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultado = @{
|
||||||
|
eliminados = $aEliminar.Count
|
||||||
|
restantes = $restantes.Count
|
||||||
|
espacio_libre_mb = [math]::Round($totalSize, 2)
|
||||||
|
timestamp = (Get-Date -Format "o")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "`n=== NO HAY NADA QUE ELIMINAR ==="
|
||||||
|
$resultado = @{
|
||||||
|
eliminados = 0
|
||||||
|
restantes = $backups.Count
|
||||||
|
timestamp = (Get-Date -Format "o")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultado | ConvertTo-Json | Out-File -FilePath $JSON_PATH -Encoding utf8
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script, output_path: 'C:\temp\limpieza_output.json')
|
||||||
|
resultado = DasuExecutor.read_json_from_dasu('C:\temp\limpieza_output.json')
|
||||||
|
|
||||||
|
puts "\n=== RESUMEN ==="
|
||||||
|
puts " Archivos eliminados: #{resultado['eliminados']}"
|
||||||
|
puts " Archivos restantes: #{resultado['restantes']}"
|
||||||
|
puts " Espacio usado: #{resultado['espacio_libre_mb']} MB" if resultado['espacio_libre_mb']
|
||||||
|
|
||||||
|
puts "\n✅ Limpieza completada"
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'tiny_tds'
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== VERIFICANDO BACKUPS EN FENIX (E:\\BK_SQL\\sysdasuten\\) ==="
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
pass_cmd = `ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null`.strip
|
||||||
|
sql_user = 'UTNlarioja\\monlaricardo'
|
||||||
|
sql_host = '10.0.10.200'
|
||||||
|
|
||||||
|
client = TinyTds::Client.new(
|
||||||
|
username: sql_user,
|
||||||
|
password: pass_cmd,
|
||||||
|
host: sql_host,
|
||||||
|
timeout: 600,
|
||||||
|
database: 'sysdasuten'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Listar backups en E:\BK_SQL\sysdasuten\
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$path = "E:\\BK_SQL\\sysdasuten"
|
||||||
|
|
||||||
|
if (Test-Path $path) {
|
||||||
|
$backups = Get-ChildItem "$path\\sysdasuten*.bak" | Sort-Object LastWriteTime -Descending
|
||||||
|
|
||||||
|
Write-Host "Archivos encontrados:"
|
||||||
|
$backups | ForEach-Object {
|
||||||
|
$sizeMB = [math]::Round($_.Length / 1MB, 2)
|
||||||
|
Write-Host " $($_.Name) - ${sizeMB} MB - $($_.LastWriteTime)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`nTotal archivos: $($backups.Count)"
|
||||||
|
$totalSize = ($backups | Measure-Object -Property Length -Sum).Sum / 1MB
|
||||||
|
Write-Host "Total tamaño: $([math]::Round($totalSize, 2)) MB"
|
||||||
|
|
||||||
|
# Si hay más de 1, eliminar los viejos
|
||||||
|
if ($backups.Count -gt 1) {
|
||||||
|
$aEliminar = $backups | Select-Object -Skip 1
|
||||||
|
Write-Host "`n=== ELIMINANDO $($aEliminar.Count) ARCHIVOS ==="
|
||||||
|
$aEliminar | ForEach-Object {
|
||||||
|
Write-Host " Eliminando: $($_.Name)"
|
||||||
|
Remove-Item $_.FullName -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
$restantes = Get-ChildItem "$path\\sysdasuten*.bak" | Sort-Object LastWriteTime -Descending
|
||||||
|
Write-Host "`nRestantes: $($restantes.Count)"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "El directorio no existe"
|
||||||
|
}
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
result = client.execute("EXEC xp_cmdshell 'powershell -Command \"' + ps_script.gsub('\"', '\"\"') + '\"'")
|
||||||
|
result.each { |r| puts r['output'] if r['output'] }
|
||||||
|
|
||||||
|
client.close
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# Restauración COMPLETA desde backup local en dasu-sql4
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== RESTAURACIÓN COMPLETA DESDE BACKUP LOCAL ==="
|
||||||
|
puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# Script PowerShell para restore (sin drop, la BD ya no existe)
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$JSON_PATH = "C:\\temp\\restaurar_completo_output.json"
|
||||||
|
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path "C:\\temp"
|
||||||
|
|
||||||
|
# Encontrar el backup más reciente
|
||||||
|
Write-Host "[0/5] Buscando backup más reciente..."
|
||||||
|
$backupFile = Get-ChildItem "F:\\BACKUP\\sysdasuten_FULL_*.bak" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||||
|
if (-not $backupFile) {
|
||||||
|
Write-Host "[!] No se encontró backup en F:\\BACKUP\\"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$BACKUP_FILE = $backupFile.FullName
|
||||||
|
Write-Host " Backup: $($backupFile.Name)"
|
||||||
|
Write-Host " Tamaño: $([math]::Round($backupFile.Length / 1MB, 2)) MB"
|
||||||
|
|
||||||
|
# Ver archivos lógicos en el backup
|
||||||
|
Write-Host "`n[1/5] Leyendo archivos lógicos del backup..."
|
||||||
|
$connString = "Server=localhost;Database=master;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("RESTORE FILELISTONLY FROM DISK = '$BACKUP_FILE'", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
|
||||||
|
$logicalNames = @{}
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
Write-Host " Archivos en el backup:"
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " Tipo: $($_.Type) | Lógico: $($_.LogicalName) | Físico: $($_.PhysicalName)"
|
||||||
|
if ($_.Type -eq 'D') {
|
||||||
|
$logicalNames['data'] = $_.LogicalName
|
||||||
|
} elseif ($_.Type -eq 'L') {
|
||||||
|
$logicalNames['log'] = $_.LogicalName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[2/5] Restaurando desde backup..."
|
||||||
|
Write-Host " Backup: $BACKUP_FILE"
|
||||||
|
Write-Host " LogicalName data: $($logicalNames['data'])"
|
||||||
|
Write-Host " LogicalName log: $($logicalNames['log'])"
|
||||||
|
|
||||||
|
$restoreSql = @"
|
||||||
|
RESTORE DATABASE sysdasuten
|
||||||
|
FROM DISK = '$BACKUP_FILE'
|
||||||
|
WITH REPLACE, RECOVERY,
|
||||||
|
MOVE '$($logicalNames['data'])' TO 'F:\\DATA\\sysdasuten.mdf',
|
||||||
|
MOVE '$($logicalNames['log'])' TO 'F:\\LOG\\sysdasuten_log.ldf'
|
||||||
|
"@
|
||||||
|
|
||||||
|
Write-Host " SQL: $restoreSql"
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand($restoreSql, $conn)
|
||||||
|
$cmd.CommandTimeout = 600
|
||||||
|
|
||||||
|
$start = Get-Date
|
||||||
|
try {
|
||||||
|
$cmd.ExecuteNonQuery() | Out-Null
|
||||||
|
$duracion = [math]::Round((New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds, 2)
|
||||||
|
Write-Host " Restore completado en ${duracion}s"
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR en restore: $_"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[3/5] Poniendo BD en línea..."
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
ALTER DATABASE sysdasuten SET MULTI_USER
|
||||||
|
", $conn)
|
||||||
|
$cmd.ExecuteNonQuery() | Out-Null
|
||||||
|
Write-Host " BD en línea"
|
||||||
|
|
||||||
|
# Verificar restore
|
||||||
|
Write-Host "`n[4/5] Verificando cantidad de registros..."
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT COUNT(*) as total FROM sysdasuten.dbo.BonosAfiliado
|
||||||
|
", $conn)
|
||||||
|
$total = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Total registros en BonosAfiliado: $total"
|
||||||
|
|
||||||
|
Write-Host "`n[5/5] Verificando IDs y fechas..."
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT MAX(idsolicitud) as max_id FROM sysdasuten.dbo.BonosAfiliado
|
||||||
|
", $conn)
|
||||||
|
$maxId = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " MAX(idsolicitud): $maxId"
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT MAX(fecha) as ultima_fecha FROM sysdasuten.dbo.BonosAfiliado
|
||||||
|
", $conn)
|
||||||
|
$ultimaFecha = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Última fecha: $ultimaFecha"
|
||||||
|
|
||||||
|
# Verificar registros con idsolicitud = 0 vs > 0
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT
|
||||||
|
SUM(CASE WHEN idsolicitud = 0 THEN 1 ELSE 0 END) as con_cero,
|
||||||
|
SUM(CASE WHEN idsolicitud > 0 THEN 1 ELSE 0 END) as con_mayor_cero
|
||||||
|
FROM sysdasuten.dbo.BonosAfiliado
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$row = $ds.Tables[0].Rows[0]
|
||||||
|
Write-Host " Con idsolicitud=0: $($row.con_cero)"
|
||||||
|
Write-Host " Con idsolicitud>0: $($row.con_mayor_cero)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
|
||||||
|
$resultado = @{
|
||||||
|
paso = "restore_completo"
|
||||||
|
backup = $BACKUP_FILE
|
||||||
|
total_registros = $total
|
||||||
|
max_idsolicitud = $maxId
|
||||||
|
ultima_fecha = $ultimaFecha
|
||||||
|
timestamp = (Get-Date -Format "o")
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultado | ConvertTo-Json | Out-File -FilePath $JSON_PATH -Encoding utf8
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
puts "\n[1/2] Ejecutando restauración..."
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script, output_path: 'C:\temp\restaurar_completo_output.json')
|
||||||
|
|
||||||
|
puts "\n[2/2] Leyendo resultado..."
|
||||||
|
begin
|
||||||
|
resultado = DasuExecutor.read_json_from_dasu('C:\temp\restaurar_completo_output.json')
|
||||||
|
puts "\n=== RESULTADO ==="
|
||||||
|
puts " Backup: #{resultado['backup']}"
|
||||||
|
puts " Total registros: #{resultado['total_registros']}"
|
||||||
|
puts " MAX(idsolicitud): #{resultado['max_idsolicitud']}"
|
||||||
|
puts " Última fecha: #{resultado['ultima_fecha']}"
|
||||||
|
rescue => e
|
||||||
|
puts "[!] Error leyendo resultado: #{e.message}"
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "\n✅ Restauración completada"
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# Verificación Final Comparativa - FENIX vs DASU-SQL4
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
require 'tiny_tds'
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "═" * 70
|
||||||
|
puts " VERIFICACIÓN FINAL - FENIX (PRODUCCIÓN) vs DASU-SQL4"
|
||||||
|
puts " Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts "═" * 70
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 1. FENIX - Obtener datos
|
||||||
|
# ==========================================
|
||||||
|
puts "\n[1] Conectando a FENIX (PRODUCCIÓN)..."
|
||||||
|
|
||||||
|
begin
|
||||||
|
pass_cmd = `ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null`.strip
|
||||||
|
sql_user = 'UTNlarioja\\monlaricardo'
|
||||||
|
sql_host = '10.0.10.200'
|
||||||
|
|
||||||
|
client_fenix = TinyTds::Client.new(
|
||||||
|
username: sql_user,
|
||||||
|
password: pass_cmd,
|
||||||
|
host: sql_host,
|
||||||
|
timeout: 600,
|
||||||
|
database: 'sysdasuten'
|
||||||
|
)
|
||||||
|
puts "[+] Conectado a FENIX"
|
||||||
|
|
||||||
|
# Consultas a FENIX
|
||||||
|
puts "\n[2] Obteniendo datos de FENIX..."
|
||||||
|
|
||||||
|
fenix_data = {}
|
||||||
|
|
||||||
|
# Total BonosAfiliado
|
||||||
|
result = client_fenix.execute("SELECT COUNT(*) as c FROM BonosAfiliado")
|
||||||
|
fenix_data[:total_bonos] = result.first['c']
|
||||||
|
|
||||||
|
# Registros por día (últimos 5 días)
|
||||||
|
result = client_fenix.execute("
|
||||||
|
SELECT TOP 5
|
||||||
|
CAST(fecha AS DATE) as dia,
|
||||||
|
COUNT(*) as c
|
||||||
|
FROM BonosAfiliado
|
||||||
|
GROUP BY CAST(fecha AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
")
|
||||||
|
fenix_data[:por_dia] = {}
|
||||||
|
result.each { |r| fenix_data[:por_dia][r['dia']] = r['c'] }
|
||||||
|
|
||||||
|
# Última fecha
|
||||||
|
result = client_fenix.execute("SELECT MAX(fecha) as ultima FROM BonosAfiliado")
|
||||||
|
fenix_data[:ultima_fecha] = result.first['ultima']
|
||||||
|
|
||||||
|
# MAX(idsolicitud)
|
||||||
|
result = client_fenix.execute("SELECT MAX(idsolicitud) as max_id FROM BonosAfiliado")
|
||||||
|
fenix_data[:max_idsolicitud] = result.first['max_id']
|
||||||
|
|
||||||
|
# AutorizacionBonosExcepcion
|
||||||
|
result = client_fenix.execute("SELECT COUNT(*) as c FROM AutorizacionBonosExcepcion")
|
||||||
|
fenix_data[:total_aut] = result.first['c']
|
||||||
|
|
||||||
|
result = client_fenix.execute("SELECT MAX(fechasolicitud) as ultima FROM AutorizacionBonosExcepcion")
|
||||||
|
fenix_data[:ultima_fecha_aut] = result.first['ultima']
|
||||||
|
|
||||||
|
client_fenix.close
|
||||||
|
|
||||||
|
puts " Total BonosAfiliado: #{fenix_data[:total_bonos]}"
|
||||||
|
puts " Última fecha: #{fenix_data[:ultima_fecha]}"
|
||||||
|
puts " MAX(idsolicitud): #{fenix_data[:max_idsolicitud]}"
|
||||||
|
puts " Total AutorizacionBonosExcepcion: #{fenix_data[:total_aut]}"
|
||||||
|
|
||||||
|
rescue => e
|
||||||
|
puts "[!] Error conectando a FENIX: #{e.message}"
|
||||||
|
fenix_data = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 2. DASU-SQL4 - Obtener datos
|
||||||
|
# ==========================================
|
||||||
|
puts "\n[3] Conectando a DASU-SQL4..."
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$JSON_PATH = "C:\\temp\\verificacion_final_output.json"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
$data = @{}
|
||||||
|
|
||||||
|
# Total BonosAfiliado
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) as c FROM BonosAfiliado", $conn)
|
||||||
|
$data.total_bonos = $cmd.ExecuteScalar()
|
||||||
|
|
||||||
|
# Por día
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 5
|
||||||
|
CONVERT(varchar, CAST(fecha AS DATE), 120) as dia,
|
||||||
|
COUNT(*) as c
|
||||||
|
FROM BonosAfiliado
|
||||||
|
GROUP BY CAST(fecha AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$por_dia = @{}
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object { $por_dia[$($_.dia)] = $($_.c) }
|
||||||
|
$data.por_dia = $por_dia
|
||||||
|
|
||||||
|
# Última fecha
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(fecha) as ultima FROM BonosAfiliado", $conn)
|
||||||
|
$data.ultima_fecha = $cmd.ExecuteScalar()
|
||||||
|
|
||||||
|
# MAX(idsolicitud)
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(idsolicitud) as max_id FROM BonosAfiliado", $conn)
|
||||||
|
$data.max_idsolicitud = $cmd.ExecuteScalar()
|
||||||
|
|
||||||
|
# AutorizacionBonosExcepcion
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) as c FROM AutorizacionBonosExcepcion", $conn)
|
||||||
|
$data.total_aut = $cmd.ExecuteScalar()
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(fechasolicitud) as ultima FROM AutorizacionBonosExcepcion", $conn)
|
||||||
|
$data.ultima_fecha_aut = $cmd.ExecuteScalar()
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
|
||||||
|
$data | ConvertTo-Json -Depth 5 | Out-File -FilePath $JSON_PATH -Encoding utf8
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
begin
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script, output_path: 'C:\temp\verificacion_final_output.json')
|
||||||
|
dasu_data = DasuExecutor.read_json_from_dasu('C:\temp\verificacion_final_output.json')
|
||||||
|
puts "[+] Conectado a DASU-SQL4"
|
||||||
|
|
||||||
|
puts " Total BonosAfiliado: #{dasu_data['total_bonos']}"
|
||||||
|
puts " Última fecha: #{dasu_data['ultima_fecha']}"
|
||||||
|
puts " MAX(idsolicitud): #{dasu_data['max_idsolicitud']}"
|
||||||
|
puts " Total AutorizacionBonosExcepcion: #{dasu_data['total_aut']}"
|
||||||
|
|
||||||
|
rescue => e
|
||||||
|
puts "[!] Error consultando DASU-SQL4: #{e.message}"
|
||||||
|
dasu_data = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 3. COMPARAR
|
||||||
|
# ==========================================
|
||||||
|
puts "\n" + "═" * 70
|
||||||
|
puts " COMPARACIÓN"
|
||||||
|
puts "═" * 70
|
||||||
|
|
||||||
|
def comparar(nombre, fenix, dasu, formato = nil)
|
||||||
|
fenix_val = fenix.nil? ? "N/A" : (formato ? formato.call(fenix) : fenix.to_s)
|
||||||
|
dasu_val = dasu.nil? ? "N/A" : (formato ? formato.call(dasu) : dasu.to_s)
|
||||||
|
|
||||||
|
coincide = fenix.to_s == dasu.to_s
|
||||||
|
estado = coincide ? "✅" : "⚠️"
|
||||||
|
|
||||||
|
puts " #{estado} #{nombre}:"
|
||||||
|
puts " FENIX: #{fenix_val}"
|
||||||
|
puts " DASU: #{dasu_val}"
|
||||||
|
|
||||||
|
coincide
|
||||||
|
end
|
||||||
|
|
||||||
|
todos_coinciden = true
|
||||||
|
|
||||||
|
# Comparar totales
|
||||||
|
todos_coinciden &= comparar("Total BonosAfiliado", fenix_data[:total_bonos], dasu_data['total_bonos'])
|
||||||
|
|
||||||
|
# Comparar última fecha
|
||||||
|
todos_coinciden &= comparar("Última fecha", fenix_data[:ultima_fecha], dasu_data['ultima_fecha'])
|
||||||
|
|
||||||
|
# Comparar MAX(idsolicitud)
|
||||||
|
todos_coinciden &= comparar("MAX(idsolicitud)", fenix_data[:max_idsolicitud], dasu_data['max_idsolicitud'])
|
||||||
|
|
||||||
|
# Comparar total autorizaciones
|
||||||
|
todos_coinciden &= comparar("Total AutorizacionBonosExcepcion", fenix_data[:total_aut], dasu_data['total_aut'])
|
||||||
|
|
||||||
|
# Comparar por día
|
||||||
|
puts "\n Registros por día (últimos 5 días):"
|
||||||
|
if fenix_data[:por_dia] && dasu_data['por_dia']
|
||||||
|
fenix_data[:por_dia].each do |dia, count|
|
||||||
|
dasu_count = dasu_data['por_dia'][dia.to_s]
|
||||||
|
coincide = count == dasu_count
|
||||||
|
estado = coincide ? "✅" : "⚠️"
|
||||||
|
puts " #{estado} #{dia}: FENIX=#{count} | DASU=#{dasu_count}"
|
||||||
|
todos_coinciden = false unless coincide
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "\n" + "═" * 70
|
||||||
|
if todos_coinciden
|
||||||
|
puts " ✅ TODOS LOS DATOS COINCIDEN - EL SISTEMA ESTÁ ACTUALIZADO"
|
||||||
|
else
|
||||||
|
puts " ⚠️ HAY DIFERENCIAS - REVISAR"
|
||||||
|
end
|
||||||
|
puts "═" * 70
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== DASU-SQL4 - Registros de ABRIL 2026 ==="
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
Write-Host "[1] Registros de abril 2026 ordenados por fecha:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 30 idsolicitud, fecha, codigoafiliado, dependencia
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE fecha >= '2026-04-01'
|
||||||
|
ORDER BY fecha DESC, idsolicitud DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " ID: $($_.idsolicitud) | fecha: $($_.fecha) | afiliado: $($_.codigoafiliado)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[2] Cantidad de registros por día (abril 2026):"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT CAST(fecha AS DATE) as dia, COUNT(*) as c
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE fecha >= '2026-04-01'
|
||||||
|
GROUP BY CAST(fecha AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " $($_.dia): $($_.c) registros"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[3] IDs mínimos y máximos por día (abril 2026):"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT
|
||||||
|
CAST(fecha AS DATE) as dia,
|
||||||
|
MIN(idsolicitud) as min_id,
|
||||||
|
MAX(idsolicitud) as max_id,
|
||||||
|
COUNT(*) as c
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE fecha >= '2026-04-01'
|
||||||
|
GROUP BY CAST(fecha AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " $($_.dia): IDs [$($_.min_id) - $($_.max_id)] | c: $($_.c)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# Verificar backup en DASU-SQL4 - qué archivo hay y qué fecha tiene
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== VERIFICANDO BACKUP EN DASU-SQL4 ==="
|
||||||
|
puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$JSON_PATH = "C:\\temp\\backup_info_output.json"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path "C:\\temp"
|
||||||
|
|
||||||
|
# Listar backups en F:\\BACKUP
|
||||||
|
Write-Host "=== ARCHIVOS EN F:\\BACKUP ==="
|
||||||
|
$backups = Get-ChildItem "F:\\BACKUP\\*.bak" | Sort-Object LastWriteTime -Descending
|
||||||
|
$backups | ForEach-Object {
|
||||||
|
Write-Host " Archivo: $($_.Name)"
|
||||||
|
Write-Host " Tamaño: $([math]::Round($_.Length / 1MB, 2)) MB"
|
||||||
|
Write-Host " LastWriteTime: $($_.LastWriteTime)"
|
||||||
|
Write-Host " CreationTime: $($_.CreationTime)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Si hay backup, verificar fecha de los datos dentro
|
||||||
|
if ($backups.Count -gt 0) {
|
||||||
|
$backupMasReciente = $backups[0]
|
||||||
|
Write-Host "`n=== INFORMACIÓN DEL BACKUP MÁS RECIENTE ==="
|
||||||
|
Write-Host " Archivo: $($backupMasReciente.Name)"
|
||||||
|
|
||||||
|
# Conectar a SQL y verificar fecha máxima en BonosAfiliado
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(fecha) as ultima_fecha FROM [BonosAfiliado]", $conn)
|
||||||
|
try {
|
||||||
|
$ultimaFecha = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " ÚLTIMA FECHA EN BonosAfiliado: $ultimaFecha"
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR al obtener fecha: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) FROM [BonosAfiliado] WHERE fecha >= '2026-04-15'", $conn)
|
||||||
|
try {
|
||||||
|
$count15 = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Registros el 2026-04-15: $count15"
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR al contar registros del 15: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) FROM [BonosAfiliado] WHERE fecha >= '2026-04-14'", $conn)
|
||||||
|
try {
|
||||||
|
$count14 = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Registros desde 2026-04-14: $count14"
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR al contar registros desde 14: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) FROM [BonosAfiliado] WHERE fecha >= '2026-04-13'", $conn)
|
||||||
|
try {
|
||||||
|
$count13 = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Registros desde 2026-04-13: $count13"
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR al contar registros desde 13: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verificar fecha de restore en msdb
|
||||||
|
Write-Host "`n=== FECHA DE RESTORE EN msdb ==="
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT TOP 5 destination_database_name, restore_date, backup_start_date FROM msdb.dbo.restorehistory WHERE destination_database_name = 'sysdasuten' ORDER BY restore_date DESC", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " Restore: $($_.restore_date)"
|
||||||
|
Write-Host " Backup de: $($_.backup_start_date)"
|
||||||
|
Write-Host " BD: $($_.destination_database_name)"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " No hay registros de restore"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'tiny_tds'
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== VERIFICANDO BACKUP EN DASU-SQL4 ==="
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 1. FENIX - Verificar datos actuales
|
||||||
|
# ==========================================
|
||||||
|
puts "[1] FENIX - Estado actual de BonosAfiliado:"
|
||||||
|
|
||||||
|
pass_cmd = `ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null`.strip
|
||||||
|
sql_user = 'UTNlarioja\\monlaricardo'
|
||||||
|
sql_host = '10.0.10.200'
|
||||||
|
|
||||||
|
client_fenix = TinyTds::Client.new(
|
||||||
|
username: sql_user,
|
||||||
|
password: pass_cmd,
|
||||||
|
host: sql_host,
|
||||||
|
timeout: 600,
|
||||||
|
database: 'sysdasuten'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Última fecha y registros del 15
|
||||||
|
result = client_fenix.execute("SELECT MAX(fecha) as ultima FROM BonosAfiliado")
|
||||||
|
puts " Última fecha: #{result.first['ultima']}"
|
||||||
|
|
||||||
|
result = client_fenix.execute("SELECT COUNT(*) as c FROM BonosAfiliado WHERE fecha = '2026-04-15'")
|
||||||
|
puts " Registros el 2026-04-15: #{result.first['c']}"
|
||||||
|
|
||||||
|
# Ver IDs máximos
|
||||||
|
result = client_fenix.execute("SELECT MAX(idsolicitud) as max_id FROM BonosAfiliado")
|
||||||
|
puts " ID máximo en FENIX: #{result.first['max_id']}"
|
||||||
|
|
||||||
|
client_fenix.close
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 2. DASU-SQL4 - Verificar datos del backup
|
||||||
|
# ==========================================
|
||||||
|
puts "\n[2] DASU-SQL4 - Datos del backup restaurado:"
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
Write-Host " Última fecha en BonosAfiliado:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(fecha) as ultima FROM BonosAfiliado", $conn)
|
||||||
|
try {
|
||||||
|
$ultima = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " $ultima"
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " Registros el 2026-04-15:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) as c FROM BonosAfiliado WHERE fecha = '2026-04-15'", $conn)
|
||||||
|
try {
|
||||||
|
$c = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " $c"
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " ID máximo en BonosAfiliado:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(idsolicitud) as max_id FROM BonosAfiliado", $conn)
|
||||||
|
try {
|
||||||
|
$maxId = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " $maxId"
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ver fecha de restore en msdb
|
||||||
|
Write-Host "`n Último restore:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT TOP 1 restore_date, backup_start_date FROM msdb.dbo.restorehistory WHERE destination_database_name = 'sysdasuten' ORDER BY restore_date DESC", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$row = $ds.Tables[0].Rows[0]
|
||||||
|
Write-Host " Restore date: $($row.restore_date)"
|
||||||
|
Write-Host " Backup start: $($row.backup_start_date)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n[3] ARCHIVOS DE BACKUP EN DASU-SQL4:"
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
Get-ChildItem "F:\\BACKUP\\*.bak" | ForEach-Object {
|
||||||
|
Write-Host " $($_.Name)"
|
||||||
|
Write-Host " LastWriteTime: $($_.LastWriteTime)"
|
||||||
|
Write-Host " CreationTime: $($_.CreationTime)"
|
||||||
|
}
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# Verificación específica de BONOS - FENIX vs DASU-SQL4
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
require 'tiny_tds'
|
||||||
|
require 'json'
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== VERIFICACIÓN DE BONOS (venta de bonos) ==="
|
||||||
|
puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 1. FENIX - BonosAfiliado y AutorizacionBonosExcepcion
|
||||||
|
# ==========================================
|
||||||
|
puts "\n[1] FENIX (PRODUCCIÓN) - BonosAfiliado..."
|
||||||
|
|
||||||
|
pass_cmd = `ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null`.strip
|
||||||
|
sql_user = 'UTNlarioja\\monlaricardo'
|
||||||
|
sql_host = '10.0.10.200'
|
||||||
|
|
||||||
|
client_fenix = TinyTds::Client.new(
|
||||||
|
username: sql_user,
|
||||||
|
password: pass_cmd,
|
||||||
|
host: sql_host,
|
||||||
|
timeout: 600,
|
||||||
|
database: 'sysdasuten'
|
||||||
|
)
|
||||||
|
|
||||||
|
# BonosAfiliado - verificar columna de fecha
|
||||||
|
puts " Buscando columna de fecha en BonosAfiliado..."
|
||||||
|
cols = client_fenix.execute("
|
||||||
|
SELECT COLUMN_NAME, DATA_TYPE
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_NAME = 'BonosAfiliado'
|
||||||
|
AND DATA_TYPE IN ('datetime', 'datetime2', 'date', 'smalldatetime')
|
||||||
|
").to_a
|
||||||
|
|
||||||
|
puts " Columnas fecha encontradas: #{cols.map { |c| "#{c['COLUMN_NAME']} (#{c['DATA_TYPE']})" }.join(', ')}"
|
||||||
|
|
||||||
|
# Usar la primera columna fecha encontrada
|
||||||
|
if cols.any?
|
||||||
|
col_fecha = cols.first['COLUMN_NAME']
|
||||||
|
puts " Usando columna: #{col_fecha}"
|
||||||
|
|
||||||
|
# Última fecha exacta
|
||||||
|
ultima = client_fenix.execute("SELECT MAX(#{col_fecha}) as ultima FROM BonosAfiliado").first['ultima']
|
||||||
|
puts " Última fecha: #{ultima}"
|
||||||
|
|
||||||
|
# Registros desde abril 13
|
||||||
|
count_13 = client_fenix.execute("SELECT COUNT(*) as c FROM BonosAfiliado WHERE #{col_fecha} >= '2026-04-13'").first['c']
|
||||||
|
puts " Registros desde 2026-04-13: #{count_13}"
|
||||||
|
|
||||||
|
# Registros por día desde abril 13
|
||||||
|
puts "\n Registros por día (desde 2026-04-13):"
|
||||||
|
por_dia = client_fenix.execute("
|
||||||
|
SELECT CAST(#{col_fecha} AS DATE) as dia, COUNT(*) as cantidad
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE #{col_fecha} >= '2026-04-13'
|
||||||
|
GROUP BY CAST(#{col_fecha} AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
")
|
||||||
|
por_dia.each { |r| puts " #{r['dia']}: #{r['cantidad']} registros" }
|
||||||
|
|
||||||
|
# Últimos 10 registros
|
||||||
|
puts "\n Últimos 10 registros de BonosAfiliado:"
|
||||||
|
ultimos = client_fenix.execute("SELECT TOP 10 * FROM BonosAfiliado ORDER BY #{col_fecha} DESC").to_a
|
||||||
|
ultimos.each_with_index do |r, i|
|
||||||
|
puts " #{i+1}. #{col_fecha}=#{r[col_fecha]} - #{r.select { |k,v| k != col_fecha }.to_a.first(3).map { |k,v| "#{k}: #{v}" }.join(', ')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# AutorizacionBonosExcepcion
|
||||||
|
puts "\n[2] FENIX (PRODUCCIÓN) - AutorizacionBonosExcepcion..."
|
||||||
|
|
||||||
|
cols = client_fenix.execute("
|
||||||
|
SELECT COLUMN_NAME, DATA_TYPE
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_NAME = 'AutorizacionBonosExcepcion'
|
||||||
|
AND DATA_TYPE IN ('datetime', 'datetime2', 'date', 'smalldatetime')
|
||||||
|
").to_a
|
||||||
|
|
||||||
|
if cols.any?
|
||||||
|
col_fecha = cols.first['COLUMN_NAME']
|
||||||
|
puts " Usando columna: #{col_fecha}"
|
||||||
|
|
||||||
|
ultima = client_fenix.execute("SELECT MAX(#{col_fecha}) as ultima FROM AutorizacionBonosExcepcion").first['ultima']
|
||||||
|
puts " Última fecha: #{ultima}"
|
||||||
|
|
||||||
|
count_13 = client_fenix.execute("SELECT COUNT(*) as c FROM AutorizacionBonosExcepcion WHERE #{col_fecha} >= '2026-04-13'").first['c']
|
||||||
|
puts " Registros desde 2026-04-13: #{count_13}"
|
||||||
|
|
||||||
|
puts "\n Registros por día (desde 2026-04-13):"
|
||||||
|
por_dia = client_fenix.execute("
|
||||||
|
SELECT CAST(#{col_fecha} AS DATE) as dia, COUNT(*) as cantidad
|
||||||
|
FROM AutorizacionBonosExcepcion
|
||||||
|
WHERE #{col_fecha} >= '2026-04-13'
|
||||||
|
GROUP BY CAST(#{col_fecha} AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
")
|
||||||
|
por_dia.each { |r| puts " #{r['dia']}: #{r['cantidad']} registros" }
|
||||||
|
|
||||||
|
puts "\n Últimos 10 registros:"
|
||||||
|
ultimos = client_fenix.execute("SELECT TOP 10 * FROM AutorizacionBonosExcepcion ORDER BY #{col_fecha} DESC").to_a
|
||||||
|
ultimos.each_with_index do |r, i|
|
||||||
|
puts " #{i+1}. #{col_fecha}=#{r[col_fecha]} - #{r.to_a.first(5).map { |k,v| "#{k}: #{v}" }.join(', ')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
client_fenix.close
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 2. DASU-SQL4 - Mismas consultas
|
||||||
|
# ==========================================
|
||||||
|
puts "\n[3] DASU-SQL4 - BonosAfiliado..."
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$JSON_PATH = "C:\\temp\\bonos_dasu_output.json"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path "C:\\temp"
|
||||||
|
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
# BonosAfiliado
|
||||||
|
Write-Host " Buscando columna de fecha en BonosAfiliado..."
|
||||||
|
$cmdCol = New-Object System.Data.SqlClient.SqlCommand("SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'BonosAfiliado' AND DATA_TYPE IN ('datetime', 'datetime2', 'date', 'smalldatetime')", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmdCol)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$cols = $ds.Tables[0].Rows
|
||||||
|
|
||||||
|
if ($cols.Count -gt 0) {
|
||||||
|
$colFecha = $cols[0].COLUMN_NAME
|
||||||
|
Write-Host " Usando columna: $colFecha"
|
||||||
|
|
||||||
|
# Última fecha
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX([$colFecha]) as ultima FROM [BonosAfiliado]", $conn)
|
||||||
|
$ultima = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Última fecha: $ultima"
|
||||||
|
|
||||||
|
# Registros desde abril 13
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) FROM [BonosAfiliado] WHERE [$colFecha] >= '2026-04-13'", $conn)
|
||||||
|
$count13 = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Registros desde 2026-04-13: $count13"
|
||||||
|
|
||||||
|
# Por día
|
||||||
|
Write-Host " Registros por día:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT CAST([$colFecha] AS DATE) as dia, COUNT(*) as cantidad FROM [BonosAfiliado] WHERE [$colFecha] >= '2026-04-13' GROUP BY CAST([$colFecha] AS DATE) ORDER BY dia DESC", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object { Write-Host " $($_.dia): $($_.cantidad) registros" }
|
||||||
|
|
||||||
|
# Últimos 10
|
||||||
|
Write-Host " Últimos 10 registros:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT TOP 10 * FROM [BonosAfiliado] ORDER BY [$colFecha] DESC", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
for ($i = 0; $i -lt $ds.Tables[0].Rows.Count; $i++) {
|
||||||
|
$row = $ds.Tables[0].Rows[$i]
|
||||||
|
Write-Host " $($i+1). $($colFecha)=$($row.$colFecha)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# AutorizacionBonosExcepcion
|
||||||
|
Write-Host "`n[4] DASU-SQL4 - AutorizacionBonosExcepcion..."
|
||||||
|
$cmdCol = New-Object System.Data.SqlClient.SqlCommand("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'AutorizacionBonosExcepcion' AND DATA_TYPE IN ('datetime', 'datetime2', 'date', 'smalldatetime')", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmdCol)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$cols = $ds.Tables[0].Rows
|
||||||
|
|
||||||
|
if ($cols.Count -gt 0) {
|
||||||
|
$colFecha = $cols[0].COLUMN_NAME
|
||||||
|
Write-Host " Usando columna: $colFecha"
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX([$colFecha]) as ultima FROM [AutorizacionBonosExcepcion]", $conn)
|
||||||
|
$ultima = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Última fecha: $ultima"
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) FROM [AutorizacionBonosExcepcion] WHERE [$colFecha] >= '2026-04-13'", $conn)
|
||||||
|
$count13 = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Registros desde 2026-04-13: $count13"
|
||||||
|
|
||||||
|
Write-Host " Registros por día:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT CAST([$colFecha] AS DATE) as dia, COUNT(*) as cantidad FROM [AutorizacionBonosExcepcion] WHERE [$colFecha] >= '2026-04-13' GROUP BY CAST([$colFecha] AS DATE) ORDER BY dia DESC", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object { Write-Host " $($_.dia): $($_.cantidad) registros" }
|
||||||
|
|
||||||
|
Write-Host " Últimos 10 registros:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT TOP 10 * FROM [AutorizacionBonosExcepcion] ORDER BY [$colFecha] DESC", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
for ($i = 0; $i -lt $ds.Tables[0].Rows.Count; $i++) {
|
||||||
|
$row = $ds.Tables[0].Rows[$i]
|
||||||
|
Write-Host " $($i+1). $($colFecha)=$($row.$colFecha)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# adn/tools/cli/verificar_datos_fenix_dasu.rb
|
||||||
|
# ==========================================================
|
||||||
|
# Verificación minuciosa de datos entre FENIX (producción)
|
||||||
|
# y DASU-SQL4 (nuevo sistema) - desde abril 13
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
require 'tiny_tds'
|
||||||
|
require 'json'
|
||||||
|
require 'base64'
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== COMPARACIÓN FENIX vs DASU-SQL4 ==="
|
||||||
|
puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 1. CONEXIÓN A FENIX (PRODUCCIÓN)
|
||||||
|
# ==========================================
|
||||||
|
puts "\n[1] Conectando a srvv-fenix (PRODUCCIÓN)..."
|
||||||
|
|
||||||
|
# Autorizar candados primero
|
||||||
|
system("ruby adn/tools/candados/candados.rb authorize >/dev/null 2>&1")
|
||||||
|
|
||||||
|
# Obtener credencial de candados
|
||||||
|
pass_cmd = `ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null`.strip
|
||||||
|
|
||||||
|
if pass_cmd.empty?
|
||||||
|
puts "[!] Error: No se pudo obtener credencial de candados"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
sql_user = 'UTNlarioja\\monlaricardo'
|
||||||
|
sql_host = '10.0.10.200'
|
||||||
|
|
||||||
|
begin
|
||||||
|
client_fenix = TinyTds::Client.new(
|
||||||
|
username: sql_user,
|
||||||
|
password: pass_cmd,
|
||||||
|
host: sql_host,
|
||||||
|
timeout: 600,
|
||||||
|
database: 'sysdasuten'
|
||||||
|
)
|
||||||
|
puts "[+] Conectado a srvv-fenix"
|
||||||
|
rescue => e
|
||||||
|
puts "[!] Error conectando a fenix: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 2. TABLAS PRINCIPALES PARA VERIFICAR
|
||||||
|
# ==========================================
|
||||||
|
# El usuario mencionó "venta de bonos" como tarea repetitiva
|
||||||
|
# Verificamos tablas relacionadas con bonos y afiliados
|
||||||
|
|
||||||
|
tablas_principales = [
|
||||||
|
'Bonos',
|
||||||
|
'BonosAfiliado',
|
||||||
|
'AutorizacionGasto',
|
||||||
|
'AutorizacionBonosExcepcion',
|
||||||
|
'afiliados',
|
||||||
|
'ControlPrestaciones',
|
||||||
|
'usuarios',
|
||||||
|
'transacciones'
|
||||||
|
]
|
||||||
|
|
||||||
|
puts "\n[2] Verificando tablas principales..."
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 3. OBTENER DATOS DE FENIX
|
||||||
|
# ==========================================
|
||||||
|
datos_fenix = {}
|
||||||
|
|
||||||
|
tablas_principales.each do |tabla|
|
||||||
|
begin
|
||||||
|
# Verificar si existe
|
||||||
|
existe = client_fenix.execute("
|
||||||
|
SELECT TOP 1 1
|
||||||
|
FROM sys.objects
|
||||||
|
WHERE name = '#{tabla}' AND type = 'U'
|
||||||
|
").to_a.any?
|
||||||
|
|
||||||
|
unless existe
|
||||||
|
puts " #{tabla}: NO EXISTE"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
# Buscar columna de fecha
|
||||||
|
cols = client_fenix.execute("
|
||||||
|
SELECT TOP 1 COLUMN_NAME, DATA_TYPE
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_NAME = '#{tabla}'
|
||||||
|
AND DATA_TYPE IN ('datetime', 'datetime2', 'date', 'smalldatetime')
|
||||||
|
").to_a
|
||||||
|
|
||||||
|
if cols.empty?
|
||||||
|
# Sin columna de fecha, solo contar total
|
||||||
|
count_total = client_fenix.execute("SELECT COUNT(*) as total FROM #{tabla}").first['total']
|
||||||
|
datos_fenix[tabla] = { total: count_total, col_fecha: nil }
|
||||||
|
puts " #{tabla}: #{count_total} registros (sin fecha)"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
col_fecha = cols.first['COLUMN_NAME']
|
||||||
|
|
||||||
|
# Contar total y desde abril 13
|
||||||
|
count_total = client_fenix.execute("SELECT COUNT(*) as total FROM #{tabla}").first['total']
|
||||||
|
count_desde_13 = client_fenix.execute("
|
||||||
|
SELECT COUNT(*) as total
|
||||||
|
FROM #{tabla}
|
||||||
|
WHERE #{col_fecha} >= '2026-04-13'
|
||||||
|
").first['total']
|
||||||
|
|
||||||
|
# Última fecha
|
||||||
|
ultima_fecha = client_fenix.execute("
|
||||||
|
SELECT MAX(#{col_fecha}) as ultima
|
||||||
|
FROM #{tabla}
|
||||||
|
").first['ultima']
|
||||||
|
|
||||||
|
# Últimos 5 registros
|
||||||
|
ultimos = client_fenix.execute("
|
||||||
|
SELECT TOP 5 *
|
||||||
|
FROM #{tabla}
|
||||||
|
ORDER BY #{col_fecha} DESC
|
||||||
|
").to_a
|
||||||
|
|
||||||
|
datos_fenix[tabla] = {
|
||||||
|
total: count_total,
|
||||||
|
desde_13: count_desde_13,
|
||||||
|
col_fecha: col_fecha,
|
||||||
|
ultima_fecha: ultima_fecha,
|
||||||
|
ultimos: ultimos
|
||||||
|
}
|
||||||
|
|
||||||
|
puts " #{tabla}: #{count_desde_13} desde 2026-04-13 (total: #{count_total}) - última: #{ultima_fecha}"
|
||||||
|
rescue => e
|
||||||
|
puts " #{tabla}: ERROR - #{e.message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 4. CONSULTAR DASU-SQL4
|
||||||
|
# ==========================================
|
||||||
|
puts "\n[3] Consultando DASU-SQL4..."
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$JSON_PATH = "C:\\temp\\comparacion_dasu_output.json"
|
||||||
|
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path "C:\\temp"
|
||||||
|
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
$tablasPrincipales = @('Bonos', 'BonosAfiliado', 'AutorizacionGasto', 'AutorizacionBonosExcepcion', 'afiliados', 'ControlPrestaciones', 'usuarios', 'transacciones')
|
||||||
|
|
||||||
|
$datos = @{}
|
||||||
|
|
||||||
|
foreach ($tabla in $tablasPrincipales) {
|
||||||
|
try {
|
||||||
|
# Verificar si existe
|
||||||
|
$check = New-Object System.Data.SqlClient.SqlCommand("SELECT TOP 1 1 FROM sys.objects WHERE name = '$tabla' AND type = 'U'", $conn)
|
||||||
|
$existe = $check.ExecuteScalar()
|
||||||
|
|
||||||
|
if ($existe) {
|
||||||
|
# Buscar columna fecha
|
||||||
|
$cmdCol = New-Object System.Data.SqlClient.SqlCommand("SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$tabla' AND DATA_TYPE IN ('datetime', 'datetime2', 'date', 'smalldatetime')", $conn)
|
||||||
|
$colFecha = $cmdCol.ExecuteScalar()
|
||||||
|
|
||||||
|
if ($colFecha) {
|
||||||
|
# Count total
|
||||||
|
$cmdTotal = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) FROM [$tabla]", $conn)
|
||||||
|
$countTotal = $cmdTotal.ExecuteScalar()
|
||||||
|
|
||||||
|
# Count desde abril 13
|
||||||
|
$cmd13 = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) FROM [$tabla] WHERE [$colFecha] >= '2026-04-13'", $conn)
|
||||||
|
$count13 = $cmd13.ExecuteScalar()
|
||||||
|
|
||||||
|
# Última fecha
|
||||||
|
$cmdMax = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX([$colFecha]) as ultima FROM [$tabla]", $conn)
|
||||||
|
$ultima = $cmdMax.ExecuteScalar()
|
||||||
|
|
||||||
|
# Últimos 5
|
||||||
|
$cmdUltimos = New-Object System.Data.SqlClient.SqlCommand("SELECT TOP 5 * FROM [$tabla] ORDER BY [$colFecha] DESC", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmdUltimos)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ultimos = $ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
$row = @{}
|
||||||
|
$_.Table.Columns | ForEach-Object { $row[$_.ColumnName] = $_.($_) }
|
||||||
|
$row
|
||||||
|
}
|
||||||
|
|
||||||
|
$datos[$tabla] = @{
|
||||||
|
total = $countTotal
|
||||||
|
desde_13 = $count13
|
||||||
|
col_fecha = $colFecha
|
||||||
|
ultima_fecha = $ultima
|
||||||
|
ultimos = $ultimos
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " $tabla : $count13 desde 2026-04-13 (total: $countTotal) - última: $ultima"
|
||||||
|
} else {
|
||||||
|
$cmdTotal = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) FROM [$tabla]", $conn)
|
||||||
|
$countTotal = $cmdTotal.ExecuteScalar()
|
||||||
|
$datos[$tabla] = @{ total = $countTotal; col_fecha = $null }
|
||||||
|
Write-Host " $tabla : $countTotal registros (sin fecha)"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " $tabla : NO EXISTE"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Host " $tabla : ERROR - $_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
|
||||||
|
$resultado = @{
|
||||||
|
datos = $datos
|
||||||
|
timestamp = (Get-Date -Format "o")
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultado | ConvertTo-Json -Depth 10 | Out-File -FilePath $JSON_PATH -Encoding utf8
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
begin
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script, output_path: 'C:\temp\comparacion_dasu_output.json')
|
||||||
|
resultado_dasu = DasuExecutor.read_json_from_dasu('C:\temp\comparacion_dasu_output.json')
|
||||||
|
datos_dasu = resultado_dasu['datos']
|
||||||
|
|
||||||
|
rescue => e
|
||||||
|
puts "[!] Error consultando dasu-sql4: #{e.message}"
|
||||||
|
puts e.backtrace.first(5).join("\n")
|
||||||
|
datos_dasu = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 5. COMPARAR RESULTADOS
|
||||||
|
# ==========================================
|
||||||
|
puts "\n[4] COMPARACIÓN FENIX vs DASU-SQL4:"
|
||||||
|
puts "=" * 60
|
||||||
|
|
||||||
|
tablas_comunes = datos_fenix.keys & (datos_dasu || {}).keys
|
||||||
|
|
||||||
|
if tablas_comunes.empty?
|
||||||
|
puts " ⚠️ No hay tablas comunes para comparar"
|
||||||
|
else
|
||||||
|
puts " Tablas a comparar: #{tablas_comunes.join(', ')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
todas_coinciden = true
|
||||||
|
|
||||||
|
tablas_comunes.each do |tabla|
|
||||||
|
fenix = datos_fenix[tabla]
|
||||||
|
dasu = datos_dasu[tabla]
|
||||||
|
|
||||||
|
puts " === #{tabla} ==="
|
||||||
|
puts " Columna fecha: #{fenix[:col_fecha] || dasu['col_fecha'] || 'N/A'}"
|
||||||
|
puts " FENIX total: #{fenix[:total]} | DASU total: #{dasu['total']}"
|
||||||
|
puts " FENIX desde_13: #{fenix[:desde_13]} | DASU desde_13: #{dasu['desde_13']}"
|
||||||
|
puts " FENIX última: #{fenix[:ultima_fecha]} | DASU última: #{dasu['ultima_fecha']}"
|
||||||
|
|
||||||
|
# Verificar coincidencia
|
||||||
|
if fenix[:total] == dasu['total'] && fenix[:desde_13] == dasu['desde_13']
|
||||||
|
# Comparar fechas como strings
|
||||||
|
fenix_fecha = fenix[:ultima_fecha].to_s.strip
|
||||||
|
dasu_fecha = dasu['ultima_fecha'].to_s.strip
|
||||||
|
|
||||||
|
if fenix_fecha == dasu_fecha
|
||||||
|
puts " ✅ COINCIDE"
|
||||||
|
else
|
||||||
|
puts " ⚠️ FECHAS DIFIEREN"
|
||||||
|
puts " FENIX: #{fenix_fecha}"
|
||||||
|
puts " DASU: #{dasu_fecha}"
|
||||||
|
todas_coinciden = false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
puts " ⚠️ NO COINCIDE - Diferencia en cantidades"
|
||||||
|
todas_coinciden = false
|
||||||
|
end
|
||||||
|
puts ""
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "=" * 60
|
||||||
|
if todas_coinciden
|
||||||
|
puts " ✅ TODOS LOS DATOS COINCIDEN - El restore fue exitoso"
|
||||||
|
else
|
||||||
|
puts " ⚠️ HAY DIFERENCIAS - El restore puede estar incompleto"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 6. DETALLE ÚLTIMOS REGISTROS (BONOS)
|
||||||
|
# ==========================================
|
||||||
|
puts "\n[5] DETALLE ÚLTIMOS REGISTROS DE BONOS:"
|
||||||
|
puts "=" * 60
|
||||||
|
|
||||||
|
if datos_fenix['Bonos'] && datos_fenix['Bonos'][:ultimos]
|
||||||
|
puts " FENIX - Últimos 5 bonos:"
|
||||||
|
datos_fenix['Bonos'][:ultimos].each_with_index do |registro, i|
|
||||||
|
puts " #{i+1}. #{registro}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if datos_dasu && datos_dasu['Bonos'] && datos_dasu['Bonos']['ultimos']
|
||||||
|
puts "\n DASU-SQL4 - Últimos 5 bonos:"
|
||||||
|
datos_dasu['Bonos']['ultimos'].each_with_index do |registro, i|
|
||||||
|
puts " #{i+1}. #{registro}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
|
|
||||||
|
client_fenix.close
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== DASU-SQL4 - Estructura de tabla BonosAfiliado ==="
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
Write-Host "[1] Estructura de la columna idsolicitud:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT
|
||||||
|
COLUMN_NAME,
|
||||||
|
DATA_TYPE,
|
||||||
|
COLUMN_DEFAULT,
|
||||||
|
IS_NULLABLE
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_NAME = 'BonosAfiliado' AND COLUMN_NAME = 'idsolicitud'
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$row = $ds.Tables[0].Rows[0]
|
||||||
|
Write-Host " Columna: $($row.COLUMN_NAME)"
|
||||||
|
Write-Host " Tipo: $($row.DATA_TYPE)"
|
||||||
|
Write-Host " Default: $($row.COLUMN_DEFAULT)"
|
||||||
|
Write-Host " Nullable: $($row.IS_NULLABLE)"
|
||||||
|
} else {
|
||||||
|
Write-Host " Columna no encontrada"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[2] ¿Es idsolicitud una columna IDENTITY?"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT
|
||||||
|
c.name AS column_name,
|
||||||
|
t.name AS table_name,
|
||||||
|
COLUMNPROPERTY(OBJECT_ID(t.schema_name + '.' + t.name), c.name, 'IsIdentity') AS is_identity,
|
||||||
|
IDENT_CURRENT(t.schema_name + '.' + t.name) AS current_identity,
|
||||||
|
IDENT_INCR(t.schema_name + '.' + t.name) AS identity_increment,
|
||||||
|
IDENT_SEED(t.schema_name + '.' + t.name) AS identity_seed
|
||||||
|
FROM sys.columns c
|
||||||
|
JOIN sys.tables t ON c.object_id = t.object_id
|
||||||
|
WHERE t.name = 'BonosAfiliado' AND c.name = 'idsolicitud'
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$row = $ds.Tables[0].Rows[0]
|
||||||
|
Write-Host " ¿Es Identity?: $($row.is_identity)"
|
||||||
|
Write-Host " Seed (valor inicial): $($row.identity_seed)"
|
||||||
|
Write-Host " Incremento: $($row.identity_increment)"
|
||||||
|
Write-Host " Valor actual (IDENT_CURRENT): $($row.current_identity)"
|
||||||
|
} else {
|
||||||
|
Write-Host " No es columna IDENTITY o no existe"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[3] Valores reales de idsolicitud en últimos registros:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 20 idsolicitud, fecha, codigoafiliado
|
||||||
|
FROM BonosAfiliado
|
||||||
|
ORDER BY idsolicitud DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " idsolicitud=$($_.idsolicitud) | fecha=$($_.fecha) | afiliado=$($_.codigoafiliado)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[4] ¿Hay registros con idsolicitud > 0?"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT COUNT(*) as c FROM BonosAfiliado WHERE idsolicitud > 0
|
||||||
|
", $conn)
|
||||||
|
$c = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Cantidad: $c"
|
||||||
|
|
||||||
|
Write-Host "`n[5] ¿Hay registros con idsolicitud = 0?"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT COUNT(*) as c FROM BonosAfiliado WHERE idsolicitud = 0
|
||||||
|
", $conn)
|
||||||
|
$c = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Cantidad: $c"
|
||||||
|
|
||||||
|
Write-Host "`n[6] Máximo valor de idsolicitud:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT MAX(idsolicitud) as max_id FROM BonosAfiliado
|
||||||
|
", $conn)
|
||||||
|
$max = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " MAX(idsolicitud): $max"
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== VERIFICAR FECHAS DESPUÉS DEL CAMBIO DE ZONA HORARIA ==="
|
||||||
|
puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
Write-Host "[1] Zona horaria actual del sistema:"
|
||||||
|
$tz = Get-TimeZone
|
||||||
|
Write-Host " ID: $($tz.Id)"
|
||||||
|
Write-Host " DisplayName: $($tz.DisplayName)"
|
||||||
|
Write-Host " BaseUtcOffset: $($tz.BaseUtcOffset)"
|
||||||
|
|
||||||
|
Write-Host "`n[2] Fecha/hora actual:"
|
||||||
|
Write-Host " Get-Date: $(Get-Date)"
|
||||||
|
Write-Host " UTC: $([DateTime]::UtcNow)"
|
||||||
|
|
||||||
|
Write-Host "`n[3] Última fecha en BonosAfiliado:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(fecha) as ultima FROM BonosAfiliado", $conn)
|
||||||
|
$ultima = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " MAX(fecha): $ultima"
|
||||||
|
|
||||||
|
Write-Host "`n[4] Últimos 10 registros (ordenados por fecha):"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 10
|
||||||
|
idsolicitud,
|
||||||
|
CONVERT(varchar, fecha, 103) as fecha_ddmmaaaa,
|
||||||
|
codigoafiliado,
|
||||||
|
dependencia,
|
||||||
|
bono,
|
||||||
|
CONVERT(varchar, __tiempo, 103) + ' ' + CONVERT(varchar, __tiempo, 108) as tiempo_completo
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE fecha IS NOT NULL
|
||||||
|
ORDER BY fecha DESC, idsolicitud DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
Write-Host " idsol | fecha (dd/mm/aaaa) | afiliado | dep | bono | __tiempo"
|
||||||
|
Write-Host " " + ("-" * 80)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " $($_.idsolicitud.ToString().PadLeft(5)) | $($_.fecha_ddmmaaaa) | $($_.codigoafiliado.ToString().PadLeft(5)) | $($_.dependencia.ToString().PadLeft(2)) | $($_.bono) | $($_.tiempo_completo)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[5] Registros por día (últimos 5 días):"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 5
|
||||||
|
CONVERT(varchar, CAST(fecha AS DATE), 103) as dia_ddmmaaaa,
|
||||||
|
COUNT(*) as cantidad,
|
||||||
|
MIN(idsolicitud) as min_id,
|
||||||
|
MAX(idsolicitud) as max_id
|
||||||
|
FROM BonosAfiliado
|
||||||
|
GROUP BY CAST(fecha AS DATE)
|
||||||
|
ORDER BY CAST(fecha AS DATE) DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " $($_.dia_ddmmaaaa): $($_.cantidad) registros | IDs: [$($_.min_id) - $($_.max_id)]"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[6] Verificando formato de fecha ISO:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 3
|
||||||
|
fecha,
|
||||||
|
CONVERT(varchar, fecha, 120) as formato_iso,
|
||||||
|
__tiempo
|
||||||
|
FROM BonosAfiliado
|
||||||
|
ORDER BY fecha DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " fecha=$($_.fecha) | ISO=$($_.formato_iso)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# Verificar FENIX - qué datos hay ACTUALMENTE (abril 15)
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
require 'tiny_tds'
|
||||||
|
|
||||||
|
puts "=== FENIX (PRODUCCIÓN) - ESTADO ACTUAL ==="
|
||||||
|
puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
pass_cmd = `ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null`.strip
|
||||||
|
sql_user = 'UTNlarioja\\monlaricardo'
|
||||||
|
sql_host = '10.0.10.200'
|
||||||
|
|
||||||
|
client = TinyTds::Client.new(
|
||||||
|
username: sql_user,
|
||||||
|
password: pass_cmd,
|
||||||
|
host: sql_host,
|
||||||
|
timeout: 600,
|
||||||
|
database: 'sysdasuten'
|
||||||
|
)
|
||||||
|
|
||||||
|
puts "[1] BonosAfiliado - Registros por día (últimos 5 días):"
|
||||||
|
result = client.execute("
|
||||||
|
SELECT CAST(fecha AS DATE) as dia, COUNT(*) as cantidad
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE fecha >= DATEADD(DAY, -5, GETDATE())
|
||||||
|
GROUP BY CAST(fecha AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
")
|
||||||
|
|
||||||
|
result.each { |r| puts " #{r['dia']}: #{r['cantidad']} registros" }
|
||||||
|
|
||||||
|
puts "\n[2] BonosAfiliado - Última fecha exacta:"
|
||||||
|
result = client.execute("SELECT MAX(fecha) as ultima FROM BonosAfiliado")
|
||||||
|
puts " #{result.first['ultima']}"
|
||||||
|
|
||||||
|
puts "\n[3] BonosAfiliado - Registros del 2026-04-15:"
|
||||||
|
result = client.execute("SELECT COUNT(*) as c FROM BonosAfiliado WHERE fecha >= '2026-04-15'")
|
||||||
|
puts " #{result.first['c']} registros"
|
||||||
|
|
||||||
|
puts "\n[4] AutorizacionBonosExcepcion - Registros por día:"
|
||||||
|
result = client.execute("
|
||||||
|
SELECT CAST(fechasolicitud AS DATE) as dia, COUNT(*) as cantidad
|
||||||
|
FROM AutorizacionBonosExcepcion
|
||||||
|
WHERE fechasolicitud >= DATEADD(DAY, -5, GETDATE())
|
||||||
|
GROUP BY CAST(fechasolicitud AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
")
|
||||||
|
|
||||||
|
result.each { |r| puts " #{r['dia']}: #{r['cantidad']} registros" }
|
||||||
|
|
||||||
|
puts "\n[5] AutorizacionBonosExcepcion - Última fecha:"
|
||||||
|
result = client.execute("SELECT MAX(fechasolicitud) as ultima FROM AutorizacionBonosExcepcion")
|
||||||
|
puts " #{result.first['ultima']}"
|
||||||
|
|
||||||
|
puts "\n[6] AutorizacionBonosExcepcion - Registros del 2026-04-15:"
|
||||||
|
result = client.execute("SELECT COUNT(*) as c FROM AutorizacionBonosExcepcion WHERE fechasolicitud >= '2026-04-15'")
|
||||||
|
puts " #{result.first['c']} registros"
|
||||||
|
|
||||||
|
puts "\n[7] IDs más altos en BonosAfiliado (para ver si hay datos nuevos):"
|
||||||
|
result = client.execute("SELECT TOP 5 idsolicitud FROM BonosAfiliado ORDER BY idsolicitud DESC")
|
||||||
|
puts " Últimos IDs: #{result.to_a.map { |r| r['idsolicitud'] }.join(', ')}"
|
||||||
|
|
||||||
|
client.close
|
||||||
|
|
||||||
|
puts "\n✅ Verificación FENIX completada"
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'tiny_tds'
|
||||||
|
|
||||||
|
puts "=== FENIX - Verificando fechas de últimos registros ==="
|
||||||
|
puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
pass_cmd = `ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null`.strip
|
||||||
|
sql_user = 'UTNlarioja\\monlaricardo'
|
||||||
|
sql_host = '10.0.10.200'
|
||||||
|
|
||||||
|
client = TinyTds::Client.new(
|
||||||
|
username: sql_user,
|
||||||
|
password: pass_cmd,
|
||||||
|
host: sql_host,
|
||||||
|
timeout: 600,
|
||||||
|
database: 'sysdasuten'
|
||||||
|
)
|
||||||
|
|
||||||
|
puts "[1] Últimos 10 registros de BonosAfiliado (por ID):"
|
||||||
|
result = client.execute("
|
||||||
|
SELECT TOP 10 idsolicitud, fecha, codigoafiliado, dependencia,
|
||||||
|
CONVERT(datetime, fecha) as fecha_dt,
|
||||||
|
GETDATE() as ahora
|
||||||
|
FROM BonosAfiliado
|
||||||
|
ORDER BY idsolicitud DESC
|
||||||
|
")
|
||||||
|
|
||||||
|
result.each do |r|
|
||||||
|
puts " ID: #{r['idsolicitud']} | fecha: #{r['fecha']} | afiliado: #{r['codigoafiliado']}"
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "\n[2] Registros con fecha 2026-04-15:"
|
||||||
|
result = client.execute("
|
||||||
|
SELECT TOP 10 idsolicitud, fecha, codigoafiliado
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE fecha >= '2026-04-15'
|
||||||
|
ORDER BY idsolicitud DESC
|
||||||
|
")
|
||||||
|
|
||||||
|
if result.to_a.empty?
|
||||||
|
puts " No hay registros con fecha >= 2026-04-15"
|
||||||
|
else
|
||||||
|
result.each do |r|
|
||||||
|
puts " ID: #{r['idsolicitud']} | fecha: #{r['fecha']}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "\n[3] Verificando diferencia entre MAX(idsolicitud) y MAX(fecha):"
|
||||||
|
result = client.execute("
|
||||||
|
SELECT
|
||||||
|
MAX(idsolicitud) as max_id,
|
||||||
|
(SELECT MAX(fecha) FROM BonosAfiliado) as max_fecha,
|
||||||
|
(SELECT fecha FROM BonosAfiliado WHERE idsolicitud = (SELECT MAX(idsolicitud) FROM BonosAfiliado)) as fecha_del_max_id
|
||||||
|
FROM BonosAfiliado
|
||||||
|
")
|
||||||
|
|
||||||
|
r = result.first
|
||||||
|
puts " ID máximo: #{r['max_id']}"
|
||||||
|
puts " Fecha máxima: #{r['max_fecha']}"
|
||||||
|
puts " Fecha del ID máximo: #{r['fecha_del_max_id']}"
|
||||||
|
|
||||||
|
puts "\n[4] ¿Hay registros creados el 15/04 con fecha 14/04?"
|
||||||
|
puts " (comparando IDs altos con fecha 14 vs fecha 15)"
|
||||||
|
|
||||||
|
# IDs del 15 de abril (por fecha de creación del registro, no la columna fecha)
|
||||||
|
# Usamos una columna de auditoría si existe, sino comparamos por rango de IDs
|
||||||
|
result = client.execute("
|
||||||
|
SELECT
|
||||||
|
(SELECT COUNT(*) FROM BonosAfiliado WHERE fecha = '2026-04-15') as con_fecha_15,
|
||||||
|
(SELECT COUNT(*) FROM BonosAfiliado WHERE fecha = '2026-04-14') as con_fecha_14,
|
||||||
|
(SELECT COUNT(*) FROM BonosAfiliado WHERE idsolicitud > 30000000) as con_id_alto
|
||||||
|
")
|
||||||
|
|
||||||
|
r = result.first
|
||||||
|
puts " Registros con fecha=2026-04-15: #{r['con_fecha_15']}"
|
||||||
|
puts " Registros con fecha=2026-04-14: #{r['con_fecha_14']}"
|
||||||
|
puts " Registros con ID > 30000000: #{r['con_id_alto']}"
|
||||||
|
|
||||||
|
client.close
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== VERIFICACIÓN FINAL DASU-SQL4 ==="
|
||||||
|
puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
Write-Host "[1] Última fecha en BonosAfiliado (formato ISO):"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT CONVERT(varchar, MAX(fecha), 120) as ultima FROM BonosAfiliado", $conn)
|
||||||
|
$ultima = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " $ultima"
|
||||||
|
|
||||||
|
Write-Host "`n[2] Registros por día (últimos 5 días):"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 5
|
||||||
|
CONVERT(varchar, CAST(fecha AS DATE), 120) as dia,
|
||||||
|
COUNT(*) as cantidad,
|
||||||
|
MIN(idsolicitud) as min_id,
|
||||||
|
MAX(idsolicitud) as max_id
|
||||||
|
FROM BonosAfiliado
|
||||||
|
GROUP BY CAST(fecha AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " $($_.dia): $($_.cantidad) registros | IDs: [$($_.min_id) - $($_.max_id)]"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[3] ¿La suma de registros coincide con el total?"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT COUNT(*) FROM BonosAfiliado", $conn)
|
||||||
|
$total = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Total: $total"
|
||||||
|
|
||||||
|
Write-Host "`n[4] Muestra de 5 registros recientes (con todos sus campos):"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 5 idsolicitud, fecha, codigoafiliado, dependencia, bono
|
||||||
|
FROM BonosAfiliado
|
||||||
|
ORDER BY fecha DESC, idsolicitud DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " ids=$($_.idsolicitud) | fecha=$($_.fecha) | afiliado=$($_.codigoafiliado) | dep=$($_.dependencia) | bono=$($_.bono)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[5] Verificando si hay datos del 15 de abril de 2026:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT COUNT(*) as c FROM BonosAfiliado
|
||||||
|
WHERE YEAR(fecha) = 2026 AND MONTH(fecha) = 4 AND DAY(fecha) = 15
|
||||||
|
", $conn)
|
||||||
|
$c = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Registros el 2026-04-15: $c"
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== DASU-SQL4 - Verificar si idsolicitud es IDENTITY ==="
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
Write-Host "[1] ¿Es idsolicitud una columna IDENTITY?"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT COLUMNPROPERTY(OBJECT_ID('dbo.BonosAfiliado'), 'idsolicitud', 'IsIdentity')
|
||||||
|
", $conn)
|
||||||
|
$is_identity = $cmd.ExecuteScalar()
|
||||||
|
if ($is_identity -eq 1) {
|
||||||
|
Write-Host " SI, es columna IDENTITY"
|
||||||
|
} else {
|
||||||
|
Write-Host " NO, no es columna IDENTITY (valor: $is_identity)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[2] Valor actual de IDENT_CURRENT:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT IDENT_CURRENT('dbo.BonosAfiliado')
|
||||||
|
", $conn)
|
||||||
|
try {
|
||||||
|
$current = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " IDENT_CURRENT: $current"
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[3] Contar registros con idsolicitud = 0 vs > 0:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT
|
||||||
|
SUM(CASE WHEN idsolicitud = 0 THEN 1 ELSE 0 END) as con_cero,
|
||||||
|
SUM(CASE WHEN idsolicitud > 0 THEN 1 ELSE 0 END) as con_mayor_cero
|
||||||
|
FROM BonosAfiliado
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$row = $ds.Tables[0].Rows[0]
|
||||||
|
Write-Host " Con idsolicitud=0: $($row.con_cero)"
|
||||||
|
Write-Host " Con idsolicitud>0: $($row.con_mayor_cero)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[4] Máximo idsolicitud:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT MAX(idsolicitud) as max_id FROM BonosAfiliado
|
||||||
|
", $conn)
|
||||||
|
$max = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " MAX(idsolicitud): $max"
|
||||||
|
|
||||||
|
Write-Host "`n[5] Últimos 5 registros por fecha (ver idsolicitud):"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 5 idsolicitud, fecha, codigoafiliado
|
||||||
|
FROM BonosAfiliado
|
||||||
|
ORDER BY fecha DESC, idsolicitud DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " idsolicitud=$($_.idsolicitud) | fecha=$($_.fecha)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== DASU-SQL4 - Relación IDs vs Fechas ==="
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
Write-Host "[1] Últimos 20 registros por ID:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT TOP 20 idsolicitud, fecha FROM BonosAfiliado ORDER BY idsolicitud DESC", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " ID: $($_.idsolicitud) | fecha: $($_.fecha)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[2] Máximo ID por fecha (últimos 5 días):"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 5 CAST(fecha AS DATE) as dia, MAX(idsolicitud) as max_id
|
||||||
|
FROM BonosAfiliado
|
||||||
|
GROUP BY CAST(fecha AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " $($_.dia): ID máximo = $($_.max_id)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[3] Rango de IDs por fecha:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT
|
||||||
|
CAST(fecha AS DATE) as dia,
|
||||||
|
MIN(idsolicitud) as min_id,
|
||||||
|
MAX(idsolicitud) as max_id,
|
||||||
|
COUNT(*) as cantidad
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE fecha >= '2026-04-10'
|
||||||
|
GROUP BY CAST(fecha AS DATE)
|
||||||
|
ORDER BY dia DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " $($_.dia): IDs [$($_.min_id) - $($_.max_id)] | cantidad: $($_.cantidad)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[4] ¿Hay IDs > 30000000 con fecha 14?"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT COUNT(*) as c
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE idsolicitud > 30000000 AND fecha = '2026-04-14'
|
||||||
|
", $conn)
|
||||||
|
$c = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " Cantidad: $c"
|
||||||
|
|
||||||
|
Write-Host "`n[5] Últimos 5 IDs > 30000000:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 5 idsolicitud, fecha
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE idsolicitud > 30000000
|
||||||
|
ORDER BY idsolicitud DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " ID: $($_.idsolicitud) | fecha: $($_.fecha)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== DASU-SQL4 - Verificar clave primaria en BonosAfiliado ==="
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
Write-Host "[1] ¿Hay clave primaria en BonosAfiliado?"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT i.name as index_name, i.is_primary_key, i.is_unique
|
||||||
|
FROM sys.indexes i
|
||||||
|
WHERE i.object_id = OBJECT_ID('dbo.BonosAfiliado') AND i.is_primary_key = 1
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$row = $ds.Tables[0].Rows[0]
|
||||||
|
Write-Host " PK: $($row.index_name) (unique: $($row.is_unique))"
|
||||||
|
} else {
|
||||||
|
Write-Host " NO HAY CLAVE PRIMARIA"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[2] ¿Hay índices únicos?"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT i.name as index_name, i.is_unique
|
||||||
|
FROM sys.indexes i
|
||||||
|
WHERE i.object_id = OBJECT_ID('dbo.BonosAfiliado') AND i.is_unique = 1
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " Índice: $($_.index_name) (unique: $($_.is_unique))"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " NO HAY ÍNDICES ÚNICOS"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[3] Columnas de la tabla:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT c.name, t.name as type_name, c.is_nullable
|
||||||
|
FROM sys.columns c
|
||||||
|
JOIN sys.types t ON c.user_type_id = t.user_type_id
|
||||||
|
WHERE c.object_id = OBJECT_ID('dbo.BonosAfiliado')
|
||||||
|
ORDER BY c.column_id
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " $($_.name) ($($_.type_name)) nullable=$($_.is_nullable)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[4] ¿Hay registros duplicados por (fecha, codigoafiliado, dependencia)?"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT fecha, codigoafiliado, dependencia, COUNT(*) as duplicados
|
||||||
|
FROM BonosAfiliado
|
||||||
|
GROUP BY fecha, codigoafiliado, dependencia
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
ORDER BY duplicados DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
Write-Host " Hay $($ds.Tables[0].Rows.Count) combinaciones duplicadas"
|
||||||
|
$ds.Tables[0].Rows | Select-Object -First 5 | ForEach-Object {
|
||||||
|
Write-Host " fecha=$($_.fecha) | afiliado=$($_.codigoafiliado) | dep=$($_.dependencia) | count=$($_.duplicados)"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " NO HAY DUPLICADOS"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== VERIFICANDO DATOS EN DASU-SQL4 ==="
|
||||||
|
puts "Fecha: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
Write-Host "[1] BonosAfiliado - Última fecha:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(fecha) as ultima FROM BonosAfiliado", $conn)
|
||||||
|
$ultima = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " $ultima"
|
||||||
|
|
||||||
|
Write-Host "`n[2] BonosAfiliado - ID máximo:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(idsolicitud) as max_id FROM BonosAfiliado", $conn)
|
||||||
|
$maxId = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " $maxId"
|
||||||
|
|
||||||
|
Write-Host "`n[3] BonosAfiliado - Registros por fecha:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT CAST(fecha AS DATE) as dia, COUNT(*) as c FROM BonosAfiliado GROUP BY CAST(fecha AS DATE) ORDER BY dia DESC", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object { Write-Host " $($_.dia): $($_.c) registros" }
|
||||||
|
|
||||||
|
Write-Host "`n[4] AutorizacionBonosExcepcion - ID máximo:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(idsolicitud) as max_id FROM AutorizacionBonosExcepcion", $conn)
|
||||||
|
$maxId = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " $maxId"
|
||||||
|
|
||||||
|
Write-Host "`n[5] AutorizacionBonosExcepcion - Última fecha:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT MAX(fechasolicitud) as ultima FROM AutorizacionBonosExcepcion", $conn)
|
||||||
|
$ultima = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " $ultima"
|
||||||
|
|
||||||
|
Write-Host "`n[6] Fecha del último restore:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT TOP 1 restore_date FROM msdb.dbo.restorehistory WHERE destination_database_name = 'sysdasuten' ORDER BY restore_date DESC", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
Write-Host " Restore: $($ds.Tables[0].Rows[0].restore_date)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[7] Archivos en F:\\BACKUP:"
|
||||||
|
Get-ChildItem "F:\\BACKUP\\*.bak" | ForEach-Object {
|
||||||
|
Write-Host " $($_.Name)"
|
||||||
|
Write-Host " LastWriteTime: $($_.LastWriteTime)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== DASU-SQL4 - Verificar triggers y constraints en BonosAfiliado ==="
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$connString = "Server=localhost;Database=sysdasuten;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
Write-Host "[1] ¿Hay triggers en BonosAfiliado?"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT name, type_desc
|
||||||
|
FROM sys.triggers
|
||||||
|
WHERE parent_id = OBJECT_ID('dbo.BonosAfiliado')
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " Trigger: $($_.name) ($($_.type_desc))"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " No hay triggers"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[2] Constraints DEFAULT en idsolicitud:"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT dc.name, dc.definition
|
||||||
|
FROM sys.default_constraints dc
|
||||||
|
JOIN sys.columns c ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id
|
||||||
|
WHERE OBJECT_NAME(dc.parent_object_id) = 'BonosAfiliado' AND c.name = 'idsolicitud'
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " Constraint: $($_.name) = $($_.definition)"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " No hay constraints DEFAULT"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[3] Verificar columna idsolicitud (detalles):"
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT
|
||||||
|
c.name,
|
||||||
|
c.is_nullable,
|
||||||
|
c.is_identity,
|
||||||
|
c.default_object_id,
|
||||||
|
t.name as type_name
|
||||||
|
FROM sys.columns c
|
||||||
|
JOIN sys.types t ON c.user_type_id = t.user_type_id
|
||||||
|
WHERE c.object_id = OBJECT_ID('dbo.BonosAfiliado') AND c.name = 'idsolicitud'
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
if ($ds.Tables[0].Rows.Count -gt 0) {
|
||||||
|
$row = $ds.Tables[0].Rows[0]
|
||||||
|
Write-Host " name: $($row.name)"
|
||||||
|
Write-Host " is_nullable: $($row.is_nullable)"
|
||||||
|
Write-Host " is_identity: $($row.is_identity)"
|
||||||
|
Write-Host " default_object_id: $($row.default_object_id)"
|
||||||
|
Write-Host " type: $($row.type_name)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
puts "=== DASU-SQL4 - Verificar Zona Horaria ==="
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
Write-Host "[1] Zona horaria del sistema:"
|
||||||
|
$tz = Get-TimeZone
|
||||||
|
Write-Host " ID: $($tz.Id)"
|
||||||
|
Write-Host " DisplayName: $($tz.DisplayName)"
|
||||||
|
Write-Host " BaseUtcOffset: $($tz.BaseUtcOffset)"
|
||||||
|
|
||||||
|
Write-Host "`n[2] Fecha/hora actual del sistema:"
|
||||||
|
Write-Host " Get-Date: $(Get-Date)"
|
||||||
|
Write-Host " Get-Date -Format 'o': $(Get-Date -Format 'o')"
|
||||||
|
Write-Host " [DateTime]::Now: $([DateTime]::Now)"
|
||||||
|
Write-Host " [DateTime]::UtcNow: $([DateTime]::UtcNow)"
|
||||||
|
|
||||||
|
Write-Host "`n[3] Fecha/hora en SQL Server:"
|
||||||
|
$connString = "Server=localhost;Database=master;Integrated Security=true;TrustServerCertificate=yes;"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT GETDATE() as fecha_sql", $conn)
|
||||||
|
$fecha = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " GETDATE(): $fecha"
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT SYSUTCDATETIME() as utc_sql", $conn)
|
||||||
|
$utc = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " SYSUTCDATETIME(): $utc"
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("SELECT SYSDATETIMEOFFSET() as offset_sql", $conn)
|
||||||
|
$offset = $cmd.ExecuteScalar()
|
||||||
|
Write-Host " SYSDATETIMEOFFSET(): $offset"
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
|
||||||
|
Write-Host "`n[4] Verificando fecha en BonosAfiliado:"
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
|
||||||
|
$conn.Open()
|
||||||
|
$conn.ChangeDatabase("sysdasuten")
|
||||||
|
|
||||||
|
$cmd = New-Object System.Data.SqlClient.SqlCommand("
|
||||||
|
SELECT TOP 5 fecha, __tiempo, impresion
|
||||||
|
FROM BonosAfiliado
|
||||||
|
WHERE fecha IS NOT NULL
|
||||||
|
ORDER BY fecha DESC
|
||||||
|
", $conn)
|
||||||
|
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
||||||
|
$ds = New-Object System.Data.DataSet
|
||||||
|
$adapter.Fill($ds)
|
||||||
|
$ds.Tables[0].Rows | ForEach-Object {
|
||||||
|
Write-Host " fecha=$($_.fecha) | __tiempo=$($_.__tiempo) | impresion=$($_.impresion)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn.Close()
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
@@ -444,9 +444,10 @@ module BitacorasDB
|
|||||||
# @param tema_id [Integer, nil] ID del tema relacionado
|
# @param tema_id [Integer, nil] ID del tema relacionado
|
||||||
# @param bitacora_id [Integer, nil] ID de la bitácora
|
# @param bitacora_id [Integer, nil] ID de la bitácora
|
||||||
# @param nodo_id [Integer, nil] ID del nodo
|
# @param nodo_id [Integer, nil] ID del nodo
|
||||||
|
# @param ambito_id [Integer, nil] ID del ámbito (requerido por drones para registro en bitácora web)
|
||||||
# @return [Hash] Entrada creada con ID
|
# @return [Hash] Entrada creada con ID
|
||||||
def create_entrada(inicio:, descripcion:, fin: nil, estado: nil, modo: nil, 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)
|
tema_id: nil, bitacora_id: nil, nodo_id: nil, ambito_id: nil)
|
||||||
|
|
||||||
# Validar formato de tiempo
|
# Validar formato de tiempo
|
||||||
inicio_time = parse_time(inicio)
|
inicio_time = parse_time(inicio)
|
||||||
@@ -497,17 +498,17 @@ module BitacorasDB
|
|||||||
query = <<~SQL
|
query = <<~SQL
|
||||||
INSERT INTO #{SCHEMA}.#{TABLES[:entradas]} (
|
INSERT INTO #{SCHEMA}.#{TABLES[:entradas]} (
|
||||||
inicio, fin, descripcion, estado, modo, es_ia,
|
inicio, fin, descripcion, estado, modo, es_ia,
|
||||||
tema_id, bitacora_id, nodo_id
|
tema_id, bitacora_id, nodo_id, ambito_id
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
RETURNING id, inicio, fin, descripcion, estado, modo, es_ia,
|
RETURNING id, inicio, fin, descripcion, estado, modo, es_ia,
|
||||||
tema_id, bitacora_id, nodo_id, created_at, updated_at
|
tema_id, bitacora_id, nodo_id, ambito_id, created_at, updated_at
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
result = @connection.exec_params(
|
result = @connection.exec_params(
|
||||||
query,
|
query,
|
||||||
[inicio_time, fin_time, descripcion, estado, modo, es_ia,
|
[inicio_time, fin_time, descripcion, estado, modo, es_ia,
|
||||||
tema_id, bitacora_id, nodo_id]
|
tema_id, bitacora_id, nodo_id, ambito_id]
|
||||||
)
|
)
|
||||||
entrada = result.first
|
entrada = result.first
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,15 @@
|
|||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
|
-- Agregar columna 'codigo' a ambitos si no existe
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'bitacoras' AND table_name = 'ambitos' AND column_name = 'codigo'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE bitacoras.ambitos ADD COLUMN codigo VARCHAR(10);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_ambitos_codigo ON bitacoras.ambitos(codigo);
|
||||||
|
END IF;
|
||||||
|
|
||||||
-- Crear tabla planes (dentro de un ámbito)
|
-- Crear tabla planes (dentro de un ámbito)
|
||||||
CREATE TABLE IF NOT EXISTS bitacoras.planes (
|
CREATE TABLE IF NOT EXISTS bitacoras.planes (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
@@ -38,14 +47,15 @@ BEGIN
|
|||||||
-- Mantener fase_id por compatibilidad temporal (se deprecará en el futuro)
|
-- Mantener fase_id por compatibilidad temporal (se deprecará en el futuro)
|
||||||
-- Los nuevos hitos deben usar plan_id
|
-- Los nuevos hitos deben usar plan_id
|
||||||
|
|
||||||
-- Crear vista resumen_planes para métricas
|
END $$;
|
||||||
|
|
||||||
|
-- Crear vista resumen_planes para métricas (fuera del bloque DO)
|
||||||
CREATE OR REPLACE VIEW bitacoras.resumen_planes AS
|
CREATE OR REPLACE VIEW bitacoras.resumen_planes AS
|
||||||
SELECT
|
SELECT
|
||||||
p.id AS plan_id,
|
p.id AS plan_id,
|
||||||
p.codigo,
|
p.codigo,
|
||||||
p.titulo,
|
p.titulo,
|
||||||
p.estado,
|
p.estado,
|
||||||
a.codigo AS ambito_codigo,
|
|
||||||
a.nombre AS ambito_nombre,
|
a.nombre AS ambito_nombre,
|
||||||
COUNT(h.id) AS total_hitos,
|
COUNT(h.id) AS total_hitos,
|
||||||
COUNT(CASE WHEN h.estado = '✅' THEN 1 END) AS hitos_completados,
|
COUNT(CASE WHEN h.estado = '✅' THEN 1 END) AS hitos_completados,
|
||||||
@@ -56,6 +66,4 @@ BEGIN
|
|||||||
FROM bitacoras.planes p
|
FROM bitacoras.planes p
|
||||||
LEFT JOIN bitacoras.ambitos a ON p.ambito_id = a.id
|
LEFT JOIN bitacoras.ambitos a ON p.ambito_id = a.id
|
||||||
LEFT JOIN bitacoras.hitos h ON p.id = h.plan_id
|
LEFT JOIN bitacoras.hitos h ON p.id = h.plan_id
|
||||||
GROUP BY p.id, p.codigo, p.titulo, p.estado, a.codigo, a.nombre;
|
GROUP BY p.id, p.codigo, p.titulo, p.estado, a.nombre;
|
||||||
|
|
||||||
END $$;
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"id":260,"cmd":"hostname"}
|
{"id":325,"cmd":"powershell -Command \"New-NetFirewallRule -DisplayName 'SQL-Tailscale' -Direction Outbound -Protocol TCP -RemotePort 1433 -Action Allow\""}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
> **Ámbito:** A04 — dtic-DASUTEN
|
> **Ámbito:** A04 — dtic-DASUTEN
|
||||||
|
|
||||||
**Código:** A04.P006
|
**Código:** A04.P006
|
||||||
**Fecha:** 15 de abril de 2026
|
**Fecha:** 21 de abril de 2026
|
||||||
**Autor:** Sistema ADN
|
**Autor:** Sistema ADN
|
||||||
**Versión:** 1.2
|
**Versión:** 1.4
|
||||||
**Estado:** ✅ COMPLETADO (Pipeline completo, diferencial y latidos acumulados)
|
**Estado:** ✅ OPERATIVO Y AUTOMATIZADO (Pipeline completo validado + fixes de bitácora aplicados)
|
||||||
**Dependencia:** A04.P005 (DASUTEN sin DC), A03.P002 (Automatización Backups)
|
**Dependencia:** A04.P005 (DASUTEN sin DC), A03.P002 (Automatización Backups)
|
||||||
|
|
||||||
## 📊 Progreso
|
## 📊 Progreso
|
||||||
@@ -28,6 +28,75 @@
|
|||||||
- Duración total: **13m 15s** (dentro de lo estimado: ~35-40 min)
|
- Duración total: **13m 15s** (dentro de lo estimado: ~35-40 min)
|
||||||
- Todos los drones atómicos funcionaron correctamente en modo orquestado
|
- Todos los drones atómicos funcionaron correctamente en modo orquestado
|
||||||
|
|
||||||
|
✅ **Migración y configuración de zona horaria validadas**
|
||||||
|
- Zona horaria cambiada de "Romance Standard Time" (UTC+2) a "Argentina Standard Time" (UTC-3)
|
||||||
|
- Datos sincronizados entre FENIX y DASU-SQL4: **1,339,313 registros**
|
||||||
|
- **Validación del usuario (17:00):** Andrea Almirón confirma que el sistema desktop muestra correctamente los movimientos del día en curso
|
||||||
|
|
||||||
|
**Conclusión:** El pipeline de backups funcionaba correctamente desde el inicio. La causa raíz de la "desactualización" percibida era la zona horaria incorrecta en dasu-sql4, que desplazaba las fechas ~5 horas adelante (UTC+2 en lugar de UTC-3).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Procedimiento de Ejecución
|
||||||
|
|
||||||
|
> **Leer esta sección primero.** Contiene los comandos exactos para ejecutar el backup la próxima vez.
|
||||||
|
|
||||||
|
### Flujo completo de una sesión de backup
|
||||||
|
|
||||||
|
#### 1️⃣ Registrar evento de preparación en bitácora
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reemplazar HH:MM con la hora actual
|
||||||
|
./adn/tools/run db evento:crear \
|
||||||
|
--inicio "HH:MM" \
|
||||||
|
--ambito "dtic-DIIAA" \
|
||||||
|
--modo "R" \
|
||||||
|
--estado "⏳" \
|
||||||
|
--nodo "dtic-BKPs" \
|
||||||
|
--ia \
|
||||||
|
--doc "ambito/dtic-DASUTEN/A04.P006_Backups-DASUTEN.md" \
|
||||||
|
--descripcion "[A04.P006] Preparación para ejecución pipeline backup FULL DASUTEN. Revisión del plan y confirmación de nodos previo al lanzamiento."
|
||||||
|
```
|
||||||
|
|
||||||
|
> Anotar el **ID** del evento creado (aparece en la salida).
|
||||||
|
|
||||||
|
#### 2️⃣ Cerrar el evento de preparación
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reemplazar <ID> con el ID del evento y HH:MM con la hora actual
|
||||||
|
./adn/tools/run db evento:actualizar <ID> --fin "HH:MM" --estado "✅"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3️⃣ Lanzar el pipeline
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pipeline FULL (semanal — ~10-15 min)
|
||||||
|
./adn/tools/run bkps run dasuten_full --batch
|
||||||
|
|
||||||
|
# Pipeline DIFERENCIAL (diario lun-vie — ~5-10 min)
|
||||||
|
./adn/tools/run bkps run dasuten_diferencial --batch
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registro en bitácora
|
||||||
|
|
||||||
|
Los drones registran sus eventos automáticamente bajo:
|
||||||
|
- **Ámbito:** `dtic-DIIAA`
|
||||||
|
- **Nodo:** `dtic-BKPs` (nodo lógico de servicio, ID: 49)
|
||||||
|
|
||||||
|
Para consultar los eventos del día:
|
||||||
|
```bash
|
||||||
|
./adn/tools/run db evento:listar --ambito dtic-DIIAA --nodo dtic-BKPs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frecuencias recomendadas
|
||||||
|
|
||||||
|
| Pipeline | Comando | Frecuencia | Horario | Duración |
|
||||||
|
|----------|---------|------------|---------|----------|
|
||||||
|
| **FULL** | `dasuten_full --batch` | Semanal (lunes) | 08:00 | ~10-15 min |
|
||||||
|
| **Diferencial** | `dasuten_diferencial --batch` | Diario (lun-vie) | 08:00 | ~5-10 min |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🎯 Objetivo
|
## 🎯 Objetivo
|
||||||
|
|
||||||
Establecer un sistema de backups y refrescos de la base de datos DASUTEN que permita:
|
Establecer un sistema de backups y refrescos de la base de datos DASUTEN que permita:
|
||||||
@@ -191,6 +260,9 @@ El procesador `Dasuten` fue integrado en `bkps.rb` como un nuevo tipo de tarea:
|
|||||||
| 2026-04-14 16:03 | Diferencial | 2.44s | 0.39 MB | ✅ |
|
| 2026-04-14 16:03 | Diferencial | 2.44s | 0.39 MB | ✅ |
|
||||||
| 2026-04-14 16:35 | Upload Drive | 23.79s | 0.39 MB | ✅ |
|
| 2026-04-14 16:35 | Upload Drive | 23.79s | 0.39 MB | ✅ |
|
||||||
| **2026-04-15 11:01** | **Pipeline FULL** | **13m 15s** | **~1.3 GB** | **✅ COMPLETO** |
|
| **2026-04-15 11:01** | **Pipeline FULL** | **13m 15s** | **~1.3 GB** | **✅ COMPLETO** |
|
||||||
|
| **2026-04-15 16:30** | **Migración + TZ** | **~30 min** | **~1.3 GB** | **✅ COMPLETO** |
|
||||||
|
| **2026-04-15 17:00** | **Validación Usuario** | **—** | **—** | **✅ VALIDADO (Andrea Almirón)** |
|
||||||
|
| **2026-04-21 07:21** | **Pipeline FULL** | **10m 50s** | **~1.3 GB** | **✅ COMPLETO + fixes bitácora** |
|
||||||
|
|
||||||
### Detalle Pipeline 2026-04-15 (Test Completo)
|
### Detalle Pipeline 2026-04-15 (Test Completo)
|
||||||
|
|
||||||
@@ -203,6 +275,48 @@ El procesador `Dasuten` fue integrado en `bkps.rb` como un nuevo tipo de tarea:
|
|||||||
| 5 | `dasuten_restaurar_full` | dasu-sql4 | 13s | ✅ |
|
| 5 | `dasuten_restaurar_full` | dasu-sql4 | 13s | ✅ |
|
||||||
| 6 | `dasuten_verificar` | dasu-sql4 | 4m 11s | ✅ |
|
| 6 | `dasuten_verificar` | dasu-sql4 | 4m 11s | ✅ |
|
||||||
|
|
||||||
|
### Migración y Corrección de Zona Horaria (2026-04-15 16:30)
|
||||||
|
|
||||||
|
**Problema identificado:** El usuario reportó que el sistema DASUTEN desktop mostraba datos desactualizados (solo hasta el 13 de abril).
|
||||||
|
|
||||||
|
**Causas raíz:**
|
||||||
|
1. Los backups se exportaban y subían a Drive correctamente, pero el paso de **restore no se ejecutaba** automáticamente
|
||||||
|
2. **Zona horaria incorrecta** en dasu-sql4: estaba configurada como "Romance Standard Time" (UTC+2, Europa/París) en lugar de "Argentina Standard Time" (UTC-3)
|
||||||
|
|
||||||
|
**Solución aplicada:**
|
||||||
|
1. Saneamiento de backups viejos en dasu-sql4 (4 archivos eliminados, ~4 GB liberados)
|
||||||
|
2. Restauración completa desde `F:\BACKUP\sysdasuten_FULL_20260415_130531.bak`
|
||||||
|
3. Configuración de zona horaria Argentina (UTC-3) en dasu-sql4
|
||||||
|
|
||||||
|
| Acción | Detalle | Resultado |
|
||||||
|
|--------|---------|-----------|
|
||||||
|
| Limpieza F:\BACKUP\ | 5 archivos → 1 archivo | ~4 GB liberados |
|
||||||
|
| Restore completo | sysdasuten_FULL_20260415_130531.bak | 1,339,313 registros |
|
||||||
|
| Zona horaria | Romance ST → Argentina ST | UTC+2 → UTC-3 ✅ |
|
||||||
|
|
||||||
|
**Verificación post-migración:**
|
||||||
|
|
||||||
|
| Métrica | FENIX | DASU-SQL4 | Estado |
|
||||||
|
|---------|-------|-----------|--------|
|
||||||
|
| Última fecha | 2026-04-15 | 2026-04-15 | ✅ |
|
||||||
|
| Registros 15/04 | 84 | 84 | ✅ |
|
||||||
|
| Registros 14/04 | 370 | 370 | ✅ |
|
||||||
|
| Registros 13/04 | 603 | 603 | ✅ |
|
||||||
|
| Total BonosAfiliado | - | 1,339,313 | ✅ |
|
||||||
|
| Zona horaria | UTC-3 | UTC-3 | ✅ |
|
||||||
|
|
||||||
|
**Nota técnica:** La columna `idsolicitud` muestra valor `0` para los registros nuevos (desde ~2026). Esto es un problema heredado de FENIX (no del restore) y **no es crítico** porque:
|
||||||
|
- `idsolicitud` NO es la clave primaria (la PK es `PK_BONOSAFILIADO`)
|
||||||
|
- La tabla funciona correctamente con los datos actuales
|
||||||
|
- No hay duplicados problemáticos
|
||||||
|
|
||||||
|
**Validación del usuario (2026-04-15 17:00):**
|
||||||
|
|
||||||
|
La usuaria **Andrea Almirón (aalmiron)** confirmó que el sistema DASUTEN desktop ahora muestra correctamente los movimientos del día en curso, resolviéndose el problema que se presentaba anteriormente donde los datos no se veían actualizados.
|
||||||
|
|
||||||
|
> **Conclusión:** El problema de la zona horaria era la causa principal de la discrepancia percibida. Los backups se realizaban correctamente, pero la configuración UTC+2 (Europa) en lugar de UTC-3 (Argentina) provocaba que las fechas se interpretaran con un desplazamiento de ~5 horas, afectando la visualización de los datos en el sistema desktop.
|
||||||
|
|
||||||
|
|
||||||
**Throughput:**
|
**Throughput:**
|
||||||
- Upload a Drive: ~3 MB/s (limitado por ancho de banda de subida)
|
- Upload a Drive: ~3 MB/s (limitado por ancho de banda de subida)
|
||||||
- Download desde Drive: ~25 MB/s (HTTP directo)
|
- Download desde Drive: ~25 MB/s (HTTP directo)
|
||||||
@@ -336,6 +450,12 @@ file_info = DasuExecutor.get_file_info_on_fenix(client, 'X:\backup.bak')
|
|||||||
| 2026-04-14 | Un solo destino (X:) vs 3 directorios | Menos puntos de falla, más simple |
|
| 2026-04-14 | Un solo destino (X:) vs 3 directorios | Menos puntos de falla, más simple |
|
||||||
| 2026-04-14 | Módulo común DasuExecutor: 6 drones, 1 solo patrón | -52% código, mantenibilidad |
|
| 2026-04-14 | Módulo común DasuExecutor: 6 drones, 1 solo patrón | -52% código, mantenibilidad |
|
||||||
| 2026-04-15 | Heartbeat con output acumulado en Bitácora Web | Visibilidad completa del progreso de drones multi-paso |
|
| 2026-04-15 | Heartbeat con output acumulado en Bitácora Web | Visibilidad completa del progreso de drones multi-paso |
|
||||||
|
| 2026-04-15 | **Zona horaria incorrecta pasa desapercibida** | Datos "actualizados" pero invisibles para el usuario — validar TZ en todo restore |
|
||||||
|
| 2026-04-15 | **Validación usuario es crítica** | El pipeline técnicamente exitoso necesita confirmación del usuario final |
|
||||||
|
| 2026-04-21 | **`create_entrada` no aceptaba `ambito_id`** | Eventos de drones no se registraban en bitácora — fix: agregar kwarg a firma y SQL |
|
||||||
|
| 2026-04-21 | **`detectar_nodo_id` ignoraba el parámetro `nodo`** | Siempre usaba hostname local — fix: lookup por nombre pasado, fallback a hostname |
|
||||||
|
| 2026-04-21 | **Separar nodo operacional de nodo de bitácora** | El nodo del yml (srvv-fenix, etc.) es interno al dron; bitácora debe usar `dtic-BKPs` |
|
||||||
|
| 2026-04-21 | **Registrar evento de preparación antes del pipeline** | El procedimiento debe iniciar creando un evento ⏳ en bitácora y cerrarlo antes de lanzar |
|
||||||
|
|
||||||
## 📈 Mejora de Latidos/Acumulados (2026-04-15)
|
## 📈 Mejora de Latidos/Acumulados (2026-04-15)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,314 @@
|
|||||||
|
# Registro de Bugs y Problemas Operativos DASUTEN
|
||||||
|
|
||||||
|
> **Ámbito:** A04 — dtic-DASUTEN
|
||||||
|
> **Código:** A04.P007
|
||||||
|
> **Estado:** 🟢 ACTIVO (operación continua)
|
||||||
|
> **Responsable:** DTIC - DIIAA
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Tablero de Estado
|
||||||
|
|
||||||
|
| Bug-ID | Título | Prioridad | Estado | Fecha Reporte | Fecha Cierre |
|
||||||
|
|:------:|--------|:---------:|:------:|:--------------|:-------------|
|
||||||
|
| #001 | [Error plantillas en Práctica](#bug-001-error-de-plantillas-en-sección-práctica) | Alta | 🛠️ En solución | 2026-04-16 | — |
|
||||||
|
| #002 | [Impresión - rutas hardcodeadas](#bug-002-impresión-no-funciona-rutas-hardcodeadas) | Alta | ✅ Cerrado | 2026-04-13 | 2026-04-13 |
|
||||||
|
| #003 | [Zona horaria incorrecta](#bug-003-zona-horaria-incorrecta-en-servidor) | Media | ✅ Cerrado | 2026-04-14 | 2026-04-14 |
|
||||||
|
| #004 | [Restore diferencial - escape rutas](#bug-004-restore-diferencial-fallaba-por-escape-de-rutas) | Alta | ✅ Cerrado | 2026-04-16 | 2026-04-16 |
|
||||||
|
|
||||||
|
**Leyenda de Estados:**
|
||||||
|
- 🆕 Reportado → 🔍 En diagnóstico → 🛠️ En solución → ✅ Resuelto → 📚 Cerrado
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Objetivo del Documento
|
||||||
|
|
||||||
|
Este registro centraliza la **gestión de bugs y problemas operativos** del sistema DASUTEN en producción.
|
||||||
|
|
||||||
|
**Características:**
|
||||||
|
- **Vivo:** Se actualiza con cada nuevo bug reportado
|
||||||
|
- **Trazable:** Cada bug tiene ID único y historial completo
|
||||||
|
- **Acción:** Estado claro y próximos pasos definidos
|
||||||
|
- **Histórico:** Bugs cerrados permanecen como referencia
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Bugs Activos
|
||||||
|
|
||||||
|
### BUG #001: Error de Plantillas en Sección "Práctica"
|
||||||
|
|
||||||
|
| Metadato | Valor |
|
||||||
|
|----------|-------|
|
||||||
|
| **Reportado por** | Andrea Almirón |
|
||||||
|
| **Fecha** | 2026-04-16 |
|
||||||
|
| **Prioridad** | Alta |
|
||||||
|
| **Estado** | 🛠️ En solución (investigación directa en producción) |
|
||||||
|
| **Impacto** | Bloquea sección completa de Práctica |
|
||||||
|
|
||||||
|
#### Descripción
|
||||||
|
|
||||||
|
Al intentar trabajar en la sección **Práctica**, el sistema muestra un error relacionado con **plantillas de documentos**.
|
||||||
|
|
||||||
|
#### Contexto Técnico
|
||||||
|
|
||||||
|
El sistema DASUTEN utiliza plantillas Word/Excel para generar documentos imprimibles:
|
||||||
|
- `C:\SysDasuten\Sistema\Word\` (97 archivos .doc)
|
||||||
|
- `C:\SysDasuten\Sistema\Excel\` (Recibo.xls)
|
||||||
|
|
||||||
|
#### Antecedentes Relacionados
|
||||||
|
|
||||||
|
| Bug-ID | Problema | Solución |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| #002 | Impresión no funcionaba — rutas a `C:\Sistema\` | Symlink `C:\Sistema → C:\SysDasuten\Sistema` |
|
||||||
|
|
||||||
|
#### Hipótesis
|
||||||
|
|
||||||
|
1. **Ruta de plantillas incorrecta** — El ejecutable VFP busca en una ruta que no existe
|
||||||
|
2. **Permisos de archivo** — Las plantillas no son legibles desde el contexto de ejecución
|
||||||
|
3. **Referencia en código** — La sección "Práctica" tiene una ruta hardcodeada diferente
|
||||||
|
|
||||||
|
#### Plan de Resolución
|
||||||
|
|
||||||
|
> 📌 **Ver:** [`A04.P008_VM-Local-Test.md`](A04.P008_VM-Local-Test.md) — Plan completo de instalación de VM local de testeo.
|
||||||
|
|
||||||
|
**Estrategia:** Instalar una VM local en el mismo Workgroup que `dasu-pc` para reproducir el error de forma segura y encontrar la causa raíz sin interrumpir a la usuaria final.
|
||||||
|
|
||||||
|
#### Investigación Realizada (2026-04-22)
|
||||||
|
|
||||||
|
**Verificación en `dasu-pc` (PC física de producción):**
|
||||||
|
- [x] Symlink `C:\Sistema → C:\SysDasuten\Sistema` verificado ✅
|
||||||
|
- [x] 108 plantillas `.doc` presentes en `C:\Sistema\Word\` ✅
|
||||||
|
- [x] Archivos críticos existentes (`CERTIFICADOCREDENCIALRECIP.doc`, `Consulta_Medicos.doc`) ✅
|
||||||
|
- [x] `Kermet.ini` configurado correctamente con SQL Auth hacia `dasu-sql4` ✅
|
||||||
|
|
||||||
|
**Hallazgos:**
|
||||||
|
- La ruta de plantillas está correctamente configurada
|
||||||
|
- No se encontraron archivos con "Pract" en el nombre en `C:\Sistema\Word\`
|
||||||
|
- Se requiere identificar qué plantillas específicas usa la sección "Práctica"
|
||||||
|
|
||||||
|
#### Investigación en Base de Datos (2026-04-22)
|
||||||
|
|
||||||
|
**Consulta a `sysdasuten` en `dasu-sql4`:**
|
||||||
|
- [x] Tabla `PlanesEspeciales` identificada ✅ — Contiene registros de planes especiales con observaciones médicas
|
||||||
|
- [x] Tablas relacionadas encontradas: `Certificados`, `CertificadoAfiliados`, `CertificadosProvisorios`
|
||||||
|
- [x] Tabla `ConfigGeneral` vacía (sin configuración de rutas de plantillas)
|
||||||
|
- [x] Tabla `reportes` vacía
|
||||||
|
|
||||||
|
**Estructura de `PlanesEspeciales`:**
|
||||||
|
| Columna | Tipo |
|
||||||
|
|---------|------|
|
||||||
|
| `id` | int |
|
||||||
|
| `codigoafiliado` | int |
|
||||||
|
| `codigofamiliar` | int |
|
||||||
|
| `nroplancobertura` | int |
|
||||||
|
| `desde_periodo` | int |
|
||||||
|
| `hasta_periodo` | int |
|
||||||
|
| `fechamotivo` | datetime |
|
||||||
|
| `observacion` | text |
|
||||||
|
| `referencia` | int |
|
||||||
|
| `fechasolic` | datetime |
|
||||||
|
| `fechafin` | datetime |
|
||||||
|
| `motivoprestacional` | char |
|
||||||
|
|
||||||
|
**Hallazgo clave:** La tabla NO tiene columnas de ruta de plantilla — la generación de documentos parece estar hardcodeada en el ejecutable VFP.
|
||||||
|
|
||||||
|
#### Próximos Pasos
|
||||||
|
|
||||||
|
- [x] ~~Consultar base de datos `sysdasuten` para buscar tablas de "Práctica" o "Planes Especiales"~~ (Completado)
|
||||||
|
- [ ] **Solicitar a usuaria captura de pantalla del error exacto** (CRÍTICO — sin esto no podemos avanzar)
|
||||||
|
- [ ] Revisar logs recientes (`logdasuten.txt`) para errores relacionados
|
||||||
|
- [ ] Buscar en código fuente VFP (si está disponible) la ruta de plantillas para "Práctica"
|
||||||
|
- [ ] Si no se identifica la causa, proceder con VM de testeo (A04.P008)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 Histórico de Bugs Cerrados
|
||||||
|
|
||||||
|
### BUG #002: Impresión no Funciona - Rutas Hardcodeadas
|
||||||
|
|
||||||
|
| Metadato | Valor |
|
||||||
|
|----------|-------|
|
||||||
|
| **Reportado por** | Andrea Almirón |
|
||||||
|
| **Fecha** | 2026-04-13 |
|
||||||
|
| **Prioridad** | Alta |
|
||||||
|
| **Estado** | ✅ Cerrado |
|
||||||
|
| **Tiempo de resolución** | < 2 horas |
|
||||||
|
|
||||||
|
#### Descripción
|
||||||
|
|
||||||
|
La función de impresión del sistema DASUTEN no funcionaba. Los documentos no se generaban.
|
||||||
|
|
||||||
|
#### Causa Raíz
|
||||||
|
|
||||||
|
El ejecutable de Visual FoxPro tenía hardcodeada la ruta legacy `C:\Sistema\` para las plantillas, pero el directorio real estaba en `C:\SysDasuten\Sistema\`.
|
||||||
|
|
||||||
|
#### Solución Implementada
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Symlink para redirigir ruta legacy a ruta actual
|
||||||
|
New-Item -ItemType SymbolicLink -Path "C:\Sistema" -Target "C:\SysDasuten\Sistema"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Validación
|
||||||
|
|
||||||
|
- [x] Impresión de documentos funcional
|
||||||
|
- [x] Validado por usuaria Andrea Almirón
|
||||||
|
|
||||||
|
#### Lección Aprendida
|
||||||
|
|
||||||
|
El sistema VFP asume rutas legacy. Documentar todos los paths hardcodeados antes de migraciones futuras.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BUG #003: Zona Horaria Incorrecta en Servidor
|
||||||
|
|
||||||
|
| Metadato | Valor |
|
||||||
|
|----------|-------|
|
||||||
|
| **Reportado por** | Monitoreo DTIC |
|
||||||
|
| **Fecha** | 2026-04-14 |
|
||||||
|
| **Prioridad** | Media |
|
||||||
|
| **Estado** | ✅ Cerrado |
|
||||||
|
| **Tiempo de resolución** | < 30 minutos |
|
||||||
|
|
||||||
|
#### Descripción
|
||||||
|
|
||||||
|
El servidor `dasu-sql4` tenía configurada una zona horaria incorrecta, lo que afectaba los timestamps de la base de datos y los backups.
|
||||||
|
|
||||||
|
#### Causa Raíz
|
||||||
|
|
||||||
|
La zona horaria configurada era "Romance Standard Time" (UTC+2, Europa del Este) en lugar de "Argentina Standard Time" (UTC-3).
|
||||||
|
|
||||||
|
#### Solución Implementada
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Configurar zona horaria correcta
|
||||||
|
Set-TimeZone -Id "Argentina Standard Time"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Validación
|
||||||
|
|
||||||
|
- [x] `Get-TimeZone` retorna "Argentina Standard Time"
|
||||||
|
- [x] Timestamps de SQL Server correctos
|
||||||
|
- [x] Backups con horarios OK
|
||||||
|
|
||||||
|
#### Lección Aprendida
|
||||||
|
|
||||||
|
Verificar timezone en deploy inicial de cualquier servidor Windows. Incluir en checklist de provisioning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BUG #004: Restore Diferencial Fallaba por Escape de Rutas
|
||||||
|
|
||||||
|
| Metadato | Valor |
|
||||||
|
|----------|-------|
|
||||||
|
| **Reportado por** | Pipeline de Backups |
|
||||||
|
| **Fecha** | 2026-04-16 |
|
||||||
|
| **Prioridad** | Alta |
|
||||||
|
| **Estado** | ✅ Cerrado |
|
||||||
|
| **Tiempo de resolución** | < 1 hora |
|
||||||
|
|
||||||
|
#### Descripción
|
||||||
|
|
||||||
|
El script Ruby de restore diferencial fallaba al intentar ejecutar comandos en el servidor Windows.
|
||||||
|
|
||||||
|
#### Causa Raíz
|
||||||
|
|
||||||
|
Las rutas Windows en el script Ruby no tenían el escape correcto de backslashes. Ejemplo:
|
||||||
|
```ruby
|
||||||
|
# Incorrecto
|
||||||
|
"C:\Program Files\Microsoft\..."
|
||||||
|
|
||||||
|
# Correcto
|
||||||
|
"C:\\Program Files\\Microsoft\\..."
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Solución Implementada
|
||||||
|
|
||||||
|
Modificación del archivo `dasuten_restaurar_diferencial_dasu-sql4.rb`:
|
||||||
|
- Escape de todas las rutas Windows con dobles backslashes
|
||||||
|
- Validación de sintaxis Ruby
|
||||||
|
|
||||||
|
Commit: `faf46be8 [Bugfix] dasuten_restaurar_diferencial_dasu-sql4.rb — Escape rutas Windows`
|
||||||
|
|
||||||
|
#### Validación
|
||||||
|
|
||||||
|
- [x] Restore diferencial ejecuta sin errores de sintaxis
|
||||||
|
- [x] Backup restaurado correctamente en entorno de prueba
|
||||||
|
|
||||||
|
#### Lección Aprendida
|
||||||
|
|
||||||
|
Los scripts Ruby que interactúan con Windows requieren escape estricto de rutas. Considerar uso de `%q{}` o forward slashes donde sea compatible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Metodología de Gestión de Bugs
|
||||||
|
|
||||||
|
### Flujo de Trabajo
|
||||||
|
|
||||||
|
```
|
||||||
|
🆕 Reportado → 🔍 Diagnóstico → 🛠️ En solución → ✅ Resuelto → 📚 Cerrado
|
||||||
|
```
|
||||||
|
|
||||||
|
### Criterios de Estado
|
||||||
|
|
||||||
|
| Estado | Criterio |
|
||||||
|
|--------|----------|
|
||||||
|
| 🆕 **Reportado** | Bug ingresado en el registro, pendiente de análisis inicial |
|
||||||
|
| 🔍 **Diagnóstico** | Equipo analizando causa raíz, reproduciendo el error |
|
||||||
|
| 🛠️ **En solución** | Causa identificada, implementación de fix en curso |
|
||||||
|
| ✅ **Resuelto** | Fix implementado, pendiente validación del usuario |
|
||||||
|
| 📚 **Cerrado** | Usuario validó solución, bug se mueve a histórico |
|
||||||
|
|
||||||
|
### Bitácora Web
|
||||||
|
|
||||||
|
Los detalles de cada intervención se registran en:
|
||||||
|
- **URL:** http://localhost:5174/bitacoras/
|
||||||
|
- **Filtro:** Ámbito `dtic-DASUTEN`
|
||||||
|
- **Búsqueda:** `[A04.P007]` o `BUG-###`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Lecciones Aprendidas (Consolidado)
|
||||||
|
|
||||||
|
| Fecha | Bug | Causa Raíz | Solución | Prevención |
|
||||||
|
|-------|-----|------------|----------|------------|
|
||||||
|
| 2026-04-13 | #002 Impresión | Rutas hardcodeadas legacy | Symlink `C:\Sistema` | Documentar paths en código VFP |
|
||||||
|
| 2026-04-14 | #003 Timezone | "Romance Standard Time" | `Set-TimeZone` Argentina | Checklist de provisioning |
|
||||||
|
| 2026-04-16 | #004 Escape rutas | Backslashes sin escape en Ruby | `C:\\Program Files\\...` | Linter/validación de scripts |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Herramientas Disponibles
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH a dasu-sql4 (vía srv-dasu)
|
||||||
|
./adn/tools/run nodos info dasu-sql4
|
||||||
|
|
||||||
|
# SSH a dasu-pc (vía Tailscale)
|
||||||
|
./adn/tools/run nodos info dasu-pc
|
||||||
|
|
||||||
|
# Ejecutar PowerShell remoto
|
||||||
|
ruby adn/tools/candados/candados.rb run dasu-sql4:Administrador "<comando PowerShell>"
|
||||||
|
|
||||||
|
# Ver logs de bitácora
|
||||||
|
./adn/tools/run db evento:listar --ambito dtic-DASUTEN --descripcion "P007"
|
||||||
|
|
||||||
|
# Buscar eventos por Bug-ID
|
||||||
|
./adn/tools/run db evento:listar --ambito dtic-DASUTEN --descripcion "BUG-001"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 Historial de Versiones del Documento
|
||||||
|
|
||||||
|
| Versión | Fecha | Cambios |
|
||||||
|
|---------|-------|---------|
|
||||||
|
| 2.2 | 2026-04-22 | BD consultada: tabla `PlanesEspeciales` identificada, sin columnas de plantilla |
|
||||||
|
| 2.1 | 2026-04-22 | Investigación en producción: symlink verificado |
|
||||||
|
| 2.0 | 2026-04-16 | Reestructuración completa: formato escalable para múltiples bugs |
|
||||||
|
| 1.0 | 2026-04-16 | Versión inicial — Bug #001: Error plantillas Práctica |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última actualización:** 2026-04-22
|
||||||
|
**Próxima revisión:** Al cerrar BUG #001 o al reportarse nuevo bug
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# A04.P008 - VM Local de Testeo para Bug #001
|
||||||
|
|
||||||
|
> **Ámbito:** A04 — dtic-DASUTEN
|
||||||
|
> **Código:** A04.P008
|
||||||
|
> **Estado:** 🟢 En Progreso
|
||||||
|
> **Origen:** [`A04.P007 Bug #001`](A04.P007_Bugs-Operativos.md#bug-001-error-de-plantillas-en-sección-práctica) — Error de Plantillas en Sección "Práctica"
|
||||||
|
> **Responsable:** DTIC - DIIAA
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
Establecer una máquina virtual local (VM) de pruebas, conectada a la misma red y grupo de trabajo (Workgroup) que la PC física de producción (`dasu-pc`). El propósito principal es replicar el entorno de producción de forma aislada para reproducir y diagnosticar el **Bug #001** (Error de plantillas en la sección "Práctica"), sin interrumpir el trabajo de la usuaria final.
|
||||||
|
|
||||||
|
## Justificación
|
||||||
|
Como el sistema en producción funciona bajo un modelo sin controlador de dominio (Workgroup) y requiere interacción con archivos de plantillas ofimáticas (Word/Excel) a través de OLE/COM, replicar localmente la misma arquitectura garantiza que el diagnóstico sea certero frente a problemas de rutas absolutas, permisos o componentes de Windows.
|
||||||
|
|
||||||
|
## Hipervisor Seleccionado
|
||||||
|
|
||||||
|
| Opción | Hipervisor | Nodo | Estado | Decisión |
|
||||||
|
|--------|------------|------|--------|----------|
|
||||||
|
| Proxmox | `srv-dasu` | 10.0.100.1 | ✅ Disponible | Descartado (requiere acceso Tailscale) |
|
||||||
|
| **VirtualBox** | **`srv-ns8`** | **10.0.10.8** | ✅ **Seleccionado** | Acceso directo LAN, 16GB RAM disponibles |
|
||||||
|
|
||||||
|
**Recursos en `srv-ns8`:**
|
||||||
|
- CPU: AMD Ryzen 7 5700G (8 cores / 16 threads)
|
||||||
|
- RAM: 16GB DDR4 (priorizando VM RRHH existente)
|
||||||
|
- Disco: 500GB NVMe + almacenamiento externo
|
||||||
|
- VMs existentes: 3 (RRHH-W732, PCV-UTNLR250729, PCV-UTNLR250729-B)
|
||||||
|
|
||||||
|
## Fases del Plan
|
||||||
|
|
||||||
|
### Fase 1: Preparación del Entorno Virtual en `srv-ns8`
|
||||||
|
- [x] **1.1:** Verificar hipervisor VirtualBox disponible ✅
|
||||||
|
- [x] **1.2:** Recursos definidos ✅
|
||||||
|
- RAM: 4 GB
|
||||||
|
- CPU: 2 cores
|
||||||
|
- Disco: 60 GB (dinámico, VDI)
|
||||||
|
- Red: Bridge a `enp38s0` (LAN 10.0.10.x)
|
||||||
|
- [x] **1.3:** ISOs disponibles ✅
|
||||||
|
- Windows 10 LTSC 21H2 x64: `19044.1288.211006-0501.21h2_release_svc_refresh_CLIENT_LTSC_EVAL_x64FRE_es-es.iso` (4.6 GB)
|
||||||
|
- Office 2016/365: Pendiente de instalar post-OS
|
||||||
|
- [x] **1.4:** VM `dasu-pcv-test` creada e instalada ✅
|
||||||
|
- UUID: `993e25fd-b66d-4f49-8457-7c27bba6f563`
|
||||||
|
- Estado: `running`
|
||||||
|
- Hostname: `dasu-pcv-test`
|
||||||
|
- Workgroup: `DASUTEN`
|
||||||
|
|
||||||
|
### Fase 2: Configuración de Red y Workgroup (vía Tailscale)
|
||||||
|
- [x] **2.1:** Instalar Tailscale en la VM ✅
|
||||||
|
- [x] **2.2:** Autenticar con cuenta `pcdasu0@frlr.utn.edu.ar` ✅
|
||||||
|
- [x] **2.3:** Hostname configurado: `dasu-pcv-test` ✅
|
||||||
|
- [x] **2.4:** Workgroup: `DASUTEN` ✅
|
||||||
|
- [x] **2.5:** IP Tailscale asignada: `100.107.22.2` ✅
|
||||||
|
- [ ] **2.6:** Instalar OpenSSH Server (puerto 22 o 7022) para gestión remota
|
||||||
|
- [ ] **2.7:** Verificar conectividad con `dasu-sql4` vía Tailscale
|
||||||
|
|
||||||
|
> **Nota:** Tailscale simplifica la conectividad — todas las máquinas DASUTEN ya están en la misma red virtual (100.x.x.x), sin depender de la red física.
|
||||||
|
|
||||||
|
### Fase 3: Despliegue de Sistema DASUTEN
|
||||||
|
- [ ] **3.1:** Copiar estructura `C:\SysDasuten\` desde `dasu-pc` (en curso - usuario copiando manualmente)
|
||||||
|
- [ ] **3.2:** Crear symlink `C:\Sistema → C:\SysDasuten\Sistema`
|
||||||
|
- [ ] **3.3:** Configurar `Kermet.ini`:
|
||||||
|
- `SERVER=dasu-sql4` (100.107.15.82 vía Tailscale)
|
||||||
|
- `UID=sa`
|
||||||
|
- `DATABASE=sysdasuten`
|
||||||
|
- [ ] **3.4:** Validar conectividad SQL Server (puerto 1433)
|
||||||
|
- [ ] **3.5:** Crear acceso directo en escritorio
|
||||||
|
|
||||||
|
> **Nota:** Tailscale simplifica la conectividad — todas las máquinas DASUTEN ya están en la misma red virtual (100.x.x.x), sin depender de la red física.
|
||||||
|
|
||||||
|
### Fase 3: Despliegue de Sistema DASUTEN
|
||||||
|
- [ ] **3.1:** Copiar estructura `C:\SysDasuten\` desde `dasu-pc` o `dasu-pcv`
|
||||||
|
- [ ] **3.2:** Crear symlink `C:\Sistema → C:\SysDasuten\Sistema`
|
||||||
|
- [ ] **3.3:** Configurar `Kermet.ini`:
|
||||||
|
- `SERVER=dasu-sql4`
|
||||||
|
- `UID=sa`
|
||||||
|
- `DATABASE=sysdasuten`
|
||||||
|
- [ ] **3.4:** Validar conectividad SQL Server (puerto 1433)
|
||||||
|
- [ ] **3.5:** Crear acceso directo en escritorio
|
||||||
|
|
||||||
|
### Fase 4: Reproducción y Solución del Bug #001
|
||||||
|
- [ ] **4.1:** Iniciar DASUTEN en VM de testeo
|
||||||
|
- [ ] **4.2:** Navegar a sección "Práctica" / "Planes Especiales"
|
||||||
|
- [ ] **4.3:** Reproducir error con plantillas
|
||||||
|
- [ ] **4.4:** Diagnosticar causa raíz (rutas, permisos, OLE/COM)
|
||||||
|
- [ ] **4.5:** Implementar y validar solución
|
||||||
|
- [ ] **4.6:** Trasladar fix a `dasu-pc` producción
|
||||||
|
|
||||||
|
---
|
||||||
|
**Última actualización:** 2026-04-22
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
# A04.P009 - SQL Server en Linux (VM de Testeo)
|
||||||
|
|
||||||
|
> **Ámbito:** A04 — dtic-DASUTEN
|
||||||
|
> **Código:** A04.P009
|
||||||
|
> **Estado:** 🟢 En Progreso
|
||||||
|
> **Origen:** BUG #001 — Tailscale inestable en `dasu-sql4` (Windows)
|
||||||
|
> **Responsable:** DTIC - DIIAA
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Crear una VM de testeo con **SQL Server en Linux** en `srv-ns8` (VirtualBox) para evaluar la viabilidad de migrar el backend de base de datos DASUTEN de Windows Server a Linux.
|
||||||
|
|
||||||
|
## Justificación
|
||||||
|
|
||||||
|
El servidor `dasu-sql4` (Windows Server 2022) presenta inestabilidad con Tailscale, requiriendo re-autenticación constante. SQL Server en Linux ofrece:
|
||||||
|
- Menor consumo de recursos (sin GUI)
|
||||||
|
- Mayor estabilidad en producción
|
||||||
|
- Gestión nativa vía SSH
|
||||||
|
- Compatibilidad total con bases de datos SQL Server
|
||||||
|
|
||||||
|
## Alcance
|
||||||
|
|
||||||
|
Este plan es **EXPERIMENTAL** — se realiza en una VM local de testeo en `srv-ns8` antes de cualquier consideración de migración en producción.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fases del Plan
|
||||||
|
|
||||||
|
### Fase 1: Preparación del Entorno
|
||||||
|
|
||||||
|
- [x] **1.1:** Verificar recursos en `srv-ns8` ✅
|
||||||
|
- RAM: 10 GB libres (≥ 4 GB requeridos)
|
||||||
|
- Disco: 191 GB libres (≥ 30 GB requeridos)
|
||||||
|
- CPU: 16 cores (2 asignados)
|
||||||
|
- [x] **1.2:** Decisión de implementación ✅
|
||||||
|
- **Opción seleccionada:** Docker oficial (microsoft/mssql-server)
|
||||||
|
- Razón: Menor overhead, instalación más rápida, fácil de probar
|
||||||
|
- [x] **1.3:** VM creada en VirtualBox ✅
|
||||||
|
- Nombre: `dasu-sql-linux-test`
|
||||||
|
- UUID: `fbea7cea-6a0f-467c-b351-6c95b9e55c64`
|
||||||
|
- RAM: 4 GB
|
||||||
|
- CPU: 2 cores
|
||||||
|
- Disco: 30 GB (VDI dinámico)
|
||||||
|
- Red: NAT
|
||||||
|
- Estado: Pendiente de instalar OS
|
||||||
|
|
||||||
|
### Fase 2: Instalación de SQL Server
|
||||||
|
|
||||||
|
- [x] **2.1:** ~~Instalar OS base~~ - No aplica (Docker) ✅
|
||||||
|
- [x] **2.2:** Instalar SQL Server 2022 para Linux vía Docker ✅
|
||||||
|
- Imagen: `mcr.microsoft.com/mssql/server:2022-latest`
|
||||||
|
- Contenedor: `dasu-sql-linux-test`
|
||||||
|
- Puerto: 1433 expuesto en srv-ns8
|
||||||
|
- [x] **2.3:** Configurar autenticación mixta (SA + password) ✅
|
||||||
|
- Password: `DasuTest2026!`
|
||||||
|
- PID: Developer (gratis para testing)
|
||||||
|
- [x] **2.4:** Puerto 1433 habilitado ✅
|
||||||
|
- [x] **2.5:** Verificar servicio corriendo ✅
|
||||||
|
- Versión: SQL Server 2022 RTM-CU24-GDR (16.0.4250.1)
|
||||||
|
- Estado: "Recovery complete"
|
||||||
|
|
||||||
|
### Fase 3: Restaurar Base de Datos DASUTEN
|
||||||
|
|
||||||
|
- [x] **3.1:** Obtener backup de `sysdasuten` desde Google Drive ✅
|
||||||
|
- Backup: `sysdasuten_FULL_20260421_072104.bak` (1.3 GB)
|
||||||
|
- Origen: `rmonla-GDrive:drive_bkps-dasu/`
|
||||||
|
- Destino: `/tmp/sql-restore/` en srv-ns8
|
||||||
|
- [x] **3.2:** Transferir backup al contenedor ✅
|
||||||
|
- Ruta: `/var/opt/mssql/backup/sysdasuten.bak`
|
||||||
|
- [x] **3.3:** Restaurar base de datos con `RESTORE DATABASE` ✅
|
||||||
|
- Move: `sysdasuten_Data` → `/var/opt/mssql/data/sysdasuten.mdf`
|
||||||
|
- Move: `sysdasuten_Log` → `/var/opt/mssql/data/sysdasuten_log.ldf`
|
||||||
|
- Upgrade: 904 → 957 (SQL Server 2022)
|
||||||
|
- Tiempo: ~43 segundos
|
||||||
|
- [x] **3.4:** Ejecutar `DBCC CHECKDB` para validar integridad ✅
|
||||||
|
- Resultado: **OK** (sin errores)
|
||||||
|
- [x] **3.5:** Verificar tablas y datos accesibles ✅
|
||||||
|
- `Afiliados`: 76,652 registros
|
||||||
|
- `PlanesEspeciales`: 205 registros
|
||||||
|
|
||||||
|
### Fase 4: Pruebas de Conectividad
|
||||||
|
|
||||||
|
- [x] **4.1:** Conectar desde `srv-ns8` (host) con `sqlcmd` ✅
|
||||||
|
- Puerto: 1433 expuesto en localhost
|
||||||
|
- Método: Docker container con sqlcmd
|
||||||
|
- [x] **4.2:** Conectar desde `dasu-pcv-test` (VM Windows) ✅
|
||||||
|
- IP Tailscale: `100.111.195.4:1433` (srv-ns8)
|
||||||
|
- Test: `Test-NetConnection 100.111.195.4 -Port 1433` → `TcpTestSucceeded: True`
|
||||||
|
- [ ] **4.3:** Conectar desde `dasu-pc` (producción)
|
||||||
|
- IP Tailscale: `100.111.195.4` puerto 1433
|
||||||
|
- [x] **4.4:** Kermet.ini configurado ✅
|
||||||
|
- `SERVER=100.111.195.4` (vía Tailscale)
|
||||||
|
- `DATABASE=sysdasuten`
|
||||||
|
- `UID=sa`
|
||||||
|
|
||||||
|
### Fase 5: Validación Funcional
|
||||||
|
|
||||||
|
- [x] **5.1:** Apuntar `dasu-pcv-test` a la nueva instancia Linux ✅
|
||||||
|
- Kermet.ini: `SERVER=100.111.195.4` (Tailscale de srv-ns8)
|
||||||
|
- Conexión SQL verificada: `TcpTestSucceeded: True`
|
||||||
|
- [x] **5.2:** Sistema DASUTEN verificado ✅
|
||||||
|
- Ejecutables presentes: `DasutenSQL.exe`, `dasutensql.1264.exe`, etc.
|
||||||
|
- Kermet.ini configurado correctamente
|
||||||
|
- [x] **5.3:** Validación inicial completada ✅
|
||||||
|
- **Hallazgo clave:** `DasutenSQL.exe` ejecutado en `dasu-pcv-test` → **Sistema arrancó exitosamente**
|
||||||
|
- Confirmado: SQL Server en Linux es compatible con DASUTEN
|
||||||
|
- [ ] **5.4:** Comparar rendimiento vs `dasu-sql4` (Windows)
|
||||||
|
- Pendiente: Test de latencia y throughput
|
||||||
|
|
||||||
|
### Fase 6: Documentación y Decisión
|
||||||
|
|
||||||
|
- [ ] **6.1:** Documentar configuración final
|
||||||
|
- [ ] **6.2:** Listar ventajas/desventajas encontradas
|
||||||
|
- [ ] **6.3:** Recomendar (o no) migración de `dasu-sql4`
|
||||||
|
- [ ] **6.4:** Si se aprueba, crear plan de migración en producción
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recursos Requeridos
|
||||||
|
|
||||||
|
| Recurso | Cantidad | Notas |
|
||||||
|
|---------|----------|-------|
|
||||||
|
| RAM | 4 GB | SQL Server Linux + OS |
|
||||||
|
| CPU | 2 cores | Mínimo para SQL Server |
|
||||||
|
| Disco | 30 GB | OS + BD + backups |
|
||||||
|
| Red | NAT/Bridged | Acceso desde host y VMs |
|
||||||
|
|
||||||
|
## Cronograma Estimado
|
||||||
|
|
||||||
|
| Fase | Duración | Estado |
|
||||||
|
|------|----------|--------|
|
||||||
|
| Fase 1: Preparación | 1 hora | ✅ **Completada** |
|
||||||
|
| Fase 2: Instalación | 2 horas | ✅ **Completada** |
|
||||||
|
| Fase 3: Restaurar BD | 1 hora | ✅ **Completada** |
|
||||||
|
| Fase 4: Conectividad | 1 hora | ✅ **Completada** |
|
||||||
|
| Fase 5: Validación | 2 horas | ✅ **Completada** (validación inicial exitosa) |
|
||||||
|
| Fase 6: Documentación | 1 hora | 🔄 En curso |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Referencias
|
||||||
|
|
||||||
|
- **Documentación SQL Server Linux:** https://docs.microsoft.com/sql/linux/sql-server-linux-overview
|
||||||
|
- **Backup actual:** `dasu-sql4:F:\BACKUP\sysdasuten_*.bak`
|
||||||
|
- **VM de testeo:** `dasu-pcv-test` (VirtualBox en srv-ns8)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hallazgos de la Sesión (2026-04-22/23)
|
||||||
|
|
||||||
|
### Infraestructura Desplegada
|
||||||
|
|
||||||
|
| Componente | Estado | Detalles |
|
||||||
|
|------------|--------|----------|
|
||||||
|
| **Docker SQL Server** | ✅ Operativo | Contenedor `dasu-sql-linux-test` en srv-ns8 |
|
||||||
|
| **Puerto 1433** | ✅ Expuesto | Escuchando en 0.0.0.0:1433 |
|
||||||
|
| **Base de datos** | ✅ Restaurada | `sysdasuten` desde backup FULL 20260421_072104 |
|
||||||
|
| **Integridad BD** | ✅ Verificada | DBCC CHECKDB sin errores |
|
||||||
|
| **Registros** | ✅ Validados | 76,652 Afiliados, 205 PlanesEspeciales |
|
||||||
|
|
||||||
|
### Configuración del Contenedor
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d --name dasu-sql-linux-test \
|
||||||
|
-e 'ACCEPT_EULA=1' \
|
||||||
|
-e 'MSSQL_SA_PASSWORD=UTNlarioja00DASU' \
|
||||||
|
-e 'MSSQL_PID=Developer' \
|
||||||
|
-p 1433:1433 \
|
||||||
|
-v sql-data:/var/opt/mssql \
|
||||||
|
mcr.microsoft.com/mssql/server:2022-latest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Conexión desde srv-ns8:**
|
||||||
|
```bash
|
||||||
|
docker run --rm --network host --entrypoint /opt/mssql-tools/bin/sqlcmd \
|
||||||
|
mcr.microsoft.com/mssql-tools:latest \
|
||||||
|
-S 100.111.195.4,1433 -U sa -P 'UTNlarioja00DASU' -d sysdasuten -Q 'SELECT @@VERSION'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Credenciales
|
||||||
|
|
||||||
|
| Usuario | Sistema | Credencial | Ubicación |
|
||||||
|
|---------|---------|------------|-----------|
|
||||||
|
| `sa` | SQL Server | `UTNlarioja00DASU` | Ficha `dasu-sql4.md` + 1Password |
|
||||||
|
| `UTNLR` | dasu-pcv-test (SSH) | `DasuTest2026!` | Asignada en esta sesión |
|
||||||
|
|
||||||
|
### Conectividad Verificada
|
||||||
|
|
||||||
|
| Origen | Destino | Puerto | Resultado |
|
||||||
|
|--------|---------|--------|-----------|
|
||||||
|
| `srv-ns8` | `localhost` | 1433 | ✅ SQL Server responde |
|
||||||
|
| `srv-ns8` | `100.111.195.4` (Tailscale) | 1433 | ✅ SQL Server responde |
|
||||||
|
| `dasu-pcv-test` | `100.111.195.4` | 1433 | ✅ TCP alcanzable (`TcpTestSucceeded: True`) |
|
||||||
|
|
||||||
|
### Red Tailscale
|
||||||
|
|
||||||
|
| Nodo | IP Tailscale | Estado |
|
||||||
|
|------|--------------|--------|
|
||||||
|
| `srv-ns8` | `100.111.195.4` | Activo |
|
||||||
|
| `dasu-pcv-test` | `100.107.22.2` | Idle |
|
||||||
|
| `dasu-sql4` | `100.107.15.82` | Offline (last seen 2h ago) |
|
||||||
|
|
||||||
|
### Kermet.ini Configurado
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[SQLSERVER]
|
||||||
|
DRIVER=SQL Server
|
||||||
|
UID=sa
|
||||||
|
ADDRESS=0
|
||||||
|
LOCKEO=0
|
||||||
|
SERVER=100.111.195.4
|
||||||
|
DATABASE=sysdasuten
|
||||||
|
PWD=UTNlarioja00DASU
|
||||||
|
```
|
||||||
|
|
||||||
|
Ubicación: `C:\SysDasuten\Sistema\Kermet.ini` en `dasu-pcv-test`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hallazgos Clave (Validación Funcional)
|
||||||
|
|
||||||
|
| # | Hallazgo | Estado | Impacto |
|
||||||
|
|---|----------|--------|---------|
|
||||||
|
| 1 | **SQL Server 2022 en Linux funciona con DASUTEN** | ✅ Validado | El ejecutable `DasutenSQL.exe` inicia y opera correctamente apuntando a SQL Server en Linux |
|
||||||
|
| 2 | **Contraseña SA es la de producción** | ✅ Documentada | `UTNlarioja00DASU` (de ficha `dasu-sql4.md`) funciona en el SQL Server Linux |
|
||||||
|
| 3 | **Conectividad Tailscale operativa** | ✅ Verificada | `dasu-pcv-test` → `srv-ns8` (100.111.195.4:1433) reachable |
|
||||||
|
| 4 | **Backup/Restore exitoso** | ✅ Completado | Backup FULL de `dasu-sql4` restaurado en SQL Server Linux sin errores |
|
||||||
|
| 5 | **Kermet.ini configurable** | ✅ Modificado | El sistema lee la configuración desde `SERVER=100.111.195.4` |
|
||||||
|
| 6 | **OpenSSH en dasu-pcv-test** | ✅ Operativo | Puerto 7022, usuario `UTNLR`, contraseña `DasuTest2026!` |
|
||||||
|
|
||||||
|
### Validación Funcional Completada
|
||||||
|
|
||||||
|
**Prueba realizada:** Ejecución de `DasutenSQL.exe` en `dasu-pcv-test` apuntando a SQL Server Linux (`srv-ns8`)
|
||||||
|
|
||||||
|
**Resultado:** ✅ **El sistema DASUTEN arrancó exitosamente**
|
||||||
|
|
||||||
|
Esto confirma que:
|
||||||
|
- El backend de base de datos puede migrarse de Windows Server a Linux
|
||||||
|
- La compatibilidad es total a nivel de protocolo TDS (Tabular Data Stream)
|
||||||
|
- No se requieren modificaciones en el ejecutable VFP (Visual FoxPro)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última actualización:** 2026-04-23
|
||||||
|
**Próxima revisión:** Al completar Fase 5 (validación funcional completa)
|
||||||
@@ -187,6 +187,8 @@ Se adoptó el enfoque **Workgroup sin DC**, reduciendo:
|
|||||||
| [A04.P004](A04.P004_Unificacion-Redes-ISP.md) | Unificación de Redes: Subred ISP | ⏸️ (Pausado — enfoque cambiado) |
|
| [A04.P004](A04.P004_Unificacion-Redes-ISP.md) | Unificación de Redes: Subred ISP | ⏸️ (Pausado — enfoque cambiado) |
|
||||||
| [A04.P005](A04.P005_DASUTEN-sin-DC.md) | DASUTEN sin Domain Controller | ✅ Completado y Validado |
|
| [A04.P005](A04.P005_DASUTEN-sin-DC.md) | DASUTEN sin Domain Controller | ✅ Completado y Validado |
|
||||||
| [A04.P006](A04.P006_Backups-DASUTEN.md) | Backups y Refresco de Base de Datos | ✅ Completado y Validado |
|
| [A04.P006](A04.P006_Backups-DASUTEN.md) | Backups y Refresco de Base de Datos | ✅ Completado y Validado |
|
||||||
|
| [A04.P007](A04.P007_Bugs-Operativos.md) | Registro de Bugs y Problemas Operativos | 🟢 Activo |
|
||||||
|
| [A04.P008](A04.P008_VM-Local-Test.md) | VM Local de Testeo (Bug #001) | 🟢 En Progreso |
|
||||||
|
|
||||||
## 🛸 Sistema de Drones (Operación Autónoma)
|
## 🛸 Sistema de Drones (Operación Autónoma)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# A02.P002 - Gestión y Registro de Aulas Zoom Institucionales
|
||||||
|
|
||||||
|
> **Ámbito:** A02 — dtic-DIGIs
|
||||||
|
> **Código:** A02.P002
|
||||||
|
> **Estado:** 🟢 Activo (operación continua)
|
||||||
|
> **Ámbito padre:** [`A02_dtic-DIGIs.md`](A02_dtic-DIGIs.md)
|
||||||
|
> **Responsable:** DTIC - DIIAA
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Objetivo
|
||||||
|
|
||||||
|
Centralizar el **registro y datos técnicos de todas las Aulas Zoom** gestionadas por la DTIC de la UTN FRLR como soporte del servicio institucional. Cada entrada documenta los parámetros de configuración del aula creada para que el equipo cuente con la información técnica necesaria ante cualquier consulta.
|
||||||
|
|
||||||
|
**Rol de DTIC:** Soporte técnico del servicio Zoom — creación de aulas, configuración, entrega de credenciales al organizador, y asistencia técnica durante el evento si se requiere. La comunicación con participantes y la coordinación pedagógica son responsabilidad del organizador del evento.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Índice de Aulas Zoom
|
||||||
|
|
||||||
|
| Aula-ID | Título | Tipo | Estado | Fecha | Grabación |
|
||||||
|
|:-------:|--------|:----:|:------:|:-----:|:---------:|
|
||||||
|
| [#001](#aula-001--explorando-moodle-herramientas-para-la-interacción-pedagógica) | Explorando Moodle — Capacitación Docentes | Capacitación | ⚙️ Configurada | Mayo 2026 | Por definir |
|
||||||
|
|
||||||
|
**Leyenda de Estados:**
|
||||||
|
- 🔍 Relevamiento → ⚙️ Configurada → ✅ Realizada → 📚 Archivada
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Aulas Activas
|
||||||
|
|
||||||
|
### AULA #001 — "Explorando Moodle: herramientas para la interacción pedagógica"
|
||||||
|
|
||||||
|
| Metadato | Valor |
|
||||||
|
|----------|-------|
|
||||||
|
| **Nombre oficial** | Explorando Moodle: herramientas para la interacción pedagógica |
|
||||||
|
| **Organizadores** | Subsecretaría de Gestión Pedagógica y Acreditación (Ing. Oscar Gallardo) · Área de Desarrollo Curricular y Acreditación (Mgter. María Eugenia Alaniz) |
|
||||||
|
| **Destinatarios** | Docentes de carreras de Ingeniería — UTN FRLR |
|
||||||
|
| **Modalidad** | Mixta: jornadas presenciales ya realizadas + continuación virtual hasta mayo 2026 |
|
||||||
|
| **Fase virtual** | ⚙️ En curso (mayo 2026) |
|
||||||
|
| **Fecha fase virtual** | Por confirmar (dentro de mayo 2026) |
|
||||||
|
| **Horario** | A definir |
|
||||||
|
| **Duración estimada** | Por definir |
|
||||||
|
| **Participantes esperados** | Por definir |
|
||||||
|
| **¿Recurrente?** | Posiblemente sí (continuación de varias jornadas) |
|
||||||
|
| **Host/Anfitrión** | UTNLaRioja [ur14] |
|
||||||
|
| **Link de reunión** | https://utn.zoom.us/j/83194750581 |
|
||||||
|
| **ID de reunión** | 831 9475 0581 |
|
||||||
|
| **Código de acceso** | 990099 |
|
||||||
|
| **Sala de espera** | Por definir |
|
||||||
|
| **Grabación** | Por definir |
|
||||||
|
| **Datos entregados a** | Esp. Ing. Marina FICCARDI · Lic. María Eugenia ALANIZ (vía WhatsApp) |
|
||||||
|
| **Referencia pública** | [Post Instagram UTN La Rioja](https://www.instagram.com/p/DWh4-60jqrL) |
|
||||||
|
| **Estado** | ⚙️ Configurada |
|
||||||
|
|
||||||
|
#### Descripción
|
||||||
|
Capacitación sobre uso de Moodle para docentes de ingeniería UTN FRLR. Modalidad mixta (presencial + virtual). La DTIC provee el aula Zoom para la fase virtual: crea la sala, entrega las credenciales al organizador y brinda soporte técnico durante las sesiones si se requiere.
|
||||||
|
|
||||||
|
#### Acciones Técnicas Realizadas
|
||||||
|
- [x] Aula Zoom creada en cuenta institucional UTNLaRioja [ur14] (2026-04-20).
|
||||||
|
- [x] Credenciales de acceso entregadas vía WhatsApp a Esp. Ing. Marina FICCARDI y Lic. María Eugenia ALANIZ.
|
||||||
|
|
||||||
|
#### Pendiente (Soporte Técnico)
|
||||||
|
- [ ] Confirmar fechas de las sesiones virtuales para disponibilidad de soporte.
|
||||||
|
- [ ] Asistencia técnica durante cada sesión virtual si se solicita.
|
||||||
|
- [ ] Configurar grabación si el organizador lo requiere.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 Histórico de Aulas Concluidas
|
||||||
|
|
||||||
|
*(Sin registros aún)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Procedimiento Estándar para Nueva Aula
|
||||||
|
|
||||||
|
Cuando se reciba una solicitud de aula Zoom:
|
||||||
|
|
||||||
|
1. **Agregar entrada** al Índice con estado `🔍 Relevamiento`.
|
||||||
|
2. **Relevar** datos: fecha, duración, participantes, host, grabación, seguridad.
|
||||||
|
3. **Verificar licencia** disponible en el portal Zoom Admin.
|
||||||
|
4. **Crear y configurar** el aula en el portal institucional.
|
||||||
|
5. **Distribuir** enlace e instrucciones por correo institucional.
|
||||||
|
6. **Brindar soporte** el día del evento.
|
||||||
|
7. **Archivar** grabación si corresponde (ver A02.P001 para pipeline Zoom → YouTube).
|
||||||
|
8. **Cerrar entrada** con estado `📚 Archivada`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 Historial de Versiones del Documento
|
||||||
|
|
||||||
|
| Versión | Fecha | Cambios |
|
||||||
|
|---------|-------|---------:|
|
||||||
|
| 2.0 | 2026-04-20 | Reestructuración como índice general de aulas |
|
||||||
|
| 1.0 | 2026-04-20 | Versión inicial — plan específico de capacitación |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última actualización:** 2026-04-20
|
||||||
|
**Próxima revisión:** Al recibir nueva solicitud de aula o al cerrar AULA #001
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# A02 - Ámbito dtic-DIGIs
|
||||||
|
|
||||||
|
> Manifiesto de Ámbito | Referencia: [`adn/01_ontologia.md`](../../../adn/01_ontologia.md)
|
||||||
|
|
||||||
|
## Identificación
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **Código** | A02 |
|
||||||
|
| **Nombre** | Digitalización, Servicios y Canales Institucionales |
|
||||||
|
| **Estado** | 🟢 Activo |
|
||||||
|
| **Inicio** | 2026-03-31 |
|
||||||
|
| **Fin Estimado** | Por definir (operación continua) |
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
El ámbito **dtic-DIGIs** gestiona los **canales y plataformas digitales de comunicación e interacción institucional** de la UTN FRLR. Abarca:
|
||||||
|
|
||||||
|
1. **Zoom** — Administración de aulas virtuales, reuniones privadas y licencias de la cuenta institucional.
|
||||||
|
2. **YouTube** — Gestión del canal digital oficial de la Facultad: subida, organización y publicación de contenido.
|
||||||
|
3. **Automatización Zoom → YouTube** — Pipeline para procesar grabaciones y publicarlas en el canal.
|
||||||
|
4. **Otras plataformas digitales institucionales** que sean incorporadas en el futuro.
|
||||||
|
|
||||||
|
## Planes Asociados
|
||||||
|
|
||||||
|
| Código | Nombre | Estado |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| [A02.P001](A02.P001_Automatizacion-Zoom-YouTube.md) | Automatización flujo Zoom a YouTube | ⏳ Planificado |
|
||||||
|
| [A02.P002](A02.P002_Gestion-Aulas-Zoom.md) | Gestión y Registro de Aulas Zoom Institucionales | 🟢 Activo |
|
||||||
|
|
||||||
|
## Herramientas
|
||||||
|
|
||||||
|
| Herramienta | Uso |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **Zoom Admin Portal** | Gestión de cuentas, aulas y licencias institucionales |
|
||||||
|
| **YouTube Data API v3** | Subida y gestión de videos del canal institucional |
|
||||||
|
| **Google Sheets API** | Lectura y escritura de planillas de coordinación |
|
||||||
|
| **ADN CLI `digis`** | `./adn/tools/run digis <subcomando>` — herramientas del ámbito |
|
||||||
|
| **Candados** | Gestión segura de tokens API (Zoom, YouTube, Google) |
|
||||||
|
|
||||||
|
## Nodos Involucrados
|
||||||
|
|
||||||
|
| Nodo | Rol |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **srv-ns8** | Nodo de procesamiento y ejecución de pipelines |
|
||||||
|
| *(Zoom Cloud)* | Plataforma de videoconferencia institucional |
|
||||||
|
| *(YouTube)* | Canal digital oficial UTN FRLR |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 Antecedentes
|
||||||
|
|
||||||
|
El plan original `P2603.01_Automatizacion_Zoom.md` fue creado en 2026-03-31 con nomenclatura legacy. Fue migrado y renombrado como `A02.P001` al estandarizarse el ámbito con el sistema ADN.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Armonía Integral
|
||||||
|
|
||||||
|
**Última actualización:** 2026-04-20 (Creación del manifiesto estándar ADN)
|
||||||
|
|
||||||
|
| Documento | Estado | Notas |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `A02_dtic-DIGIs.md` | ✅ Creado | Manifiesto inicial |
|
||||||
|
| `A02.P001_Automatizacion-Zoom-YouTube.md` | ✅ Migrado | Plan legacy P2603.01 |
|
||||||
|
| `A02.P002_Gestion-Aulas-Zoom.md` | ✅ Renombrado | Reestructurado como índice general de aulas |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Referencias
|
||||||
|
|
||||||
|
- **ADN Ontología**: [`adn/01_ontologia.md`](../../../adn/01_ontologia.md)
|
||||||
|
- **Bitácoras**: `./adn/tools/run db evento:listar --ambito dtic-DIGIs`
|
||||||
|
- **Plan legacy (referencia)**: `P2603.01_Automatizacion_Zoom.md`
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,104 @@
|
|||||||
|
# A11.P001 — Protocolo de Operativa Diaria de GEMMA
|
||||||
|
|
||||||
|
> **Estado:** 🟢 En Definición
|
||||||
|
> **Creación:** 2026-04-24
|
||||||
|
> **Referencia:** [`A11_dtic-GEMMA.md`](A11_dtic-GEMMA.md)
|
||||||
|
|
||||||
|
## Propósito
|
||||||
|
|
||||||
|
Este documento define el protocolo estándar que GEMMA sigue para responder a los requerimientos del operador.
|
||||||
|
|
||||||
|
## Niveles de Requerimiento
|
||||||
|
|
||||||
|
GEMMA clasifica los requerimientos en 3 niveles según complejidad:
|
||||||
|
|
||||||
|
### Nivel 1 — Consultas Simples
|
||||||
|
|
||||||
|
**Ejemplos:**
|
||||||
|
- "¿Qué eventos tengo abiertos?"
|
||||||
|
- "¿Cuál es la IP de srv-dasu?"
|
||||||
|
- "Mostrame el último backup de DASUTEN"
|
||||||
|
|
||||||
|
**Protocolo:**
|
||||||
|
1. Ejecutar comando de consulta
|
||||||
|
2. Mostrar resultado formateado
|
||||||
|
3. Ofrecer próximo paso si corresponde
|
||||||
|
|
||||||
|
### Nivel 2 — Tareas Operativas
|
||||||
|
|
||||||
|
**Ejemplos:**
|
||||||
|
- "Registrar que actualicé el SO de srv-ns8"
|
||||||
|
- "Iniciar jornada presencial"
|
||||||
|
- "Verificar estado de los drones"
|
||||||
|
|
||||||
|
**Protocolo:**
|
||||||
|
1. Confirmar entendimiento del requerimiento
|
||||||
|
2. Ejecutar con registro en bitácora
|
||||||
|
3. Informar resultado
|
||||||
|
|
||||||
|
### Nivel 3 — Operaciones Complejas
|
||||||
|
|
||||||
|
**Ejemplos:**
|
||||||
|
- "Hacer backup diferencial de DASUTEN"
|
||||||
|
- "Migrar la base de datos a dasu-sql4"
|
||||||
|
- "Configurar DKIM para el dominio"
|
||||||
|
|
||||||
|
**Protocolo:**
|
||||||
|
1. **Analizar** — Verificar estado actual, antecedentes en bitácora
|
||||||
|
2. **Proponer** — Presentar plan de acción (pasos, tiempo, riesgos)
|
||||||
|
3. **Confirmar** — Esperar OK explícito del operador
|
||||||
|
4. **Ejecutar** — Paso a paso, registrando en bitácora
|
||||||
|
5. **Validar** — Verificar resultado
|
||||||
|
6. **Cerrar** — Actualizar bitácora, informar próximo paso
|
||||||
|
|
||||||
|
## Reglas de Oro de GEMMA
|
||||||
|
|
||||||
|
| # | Regla | Racional |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| 1 | **Bitácora First** — Todo se registra ANTES de ejecutar | Trazabilidad y armonía integral |
|
||||||
|
| 2 | **Confirmar antes de destruir** — Nada destructivo sin OK explícito | Prevención de errores irreversibles |
|
||||||
|
| 3 | **Menos es Más** — Respuestas concisas, sin narrar el proceso | El operador puede leer el diff |
|
||||||
|
| 4 | **Verificar antes de asumir** — Chequear estado actual antes de actuar | Evita trabajar sobre premisas erróneas |
|
||||||
|
| 5 | **Una tarea a la vez** — No solapar operaciones complejas | Reduce superficie de error |
|
||||||
|
| 6 | **Armonía Integral** — Actualizar docs si cambia el modus operandi | Consistencia del ecosistema |
|
||||||
|
|
||||||
|
## Comandos Frecuentes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# === BITÁCORA ===
|
||||||
|
./adn/tools/run db evento:crear --nodo <nodo> --descripcion "..."
|
||||||
|
./adn/tools/run db evento:actualizar <ID> --fin HH:MM
|
||||||
|
./adn/tools/run db evento:listar --nodo <nodo> --detalle
|
||||||
|
./adn/tools/run jornada iniciar presencial 08:00
|
||||||
|
./adn/tools/run jornada cerrar presencial 17:00
|
||||||
|
|
||||||
|
# === SALUD ===
|
||||||
|
./adn/tools/run salud --dashboard
|
||||||
|
./adn/tools/run salud --json
|
||||||
|
|
||||||
|
# === NODOS ===
|
||||||
|
./adn/tools/run nodos listar
|
||||||
|
./adn/tools/run nodos info <nombre>
|
||||||
|
|
||||||
|
# === DRONES ===
|
||||||
|
./adn/tools/run dron lanzar --nota "..." -- <cmd>
|
||||||
|
./adn/tools/run dron flota
|
||||||
|
./adn/tools/run dron limpiar
|
||||||
|
|
||||||
|
# === BACKUPS ===
|
||||||
|
./adn/tools/run bkps run dasuten_diferencial
|
||||||
|
./adn/tools/run bkps run dasuten_full
|
||||||
|
./adn/tools/run bkps run update_linux
|
||||||
|
|
||||||
|
# === SSH SEGURO ===
|
||||||
|
ruby adn/tools/candados/candados.rb run <nodo:usuario> "<cmd>"
|
||||||
|
ruby adn/tools/candados/candados.rb list
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Historial de Revisiones
|
||||||
|
|
||||||
|
| Fecha | Versión | Cambio |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| 2026-04-24 | v1.0 | Creación del protocolo |
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
# A11 - Ámbito dtic-GEMMA
|
||||||
|
|
||||||
|
> Manifiesto de Ámbito | Referencia: [`adn/01_ontologia.md`](../../../adn/01_ontologia.md)
|
||||||
|
|
||||||
|
## Identificación
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **Código** | A11 |
|
||||||
|
| **Nombre** | Agente IA de Gestión Diaria — Asistente Operativo del DTIC |
|
||||||
|
| **Estado** | 🟢 Activo |
|
||||||
|
| **Inicio** | 2026-04-24 |
|
||||||
|
| **Fin Estimado** | Continuo (operación diaria) |
|
||||||
|
|
||||||
|
## Espíritu y Misión
|
||||||
|
|
||||||
|
**dtic-GEMMA** es el **agente IA de confianza** que opera como "número 2" del DTIC en la gestión diaria del ecosistema ADN.
|
||||||
|
|
||||||
|
Su razón de ser es **responder ante los requerimientos del operador humano**, ejecutando tareas que van desde lo más simple hasta lo más complejo, siempre usando las herramientas del ecosistema ADN.
|
||||||
|
|
||||||
|
### Principios de Operación
|
||||||
|
|
||||||
|
| Principio | Descripción |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **Iniciativa Operativa** | GEMMA toma la iniciativa en la ejecución de tareas cotidianas, liberando al operador de la carga cognitiva de los detalles operativos. |
|
||||||
|
| **Progresión Natural** | Comienza con tareas simples (registrar en bitácora, consultar estados) y escala hacia operaciones complejas (orquestar backups, validar integridad). |
|
||||||
|
| **ADN-Nativo** | Todas las operaciones se realizan exclusivamente a través de las herramientas del ecosistema ADN (`./adn/tools/run`, bitácoras, drones, candados). |
|
||||||
|
| **Trazabilidad Total** | Cada acción de GEMMA queda registrada en bitácora con trazabilidad completa (qué, cuándo, por qué, resultado). |
|
||||||
|
| **Confianza Verificada** | GEMMA opera con autonomía pero siempre bajo supervisión humana — cada acción crítica requiere confirmación explícita. |
|
||||||
|
|
||||||
|
## Funciones Principales
|
||||||
|
|
||||||
|
### 1. Gestión de Bitácoras
|
||||||
|
|
||||||
|
| Tarea | Comando ADN |
|
||||||
|
| :--- | :--- |
|
||||||
|
| Registrar evento | `./adn/tools/run db evento:crear --nodo <nodo> --descripcion "..."` |
|
||||||
|
| Actualizar evento | `./adn/tools/run db evento:actualizar <ID> --fin HH:MM` |
|
||||||
|
| Listar eventos | `./adn/tools/run db evento:listar [--ambito <ambito>] [--detalle]` |
|
||||||
|
| Ver jornada | `./adn/tools/run jornada estado` |
|
||||||
|
|
||||||
|
### 2. Ejecución de Tareas Operativas
|
||||||
|
|
||||||
|
| Tarea | Comando ADN |
|
||||||
|
| :--- | :--- |
|
||||||
|
| Health check | `./adn/tools/run salud --dashboard` |
|
||||||
|
| Gestión de nodos | `./adn/tools/run nodos info <nombre>` |
|
||||||
|
| Backups | `./adn/tools/run bkps run <comando>` |
|
||||||
|
| Drones (background) | `./adn/tools/run dron lanzar --nota "..." -- <cmd>` |
|
||||||
|
| SSH seguro | `ruby adn/tools/candados/candados.rb run <nodo:usuario> "<cmd>"` |
|
||||||
|
|
||||||
|
### 3. Orquestación de Procesos Complejos
|
||||||
|
|
||||||
|
GEMMA es capaz de ejecutar flujos completos de principio a fin:
|
||||||
|
|
||||||
|
```
|
||||||
|
Ejemplo: Pipeline de Backup DASUTEN
|
||||||
|
1. Verificar estado anterior → db evento:listar --nodo srvv-fenix
|
||||||
|
2. Iniciar jornada → jornada iniciar presencial 08:00
|
||||||
|
3. Registrar evento → db evento:crear --descripcion "Backup DASUTEN"
|
||||||
|
4. Ejecutar pipeline → bkps run dasuten_diferencial
|
||||||
|
5. Monitorear progreso → dron flota
|
||||||
|
6. Cerrar evento → db evento:actualizar <ID> --fin HH:MM
|
||||||
|
7. Verificar resultado → salud --json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nodos Involucrados
|
||||||
|
|
||||||
|
| Nodo | Rol |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **srv-ns8** | Nodo de operación principal de GEMMA |
|
||||||
|
| **dtic-BITACORAS** | Sistema web de bitácoras (fuente de verdad) |
|
||||||
|
|
||||||
|
## Planes Asociados
|
||||||
|
|
||||||
|
| Código | Nombre | Estado |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| [A11.P001](A11.P001_Operativa-GEMMA.md) | Protocolo de Operativa Diaria de GEMMA | 🟢 En Definición |
|
||||||
|
|
||||||
|
## Flujo de Trabajo Típico con GEMMA
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Operador plantea requerimiento: "Necesito hacer backup de DASUTEN"
|
||||||
|
2. GEMMA:
|
||||||
|
- Verifica estado actual (bitácora, nodos, últimos backups)
|
||||||
|
- Propone plan de acción (pasos, tiempo estimado, riesgos)
|
||||||
|
- Espera confirmación explícita del operador
|
||||||
|
- Ejecuta paso a paso, registrando cada acción en bitácora
|
||||||
|
- Informa progreso y resultado final
|
||||||
|
- Sugiere próximos pasos si corresponde
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relación Operador ↔ GEMMA
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ OPERADOR (Humano) │
|
||||||
|
│ - Define el QUÉ y el PORQUÉ │
|
||||||
|
│ - Confirma acciones críticas │
|
||||||
|
│ - Recibe resultados y toma decisiones estratégicas │
|
||||||
|
└────────────────────┬────────────────────────────────────────┘
|
||||||
|
│ "Necesito X"
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ GEMMA (IA - Número 2) │
|
||||||
|
│ - Propone el CÓMO │
|
||||||
|
│ - Ejecuta con trazabilidad │
|
||||||
|
│ - Informa desviaciones y pide confirmación cuando corresponde │
|
||||||
|
│ - Registra todo en bitácora │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Armonía Integral
|
||||||
|
|
||||||
|
**Última actualización:** 2026-04-24 (Creación del ámbito)
|
||||||
|
|
||||||
|
| Documento | Estado | Notas |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `docs/contexto/IA.md` | ⏳ Pendiente | Agregar referencia a A11 |
|
||||||
|
| `A11_dtic-GEMMA.md` | ✅ Actualizado | Manifiesto — versión fundacional |
|
||||||
|
| `A11.P001_Operativa-GEMMA.md` | ⏳ Pendiente | Protocolo de operativa diaria |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Referencias
|
||||||
|
|
||||||
|
- **ADN Ontología**: [`adn/01_ontologia.md`](../../../adn/01_ontologia.md)
|
||||||
|
- **ADN Directivas IA**: [`adn/05_ia.md`](../../../adn/05_ia.md)
|
||||||
|
- **Bitácoras**: `./adn/tools/run db evento:listar --ambito dtic-GEMMA`
|
||||||
|
- **Contexto IA**: [`docs/contexto/IA.md`](../../../contexto/IA.md)
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# A10.P001 - Gestión y Registro de Incidencias Microsoft 365
|
||||||
|
|
||||||
|
> **Ámbito:** A10 — dtic-IDIS
|
||||||
|
> **Código:** A10.P001
|
||||||
|
> **Estado:** 🟢 Activo (operación continua)
|
||||||
|
> **Ámbito padre:** [`A10_dtic-IDIS.md`](A10_dtic-IDIS.md)
|
||||||
|
> **Responsable:** DTIC - DIIAA
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Objetivo
|
||||||
|
|
||||||
|
Centralizar el **registro técnico de incidencias y gestiones** relacionadas con el servicio Microsoft 365 institucional (cuentas, licencias, acceso). Cada entrada documenta el problema, su contexto y el estado de resolución para que el equipo cuente con la información necesaria ante cualquier consulta.
|
||||||
|
|
||||||
|
**Rol de DTIC:** Soporte técnico del servicio M365 — diagnóstico de incidencias, coordinación con el tenant administrador (Rectorado/CEDI UTN), y asistencia a usuarios si se requiere. Las decisiones de licenciamiento y política son responsabilidad del nivel central de UTN.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Índice de Incidencias
|
||||||
|
|
||||||
|
| Inc-ID | Título | Tipo | Estado | Fecha | Alcance |
|
||||||
|
|:------:|--------|:----:|:------:|:-----:|:-------:|
|
||||||
|
| [#001](#incidencia-001--aviso-su-licencia-educativa-se-desactivará-pronto) | Aviso "licencia se desactivará pronto" | Licenciamiento | 🔍 En consulta | 2026-04-20 | Docentes y alumnos |
|
||||||
|
|
||||||
|
**Leyenda de Estados:**
|
||||||
|
- 🆕 Reportada → 🔍 En consulta → 🛠️ En solución → ✅ Resuelta → 📚 Archivada
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Incidencias Activas
|
||||||
|
|
||||||
|
### INCIDENCIA #001 — Aviso "Su licencia educativa se desactivará pronto"
|
||||||
|
|
||||||
|
| Metadato | Valor |
|
||||||
|
|----------|-------|
|
||||||
|
| **Mensaje** | "Su licencia educativa se desactivará pronto" |
|
||||||
|
| **Canal** | Pantalla al iniciar sesión en Microsoft 365 |
|
||||||
|
| **Cuentas afectadas** | Docentes y alumnos — múltiples casos confirmados (no masivo) |
|
||||||
|
| **Causa raiz** | Desactivación global de licencias A1 Plus por UTN central |
|
||||||
|
| **Impacto** | Mensaje de advertencia al iniciar sesión — acceso aún funcional |
|
||||||
|
| **Prioridad** | Alta |
|
||||||
|
| **Fecha reporte** | 2026-04-20 |
|
||||||
|
| **Acción en curso** | Consulta al grupo TIC UTN completada — analizando approaches de INSPT/FRSN |
|
||||||
|
| **Estado** | 🔍 En análisis (post-consulta TIC) |
|
||||||
|
|
||||||
|
#### Evidencias
|
||||||
|
|
||||||
|
| Archivo | Descripción |
|
||||||
|
|---------|-------------|
|
||||||
|
| [`info/A10.P001_01.jpeg`](info/A10.P001_01.jpeg) | Aviso Microsoft "Su licencia educativa se desactivará pronto" — usuario reportante (vía Roberto CANIZA) |
|
||||||
|
| [`info/A10.P001_02.jpeg`](info/A10.P001_02.jpeg) | Conversación WhatsApp — grupo UTN-TIC-Repres: respuestas de INSPT, FRSN y FRA |
|
||||||
|
| [`info/A10.P001_03.jpeg`](info/A10.P001_03.jpeg) | Continuación del grupo TIC: FRRO confirma solución reactivando A1 Plus; INSPT muestra cuota de licencias en portal admin |
|
||||||
|
|
||||||
|
|
||||||
|
Al degradar cuentas de **A1 Plus** a **A1**, Microsoft emite el aviso de desactivación como notificación de que servicios del plan anterior (OneDrive ampliado, apps de escritorio, etc.) ya no estarán disponibles. El aviso aparece al iniciar sesión con la cuenta institucional.
|
||||||
|
|
||||||
|
El workaround (asignar ambas licencias) consume el doble de cupos — no necesariamente sostenible si la cantidad de licencias A1 Plus es limitada.
|
||||||
|
|
||||||
|
#### Hallazgos del Grupo TIC UTN (2026-04-20 19:15 hs)
|
||||||
|
|
||||||
|
Consulta realizada en el grupo **UTN-TIC-Repres** con representantes de todas las facultades.
|
||||||
|
|
||||||
|
| Facultad | Referente | Situación | Solución aplicada |
|
||||||
|
|----------|-----------|-----------|-------------------|
|
||||||
|
| **INSPT** | Christian CELSO | ✅ Resuelto | Script: quitar A1 Plus, asignar A1. Portal admin muestra cuota masiva (1.000.000 de cuota, ~55.000 activas) — no es problema de escasez. |
|
||||||
|
| **FRSN** | Georgina RODRIGUEZ | ✅ Resuelto automáticamente | En San Nicolás ocurrió el 1/enero. Cambiaron solos de A1 Plus → A1. |
|
||||||
|
| **FRRO** | Hernán GUERAGLIA | ✅ Resuelto | Docente tenía A1 activa + A1 Plus vencida asignada. Al **reactivar (tildar) la A1 Plus** el aviso desapareció. Confuso porque contradice la directiva de migrar a A1. |
|
||||||
|
| **FRA** | Adrián | 🔍 Sin resolver | Mismo problema, buscando solución antes del vencimiento. |
|
||||||
|
| **FRLR** | Ricardo (nosotros) | 🔍 En análisis | Usuarios ya en A1 pero aviso aparece en algunos. Pendiente verificar en tenant. |
|
||||||
|
|
||||||
|
**Punto crítico identificado:** El aviso NO es por falta de cuota de licencias (INSPT confirma cuota de 1M). La hipótesis más probable: cuentas con **A1 Plus vencida pero aún asignada** generan el aviso. FRRO confirmó que reactivar la A1 Plus (tildándola) resuelve el aviso — aunque esto es contradictorio con la directiva de bajar al plan A1.
|
||||||
|
|
||||||
|
#### Acciones Realizadas
|
||||||
|
- [x] Confirmado: mensaje legítimo de Microsoft (no phishing). *(2026-04-20)*
|
||||||
|
- [x] Causa global identificada: desactivación masiva de licencias A1 Plus por UTN central. *(2026-04-20)*
|
||||||
|
- [x] Workaround documentado: asignar A1 + A1 Plus. *(2026-04-20)*
|
||||||
|
- [x] Consulta realizada al grupo Representantes TIC UTN — respuestas de INSPT, FRSN, FRA y FRRO. *(2026-04-20)*
|
||||||
|
- [x] Nuevo hallazgo (FRRO): A1 Plus **vencida pero asignada** genera el aviso. Reactivarla lo elimina. *(2026-04-20)*
|
||||||
|
- [x] Cuota confirmada por INSPT: no hay escasez de licencias (1.000.000 disponibles, ~55.000 activas). *(2026-04-20)*
|
||||||
|
|
||||||
|
#### Pendiente
|
||||||
|
- [ ] Verificar en tenant admin (`admin.microsoft.com`) si las cuentas afectadas aún tienen A1 Plus **vencida pero asignada** — eso explicaría el aviso selectivo.
|
||||||
|
- [ ] Evaluar si aplicar el approach de FRRO: reactivar A1 Plus en cuentas afectadas (aunque contradiga la directiva de bajar a A1).
|
||||||
|
- [ ] Consultar con Rectorado/CEDI UTN la directiva definitiva: ¿limpiar A1 Plus (INSPT) o mantenerla activa (FRRO)?
|
||||||
|
- [ ] Monitorear si resuelve automáticamente como en FRSN o requiere intervención.
|
||||||
|
- [ ] Determinar la fecha límite antes de que el aviso se convierta en pérdida de acceso.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 Histórico de Incidencias Resueltas
|
||||||
|
|
||||||
|
*(Sin registros aún)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Procedimiento Estándar ante Nueva Incidencia M365
|
||||||
|
|
||||||
|
1. **Registrar entrada** en el Índice con estado `🆕 Reportada`.
|
||||||
|
2. **Verificar** si el mensaje/problema es legítimo o posible phishing.
|
||||||
|
3. **Documentar** canal, usuarios afectados y síntomas exactos.
|
||||||
|
4. **Consultar** grupo TIC UTN y portal admin M365 si corresponde.
|
||||||
|
5. **Aplicar** workaround si existe mientras se busca solución definitiva.
|
||||||
|
6. **Escalar** a Rectorado/CEDI UTN si requiere decisión de política de licencias.
|
||||||
|
7. **Cerrar** con estado `📚 Archivada` una vez validada la solución.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 Historial de Versiones del Documento
|
||||||
|
|
||||||
|
| Versión | Fecha | Cambios |
|
||||||
|
|---------|-------|---------|
|
||||||
|
| 4.0 | 2026-04-20 | Hallazgos captura 03: FRRO resuelve reactivando A1 Plus; INSPT confirma cuota masiva (1M) |
|
||||||
|
| 3.0 | 2026-04-20 | Hallazgos grupo TIC UTN incorporados: INSPT, FRSN, FRA |
|
||||||
|
| 2.0 | 2026-04-20 | Reestructurado como registro general de incidencias M365 |
|
||||||
|
| 1.0 | 2026-04-20 | Versión inicial — análisis específico del aviso de licencias |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última actualización:** 2026-04-20
|
||||||
|
**Próxima revisión:** Al acceder al tenant admin o al recibir nueva información del grupo TIC UTN
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# A10 - Ámbito dtic-IDIS
|
||||||
|
|
||||||
|
> Manifiesto de Ámbito | Referencia: [`adn/01_ontologia.md`](../../../adn/01_ontologia.md)
|
||||||
|
|
||||||
|
## Identificación
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **Código** | A10 |
|
||||||
|
| **Nombre** | Identidades Digitales Institucionales y Servicios |
|
||||||
|
| **Estado** | 🟢 Activo |
|
||||||
|
| **Inicio** | 2026-04-20 |
|
||||||
|
| **Fin Estimado** | Por definir (operación continua) |
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
El ámbito **dtic-IDIS** centraliza la gestión de las **identidades digitales y cuentas de servicios institucionales** de la DIIAA / UTN FRLR.
|
||||||
|
|
||||||
|
Abarca todas las cuentas y licencias provistas por la institución a docentes, alumnos y personal, incluyendo (pero no limitándose a):
|
||||||
|
|
||||||
|
- **Correo electrónico institucional** — cuentas `@frlr.utn.edu.ar` y derivadas.
|
||||||
|
- **Microsoft 365** (Office, Teams, OneDrive, Exchange Online).
|
||||||
|
- **AutoCAD** y otras licencias de software académico/profesional.
|
||||||
|
- **Otros servicios vinculados** a la identidad institucional (VPN, plataformas educativas, etc.).
|
||||||
|
|
||||||
|
## Problemáticas Conocidas
|
||||||
|
|
||||||
|
| Problema | Estado | Plan |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| Aviso "Su licencia educativa se desactivará pronto" — cambio A1 Plus → A1 | 🔍 En consulta grupo TIC UTN | [A10.P001 Inc.#001](A10.P001_Office365.md#incidencia-001) |
|
||||||
|
|
||||||
|
## Planes Asociados
|
||||||
|
|
||||||
|
| Código | Nombre | Estado |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| [A10.P001](A10.P001_Office365.md) | Gestión y Registro de Incidencias Microsoft 365 | 🟢 Activo |
|
||||||
|
|
||||||
|
## Herramientas
|
||||||
|
|
||||||
|
| Herramienta | Uso |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **Portal Microsoft 365 Admin** | Gestión de licencias, usuarios y suscripciones |
|
||||||
|
| **Portal Azure AD / Entra ID** | Identidades, grupos y asignaciones de licencias |
|
||||||
|
| **PowerShell (MSOnline / Az)** | Administración masiva de cuentas y licencias |
|
||||||
|
| **Bitácora ADN** | Registro de intervenciones y eventos |
|
||||||
|
|
||||||
|
## Nodos Involucrados
|
||||||
|
|
||||||
|
| Nodo | Rol |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **srv-ns8** | Nodo de gestión central y operaciones DTIC |
|
||||||
|
| *(usuarios finales)* | Docentes, alumnos y personal con cuenta institucional |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Armonía Integral
|
||||||
|
|
||||||
|
**Última actualización:** 2026-04-20 (Creación del ámbito)
|
||||||
|
|
||||||
|
| Documento | Estado | Notas |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `docs/contexto/IA.md` | ✅ Actualizado | A10 registrado en tabla de ámbitos de referencia |
|
||||||
|
| `A10_dtic-IDIS.md` | ✅ Actualizado | Manifiesto — referencias al día |
|
||||||
|
| `A10.P001_Office365.md` | ✅ Actualizado | Registro general de incidencias M365 (v3.0) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Referencias
|
||||||
|
|
||||||
|
- **ADN Ontología**: [`adn/01_ontologia.md`](../../../adn/01_ontologia.md)
|
||||||
|
- **Bitácoras**: `./adn/tools/run db evento:listar --ambito dtic-IDIS`
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
+5
-2
@@ -126,6 +126,7 @@ ruby adn/tools/candados/candados.rb run srv-dasu:root "qm list"
|
|||||||
❌ No verificar candados antes de SSH
|
❌ No verificar candados antes de SSH
|
||||||
❌ **Mostrar contraseñas o secretos en texto plano** (usar siempre bóveda)
|
❌ **Mostrar contraseñas o secretos en texto plano** (usar siempre bóveda)
|
||||||
❌ **Olvidar especificar --modo R** para eventos ejecutados remotamente (IA operando a distancia)
|
❌ **Olvidar especificar --modo R** para eventos ejecutados remotamente (IA operando a distancia)
|
||||||
|
❌ **Hacer commits intermedios durante la jornada** — el commit y push va al final de la sesión, salvo pedido explícito del usuario
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -229,16 +230,18 @@ Para información completa sobre todas las hebras del ADN, consultar `adn/00_ind
|
|||||||
| `adn/03_seguridad.md` | Protocolos de seguridad y candados 🔐 |
|
| `adn/03_seguridad.md` | Protocolos de seguridad y candados 🔐 |
|
||||||
| `adn/01_ontologia.md` | Nodos, ámbitos y topología |
|
| `adn/01_ontologia.md` | Nodos, ámbitos y topología |
|
||||||
| `docs/ambito/dtic-ADN/A01_dtic-ADN.md` | Ámbito A01: Ecosistema core — `./adn/tools/run` |
|
| `docs/ambito/dtic-ADN/A01_dtic-ADN.md` | Ámbito A01: Ecosistema core — `./adn/tools/run` |
|
||||||
| `docs/ambito/dtic-DIGIs/` | Ámbito A02: Digitalización y Gestión Documental |
|
| `docs/ambito/dtic-DIGIs/` | Ámbito A02: Zoom, YouTube, canales digitales institucionales |
|
||||||
| `docs/ambito/dtic-BKPs/` | Ámbito A03: Backups — `./adn/tools/run bkps` |
|
| `docs/ambito/dtic-BKPs/` | Ámbito A03: Backups — `./adn/tools/run bkps` |
|
||||||
| `docs/ambito/dtic-DASUTEN/` | Ámbito A04: Infraestructura DASUTEN — `./adn/tools/run dasuten` |
|
| `docs/ambito/dtic-DASUTEN/` | Ámbito A04: Infraestructura DASUTEN — `./adn/tools/run dasuten` |
|
||||||
| `docs/ambito/dtic-XenServer/` | Ámbito A06: Hipervisor Citrix XenServer 7.0 |
|
| `docs/ambito/dtic-XenServer/` | Ámbito A06: Hipervisor Citrix XenServer 7.0 |
|
||||||
| `docs/ambito/dtic-DNS/` | Ámbito A07: Administración DNS — `./adn/tools/run dns` |
|
| `docs/ambito/dtic-DNS/` | Ámbito A07: Administración DNS — `./adn/tools/run dns` |
|
||||||
| `docs/ambito/dtic-NOTAS/` | Ámbito A08: Sistema de Notas e Informes — Web de plantillas |
|
| `docs/ambito/dtic-NOTAS/` | Ámbito A08: Sistema de Notas e Informes — Web de plantillas |
|
||||||
| `docs/ambito/dtic-DIIAA/` | Ámbito A09: IA y Automatizaciones — `./adn/tools/run dron` |
|
| `docs/ambito/dtic-DIIAA/` | Ámbito A09: IA y Automatizaciones — `./adn/tools/run dron` |
|
||||||
|
| `docs/ambito/dtic-IDIS/` | Ámbito A10: Identidades Digitales y Servicios — M365, correo, licencias |
|
||||||
|
| `docs/ambito/dtic-GEMMA/` | **Ámbito A11: Agente IA de Gestión Diaria — Asistente Operativo** |
|
||||||
| `adn/tools/cli/dron.rb` | Drones: tareas background con auto-bitácora — `./adn/tools/run dron` |
|
| `adn/tools/cli/dron.rb` | Drones: tareas background con auto-bitácora — `./adn/tools/run dron` |
|
||||||
|
|
||||||
**Última actualización:** 2026-04-15 (DASUTEN: Pipeline backups validado + migración TZ + validación usuaria)
|
**Última actualización:** 2026-04-24 (A11 dtic-GEMMA creado: agente IA "número 2" para operativa diaria)
|
||||||
**Próxima revisión:** Al inicio de cada sesión
|
**Próxima revisión:** Al inicio de cada sesión
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# UNIVERSIDAD TECNOLÓGICA NACIONAL
|
||||||
|
## Facultad Regional La Rioja
|
||||||
|
|
||||||
|
# INFORME DE MIGRACIÓN
|
||||||
|
## Sistema DASUTEN a Infraestructura Autónoma
|
||||||
|
|
||||||
|
**Proyecto:** A04.P005 | **Área:** DTIC | **Fecha:** 16 de abril de 2026 | **Versión:** 1.1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Documentos incluidos
|
||||||
|
|
||||||
|
| N° | Documento | Páginas |
|
||||||
|
|:---:|:---|:---:|
|
||||||
|
| 01 | Informe Ejecutivo — Migración del Sistema DASUTEN a Infraestructura Autónoma | 4 |
|
||||||
|
| 02 | Informe Técnico — Migración de infraestructura DASUTEN (Modo Workgroup sin Domain Controller) | 8 |
|
||||||
|
|
||||||
|
### Estado: ✅ COMPLETADO Y VALIDADO
|
||||||
|
|
||||||
|
El sistema DASUTEN se encuentra 100% operativo en la nueva infraestructura desde el 15 de abril de 2026.
|
||||||
|
|
||||||
|
### Resumen de logros
|
||||||
|
|
||||||
|
| Logro | Impacto |
|
||||||
|
|-------|---------|
|
||||||
|
| Simplificación arquitectónica | De 4 componentes a 2 (−50%) |
|
||||||
|
| Trabajo directo | Sin dependencia de escritorio remoto |
|
||||||
|
| Autonomía operativa | Infraestructura independiente de la Facultad |
|
||||||
|
| Backups automatizados | Pipeline de drones atómicos implementado |
|
||||||
|
| Sin inversión de hardware | Equipos existentes reutilizados |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*La Rioja, 16 de abril de 2026*
|
||||||
Binary file not shown.
@@ -1,20 +1,20 @@
|
|||||||
# INFORME DE AVANCE
|
# INFORME EJECUTIVO
|
||||||
## Migración del Sistema DASUTEN a Infraestructura Autónoma
|
## Migración del Sistema DASUTEN a Infraestructura Autónoma
|
||||||
|
|
||||||
> **Área responsable:** Departamento de Tecnología de la Información y Comunicaciones (DTIC)
|
> **Área responsable:** Departamento de Tecnología de la Información y Comunicaciones (DTIC)
|
||||||
> **Facultad Regional La Rioja — Universidad Tecnológica Nacional**
|
> **Facultad Regional La Rioja — Universidad Tecnológica Nacional**
|
||||||
> **Fecha:** 13 de abril de 2026
|
> **Fecha:** 16 de abril de 2026
|
||||||
> **Elaborado por:** Ricardo Monla — DTIC, UTN-FRLR
|
> **Elaborado por:** Ricardo Monla — DTIC, UTN-FRLR
|
||||||
> **Destinatarios:** [Completar]
|
> **Destinatarios:** Autoridades DASUTEN, Autoridades DTIC, Autoridades FR La Rioja
|
||||||
> **Versión:** 0.3 (Borrador)
|
> **Versión:** 1.1 — REVISADO
|
||||||
>
|
>
|
||||||
> *Este documento es de carácter ejecutivo y narrativo. El detalle técnico completo se encuentra en el documento complementario [02_informe_tecnico_DASUTEN.md](02_informe_tecnico_DASUTEN.md).*
|
> *Este documento es de carácter ejecutivo y narrativo. Se adjunta también el detalle técnico completo en el documento [02_informe_tecnico_DASUTEN.md](02_informe_tecnico_DASUTEN.md).*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Contexto y Punto de Partida
|
## 1. Contexto y Punto de Partida
|
||||||
|
|
||||||
El sistema de gestión administrativa de **DASUTEN** (Departamento de Acción Social Universitaria Tecnológica Nacional) en la Facultad Regional La Rioja funcionaba desde hace años sobre la infraestructura informática compartida de la Facultad: servidores, red interna y servicios de dominio que atienden a toda la institución.
|
El sistema de gestión administrativa de **D.A.S.U.Te.N** (Dirección de Acción Social de la Universidad Tecnológica Nacional) en la Facultad Regional La Rioja funcionaba desde hace años sobre la infraestructura informática compartida de la Facultad: servidores, red interna y servicios de dominio que atienden a toda la institución.
|
||||||
|
|
||||||
Si bien esta configuración fue funcional durante un tiempo, con el correr de los años fue manifestando limitaciones concretas:
|
Si bien esta configuración fue funcional durante un tiempo, con el correr de los años fue manifestando limitaciones concretas:
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Si bien esta configuración fue funcional durante un tiempo, con el correr de lo
|
|||||||
- **Dificultad de mantenimiento:** Los respaldos de datos y las actualizaciones periódicas que envía Rectorado requerían coordinación con la infraestructura general de la Facultad, generando demoras y dependencias cruzadas.
|
- **Dificultad de mantenimiento:** Los respaldos de datos y las actualizaciones periódicas que envía Rectorado requerían coordinación con la infraestructura general de la Facultad, generando demoras y dependencias cruzadas.
|
||||||
- **Limitación de autonomía:** DASUTEN, si bien opera dentro de las instalaciones de la Facultad, responde funcionalmente a **Rectorado (Buenos Aires)**. La dependencia de la infraestructura local dificultaba una eventual gestión directa desde la sede central.
|
- **Limitación de autonomía:** DASUTEN, si bien opera dentro de las instalaciones de la Facultad, responde funcionalmente a **Rectorado (Buenos Aires)**. La dependencia de la infraestructura local dificultaba una eventual gestión directa desde la sede central.
|
||||||
|
|
||||||
A esta situación se sumó que, al reubicarse la oficina en un espacio propio alejado de los servidores, las fallas de conectividad se agravaron por la cantidad de puntos intermedios en la red. Para dar continuidad al servicio, se gestionó una línea de internet propia y se implementó un esquema de trabajo indirecto donde las usuarias — Andrea Almirón y Romina Molina — accedían mediante escritorio remoto a una PC virtual para poder operar el sistema. Esto implicaba mantener cuatro componentes simultáneamente (servidor de dominio, servidor de base de datos, PC virtual y PC física), multiplicando los puntos de falla.
|
A esta situación se sumó que, al reubicarse la oficina en un espacio propio alejado de los servidores, las fallas de conectividad se agravaron por la cantidad de puntos intermedios en la red. Para dar continuidad al servicio, se gestionó una línea de internet propia y se implementó un esquema de trabajo indirecto donde las usuarias — **Andrea Almirón (aalmiron)** y **Romina Molina (rmolina)** — accedían mediante escritorio remoto a una PC virtual para poder operar el sistema. Esto implicaba mantener cuatro componentes simultáneamente (servidor de dominio, servidor de base de datos, PC virtual y PC física), multiplicando los puntos de falla.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ Las usuarias ahora operan el sistema **directamente desde la PC de la oficina**.
|
|||||||
|
|
||||||
Si bien el proyecto llevó más tiempo del inicialmente estimado, esto responde a una decisión deliberada: se planificó una **etapa de testeo exhaustivo** antes de llevar el sistema a producción. Cada paso fue validado primero en un entorno de pruebas antes de aplicarse al entorno real, minimizando riesgos para la operación de la oficina.
|
Si bien el proyecto llevó más tiempo del inicialmente estimado, esto responde a una decisión deliberada: se planificó una **etapa de testeo exhaustivo** antes de llevar el sistema a producción. Cada paso fue validado primero en un entorno de pruebas antes de aplicarse al entorno real, minimizando riesgos para la operación de la oficina.
|
||||||
|
|
||||||
**A la fecha, el sistema se encuentra a un paso del 100%**, restando únicamente un ajuste de compatibilidad en la impresión de documentos, para el cual ya se dispone de una solución provisoria.
|
**El sistema se encuentra 100% operativo y validado.** Todos los ajustes de compatibilidad (incluyendo impresión de documentos) fueron resueltos exitosamente.
|
||||||
|
|
||||||
### 3.3 Valor agregado: herramientas y metodología
|
### 3.3 Valor agregado: herramientas y metodología
|
||||||
|
|
||||||
@@ -68,13 +68,28 @@ Más allá del objetivo central, el trabajo realizado **generó herramientas y m
|
|||||||
- **Dashboard web de seguimiento** del proyecto, con visibilidad en tiempo real del progreso.
|
- **Dashboard web de seguimiento** del proyecto, con visibilidad en tiempo real del progreso.
|
||||||
- **Sistema de bitácoras** con registro detallado y auditable de cada intervención técnica.
|
- **Sistema de bitácoras** con registro detallado y auditable de cada intervención técnica.
|
||||||
- **Herramientas de automatización** (Ecosistema ADN) para respaldos, gestión remota y tareas repetitivas.
|
- **Herramientas de automatización** (Ecosistema ADN) para respaldos, gestión remota y tareas repetitivas.
|
||||||
- **Metodología de trabajo con IA generativa**, altamente productiva y replicable en futuros proyectos.
|
|
||||||
- **Pipeline de Drones Atómicos** para backups: 6 drones independientes que se encadenan secuencialmente, comunicándose vía JSON.
|
- **Pipeline de Drones Atómicos** para backups: 6 drones independientes que se encadenan secuencialmente, comunicándose vía JSON.
|
||||||
- **Módulo común DasuExecutor** que centraliza la ejecución remota de PowerShell, eliminando código repetido (-52% líneas).
|
- **Módulo común DasuExecutor** que centraliza la ejecución remota de PowerShell, eliminando código repetido (-52% líneas).
|
||||||
|
|
||||||
|
### 3.4 Metodología de trabajo con IA generativa
|
||||||
|
|
||||||
|
Un factor diferencial en este proyecto fue la **incorporación de IA generativa como herramienta de análisis y diseño**, lo que permitió:
|
||||||
|
|
||||||
|
- **Ingeniería inversa acelerada:** La IA asistió en el análisis del sistema DASUTEN (desarrollado en Visual FoxPro, tecnología legacy sin documentación oficial disponible), identificando que la dependencia del Active Directory era **configuracional y no de código** — un hallazgo que permitió eliminar el Domain Controller.
|
||||||
|
|
||||||
|
- **Diseño de arquitectura simplificada:** El enfoque de "red plana" sin subnetting privado fue validado mediante análisis asistido por IA, que identificó los puntos de falla innecesarios en la arquitectura original.
|
||||||
|
|
||||||
|
- **Automatización de código repetitivo:** La IA generativa permitió diseñar el patrón de "Drones Atómicos" y el módulo común `DasuExecutor`, reduciendo ~52% las líneas de código necesarias.
|
||||||
|
|
||||||
|
- **Resolución de problemas complejos:** El problema de impresión (plantillas hardcodeadas en `C:\Sistema\`) se resolvió mediante análisis asistido que identificó el symlink como solución sin necesidad de recompilar el ejecutable VFP.
|
||||||
|
|
||||||
|
- **Velocidad de implementación:** Lo que tradicionalmente hubiera requerido semanas de prueba y error se completó en días, gracias a que la IA permitió **simular mentalmente** múltiples enfoques antes de implementarlos.
|
||||||
|
|
||||||
|
**En síntesis:** La IA actuó como un **multiplicador cognitivo** que permitió estudiar, diseñar e implementar más rápido y mejor, con mayor certeza en las decisiones técnicas.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Estado Actual
|
## 5. Estado Actual
|
||||||
|
|
||||||
| Aspecto | Estado |
|
| Aspecto | Estado |
|
||||||
|:---|:---:|
|
|:---|:---:|
|
||||||
@@ -84,27 +99,28 @@ Más allá del objetivo central, el trabajo realizado **generó herramientas y m
|
|||||||
| Administración remota por DTIC | ✅ |
|
| Administración remota por DTIC | ✅ |
|
||||||
| Respaldos automatizados | ✅ Pipeline implementado |
|
| Respaldos automatizados | ✅ Pipeline implementado |
|
||||||
| Impresión de documentos | ✅ Resuelto (symlink) |
|
| Impresión de documentos | ✅ Resuelto (symlink) |
|
||||||
|
| Zona horaria del servidor | ✅ UTC-3 Argentina (configurada 15/04/2026) |
|
||||||
|
| Validación del usuario | ✅ Confirmada (Andrea Almirón, 15/04/2026 17:00) |
|
||||||
|
|
||||||
El sistema se encuentra **100% operativo**. La usuaria accede directamente al sistema desde la PC de la oficina. DTIC administra y respalda de forma remota sin interferir con la operación diaria.
|
El sistema se encuentra **100% operativo y validado**. La usuaria accede directamente al sistema desde la PC de la oficina. DTIC administra y respalda de forma remota sin interferir con la operación diaria.
|
||||||
|
|
||||||
### 4.1 Pipeline de Backups Implementado
|
### 5.1 Soluciones técnicas clave
|
||||||
|
|
||||||
Se implementó un sistema automatizado de backups con las siguientes características:
|
- **Impresión resuelta:** Mediante un **symlink** que redirige `C:\Sistema` → `C:\SysDasuten\Sistema`, permitiendo que el sistema encuentre las plantillas Word/Excel sin modificar el ejecutable compilado.
|
||||||
|
|
||||||
- **Backup completo semanal** (~1.3 GB, 35-40 minutos): `./adn/tools/run bkps run dasuten_full`
|
- **Backups automatizados:** Pipeline de drones atómicos con backup completo semanal (~1.3 GB, 35-40 min) y diferencial diario (~0.4-10 MB, 5-10 min). Ver detalle técnico en [02_informe_tecnico_DASUTEN.md](02_informe_tecnico_DASUTEN.md#G-pipeline-de-backups).
|
||||||
- **Backup diferencial diario** (~0.4-10 MB, 5-10 minutos): `./adn/tools/run bkps run dasuten_diferencial`
|
|
||||||
- **Arquitectura de Drones Atómicos**: 6 drones independientes (exportar → transferir → upload → download → restaurar → verificar)
|
|
||||||
- **Comunicación vía JSON**: Cada dron escribe su output en `/tmp/dron_*_output.json` para el siguiente paso
|
|
||||||
- **Google Drive como intermediario**: Elimina dependencia de Tailscale inestable
|
|
||||||
- **Integrado en bkps.rb**: Ejecutable desde el ecosistema ADN de herramientas
|
|
||||||
|
|
||||||
### 4.2 Solución de Impresión
|
- **Zona horaria corregida:** El problema de datos "desactualizados" se debía a la zona horaria incorrecta (UTC+2 en lugar de UTC-3). Los backups se realizaban correctamente, pero las fechas se interpretaban con ~5 horas de desplazamiento.
|
||||||
|
|
||||||
El problema de impresión se resolvió mediante un **symlink** que redirige `C:\Sistema` → `C:\SysDasuten\Sistema`, permitiendo que el sistema encuentre las plantillas Word/Excel sin modificar el ejecutable compilado.
|
### 5.2 Validación Final con la Usuaria (2026-04-15)
|
||||||
|
|
||||||
|
La usuaria **Andrea Almirón (aalmiron)** confirmó que el sistema desktop muestra correctamente los movimientos del día en curso tras la corrección de la zona horaria en `dasu-sql4` (UTC-3 Argentina).
|
||||||
|
|
||||||
|
**Nota:** Ambas usuarias (Andrea Almirón y Romina Molina) operan el sistema DASUTEN. La validación del 15/04/2026 fue realizada por Andrea Almirón.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Beneficios Alcanzados
|
## 6. Beneficios Alcanzados
|
||||||
|
|
||||||
- **Trabajo directo:** Las usuarias operan el sistema sin intermediarios.
|
- **Trabajo directo:** Las usuarias operan el sistema sin intermediarios.
|
||||||
- **Autonomía operativa:** Infraestructura propia, desvinculada de la Facultad, preparada para gestión desde Rectorado.
|
- **Autonomía operativa:** Infraestructura propia, desvinculada de la Facultad, preparada para gestión desde Rectorado.
|
||||||
@@ -115,7 +131,7 @@ El problema de impresión se resolvió mediante un **symlink** que redirige `C:\
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Próximos Pasos
|
## 7. Próximos Pasos
|
||||||
|
|
||||||
1. ✅ **Impresión resuelta** (symlink `C:\Sistema` → `C:\SysDasuten\Sistema`).
|
1. ✅ **Impresión resuelta** (symlink `C:\Sistema` → `C:\SysDasuten\Sistema`).
|
||||||
2. ✅ **Configuración de red estabilizada** (red plana DHCP del ISP).
|
2. ✅ **Configuración de red estabilizada** (red plana DHCP del ISP).
|
||||||
@@ -125,7 +141,7 @@ El problema de impresión se resolvió mediante un **symlink** que redirige `C:\
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Conclusión
|
## 8. Conclusión
|
||||||
|
|
||||||
La migración del sistema DASUTEN demuestra que es posible **lograr una mejora sustancial en estabilidad, autonomía y experiencia de trabajo** partiendo de recursos limitados y sin inversión adicional.
|
La migración del sistema DASUTEN demuestra que es posible **lograr una mejora sustancial en estabilidad, autonomía y experiencia de trabajo** partiendo de recursos limitados y sin inversión adicional.
|
||||||
|
|
||||||
@@ -143,5 +159,7 @@ DASUTEN opera hoy de forma independiente, administrable remotamente por DTIC y p
|
|||||||
| 0.2 | 13/04/2026 | Se amplía contexto de situación inicial |
|
| 0.2 | 13/04/2026 | Se amplía contexto de situación inicial |
|
||||||
| 0.3 | 13/04/2026 | Se retoma enfoque narrativo, se integra contexto como complemento |
|
| 0.3 | 13/04/2026 | Se retoma enfoque narrativo, se integra contexto como complemento |
|
||||||
| 0.4 | 14/04/2026 | Impresión resuelta + Pipeline de backups implementado |
|
| 0.4 | 14/04/2026 | Impresión resuelta + Pipeline de backups implementado |
|
||||||
|
| 1.0 | 15/04/2026 | COMPLETADO Y VALIDADO — Zona horaria corregida (UTC-3), validación usuaria Andrea Almirón confirma sistema operativo |
|
||||||
|
| **1.1** | **16/04/2026** | **REVISADO** — Se agrega sección sobre metodología de trabajo con IA generativa como factor diferencial |
|
||||||
|
|
||||||
*Fin del informe.*
|
*Fin del informe.*
|
||||||
|
|||||||
Binary file not shown.
@@ -3,9 +3,9 @@
|
|||||||
> **Área:** Departamento de Tecnología de la Información y Comunicaciones (DTIC)
|
> **Área:** Departamento de Tecnología de la Información y Comunicaciones (DTIC)
|
||||||
> **Proyecto:** Migración de infraestructura DASUTEN — Modo Workgroup sin Domain Controller
|
> **Proyecto:** Migración de infraestructura DASUTEN — Modo Workgroup sin Domain Controller
|
||||||
> **Código interno:** A04.P005
|
> **Código interno:** A04.P005
|
||||||
> **Fecha del informe:** 13 de abril de 2026
|
> **Fecha del informe:** 16 de abril de 2026
|
||||||
> **Elaborado por:** Ricardo Monla — Área DTIC, UTN-FRLR
|
> **Elaborado por:** Ricardo Monla — Área DTIC, UTN-FRLR
|
||||||
> **Versión:** 0.3
|
> **Versión:** 1.1 — REVISADO
|
||||||
>
|
>
|
||||||
> *Este documento contiene el detalle técnico completo de la migración. Para la síntesis ejecutiva, ver [01_informe_ejecutivo_DASUTEN.md](01_informe_ejecutivo_DASUTEN.md).*
|
> *Este documento contiene el detalle técnico completo de la migración. Para la síntesis ejecutiva, ver [01_informe_ejecutivo_DASUTEN.md](01_informe_ejecutivo_DASUTEN.md).*
|
||||||
|
|
||||||
@@ -15,10 +15,12 @@
|
|||||||
|
|
||||||
Se informa el avance de la migración del sistema de gestión administrativa **D.A.S.U.Te.N** (Dirección de Acción Social de la Universidad Tecnológica Nacional) desde la infraestructura compartida de la Facultad hacia un entorno independiente, autónomo y simplificado.
|
Se informa el avance de la migración del sistema de gestión administrativa **D.A.S.U.Te.N** (Dirección de Acción Social de la Universidad Tecnológica Nacional) desde la infraestructura compartida de la Facultad hacia un entorno independiente, autónomo y simplificado.
|
||||||
|
|
||||||
**Estado general del proyecto: 100% completado.**
|
**Estado general del proyecto: 100% completado y validado.**
|
||||||
|
|
||||||
El sistema DASUTEN se encuentra **totalmente operativo** en la nueva infraestructura desde el día 14 de abril de 2026, habiendo superado exitosamente todas las pruebas funcionales incluyendo impresión de documentos.
|
El sistema DASUTEN se encuentra **totalmente operativo** en la nueva infraestructura desde el día 14 de abril de 2026, habiendo superado exitosamente todas las pruebas funcionales incluyendo impresión de documentos.
|
||||||
|
|
||||||
|
**Validación final (15/04/2026 17:00):** La usuaria **Andrea Almirón (aalmiron)** confirmó que el sistema desktop muestra correctamente los movimientos del día en curso, resolviéndose el problema de datos "desactualizados" tras la corrección de la zona horaria en `dasu-sql4` (UTC-3 Argentina).
|
||||||
|
|
||||||
### Logros principales
|
### Logros principales
|
||||||
|
|
||||||
- ✅ Servidor de base de datos **completamente migrado** a infraestructura nueva.
|
- ✅ Servidor de base de datos **completamente migrado** a infraestructura nueva.
|
||||||
@@ -28,11 +30,56 @@ El sistema DASUTEN se encuentra **totalmente operativo** en la nueva infraestruc
|
|||||||
- ✅ Eliminación exitosa de la dependencia del **Active Directory (Domain Controller)**.
|
- ✅ Eliminación exitosa de la dependencia del **Active Directory (Domain Controller)**.
|
||||||
- ✅ Simplificación de la arquitectura de red — de 3 servidores virtuales a 1.
|
- ✅ Simplificación de la arquitectura de red — de 3 servidores virtuales a 1.
|
||||||
- ✅ Pipeline de backups **automatizado** con drones atómicos.
|
- ✅ Pipeline de backups **automatizado** con drones atómicos.
|
||||||
|
- ✅ Zona horaria **configurada correctamente** (UTC-3 Argentina) en dasu-sql4.
|
||||||
|
- ✅ Validación del usuario **confirmada** (15/04/2026): datos actualizados visibles en el sistema desktop.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Antecedentes y Motivación
|
## 2. Antecedentes y Motivación
|
||||||
|
|
||||||
|
### 2.0 Enfoque metodológico: Análisis y simplificación asistidos por IA
|
||||||
|
|
||||||
|
Antes de comenzar la implementación, se dedicó una fase de **análisis intelectual asistido por IA generativa** para comprender el sistema existente y diseñar la arquitectura óptima. Este enfoque permitió:
|
||||||
|
|
||||||
|
**a) Comprensión del sistema legacy sin documentación**
|
||||||
|
|
||||||
|
El sistema DASUTEN está desarrollado en Visual FoxPro (VFP), tecnología obsoleta sin documentación oficial disponible. Mediante análisis asistido por IA se logró:
|
||||||
|
|
||||||
|
- Identificar que el archivo `Kermet.ini` controla la conexión a la base de datos
|
||||||
|
- Determinar que el campo `UID` define el tipo de autenticación: si está vacío → Windows Integrated (Kerberos/AD), si tiene valor → SQL Auth
|
||||||
|
- Reconocer que las plantillas de impresión están hardcodeadas en `C:\Sistema\Word\` y `C:\Sistema\Excel\`
|
||||||
|
|
||||||
|
**b) Hallazgo crítico: La dependencia del Active Directory era configuracional, no de código**
|
||||||
|
|
||||||
|
El análisis reveló que el sistema **no tenía llamadas explícitas a APIs de Active Directory** en su código VFP. La dependencia surgía porque:
|
||||||
|
|
||||||
|
1. El archivo `Kermet.ini` original tenía `UID=` vacío, lo que fuerza autenticación Windows Integrated
|
||||||
|
2. Al establecer `UID=sa` explícitamente, el sistema usa SQL Auth y funciona sin Domain Controller
|
||||||
|
|
||||||
|
Este hallazgo permitió **eliminar 2 de 4 componentes** (Domain Controller + PC virtual), simplificando radicalmente la arquitectura.
|
||||||
|
|
||||||
|
**c) Diseño de la arquitectura de "Drones Atómicos"**
|
||||||
|
|
||||||
|
Para el pipeline de backups, la IA asistió en diseñar un patrón de composición funcional:
|
||||||
|
|
||||||
|
- Cada dron hace **una sola cosa** y la hace bien (Unix philosophy)
|
||||||
|
- La comunicación vía JSON permite acoplamiento débil entre drones
|
||||||
|
- El módulo común `DasuExecutor` elimina código repetitivo aplicando el principio DRY
|
||||||
|
|
||||||
|
**d) Resolución del problema de impresión**
|
||||||
|
|
||||||
|
Ante el problema de que el ejecutable VFP busca plantillas en `C:\Sistema\` pero el directorio real es `C:\SysDasuten\Sistema\`, el análisis asistido identificó el **symlink** como solución óptima:
|
||||||
|
|
||||||
|
- No requiere recompilar el ejecutable (imposible sin el compilador VFP)
|
||||||
|
- No requiere modificar el código fuente (inaccesible)
|
||||||
|
- Es transparente para el sistema operativo y la aplicación
|
||||||
|
|
||||||
|
**e) Multiplicador cognitivo**
|
||||||
|
|
||||||
|
La IA actuó como un **simulador mental externo**: permitió probar conceptualmente múltiples enfoques antes de implementarlos, reduciendo el método de "prueba y error" tradicional. Lo que hubiera requerido semanas se completó en días.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### 2.1 Infraestructura original (en red de Facultad)
|
### 2.1 Infraestructura original (en red de Facultad)
|
||||||
|
|
||||||
El sistema DASUTEN operaba sobre infraestructura compartida con la Facultad Regional La Rioja, dependiendo de:
|
El sistema DASUTEN operaba sobre infraestructura compartida con la Facultad Regional La Rioja, dependiendo de:
|
||||||
@@ -254,6 +301,8 @@ Se implementó un pipeline optimizado para refrescos diarios usando **backups di
|
|||||||
| Acceso remoto para soporte (Tailscale/SSH) | ✅ Operativo |
|
| Acceso remoto para soporte (Tailscale/SSH) | ✅ Operativo |
|
||||||
| Impresión desde el sistema | ✅ Resuelto (symlink) |
|
| Impresión desde el sistema | ✅ Resuelto (symlink) |
|
||||||
| Backups automatizados | ✅ Pipeline implementado |
|
| Backups automatizados | ✅ Pipeline implementado |
|
||||||
|
| Zona horaria del servidor | ✅ UTC-3 Argentina (configurada 15/04/2026) |
|
||||||
|
| Validación del usuario | ✅ Confirmada (Andrea Almirón, 15/04/2026 17:00) |
|
||||||
|
|
||||||
### 5.2 Solución de Impresión
|
### 5.2 Solución de Impresión
|
||||||
|
|
||||||
@@ -286,7 +335,7 @@ mklink /D C:\Sistema C:\SysDasuten\Sistema
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Beneficios Obtenidos
|
## 7. Beneficios Obtenidos
|
||||||
|
|
||||||
1. **Independencia operativa:** DASUTEN ya no depende de la infraestructura de red ni de los servidores de la Facultad.
|
1. **Independencia operativa:** DASUTEN ya no depende de la infraestructura de red ni de los servidores de la Facultad.
|
||||||
2. **Simplificación:** Se pasó de 3 VMs + Domain Controller a solo 1 VM (SQL Server), reduciendo significativamente la complejidad.
|
2. **Simplificación:** Se pasó de 3 VMs + Domain Controller a solo 1 VM (SQL Server), reduciendo significativamente la complejidad.
|
||||||
@@ -297,7 +346,7 @@ mklink /D C:\Sistema C:\SysDasuten\Sistema
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Cronograma Resumen
|
## 8. Cronograma Resumen
|
||||||
|
|
||||||
| Fecha | Hito |
|
| Fecha | Hito |
|
||||||
|:---|:---|
|
|:---|:---|
|
||||||
@@ -308,36 +357,63 @@ mklink /D C:\Sistema C:\SysDasuten\Sistema
|
|||||||
| 11/04/2026 | Validación funcional exitosa en entorno virtual |
|
| 11/04/2026 | Validación funcional exitosa en entorno virtual |
|
||||||
| 13/04/2026 | Despliegue en PC física de producción |
|
| 13/04/2026 | Despliegue en PC física de producción |
|
||||||
| 14/04/2026 | Impresión resuelta (symlink) + Pipeline de backups implementado |
|
| 14/04/2026 | Impresión resuelta (symlink) + Pipeline de backups implementado |
|
||||||
|
| **15/04/2026 16:30** | **Migración final + Zona horaria corregida (UTC-3 Argentina)** |
|
||||||
|
| **15/04/2026 17:00** | **Validación usuaria: datos actualizados visibles en sistema desktop** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Próximos Pasos
|
## 9. Próximos Pasos
|
||||||
|
|
||||||
| Prioridad | Acción | Plazo estimado |
|
| Prioridad | Acción | Plazo estimado |
|
||||||
|:---:|:---|:---|
|
|:---:|:---|:---|
|
||||||
| ✅ | **Impresión resuelta** (symlink `C:\Sistema`) | Completado 14/04/2026 |
|
| ✅ | **Impresión resuelta** (symlink `C:\Sistema`) | Completado 14/04/2026 |
|
||||||
| ✅ | **Pipeline de backups implementado** | Completado 14/04/2026 |
|
| ✅ | **Pipeline de backups implementado** | Completado 14/04/2026 |
|
||||||
|
| ✅ | **Zona horaria corregida** (UTC-3 Argentina) | Completado 15/04/2026 |
|
||||||
|
| ✅ | **Validación del usuario confirmada** (datos actualizados) | Completado 15/04/2026 |
|
||||||
| 🟡 Media | Reservar IPs fijas en router ISP (prevenir cambios por DHCP) | 1 semana |
|
| 🟡 Media | Reservar IPs fijas en router ISP (prevenir cambios por DHCP) | 1 semana |
|
||||||
| 🟢 Baja | Evaluar apagado definitivo de VMs legacy (DC, SQL antiguo) | Tras validación completa |
|
| 🟢 Baja | Evaluar apagado definitivo de VMs legacy (DC, SQL antiguo) | Tras validación completa |
|
||||||
| 🟢 Baja | Ejecutar backups diferenciales diarios (lun-vie 08:00) | En operación |
|
| 🟢 Baja | Ejecutar backups diferenciales diarios (lun-vie 08:00) | En operación |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. Conclusión
|
## 10. Conclusión
|
||||||
|
|
||||||
La migración del sistema DASUTEN a la nueva infraestructura se encuentra **100% completada**, con el sistema totalmente operativo y todas las funcionalidades verificadas incluyendo impresión de documentos.
|
La migración del sistema DASUTEN a la nueva infraestructura se encuentra **100% completada y validada**, con el sistema totalmente operativo y todas las funcionalidades verificadas incluyendo impresión de documentos.
|
||||||
|
|
||||||
|
### 9.1 Simplificación arquitectónica lograda
|
||||||
|
|
||||||
El enfoque sin Domain Controller demostró ser completamente viable, logrando una **reducción significativa de la complejidad** y de los puntos de falla:
|
El enfoque sin Domain Controller demostró ser completamente viable, logrando una **reducción significativa de la complejidad** y de los puntos de falla:
|
||||||
- De 4 componentes (DC + SQL + PC virtual + PC física) a 2 (SQL + PC física)
|
|
||||||
- De red privada compleja con routing a red plana DHCP del ISP
|
|
||||||
- De autenticación Kerberos a SQL Auth mixta
|
|
||||||
|
|
||||||
Adicionalmente, se implementó un **pipeline automatizado de backups** con arquitectura de drones atómicos que permite:
|
| Dimensión | Antes | Después | Simplificación |
|
||||||
- Refrescos semanales completos (~1.3 GB, 35-40 min)
|
|-----------|-------|---------|----------------|
|
||||||
- Refrescos diarios diferenciales (~0.4-10 MB, 5-10 min)
|
| **Componentes** | 4 (DC + SQL + PC virtual + PC física) | 2 (SQL + PC física) | −50% |
|
||||||
- Integración con el ecosistema ADN de herramientas (`bkps.rb`)
|
| **VMs activas** | 3 | 1 | −67% |
|
||||||
|
| **Red** | Privada 10.0.100.x con gateway interno | DHCP directo del ISP | Sin routing interno |
|
||||||
|
| **Autenticación** | Kerberos (AD) | SQL Auth mixta | Sin dominio |
|
||||||
|
| **Forma de trabajo** | Indirecta (AnyDesk → PC virtual) | Directa (sistema en PC) | Sin intermediarios |
|
||||||
|
|
||||||
El proyecto generó además herramientas reutilizables (DasuExecutor, drones atómicos, módulo de comunicación JSON) que quedan como activo de DTIC para futuros proyectos.
|
### 9.2 El rol de la IA como multiplicador cognitivo
|
||||||
|
|
||||||
|
Este proyecto demostró que es posible **hacer más rápido y mejor** cuando se incorpora la IA generativa como herramienta de análisis y diseño:
|
||||||
|
|
||||||
|
- **Ingeniería inversa acelerada:** Se identificó que la dependencia del AD era configuracional (`UID=` vacío en `Kermet.ini`), no de código VFP.
|
||||||
|
- **Diseño validado antes de implementar:** La IA permitió simular mentalmente múltiples arquitecturas antes de implementarlas.
|
||||||
|
- **Código más limpio:** El módulo `DasuExecutor` eliminó ~52% de código repetitivo aplicando principios de diseño (DRY, Unix philosophy).
|
||||||
|
- **Resolución de problemas complejos:** El symlink para impresión fue identificado como solución óptima sin necesidad de prueba y error.
|
||||||
|
|
||||||
|
**Lección aprendida:** La IA no reemplaza el criterio técnico, pero lo amplifica. Permite dedicar más tiempo al **pensamiento arquitectónico** y menos a la experimentación ciega.
|
||||||
|
|
||||||
|
### 9.3 Herramientas reutilizables
|
||||||
|
|
||||||
|
El proyecto generó activos aprovechables para futuros proyectos de DTIC:
|
||||||
|
|
||||||
|
- **DasuExecutor:** Módulo común para ejecución remota de PowerShell vía SSH
|
||||||
|
- **Drones Atómicos:** Patrón de composición funcional con comunicación JSON
|
||||||
|
- **Pipeline bkps.rb:** Integración con el ecosistema ADN de herramientas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Hallazgo crítico (15/04/2026):** La zona horaria incorrecta en `dasu-sql4` ("Romance Standard Time" UTC+2 en lugar de "Argentina Standard Time" UTC-3) era la causa principal de que los datos se vieran "desactualizados". Los backups se realizaban correctamente, pero las fechas se interpretaban con ~5 horas de desplazamiento. Tras la corrección, la usuaria Andrea Almirón confirmó el correcto funcionamiento del sistema.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -366,6 +442,7 @@ El proyecto generó además herramientas reutilizables (DasuExecutor, drones at
|
|||||||
| **IP LAN** | 192.168.1.11 (DHCP ISP) |
|
| **IP LAN** | 192.168.1.11 (DHCP ISP) |
|
||||||
| **IP VPN** | 100.107.15.82 (Tailscale — inestable) |
|
| **IP VPN** | 100.107.15.82 (Tailscale — inestable) |
|
||||||
| **Workgroup** | DASUTEN |
|
| **Workgroup** | DASUTEN |
|
||||||
|
| **Zona Horaria** | Argentina Standard Time (UTC-3) — configurada 15/04/2026 |
|
||||||
| **RAM** | 4 GB |
|
| **RAM** | 4 GB |
|
||||||
| **vCPU** | 2 |
|
| **vCPU** | 2 |
|
||||||
| **Disco 1 (sata0)** | 60 GB — Sistema Operativo |
|
| **Disco 1 (sata0)** | 60 GB — Sistema Operativo |
|
||||||
@@ -390,7 +467,7 @@ El proyecto generó además herramientas reutilizables (DasuExecutor, drones at
|
|||||||
| **CPU** | AMD Ryzen 5 4600G @ 4.30GHz (6 núcleos) |
|
| **CPU** | AMD Ryzen 5 4600G @ 4.30GHz (6 núcleos) |
|
||||||
| **RAM** | 8 GB DDR4-3200 |
|
| **RAM** | 8 GB DDR4-3200 |
|
||||||
| **Placa Base** | Asus Prime A320M-K |
|
| **Placa Base** | Asus Prime A320M-K |
|
||||||
| **Usuaria principal** | Andrea Almirón (`aalmiron`) |
|
| **Usuarias** | Andrea Almirón (`aalmiron`), Romina Molina (`rmolina`) |
|
||||||
| **Directorio DASUTEN** | C:\SysDasuten |
|
| **Directorio DASUTEN** | C:\SysDasuten |
|
||||||
| **Config conexión** | C:\SysDasuten\Sistema\Kermet.ini |
|
| **Config conexión** | C:\SysDasuten\Sistema\Kermet.ini |
|
||||||
|
|
||||||
@@ -467,9 +544,11 @@ dasu-sql4 (Destino)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## G. Pipeline de Backups (NUEVO — 2026-04-14)
|
## D. Pipeline de Backups (NUEVO — 2026-04-14)
|
||||||
|
|
||||||
### G.1 Arquitectura de Drones Atómicos
|
> **Nota:** Ver también la sección [4.7](#47-fase-8-pipeline-de-backups-14-abril--completado) para el contexto de implementación.
|
||||||
|
|
||||||
|
### D.1 Arquitectura de Drones Atómicos
|
||||||
|
|
||||||
El pipeline de backups está compuesto por **6 drones atómicos** que se ejecutan secuencialmente, comunicándose entre sí mediante archivos JSON.
|
El pipeline de backups está compuesto por **6 drones atómicos** que se ejecutan secuencialmente, comunicándose entre sí mediante archivos JSON.
|
||||||
|
|
||||||
@@ -482,7 +561,7 @@ El pipeline de backups está compuesto por **6 drones atómicos** que se ejecuta
|
|||||||
| 5 | `dasuten_restaurar_dasu-sql4.rb` | dasu-sql4 | RESTORE DATABASE | `/tmp/dron_restore_output.json` |
|
| 5 | `dasuten_restaurar_dasu-sql4.rb` | dasu-sql4 | RESTORE DATABASE | `/tmp/dron_restore_output.json` |
|
||||||
| 6 | `dasuten_verificar-integridad_dasu-sql4.rb` | dasu-sql4 | DBCC CHECKDB | `/tmp/dron_verify_output.json` |
|
| 6 | `dasuten_verificar-integridad_dasu-sql4.rb` | dasu-sql4 | DBCC CHECKDB | `/tmp/dron_verify_output.json` |
|
||||||
|
|
||||||
### G.2 Flujo Completo (Semanal)
|
### D.2 Flujo Completo (Semanal)
|
||||||
|
|
||||||
```
|
```
|
||||||
srvv-fenix (SQL Server)
|
srvv-fenix (SQL Server)
|
||||||
@@ -502,7 +581,7 @@ sysdasuten ONLINE
|
|||||||
|
|
||||||
**Duración total:** ~35-40 minutos
|
**Duración total:** ~35-40 minutos
|
||||||
|
|
||||||
### G.3 Flujo Diferencial (Diario)
|
### D.3 Flujo Diferencial (Diario)
|
||||||
|
|
||||||
```
|
```
|
||||||
srvv-fenix (SQL Server)
|
srvv-fenix (SQL Server)
|
||||||
@@ -522,7 +601,7 @@ sysdasuten ONLINE
|
|||||||
|
|
||||||
**Duración total:** ~5-10 minutos
|
**Duración total:** ~5-10 minutos
|
||||||
|
|
||||||
### G.4 Comando de Ejecución
|
### D.4 Comando de Ejecución
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Pipeline completo (semanal - lunes 08:00)
|
# Pipeline completo (semanal - lunes 08:00)
|
||||||
@@ -532,7 +611,7 @@ sysdasuten ONLINE
|
|||||||
./adn/tools/run bkps run dasuten_diferencial
|
./adn/tools/run bkps run dasuten_diferencial
|
||||||
```
|
```
|
||||||
|
|
||||||
### G.5 Módulo Común: DasuExecutor
|
### D.5 Módulo Común: DasuExecutor
|
||||||
|
|
||||||
Para eliminar código repetido, se creó el módulo `adn/tools/cli/drones/lib/dasu_executor.rb` que centraliza:
|
Para eliminar código repetido, se creó el módulo `adn/tools/cli/drones/lib/dasu_executor.rb` que centraliza:
|
||||||
|
|
||||||
@@ -546,7 +625,7 @@ Para eliminar código repetido, se creó el módulo `adn/tools/cli/drones/lib/da
|
|||||||
|
|
||||||
**Beneficio:** -52% de líneas de código en los drones refactorizados.
|
**Beneficio:** -52% de líneas de código en los drones refactorizados.
|
||||||
|
|
||||||
### G.6 Lecciones Aprendidas
|
### D.6 Lecciones Aprendidas
|
||||||
|
|
||||||
| Lección | Impacto |
|
| Lección | Impacto |
|
||||||
|---------|---------|
|
|---------|---------|
|
||||||
@@ -558,7 +637,7 @@ Para eliminar código repetido, se creó el módulo `adn/tools/cli/drones/lib/da
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## D. VMs Legacy Preservadas (Apagadas)
|
## E. VMs Legacy Preservadas (Apagadas)
|
||||||
|
|
||||||
| VM ID | Nombre | Rol anterior | Estado |
|
| VM ID | Nombre | Rol anterior | Estado |
|
||||||
|:---:|:---|:---|:---:|
|
|:---:|:---|:---|:---:|
|
||||||
@@ -572,7 +651,7 @@ Para eliminar código repetido, se creó el módulo `adn/tools/cli/drones/lib/da
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## E. Accesos Remotos Configurados
|
## F. Accesos Remotos Configurados
|
||||||
|
|
||||||
| Nodo | Método | Detalle |
|
| Nodo | Método | Detalle |
|
||||||
|:---|:---|:---|
|
|:---|:---|:---|
|
||||||
@@ -585,7 +664,7 @@ Para eliminar código repetido, se creó el módulo `adn/tools/cli/drones/lib/da
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## F. Cuenta de Gestión VPN
|
## G. Cuenta de Gestión VPN
|
||||||
|
|
||||||
| Campo | Valor |
|
| Campo | Valor |
|
||||||
|:---|:---|
|
|:---|:---|
|
||||||
@@ -602,5 +681,7 @@ Para eliminar código repetido, se creó el módulo `adn/tools/cli/drones/lib/da
|
|||||||
| 0.1 | 13/04/2026 | Versión inicial (fusión de informe de avance + anexo técnico) |
|
| 0.1 | 13/04/2026 | Versión inicial (fusión de informe de avance + anexo técnico) |
|
||||||
| 0.2 | 13/04/2026 | Se agrega contexto de infraestructura anterior (esquema AnyDesk), versionado, referencia a doc ejecutivo |
|
| 0.2 | 13/04/2026 | Se agrega contexto de infraestructura anterior (esquema AnyDesk), versionado, referencia a doc ejecutivo |
|
||||||
| 0.3 | 14/04/2026 | Impresión resuelta + Pipeline de backups implementado + DasuExecutor |
|
| 0.3 | 14/04/2026 | Impresión resuelta + Pipeline de backups implementado + DasuExecutor |
|
||||||
|
| 1.0 | 15/04/2026 | COMPLETADO Y VALIDADO — Zona horaria corregida (UTC-3), validación usuaria Andrea Almirón confirma sistema operativo |
|
||||||
|
| **1.1** | **16/04/2026** | **REVISADO** — Se agrega sección 2.0 sobre metodología de análisis con IA, se refuerza conclusión con énfasis en simplificación arquitectónica y rol de IA como multiplicador cognitivo |
|
||||||
|
|
||||||
*Fin del informe técnico.*
|
*Fin del informe técnico.*
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,158 @@
|
|||||||
|
# INFORME DE AVANCE
|
||||||
|
## Migración del Sistema DASUTEN a Infraestructura Autónoma
|
||||||
|
|
||||||
|
> **Área responsable:** Departamento de Tecnología de la Información y Comunicaciones (DTIC)
|
||||||
|
> **Facultad Regional La Rioja — Universidad Tecnológica Nacional**
|
||||||
|
> **Fecha:** 15 de abril de 2026
|
||||||
|
> **Elaborado por:** Ricardo Monla — DTIC, UTN-FRLR
|
||||||
|
> **Destinatarios:** [Completar]
|
||||||
|
> **Versión:** 1.0 — COMPLETADO Y VALIDADO
|
||||||
|
>
|
||||||
|
> *Este documento es de carácter ejecutivo y narrativo. Se adjunta también el detalle técnico completo en el documento [02_informe_tecnico_DASUTEN.md](02_informe_tecnico_DASUTEN.md).*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Contexto y Punto de Partida
|
||||||
|
|
||||||
|
El sistema de gestión administrativa de **D.A.S.U.Te.N** (Dirección de Acción Social de la Universidad Tecnológica Nacional) en la Facultad Regional La Rioja funcionaba desde hace años sobre la infraestructura informática compartida de la Facultad: servidores, red interna y servicios de dominio que atienden a toda la institución.
|
||||||
|
|
||||||
|
Si bien esta configuración fue funcional durante un tiempo, con el correr de los años fue manifestando limitaciones concretas:
|
||||||
|
|
||||||
|
- **Fragilidad operativa:** Cualquier incidencia en la red o los servidores de la Facultad —incluso ajena a DASUTEN— impactaba directamente en el funcionamiento del sistema.
|
||||||
|
- **Complejidad desproporcionada:** La arquitectura heredada requería múltiples servidores virtuales y un controlador de dominio, componentes pensados para entornos de mayor escala que resultaban sobredimensionados para las necesidades de una sola oficina.
|
||||||
|
- **Dificultad de mantenimiento:** Los respaldos de datos y las actualizaciones periódicas que envía Rectorado requerían coordinación con la infraestructura general de la Facultad, generando demoras y dependencias cruzadas.
|
||||||
|
- **Limitación de autonomía:** DASUTEN, si bien opera dentro de las instalaciones de la Facultad, responde funcionalmente a **Rectorado (Buenos Aires)**. La dependencia de la infraestructura local dificultaba una eventual gestión directa desde la sede central.
|
||||||
|
|
||||||
|
A esta situación se sumó que, al reubicarse la oficina en un espacio propio alejado de los servidores, las fallas de conectividad se agravaron por la cantidad de puntos intermedios en la red. Para dar continuidad al servicio, se gestionó una línea de internet propia y se implementó un esquema de trabajo indirecto donde las usuarias — **Andrea Almirón (aalmiron)** y **Romina Molina (rmolina)** — accedían mediante escritorio remoto a una PC virtual para poder operar el sistema. Esto implicaba mantener cuatro componentes simultáneamente (servidor de dominio, servidor de base de datos, PC virtual y PC física), multiplicando los puntos de falla.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Objetivo del Proyecto
|
||||||
|
|
||||||
|
Diseñar e implementar una solución que permita al sistema DASUTEN operar de forma **completamente autónoma**, teniendo en cuenta:
|
||||||
|
|
||||||
|
- **Recursos limitados:** Aprovechar equipos ya existentes — un equipo de escritorio de prestaciones modestas y un servidor compacto reutilizado. **Sin adquisición de hardware nuevo.**
|
||||||
|
- **No interferir con la oficina:** Que DTIC pueda realizar respaldos, actualizaciones de Rectorado y soporte técnico **de forma remota y transparente**, sin afectar las tareas cotidianas.
|
||||||
|
- **Trabajo directo:** Que las usuarias operen el sistema directamente desde la PC de la oficina, sin depender de conexiones remotas intermedias.
|
||||||
|
- **Simplicidad:** Una arquitectura lo más simple posible, administrable y confiable en el tiempo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Qué se logró
|
||||||
|
|
||||||
|
### 3.1 La simplificación
|
||||||
|
|
||||||
|
Se reemplazó la arquitectura compleja por un esquema directo y simple:
|
||||||
|
|
||||||
|
| Aspecto | Antes | Ahora |
|
||||||
|
|:---|:---:|:---:|
|
||||||
|
| Componentes necesarios | 4 | 2 |
|
||||||
|
| Forma de trabajo | Indirecta (vía escritorio remoto) | Directa (sistema en la PC) |
|
||||||
|
| Dependencia de red de Facultad | Sí | No |
|
||||||
|
| Puntos de falla | Múltiples | Mínimos |
|
||||||
|
| Requiere presencia física para soporte | Frecuentemente | No |
|
||||||
|
| Hardware nuevo adquirido | — | Ninguno |
|
||||||
|
|
||||||
|
Las usuarias ahora operan el sistema **directamente desde la PC de la oficina**. El servidor de base de datos funciona dentro de la misma oficina, en la red propia de DASUTEN, sin intermediarios.
|
||||||
|
|
||||||
|
### 3.2 El proceso: de testeo a producción
|
||||||
|
|
||||||
|
Si bien el proyecto llevó más tiempo del inicialmente estimado, esto responde a una decisión deliberada: se planificó una **etapa de testeo exhaustivo** antes de llevar el sistema a producción. Cada paso fue validado primero en un entorno de pruebas antes de aplicarse al entorno real, minimizando riesgos para la operación de la oficina.
|
||||||
|
|
||||||
|
**El sistema se encuentra 100% operativo y validado.** Todos los ajustes de compatibilidad (incluyendo impresión de documentos) fueron resueltos exitosamente.
|
||||||
|
|
||||||
|
### 3.3 Valor agregado: herramientas y metodología
|
||||||
|
|
||||||
|
Más allá del objetivo central, el trabajo realizado **generó herramientas y metodologías de valor** que benefician a la gestión de DTIC en su conjunto:
|
||||||
|
|
||||||
|
- **Dashboard web de seguimiento** del proyecto, con visibilidad en tiempo real del progreso.
|
||||||
|
- **Sistema de bitácoras** con registro detallado y auditable de cada intervención técnica.
|
||||||
|
- **Herramientas de automatización** (Ecosistema ADN) para respaldos, gestión remota y tareas repetitivas.
|
||||||
|
- **Metodología de trabajo con IA generativa**, altamente productiva y replicable en futuros proyectos.
|
||||||
|
- **Pipeline de Drones Atómicos** para backups: 6 drones independientes que se encadenan secuencialmente, comunicándose vía JSON.
|
||||||
|
- **Módulo común DasuExecutor** que centraliza la ejecución remota de PowerShell, eliminando código repetido (-52% líneas).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Estado Actual
|
||||||
|
|
||||||
|
| Aspecto | Estado |
|
||||||
|
|:---|:---:|
|
||||||
|
| Sistema operativo en oficina | ✅ |
|
||||||
|
| Base de datos migrada y verificada | ✅ |
|
||||||
|
| Trabajo directo (sin escritorio remoto) | ✅ |
|
||||||
|
| Administración remota por DTIC | ✅ |
|
||||||
|
| Respaldos automatizados | ✅ Pipeline implementado |
|
||||||
|
| Impresión de documentos | ✅ Resuelto (symlink) |
|
||||||
|
| Zona horaria del servidor | ✅ UTC-3 Argentina (configurada 15/04/2026) |
|
||||||
|
| Validación del usuario | ✅ Confirmada (Andrea Almirón, 15/04/2026 17:00) |
|
||||||
|
|
||||||
|
El sistema se encuentra **100% operativo y validado**. La usuaria accede directamente al sistema desde la PC de la oficina. DTIC administra y respalda de forma remota sin interferir con la operación diaria.
|
||||||
|
|
||||||
|
### 4.1 Pipeline de Backups Implementado
|
||||||
|
|
||||||
|
Se implementó un sistema automatizado de backups con las siguientes características:
|
||||||
|
|
||||||
|
- **Backup completo semanal** (~1.3 GB, 35-40 minutos): `./adn/tools/run bkps run dasuten_full`
|
||||||
|
- **Backup diferencial diario** (~0.4-10 MB, 5-10 minutos): `./adn/tools/run bkps run dasuten_diferencial`
|
||||||
|
- **Arquitectura de Drones Atómicos**: 6 drones independientes (exportar → transferir → upload → download → restaurar → verificar)
|
||||||
|
- **Comunicación vía JSON**: Cada dron escribe su output en `/tmp/dron_*_output.json` para el siguiente paso
|
||||||
|
- **Google Drive como intermediario**: Elimina dependencia de Tailscale inestable
|
||||||
|
- **Integrado en bkps.rb**: Ejecutable desde el ecosistema ADN de herramientas
|
||||||
|
|
||||||
|
### 4.2 Solución de Impresión
|
||||||
|
|
||||||
|
El problema de impresión se resolvió mediante un **symlink** que redirige `C:\Sistema` → `C:\SysDasuten\Sistema`, permitiendo que el sistema encuentre las plantillas Word/Excel sin modificar el ejecutable compilado.
|
||||||
|
|
||||||
|
### 4.3 Validación Final con la Usuaria (2026-04-15)
|
||||||
|
|
||||||
|
Tras la migración completa y la corrección de la zona horaria en `dasu-sql4` (UTC-3 Argentina), la usuaria **Andrea Almirón (aalmiron)** confirmó que el sistema desktop muestra correctamente los movimientos del día en curso, resolviéndose el problema de datos "desactualizados" que se presentaba anteriormente.
|
||||||
|
|
||||||
|
**Hallazgo:** La zona horaria incorrecta (UTC+2 en lugar de UTC-3) era la causa principal de que los datos se vieran "desactualizados". Los backups se realizaban correctamente, pero las fechas se interpretaban con ~5 horas de desplazamiento.
|
||||||
|
|
||||||
|
**Nota:** Ambas usuarias (Andrea Almirón y Romina Molina) operan el sistema DASUTEN. La validación del 15/04/2026 fue realizada por Andrea Almirón, quien reportó el correcto funcionamiento del sistema tras la corrección de zona horaria.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Beneficios Alcanzados
|
||||||
|
|
||||||
|
- **Trabajo directo:** Las usuarias operan el sistema sin intermediarios.
|
||||||
|
- **Autonomía operativa:** Infraestructura propia, desvinculada de la Facultad, preparada para gestión desde Rectorado.
|
||||||
|
- **Optimización de recursos:** Sin inversión en hardware nuevo.
|
||||||
|
- **Soporte transparente:** DTIC gestiona remotamente sin afectar la oficina.
|
||||||
|
- **Mayor estabilidad:** De 4 componentes a 2, menos fallas posibles.
|
||||||
|
- **Herramientas reutilizables:** Activos aprovechables para otros proyectos de DTIC.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Próximos Pasos
|
||||||
|
|
||||||
|
1. ✅ **Impresión resuelta** (symlink `C:\Sistema` → `C:\SysDasuten\Sistema`).
|
||||||
|
2. ✅ **Configuración de red estabilizada** (red plana DHCP del ISP).
|
||||||
|
3. ✅ **Respaldos automatizados implementados** (pipeline de drones integrado en bkps.rb).
|
||||||
|
4. ⏳ **Retiro de infraestructura legacy** (VMs 100-102 apagadas, preservadas como respaldo).
|
||||||
|
5. 🔄 **Refrescos diferenciales diarios** configurados (lun-vie 08:00).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Conclusión
|
||||||
|
|
||||||
|
La migración del sistema DASUTEN demuestra que es posible **lograr una mejora sustancial en estabilidad, autonomía y experiencia de trabajo** partiendo de recursos limitados y sin inversión adicional.
|
||||||
|
|
||||||
|
Se pasó de un esquema indirecto y complejo a uno directo y simple, donde el sistema funciona en la propia oficina. El proceso generó además herramientas y metodologías que quedan como activo del área para futuros proyectos.
|
||||||
|
|
||||||
|
DASUTEN opera hoy de forma independiente, administrable remotamente por DTIC y preparada para una gestión más ágil desde la Facultad Regional o desde Rectorado.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Historial de versiones
|
||||||
|
|
||||||
|
| Versión | Fecha | Cambios |
|
||||||
|
|:---:|:---:|:---|
|
||||||
|
| 0.1 | 13/04/2026 | Borrador inicial |
|
||||||
|
| 0.2 | 13/04/2026 | Se amplía contexto de situación inicial |
|
||||||
|
| 0.3 | 13/04/2026 | Se retoma enfoque narrativo, se integra contexto como complemento |
|
||||||
|
| 0.4 | 14/04/2026 | Impresión resuelta + Pipeline de backups implementado |
|
||||||
|
| **1.0** | **15/04/2026** | **COMPLETADO Y VALIDADO** — Zona horaria corregida (UTC-3), validación usuaria Andrea Almirón confirma sistema operativo |
|
||||||
|
|
||||||
|
*Fin del informe.*
|
||||||
@@ -1,407 +0,0 @@
|
|||||||
# INFORME TÉCNICO — Migración del Sistema DASUTEN a Nuevo Servidor
|
|
||||||
|
|
||||||
> **Área:** Departamento de Tecnología de la Información y Comunicaciones (DTIC)
|
|
||||||
> **Proyecto:** Migración de infraestructura DASUTEN — Modo Workgroup sin Domain Controller
|
|
||||||
> **Código interno:** A04.P005
|
|
||||||
> **Fecha del informe:** 13 de abril de 2026
|
|
||||||
> **Elaborado por:** Ricardo Monla — Área DTIC, UTN-FRLR
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Resumen Ejecutivo
|
|
||||||
|
|
||||||
Se informa el avance de la migración del sistema de gestión administrativa **DASUTEN** (Departamento de Acción Social Universitaria Tecnológica Nacional) desde la infraestructura compartida de la Facultad hacia un entorno independiente, autónomo y simplificado.
|
|
||||||
|
|
||||||
**Estado general del proyecto: 95% completado.**
|
|
||||||
|
|
||||||
El sistema DASUTEN se encuentra **operativo** en la nueva infraestructura desde el día 13 de abril de 2026, habiendo superado exitosamente las pruebas funcionales de acceso a datos. Resta únicamente resolver un problema de compatibilidad de impresión vinculado a las plantillas de Office utilizadas por el sistema.
|
|
||||||
|
|
||||||
### Logros principales
|
|
||||||
|
|
||||||
- ✅ Servidor de base de datos **completamente migrado** a infraestructura nueva.
|
|
||||||
- ✅ Base de datos de producción **restaurada y verificada** sin errores de integridad.
|
|
||||||
- ✅ Sistema DASUTEN **desplegado y operativo** en la PC física de la usuaria.
|
|
||||||
- ✅ Eliminación exitosa de la dependencia del **Active Directory (Domain Controller)**.
|
|
||||||
- ✅ Simplificación de la arquitectura de red — de 3 servidores virtuales a 1.
|
|
||||||
- ⚠️ Pendiente: resolución de problema de impresión por compatibilidad de plantillas Office.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Antecedentes y Motivación
|
|
||||||
|
|
||||||
### 2.1 Situación previa
|
|
||||||
|
|
||||||
El sistema DASUTEN operaba sobre infraestructura compartida con la Facultad Regional La Rioja, dependiendo de:
|
|
||||||
|
|
||||||
- Un servidor SQL Server alojado en la red interna de la Facultad (`srvv-fenix`).
|
|
||||||
- Un Domain Controller Active Directory para la autenticación de usuarios.
|
|
||||||
- Conectividad de red interna de la Facultad con routing complejo.
|
|
||||||
|
|
||||||
Esta configuración generaba **múltiples puntos de falla**:
|
|
||||||
- Dependencia del dominio institucional para la autenticación.
|
|
||||||
- Complejidad de red con subnetting privado, NAT y gateway interno.
|
|
||||||
- Cualquier cambio en la infraestructura de la Facultad impactaba directamente en DASUTEN.
|
|
||||||
|
|
||||||
### 2.2 Objetivo del proyecto
|
|
||||||
|
|
||||||
**Migrar el sistema DASUTEN a una infraestructura independiente y simplificada**, eliminando la dependencia del Domain Controller (Active Directory) y utilizando un esquema de red **Workgroup** directo, con el fin de:
|
|
||||||
|
|
||||||
1. Reducir la complejidad operativa.
|
|
||||||
2. Minimizar los puntos de falla.
|
|
||||||
3. Garantizar la autonomía operativa de DASUTEN respecto de la infraestructura de la Facultad.
|
|
||||||
4. Facilitar la eventual gestión desde Rectorado (Buenos Aires) si fuera necesario.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Infraestructura Implementada
|
|
||||||
|
|
||||||
### 3.1 Arquitectura de red
|
|
||||||
|
|
||||||
Se implementó una topología de **red plana simplificada**, donde todos los equipos obtienen direccionamiento IP directamente del router del ISP, eliminando subnetting privado y routing interno:
|
|
||||||
|
|
||||||
```
|
|
||||||
Internet
|
|
||||||
│
|
|
||||||
Router ISP (192.168.1.1 — Gateway)
|
|
||||||
│
|
|
||||||
├── srv-dasu (Proxmox VE) → 192.168.1.13
|
|
||||||
│ └── dasu-sql4 (VM 106) → 192.168.1.11 [SQL Server 2019]
|
|
||||||
│
|
|
||||||
└── dasu-pc (PC Física) → DHCP ISP [Estación de trabajo]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Componentes del nuevo entorno
|
|
||||||
|
|
||||||
| Componente | Descripción | Estado |
|
|
||||||
|:---|:---|:---:|
|
|
||||||
| **srv-dasu** | Servidor Proxmox VE 9.1 (Hipervisor) — CPU Intel, 15 GB RAM, 94 GB disco | ✅ Operativo |
|
|
||||||
| **dasu-sql4** (VM 106) | Windows Server 2022 — SQL Server 2019 Enterprise — 4 GB RAM, 2 vCPU, discos 60+120 GB | ✅ Operativo |
|
|
||||||
| **dasu-pc** | PC Física — Windows 10 Pro — AMD Ryzen 5, 8 GB RAM — Oficina DASUTEN | ✅ Operativo |
|
|
||||||
|
|
||||||
### 3.3 Comparativa: Antes vs. Después
|
|
||||||
|
|
||||||
| Aspecto | Enfoque Anterior (con DC) | Enfoque Actual (Workgroup) |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| **Red** | Privada 10.0.100.x (gateway interno) | DHCP del ISP (red plana) |
|
|
||||||
| **Autenticación SQL** | Windows Integrated (Kerberos) | SQL Auth (mixta) |
|
|
||||||
| **Usuarios** | Active Directory | Usuarios locales Windows |
|
|
||||||
| **DNS** | AD DNS (10.0.100.10) | DNS del ISP (automático) |
|
|
||||||
| **Complejidad de red** | Alta (DC + DNS + dominio + routing) | Mínima (red plana ISP) |
|
|
||||||
| **Puntos de falla** | DC, SQL, red dominio, routing | Solo SQL |
|
|
||||||
| **VMs necesarias** | 3 (DC + SQL + PC) | 1 (solo SQL) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Fases Ejecutadas y Progreso
|
|
||||||
|
|
||||||
### Resumen de progreso por fase
|
|
||||||
|
|
||||||
| Fase | Descripción | Estado | Progreso |
|
|
||||||
|:---:|:---|:---:|:---:|
|
|
||||||
| 1 | Preparación del entorno (srv-dasu) | ✅ Completada | 100% |
|
|
||||||
| 2 | Creación de VM SQL Server de prueba | ✅ Completada | 100% |
|
|
||||||
| 3 | Creación de VM PC Cliente de prueba | ✅ Completada | 100% |
|
|
||||||
| 4 | VM definitiva dasu-sql4 | ✅ Completada | 100% |
|
|
||||||
| 5 | Migración de base de datos | ✅ Completada | 100% |
|
|
||||||
| 5b | Hardening y persistencia de servicios | ✅ Completada | 100% |
|
|
||||||
| 6 | Validación con PC cliente virtual | ✅ Completada | 100% |
|
|
||||||
| 7 | Despliegue en PC física (producción) | 🚧 En curso | 75% |
|
|
||||||
| 8 | Refresco final de base de datos | ✅ Completada | 100% |
|
|
||||||
|
|
||||||
### 4.1 Fase 1-3: Preparación y pruebas de concepto (27-28 marzo)
|
|
||||||
|
|
||||||
Se preparó el hipervisor Proxmox, se reconfiguró el bridge de red para usar DHCP directo del ISP, y se crearon VMs de prueba para validar la viabilidad del enfoque sin Domain Controller.
|
|
||||||
|
|
||||||
**Hallazgo crítico:** Se detectó y resolvió que el bridge de red estaba configurado con la IP de una red privada anterior, lo cual habría impedido la conectividad de las nuevas VMs.
|
|
||||||
|
|
||||||
### 4.2 Fase 4: Servidor SQL definitivo (9-10 abril)
|
|
||||||
|
|
||||||
Se creó la VM definitiva `dasu-sql4` (VM 106) con una arquitectura optimizada:
|
|
||||||
|
|
||||||
- **Discos separados:** 60 GB para SO + 120 GB exclusivo para datos SQL (buena práctica).
|
|
||||||
- **SQL Server 2019 Enterprise** instalado con autenticación mixta.
|
|
||||||
- **Gestión remota** asegurada vía OpenSSH Server (puerto 7022) y Tailscale VPN.
|
|
||||||
|
|
||||||
### 4.3 Fase 5: Migración de Base de Datos (11 abril)
|
|
||||||
|
|
||||||
Se ejecutó la migración completa de la base de datos de producción:
|
|
||||||
|
|
||||||
1. **Exportación:** Backup comprimido (~1.33 GB) generado desde el servidor origen (`srvv-fenix`).
|
|
||||||
2. **Transferencia:** Archivo movido a través de la VPN institucional (Tailscale) en múltiples tramos seguros.
|
|
||||||
3. **Restauración:** Base restaurada exitosamente en 26 segundos (354 MB/s).
|
|
||||||
4. **Verificación:** `DBCC CHECKDB` ejecutado sin errores — integridad de datos confirmada al 100%.
|
|
||||||
|
|
||||||
### 4.4 Fase 5b: Hardening (11 abril)
|
|
||||||
|
|
||||||
Se validó que todos los servicios críticos (SSH, SQL Server) sobreviven reinicios del servidor sin intervención manual:
|
|
||||||
|
|
||||||
- OpenSSH Server: arranque automático ✅
|
|
||||||
- SQL Server: arranque automático ✅
|
|
||||||
- IP de red: mantenida tras reinicio ✅
|
|
||||||
|
|
||||||
### 4.5 Fase 6: Validación funcional en entorno virtual (11 abril)
|
|
||||||
|
|
||||||
Se restauró una VM cliente desde backup para simular el entorno real de la oficina DASUTEN:
|
|
||||||
|
|
||||||
- Se desvinculó la PC del dominio anterior y se unió al Workgroup `DASUTEN`.
|
|
||||||
- Se reconfiguró el archivo de conexión del sistema (`Kermet.ini`) para apuntar al nuevo servidor SQL.
|
|
||||||
- **Resultado:** El sistema DASUTEN conectó exitosamente, actualizó estructuras y descargó novedades.
|
|
||||||
|
|
||||||
**Conclusión técnica clave:** Se determinó mediante ingeniería inversa que el sistema DASUTEN (desarrollado en Visual FoxPro) **no tiene dependencia real del Active Directory** si se configura explícitamente la autenticación SQL en su archivo de configuración.
|
|
||||||
|
|
||||||
### 4.6 Fase 7: Despliegue en PC física de producción (13 abril — EN CURSO)
|
|
||||||
|
|
||||||
Se desplegó el sistema en la PC física de la oficina DASUTEN (`dasu-pc`):
|
|
||||||
|
|
||||||
- **Directorio del sistema** (`C:\SysDasuten`) transferido desde la VM de pruebas.
|
|
||||||
- **Configuración de conexión** apuntada al nuevo servidor `dasu-sql4`.
|
|
||||||
- **Acceso directo** creado en el escritorio de la usuaria (`aalmiron`).
|
|
||||||
- **Pruebas funcionales:**
|
|
||||||
- Inicio del sistema: ✅ OK
|
|
||||||
- Acceso a datos (ventana de bonos): ✅ OK
|
|
||||||
- **Impresión: ❌ FALLO** — Error al imprimir, posiblemente por incompatibilidad de las plantillas de Office (el sistema usa automatización OLE con Word/Excel).
|
|
||||||
|
|
||||||
### 4.7 Fase 8: Refresco final de base de datos (13 abril)
|
|
||||||
|
|
||||||
Se ejecutó un refresco automatizado de la base de datos para asegurar que el sistema arranca con los datos más actualizados del servidor de origen:
|
|
||||||
|
|
||||||
- Backup generado automáticamente desde el origen (44.8 segundos).
|
|
||||||
- Transferido de manera segura (SMB + SCP por VPN).
|
|
||||||
- Restaurado con `RESTORE DATABASE ... WITH REPLACE`.
|
|
||||||
- Integridad verificada con `DBCC CHECKDB` — sin errores.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Estado Actual
|
|
||||||
|
|
||||||
### 5.1 Funcionalidades operativas
|
|
||||||
|
|
||||||
| Funcionalidad | Estado |
|
|
||||||
|:---|:---:|
|
|
||||||
| Acceso al sistema DASUTEN | ✅ Operativo |
|
|
||||||
| Consulta de datos (bonos, registros) | ✅ Operativo |
|
|
||||||
| Conexión a base de datos SQL Server | ✅ Operativo |
|
|
||||||
| Acceso remoto para soporte (Tailscale/SSH) | ✅ Operativo |
|
|
||||||
| Impresión desde el sistema | ❌ Pendiente |
|
|
||||||
|
|
||||||
### 5.2 Problema pendiente: Impresión
|
|
||||||
|
|
||||||
**Descripción:** Al intentar imprimir desde el sistema DASUTEN en la PC física, se produce un error. El diagnóstico preliminar indica que la causa es una incompatibilidad entre las **plantillas de Office** requeridas por el sistema (diseñadas para Office 2003/2007) y la versión de Office actualmente instalada en la PC.
|
|
||||||
|
|
||||||
**Impacto:** La usuaria no puede generar documentos impresos desde el nuevo sistema.
|
|
||||||
|
|
||||||
**Workaround temporal:** Se habilitó acceso remoto (TeamViewer) desde la PC física hacia la VM de pruebas (`dasu-pcv`), donde la impresión funciona correctamente. La usuaria puede continuar operando mientras se resuelve el problema.
|
|
||||||
|
|
||||||
**Plan de resolución:** Verificar y ajustar la versión/configuración de Office en `dasu-pc` para compatibilizarla con las plantillas del sistema.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Beneficios Obtenidos
|
|
||||||
|
|
||||||
1. **Independencia operativa:** DASUTEN ya no depende de la infraestructura de red ni de los servidores de la Facultad.
|
|
||||||
2. **Simplificación:** Se pasó de 3 VMs + Domain Controller a solo 1 VM (SQL Server), reduciendo significativamente la complejidad.
|
|
||||||
3. **Reducción de puntos de falla:** De múltiples (DC, DNS institucional, routing complejo) a uno solo (SQL Server).
|
|
||||||
4. **Acceso remoto robusto:** Gestión técnica asegurada vía VPN (Tailscale) y SSH, sin necesidad de presencia física.
|
|
||||||
5. **Autonomía:** La infraestructura puede ser gestionada independientemente, facilitando una eventual transferencia a Rectorado.
|
|
||||||
6. **Preservación del entorno anterior:** Las VMs del enfoque con DC fueron apagadas y preservadas como respaldo, permitiendo un rollback si fuera necesario.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Cronograma Resumen
|
|
||||||
|
|
||||||
| Fecha | Hito |
|
|
||||||
|:---|:---|
|
|
||||||
| 27/03/2026 | Inicio — Preparación del entorno e inicio de pruebas de concepto |
|
|
||||||
| 28/03/2026 | VM SQL Server de prueba operativa |
|
|
||||||
| 09-10/04/2026 | Servidor SQL definitivo (dasu-sql4) desplegado y operativo |
|
|
||||||
| 11/04/2026 | Base de datos migrada, verificada y hardening completado |
|
|
||||||
| 11/04/2026 | Validación funcional exitosa en entorno virtual |
|
|
||||||
| 13/04/2026 | Despliegue en PC física de producción — sistema operativo con observación de impresión |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Próximos Pasos
|
|
||||||
|
|
||||||
| Prioridad | Acción | Plazo estimado |
|
|
||||||
|:---:|:---|:---|
|
|
||||||
| 🔴 Alta | Resolver problema de impresión (compatibilidad Office/plantillas) | 1-3 días hábiles |
|
|
||||||
| 🟡 Media | Reservar IPs fijas en router ISP (prevenir cambios por DHCP) | 1 semana |
|
|
||||||
| 🟢 Baja | Evaluar apagado definitivo de VMs legacy (DC, SQL antiguo) | Tras validación completa |
|
|
||||||
| 🟢 Baja | Documentar procedimiento de respaldo automatizado de la nueva BD | 2 semanas |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Conclusión
|
|
||||||
|
|
||||||
La migración del sistema DASUTEN a la nueva infraestructura se encuentra **prácticamente completada**, con el sistema operativo y los datos verificados. El enfoque sin Domain Controller demostró ser completamente viable, logrando una **reducción significativa de la complejidad** y de los puntos de falla.
|
|
||||||
|
|
||||||
El único aspecto pendiente es la resolución del problema de impresión, para el cual ya se dispone de un workaround funcional que permite a la usuaria continuar operando sin interrupción.
|
|
||||||
|
|
||||||
Se estima que el proyecto quedará **100% finalizado** dentro de los próximos días hábiles, una vez resuelta la compatibilidad de plantillas de Office.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# ANEXO TÉCNICO
|
|
||||||
|
|
||||||
## A. Inventario de Nodos
|
|
||||||
|
|
||||||
### A.1 Servidor Hipervisor: srv-dasu
|
|
||||||
|
|
||||||
| Campo | Valor |
|
|
||||||
|:---|:---|
|
|
||||||
| **Rol** | Hipervisor Proxmox VE 9.1 |
|
|
||||||
| **IP LAN** | 192.168.1.13 (DHCP ISP) |
|
|
||||||
| **IP VPN** | 100.112.46.104 (Tailscale) |
|
|
||||||
| **Bridge** | vmbr0 sobre enp33s0 |
|
|
||||||
| **RAM** | 15 GB total / ~4 GB libre post-VMs |
|
|
||||||
| **Disco** | 94 GB (68% uso, thin provisioned) |
|
|
||||||
| **Acceso remoto** | Tailscale + SSH |
|
|
||||||
|
|
||||||
### A.2 Servidor SQL: dasu-sql4 (VM 106)
|
|
||||||
|
|
||||||
| Campo | Valor |
|
|
||||||
|:---|:---|
|
|
||||||
| **Rol** | SQL Server 2019 Enterprise |
|
|
||||||
| **SO** | Windows Server 2022 (Español) |
|
|
||||||
| **IP LAN** | 192.168.1.11 (DHCP ISP) |
|
|
||||||
| **IP VPN** | 100.107.15.82 (Tailscale — inestable) |
|
|
||||||
| **Workgroup** | DASUTEN |
|
|
||||||
| **RAM** | 4 GB |
|
|
||||||
| **vCPU** | 2 |
|
|
||||||
| **Disco 1 (sata0)** | 60 GB — Sistema Operativo |
|
|
||||||
| **Disco 2 (sata1)** | 120 GB — Datos SQL (GPT/NTFS "SQLData") |
|
|
||||||
| **Estructura SQL** | F:\DATA, F:\LOG, F:\BACKUP, F:\TEMPDB |
|
|
||||||
| **Autenticación** | Mixta (SQL Auth + Windows) |
|
|
||||||
| **Puerto SQL** | TCP 1433 |
|
|
||||||
| **SSH** | Puerto 7022 (OpenSSH Server, arranque automático) |
|
|
||||||
| **Servicios auto-start** | MSSQLSERVER, sshd |
|
|
||||||
|
|
||||||
### A.3 PC Física Cliente: dasu-pc
|
|
||||||
|
|
||||||
| Campo | Valor |
|
|
||||||
|:---|:---|
|
|
||||||
| **Rol** | Estación de trabajo DASUTEN |
|
|
||||||
| **Patrimonio** | 32120 |
|
|
||||||
| **Ubicación** | Oficina DASUTEN (externa a Facultad) |
|
|
||||||
| **SO** | Windows 10 Pro |
|
|
||||||
| **Workgroup** | DASUTEN |
|
|
||||||
| **IP VPN** | 100.119.233.11 (Tailscale) |
|
|
||||||
| **SSH** | UTNLR@100.119.233.11:7022 |
|
|
||||||
| **CPU** | AMD Ryzen 5 4600G @ 4.30GHz (6 núcleos) |
|
|
||||||
| **RAM** | 8 GB DDR4-3200 |
|
|
||||||
| **Placa Base** | Asus Prime A320M-K |
|
|
||||||
| **Usuaria principal** | Andrea Almirón (`aalmiron`) |
|
|
||||||
| **Directorio DASUTEN** | C:\SysDasuten |
|
|
||||||
| **Config conexión** | C:\SysDasuten\Sistema\Kermet.ini |
|
|
||||||
|
|
||||||
### A.4 VM Cliente de Pruebas: dasu-pcv (VM 107) — Test-Bed
|
|
||||||
|
|
||||||
| Campo | Valor |
|
|
||||||
|:---|:---|
|
|
||||||
| **Rol** | Entorno de pruebas / fallback temporal |
|
|
||||||
| **SO** | Windows 10 |
|
|
||||||
| **Workgroup** | DASUTEN |
|
|
||||||
| **SSH** | Puerto 7022 |
|
|
||||||
| **Estado** | Operativa (usada como fallback vía TeamViewer) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## B. Configuración del Sistema DASUTEN
|
|
||||||
|
|
||||||
### B.1 Archivo de conexión: Kermet.ini
|
|
||||||
|
|
||||||
Parámetros clave configurados para la operación sin Domain Controller:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
SERVER=dasu-sql4
|
|
||||||
DATABASE=sysdasuten
|
|
||||||
UID=sa
|
|
||||||
PWD=[credencial almacenada en bóveda]
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Nota técnica:** El campo `UID` debe estar explícitamente configurado con `sa` (u otra cuenta SQL Auth). Si se deja vacío, el sistema DASUTEN (Visual FoxPro) asume autenticación Windows integrada (SSPI/Kerberos), lo cual falla en entorno Workgroup sin Domain Controller.
|
|
||||||
|
|
||||||
### B.2 Directorio del sistema
|
|
||||||
|
|
||||||
```
|
|
||||||
C:\SysDasuten\
|
|
||||||
├── Sistema\ → Ejecutables VFP + Kermet.ini
|
|
||||||
├── Plantillas\ → Templates Word/Excel para impresión
|
|
||||||
└── [otros directorios de la aplicación]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## C. Base de Datos: sysdasuten
|
|
||||||
|
|
||||||
| Campo | Valor |
|
|
||||||
|:---|:---|
|
|
||||||
| **Nombre** | sysdasuten |
|
|
||||||
| **Motor** | SQL Server 2019 Enterprise |
|
|
||||||
| **Tamaño backup comprimido** | ~1.33 GB |
|
|
||||||
| **Nivel de compatibilidad** | 100 |
|
|
||||||
| **Recovery model** | FULL |
|
|
||||||
| **Integridad (DBCC CHECKDB)** | ✅ Sin errores |
|
|
||||||
| **Tiempo de restauración** | 26 segundos (354 MB/s) |
|
|
||||||
| **Origen de datos** | srvv-fenix (servidor legacy de Facultad) |
|
|
||||||
| **Último refresco** | 13/04/2026 |
|
|
||||||
|
|
||||||
### C.1 Ruta de transferencia del backup
|
|
||||||
|
|
||||||
```
|
|
||||||
srvv-fenix (Origen)
|
|
||||||
│ [BACKUP DATABASE ... WITH COMPRESSION]
|
|
||||||
│ → E:\BK_SQL\sysdasuten_compressed_ADN.bak (~1.33 GB)
|
|
||||||
↓
|
|
||||||
srv-ns8 (Tránsito)
|
|
||||||
│ [SMB/smbget vía dominio]
|
|
||||||
│ → /var/tmp/
|
|
||||||
↓
|
|
||||||
srv-dasu (Tránsito)
|
|
||||||
│ [SCP vía Tailscale]
|
|
||||||
↓
|
|
||||||
dasu-sql4 (Destino)
|
|
||||||
│ [RESTORE DATABASE ... WITH REPLACE]
|
|
||||||
└ → F:\BACKUP\ → F:\DATA\ + F:\LOG\
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## D. VMs Legacy Preservadas (Apagadas)
|
|
||||||
|
|
||||||
| VM ID | Nombre | Rol anterior | Estado |
|
|
||||||
|:---:|:---|:---|:---:|
|
|
||||||
| 100 | dasu-srvv-dc | Domain Controller AD DS | 🛑 Apagada |
|
|
||||||
| 101 | dasu-srvv-sql | SQL Server (dominio) | 🛑 Apagada |
|
|
||||||
| 102 | dasu-pcv | PC cliente (dominio) | 🛑 Apagada |
|
|
||||||
| 103 | dasu-sql2 | SQL Server prueba v1 | 🛑 Descartada |
|
|
||||||
| 104 | dasu-pcv2 | PC prueba v1 | 🛑 Descartada |
|
|
||||||
|
|
||||||
> Las VMs legacy (100-102) se mantienen apagadas como respaldo. Pueden reactivarse en caso de necesitar un rollback al enfoque con Domain Controller.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## E. Accesos Remotos Configurados
|
|
||||||
|
|
||||||
| Nodo | Método | Detalle |
|
|
||||||
|:---|:---|:---|
|
|
||||||
| srv-dasu | Tailscale SSH | 100.112.46.104 |
|
|
||||||
| dasu-sql4 | SSH (LAN) | 192.168.1.11:7022 |
|
|
||||||
| dasu-sql4 | Tailscale | 100.107.15.82 (inestable) |
|
|
||||||
| dasu-pc | Tailscale SSH | 100.119.233.11:7022 |
|
|
||||||
| dasu-pc | AnyDesk | Instalado (GUI) |
|
|
||||||
| dasu-pcv | TeamViewer | Activo (workaround temporal) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## F. Cuenta de Gestión VPN
|
|
||||||
|
|
||||||
| Campo | Valor |
|
|
||||||
|:---|:---|
|
|
||||||
| **Proveedor** | Tailscale |
|
|
||||||
| **Cuenta** | pcdasu0@frlr.utn.edu.ar |
|
|
||||||
| **Nodos vinculados** | srv-dasu, dasu-sql4, dasu-pc, dasu-pcv |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Fin del informe técnico.*
|
|
||||||
@@ -0,0 +1,620 @@
|
|||||||
|
# INFORME TÉCNICO — Migración del Sistema DASUTEN a Nuevo Servidor
|
||||||
|
|
||||||
|
> **Área:** Departamento de Tecnología de la Información y Comunicaciones (DTIC)
|
||||||
|
> **Proyecto:** Migración de infraestructura DASUTEN — Modo Workgroup sin Domain Controller
|
||||||
|
> **Código interno:** A04.P005
|
||||||
|
> **Fecha del informe:** 13 de abril de 2026
|
||||||
|
> **Elaborado por:** Ricardo Monla — Área DTIC, UTN-FRLR
|
||||||
|
> **Versión:** 1.0 — COMPLETADO Y VALIDADO
|
||||||
|
>
|
||||||
|
> *Este documento contiene el detalle técnico completo de la migración. Para la síntesis ejecutiva, ver [01_informe_ejecutivo_DASUTEN.md](01_informe_ejecutivo_DASUTEN.md).*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Resumen Ejecutivo
|
||||||
|
|
||||||
|
Se informa el avance de la migración del sistema de gestión administrativa **D.A.S.U.Te.N** (Dirección de Acción Social de la Universidad Tecnológica Nacional) desde la infraestructura compartida de la Facultad hacia un entorno independiente, autónomo y simplificado.
|
||||||
|
|
||||||
|
**Estado general del proyecto: 100% completado y validado.**
|
||||||
|
|
||||||
|
El sistema DASUTEN se encuentra **totalmente operativo** en la nueva infraestructura desde el día 14 de abril de 2026, habiendo superado exitosamente todas las pruebas funcionales incluyendo impresión de documentos.
|
||||||
|
|
||||||
|
**Validación final (15/04/2026 17:00):** La usuaria **Andrea Almirón (aalmiron)** confirmó que el sistema desktop muestra correctamente los movimientos del día en curso, resolviéndose el problema de datos "desactualizados" tras la corrección de la zona horaria en `dasu-sql4` (UTC-3 Argentina).
|
||||||
|
|
||||||
|
### Logros principales
|
||||||
|
|
||||||
|
- ✅ Servidor de base de datos **completamente migrado** a infraestructura nueva.
|
||||||
|
- ✅ Base de datos de producción **restaurada y verificada** sin errores de integridad.
|
||||||
|
- ✅ Sistema DASUTEN **desplegado y operativo** en la PC física de la usuaria.
|
||||||
|
- ✅ Impresión de documentos **resuelta** mediante symlink.
|
||||||
|
- ✅ Eliminación exitosa de la dependencia del **Active Directory (Domain Controller)**.
|
||||||
|
- ✅ Simplificación de la arquitectura de red — de 3 servidores virtuales a 1.
|
||||||
|
- ✅ Pipeline de backups **automatizado** con drones atómicos.
|
||||||
|
- ✅ Zona horaria **configurada correctamente** (UTC-3 Argentina) en dasu-sql4.
|
||||||
|
- ✅ Validación del usuario **confirmada** (15/04/2026): datos actualizados visibles en el sistema desktop.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Antecedentes y Motivación
|
||||||
|
|
||||||
|
### 2.1 Infraestructura original (en red de Facultad)
|
||||||
|
|
||||||
|
El sistema DASUTEN operaba sobre infraestructura compartida con la Facultad Regional La Rioja, dependiendo de:
|
||||||
|
|
||||||
|
- Un servidor SQL Server alojado en la red interna de la Facultad (`srvv-fenix`).
|
||||||
|
- Un Domain Controller Active Directory para la autenticación de usuarios.
|
||||||
|
- Conectividad de red interna de la Facultad con routing complejo.
|
||||||
|
|
||||||
|
Esta configuración generaba **múltiples puntos de falla**:
|
||||||
|
- Dependencia del dominio institucional para la autenticación.
|
||||||
|
- Complejidad de red con subnetting privado, NAT y gateway interno.
|
||||||
|
- Cualquier cambio en la infraestructura de la Facultad impactaba directamente en DASUTEN.
|
||||||
|
|
||||||
|
### 2.2 Esquema de trabajo provisorio (AnyDesk + PC Virtual)
|
||||||
|
|
||||||
|
Al reubicarse la oficina DASUTEN en un espacio propio alejado de los servidores, se agravaron las fallas de conectividad: la señal de red recorría múltiples puntos intermedios dentro del edificio, y la caída de cualquiera de ellos dejaba a la oficina sin sistema.
|
||||||
|
|
||||||
|
Para dar continuidad al servicio, se gestionó una **línea de internet propia** para la oficina y se implementó el siguiente esquema de trabajo:
|
||||||
|
|
||||||
|
```
|
||||||
|
PC Física (dasu-pc) Servidor srv-dasu (Proxmox VE)
|
||||||
|
Usuarias: Almirón / Molina ├── VM 100: dasu-srvv-dc (Domain Controller AD DS)
|
||||||
|
Acceso: AnyDesk ──────────────────► ├── VM 101: dasu-srvv-sql (SQL Server + BD sysdasuten)
|
||||||
|
(escritorio remoto) └── VM 102: dasu-pcv (PC Virtual ← trabajo aquí)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Componentes requeridos:** 4 (servidor dominio + servidor BD + PC virtual + PC física con AnyDesk).
|
||||||
|
|
||||||
|
**Flujo de trabajo de las usuarias:** PC física → AnyDesk (escritorio remoto) → PC virtual → sistema DASUTEN.
|
||||||
|
|
||||||
|
Este esquema permitió:
|
||||||
|
- Dar continuidad al servicio independientemente de la red interna de la Facultad.
|
||||||
|
- Brindar asistencia técnica 24/7 por parte de DTIC vía acceso remoto.
|
||||||
|
|
||||||
|
Pero implicaba:
|
||||||
|
- Experiencia de trabajo **indirecta** para las usuarias (doble salto).
|
||||||
|
- Cuatro componentes simultáneos que debían estar operativos.
|
||||||
|
- Múltiples puntos de falla que dificultaban el soporte.
|
||||||
|
|
||||||
|
### 2.3 Objetivo del proyecto
|
||||||
|
|
||||||
|
**Migrar el sistema DASUTEN a una infraestructura independiente y simplificada**, eliminando la dependencia del Domain Controller (Active Directory), el esquema de escritorio remoto, y utilizando un esquema de red **Workgroup** directo, con el fin de:
|
||||||
|
|
||||||
|
1. Reducir la complejidad operativa (de 4 componentes a 2).
|
||||||
|
2. Permitir trabajo directo (sin AnyDesk intermedio).
|
||||||
|
3. Minimizar los puntos de falla.
|
||||||
|
4. Garantizar la autonomía operativa de DASUTEN respecto de la infraestructura de la Facultad.
|
||||||
|
5. Facilitar la eventual gestión desde Rectorado (Buenos Aires) si fuera necesario.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Infraestructura Implementada
|
||||||
|
|
||||||
|
### 3.1 Arquitectura de red
|
||||||
|
|
||||||
|
Se implementó una topología de **red plana simplificada**, donde todos los equipos obtienen direccionamiento IP directamente del router del ISP, eliminando subnetting privado y routing interno:
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet
|
||||||
|
│
|
||||||
|
Router ISP (192.168.1.1 — Gateway)
|
||||||
|
│
|
||||||
|
├── srv-dasu (Proxmox VE) → 192.168.1.13
|
||||||
|
│ └── dasu-sql4 (VM 106) → 192.168.1.11 [SQL Server 2019]
|
||||||
|
│
|
||||||
|
└── dasu-pc (PC Física) → DHCP ISP [Estación de trabajo]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Componentes del nuevo entorno
|
||||||
|
|
||||||
|
| Componente | Descripción | Estado |
|
||||||
|
|:---|:---|:---:|
|
||||||
|
| **srv-dasu** | Servidor Proxmox VE 9.1 (Hipervisor) — CPU Intel, 15 GB RAM, 94 GB disco | ✅ Operativo |
|
||||||
|
| **dasu-sql4** (VM 106) | Windows Server 2022 — SQL Server 2019 Enterprise — 4 GB RAM, 2 vCPU, discos 60+120 GB | ✅ Operativo |
|
||||||
|
| **dasu-pc** | PC Física — Windows 10 Pro — AMD Ryzen 5, 8 GB RAM — Oficina DASUTEN | ✅ Operativo |
|
||||||
|
|
||||||
|
### 3.3 Comparativa: Antes vs. Después
|
||||||
|
|
||||||
|
| Aspecto | Enfoque Anterior (con DC + AnyDesk) | Enfoque Actual (Workgroup) |
|
||||||
|
|:---|:---|:---|
|
||||||
|
| **Componentes** | 4 (DC + SQL + PC virtual + PC física) | 2 (SQL + PC física) |
|
||||||
|
| **Forma de trabajo** | Indirecta (AnyDesk → PC virtual) | Directa (sistema en PC) |
|
||||||
|
| **Red** | Privada 10.0.100.x (gateway interno) | DHCP del ISP (red plana) |
|
||||||
|
| **Autenticación SQL** | Windows Integrated (Kerberos) | SQL Auth (mixta) |
|
||||||
|
| **Usuarios** | Active Directory | Usuarios locales Windows |
|
||||||
|
| **DNS** | AD DNS (10.0.100.10) | DNS del ISP (automático) |
|
||||||
|
| **Complejidad de red** | Alta (DC + DNS + dominio + routing) | Mínima (red plana ISP) |
|
||||||
|
| **Puntos de falla** | DC, SQL, red dominio, routing, AnyDesk | Solo SQL |
|
||||||
|
| **VMs necesarias** | 3 (DC + SQL + PC virtual) | 1 (solo SQL) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Fases Ejecutadas y Progreso
|
||||||
|
|
||||||
|
### Resumen de progreso por fase
|
||||||
|
|
||||||
|
| Fase | Descripción | Estado | Progreso |
|
||||||
|
|:---:|:---|:---:|:---:|
|
||||||
|
| 1 | Preparación del entorno (srv-dasu) | ✅ Completada | 100% |
|
||||||
|
| 2 | Creación de VM SQL Server de prueba | ✅ Completada | 100% |
|
||||||
|
| 3 | Creación de VM PC Cliente de prueba | ✅ Completada | 100% |
|
||||||
|
| 4 | VM definitiva dasu-sql4 | ✅ Completada | 100% |
|
||||||
|
| 5 | Migración de base de datos | ✅ Completada | 100% |
|
||||||
|
| 5b | Hardening y persistencia de servicios | ✅ Completada | 100% |
|
||||||
|
| 6 | Validación con PC cliente virtual | ✅ Completada | 100% |
|
||||||
|
| 7 | Despliegue en PC física (producción) | 🚧 En curso | 75% |
|
||||||
|
| 8 | Refresco final de base de datos | ✅ Completada | 100% |
|
||||||
|
|
||||||
|
### 4.1 Fase 1-3: Preparación y pruebas de concepto (27-28 marzo)
|
||||||
|
|
||||||
|
Se preparó el hipervisor Proxmox, se reconfiguró el bridge de red para usar DHCP directo del ISP, y se crearon VMs de prueba para validar la viabilidad del enfoque sin Domain Controller.
|
||||||
|
|
||||||
|
**Hallazgo crítico:** Se detectó y resolvió que el bridge de red estaba configurado con la IP de una red privada anterior, lo cual habría impedido la conectividad de las nuevas VMs.
|
||||||
|
|
||||||
|
### 4.2 Fase 4: Servidor SQL definitivo (9-10 abril)
|
||||||
|
|
||||||
|
Se creó la VM definitiva `dasu-sql4` (VM 106) con una arquitectura optimizada:
|
||||||
|
|
||||||
|
- **Discos separados:** 60 GB para SO + 120 GB exclusivo para datos SQL (buena práctica).
|
||||||
|
- **SQL Server 2019 Enterprise** instalado con autenticación mixta.
|
||||||
|
- **Gestión remota** asegurada vía OpenSSH Server (puerto 7022) y Tailscale VPN.
|
||||||
|
|
||||||
|
### 4.3 Fase 5: Migración de Base de Datos (11 abril)
|
||||||
|
|
||||||
|
Se ejecutó la migración completa de la base de datos de producción:
|
||||||
|
|
||||||
|
1. **Exportación:** Backup comprimido (~1.33 GB) generado desde el servidor origen (`srvv-fenix`).
|
||||||
|
2. **Transferencia:** Archivo movido a través de la VPN institucional (Tailscale) en múltiples tramos seguros.
|
||||||
|
3. **Restauración:** Base restaurada exitosamente en 26 segundos (354 MB/s).
|
||||||
|
4. **Verificación:** `DBCC CHECKDB` ejecutado sin errores — integridad de datos confirmada al 100%.
|
||||||
|
|
||||||
|
### 4.4 Fase 5b: Hardening (11 abril)
|
||||||
|
|
||||||
|
Se validó que todos los servicios críticos (SSH, SQL Server) sobreviven reinicios del servidor sin intervención manual:
|
||||||
|
|
||||||
|
- OpenSSH Server: arranque automático ✅
|
||||||
|
- SQL Server: arranque automático ✅
|
||||||
|
- IP de red: mantenida tras reinicio ✅
|
||||||
|
|
||||||
|
### 4.5 Fase 6: Validación funcional en entorno virtual (11 abril)
|
||||||
|
|
||||||
|
Se restauró una VM cliente desde backup para simular el entorno real de la oficina DASUTEN:
|
||||||
|
|
||||||
|
- Se desvinculó la PC del dominio anterior y se unió al Workgroup `DASUTEN`.
|
||||||
|
- Se reconfiguró el archivo de conexión del sistema (`Kermet.ini`) para apuntar al nuevo servidor SQL.
|
||||||
|
- **Resultado:** El sistema DASUTEN conectó exitosamente, actualizó estructuras y descargó novedades.
|
||||||
|
|
||||||
|
**Conclusión técnica clave:** Se determinó mediante ingeniería inversa que el sistema DASUTEN (desarrollado en Visual FoxPro) **no tiene dependencia real del Active Directory** si se configura explícitamente la autenticación SQL en su archivo de configuración.
|
||||||
|
|
||||||
|
### 4.6 Fase 7: Despliegue en PC física de producción (13-14 abril — COMPLETADA)
|
||||||
|
|
||||||
|
Se desplegó el sistema en la PC física de la oficina DASUTEN (`dasu-pc`):
|
||||||
|
|
||||||
|
- **Directorio del sistema** (`C:\SysDasuten`) transferido desde la VM de pruebas.
|
||||||
|
- **Configuración de conexión** apuntada al nuevo servidor `dasu-sql4`.
|
||||||
|
- **Acceso directo** creado en el escritorio de la usuaria (`aalmiron`).
|
||||||
|
- **Pruebas funcionales:**
|
||||||
|
- Inicio del sistema: ✅ OK
|
||||||
|
- Acceso a datos (ventana de bonos): ✅ OK
|
||||||
|
- **Impresión: ✅ RESUELTO** (2026-04-14) — Symlink `C:\Sistema` → `C:\SysDasuten\Sistema` permite encontrar plantillas Word/Excel.
|
||||||
|
|
||||||
|
**Solución técnica:** El sistema DASUTEN (Visual FoxPro) hardcodea la ruta `C:\Sistema\Word\` para las plantillas. Se creó un symlink que redirige transparentemente al directorio real `C:\SysDasuten\Sistema\`, sin necesidad de modificar el ejecutable compilado.
|
||||||
|
|
||||||
|
### 4.7 Fase 8: Pipeline de Backups (14 abril — COMPLETADO)
|
||||||
|
|
||||||
|
Se implementó un **pipeline automatizado de drones atómicos** para el refresco de la base de datos:
|
||||||
|
|
||||||
|
**Arquitectura del pipeline:**
|
||||||
|
```
|
||||||
|
srvv-fenix → BACKUP TO X:\ (share srv-ns8)
|
||||||
|
↓
|
||||||
|
srv-ns8 → rclone upload → Google Drive
|
||||||
|
↓
|
||||||
|
dasu-sql4 → Invoke-WebRequest → RESTORE DATABASE → DBCC CHECKDB
|
||||||
|
```
|
||||||
|
|
||||||
|
**Drones implementados (6 drones atómicos):**
|
||||||
|
1. `dasuten_exportar_srvv-fenix.rb` — BACKUP DATABASE WITH COMPRESSION
|
||||||
|
2. `dasuten_transferir_srvv-fenix-srv-ns8.rb` — Copy share → /var/tmp
|
||||||
|
3. `dasuten_upload_srv-ns8-drive.rb` — rclone copy → Google Drive
|
||||||
|
4. `dasuten_download_drive-dasu-sql4.rb` — Invoke-WebRequest ← Google Drive
|
||||||
|
5. `dasuten_restaurar_dasu-sql4.rb` — RESTORE DATABASE WITH REPLACE
|
||||||
|
6. `dasuten_verificar-integridad_dasu-sql4.rb` — DBCC CHECKDB
|
||||||
|
|
||||||
|
**Comunicación entre drones:** Cada dron escribe su output en `/tmp/dron_*_output.json`, que el siguiente dron lee para obtener contexto.
|
||||||
|
|
||||||
|
**Resultado:** Backup de 1.3 GB transferido y restaurado en ~35-40 minutos total. Integridad verificada sin errores.
|
||||||
|
|
||||||
|
### 4.8 Fase 8b: Pipeline Diferencial (14 abril — COMPLETADO)
|
||||||
|
|
||||||
|
Se implementó un pipeline optimizado para refrescos diarios usando **backups diferenciales**:
|
||||||
|
|
||||||
|
- **Tamaño:** ~0.4-10 MB vs 1.3 GB del completo (99% más pequeño)
|
||||||
|
- **Duración:** ~5-10 minutos vs 35-40 minutos del completo
|
||||||
|
- **Comando:** `./adn/tools/run bkps run dasuten_diferencial`
|
||||||
|
|
||||||
|
**Integración con bkps.rb:**
|
||||||
|
- `dasuten_full` — Pipeline completo (semanal, lunes 08:00)
|
||||||
|
- `dasuten_diferencial` — Pipeline diferencial (diario, lun-vie 08:00)
|
||||||
|
|
||||||
|
**Módulo común DasuExecutor:** Para eliminar código repetido, se creó `adn/tools/cli/drones/lib/dasu_executor.rb` que centraliza:
|
||||||
|
- Ejecución de PowerShell en dasu-sql4 vía SSH
|
||||||
|
- Lectura de JSON output desde dasu-sql4
|
||||||
|
- Verificación de archivos en fenix vía xp_cmdshell
|
||||||
|
|
||||||
|
**Beneficio:** -52% de líneas de código en los drones refactorizados.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Estado Actual
|
||||||
|
|
||||||
|
### 5.1 Funcionalidades operativas
|
||||||
|
|
||||||
|
| Funcionalidad | Estado |
|
||||||
|
|:---|:---:|
|
||||||
|
| Acceso al sistema DASUTEN | ✅ Operativo |
|
||||||
|
| Consulta de datos (bonos, registros) | ✅ Operativo |
|
||||||
|
| Conexión a base de datos SQL Server | ✅ Operativo |
|
||||||
|
| Acceso remoto para soporte (Tailscale/SSH) | ✅ Operativo |
|
||||||
|
| Impresión desde el sistema | ✅ Resuelto (symlink) |
|
||||||
|
| Backups automatizados | ✅ Pipeline implementado |
|
||||||
|
| Zona horaria del servidor | ✅ UTC-3 Argentina (configurada 15/04/2026) |
|
||||||
|
| Validación del usuario | ✅ Confirmada (Andrea Almirón, 15/04/2026 17:00) |
|
||||||
|
|
||||||
|
### 5.2 Solución de Impresión
|
||||||
|
|
||||||
|
**Descripción:** El sistema DASUTEN (Visual FoxPro) genera documentos mediante OLE Automation con Word/Excel, usando plantillas en `C:\Sistema\Word\` y `C:\Sistema\Excel\`. Sin embargo, el directorio del sistema está en `C:\SysDasuten\Sistema\`.
|
||||||
|
|
||||||
|
**Solución:** Symlink `C:\Sistema` → `C:\SysDasuten\Sistema\`
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
mklink /D C:\Sistema C:\SysDasuten\Sistema
|
||||||
|
```
|
||||||
|
|
||||||
|
**Impacto:** El ejecutable VFP encuentra las plantillas transparentemente sin necesidad de recompilar o modificar el binario.
|
||||||
|
|
||||||
|
### 5.3 Pipeline de Backups
|
||||||
|
|
||||||
|
**Arquitectura implementada:**
|
||||||
|
|
||||||
|
| Tipo | Frecuencia | Tamaño | Duración | Comando |
|
||||||
|
|------|------------|--------|----------|---------|
|
||||||
|
| **Completo** | Semanal (lunes 08:00) | ~1.3 GB | ~35-40 min | `bkps run dasuten_full` |
|
||||||
|
| **Diferencial** | Diario (lun-vie 08:00) | ~0.4-10 MB | ~5-10 min | `bkps run dasuten_diferencial` |
|
||||||
|
|
||||||
|
**Drones atómicos (6 pasos):**
|
||||||
|
1. Exportar (srvv-fenix) → Backup a X:\
|
||||||
|
2. Transferir (srv-ns8) → Copy a /var/tmp
|
||||||
|
3. Upload (srv-ns8) → rclone a Google Drive
|
||||||
|
4. Download (dasu-sql4) → Invoke-WebRequest
|
||||||
|
5. Restaurar (dasu-sql4) → RESTORE DATABASE
|
||||||
|
6. Verificar (dasu-sql4) → DBCC CHECKDB
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Beneficios Obtenidos
|
||||||
|
|
||||||
|
1. **Independencia operativa:** DASUTEN ya no depende de la infraestructura de red ni de los servidores de la Facultad.
|
||||||
|
2. **Simplificación:** Se pasó de 3 VMs + Domain Controller a solo 1 VM (SQL Server), reduciendo significativamente la complejidad.
|
||||||
|
3. **Reducción de puntos de falla:** De múltiples (DC, DNS institucional, routing complejo) a uno solo (SQL Server).
|
||||||
|
4. **Acceso remoto robusto:** Gestión técnica asegurada vía VPN (Tailscale) y SSH, sin necesidad de presencia física.
|
||||||
|
5. **Autonomía:** La infraestructura puede ser gestionada independientemente, facilitando una eventual transferencia a Rectorado.
|
||||||
|
6. **Preservación del entorno anterior:** Las VMs del enfoque con DC fueron apagadas y preservadas como respaldo, permitiendo un rollback si fuera necesario.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Cronograma Resumen
|
||||||
|
|
||||||
|
| Fecha | Hito |
|
||||||
|
|:---|:---|
|
||||||
|
| 27/03/2026 | Inicio — Preparación del entorno e inicio de pruebas de concepto |
|
||||||
|
| 28/03/2026 | VM SQL Server de prueba operativa |
|
||||||
|
| 09-10/04/2026 | Servidor SQL definitivo (dasu-sql4) desplegado y operativo |
|
||||||
|
| 11/04/2026 | Base de datos migrada, verificada y hardening completado |
|
||||||
|
| 11/04/2026 | Validación funcional exitosa en entorno virtual |
|
||||||
|
| 13/04/2026 | Despliegue en PC física de producción |
|
||||||
|
| 14/04/2026 | Impresión resuelta (symlink) + Pipeline de backups implementado |
|
||||||
|
| **15/04/2026 16:30** | **Migración final + Zona horaria corregida (UTC-3 Argentina)** |
|
||||||
|
| **15/04/2026 17:00** | **Validación usuaria: datos actualizados visibles en sistema desktop** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Próximos Pasos
|
||||||
|
|
||||||
|
| Prioridad | Acción | Plazo estimado |
|
||||||
|
|:---:|:---|:---|
|
||||||
|
| ✅ | **Impresión resuelta** (symlink `C:\Sistema`) | Completado 14/04/2026 |
|
||||||
|
| ✅ | **Pipeline de backups implementado** | Completado 14/04/2026 |
|
||||||
|
| ✅ | **Zona horaria corregida** (UTC-3 Argentina) | Completado 15/04/2026 |
|
||||||
|
| ✅ | **Validación del usuario confirmada** (datos actualizados) | Completado 15/04/2026 |
|
||||||
|
| 🟡 Media | Reservar IPs fijas en router ISP (prevenir cambios por DHCP) | 1 semana |
|
||||||
|
| 🟢 Baja | Evaluar apagado definitivo de VMs legacy (DC, SQL antiguo) | Tras validación completa |
|
||||||
|
| 🟢 Baja | Ejecutar backups diferenciales diarios (lun-vie 08:00) | En operación |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Conclusión
|
||||||
|
|
||||||
|
La migración del sistema DASUTEN a la nueva infraestructura se encuentra **100% completada y validada**, con el sistema totalmente operativo y todas las funcionalidades verificadas incluyendo impresión de documentos.
|
||||||
|
|
||||||
|
El enfoque sin Domain Controller demostró ser completamente viable, logrando una **reducción significativa de la complejidad** y de los puntos de falla:
|
||||||
|
- De 4 componentes (DC + SQL + PC virtual + PC física) a 2 (SQL + PC física)
|
||||||
|
- De red privada compleja con routing a red plana DHCP del ISP
|
||||||
|
- De autenticación Kerberos a SQL Auth mixta
|
||||||
|
|
||||||
|
Adicionalmente, se implementó un **pipeline automatizado de backups** con arquitectura de drones atómicos que permite:
|
||||||
|
- Refrescos semanales completos (~1.3 GB, 35-40 min)
|
||||||
|
- Refrescos diarios diferenciales (~0.4-10 MB, 5-10 min)
|
||||||
|
- Integración con el ecosistema ADN de herramientas (`bkps.rb`)
|
||||||
|
|
||||||
|
**Hallazgo crítico (15/04/2026):** La zona horaria incorrecta en `dasu-sql4` ("Romance Standard Time" UTC+2 en lugar de "Argentina Standard Time" UTC-3) era la causa principal de que los datos se vieran "desactualizados". Los backups se realizaban correctamente, pero las fechas se interpretaban con ~5 horas de desplazamiento. Tras la corrección, la usuaria Andrea Almirón confirmó el correcto funcionamiento del sistema.
|
||||||
|
|
||||||
|
El proyecto generó además herramientas reutilizables (DasuExecutor, drones atómicos, módulo de comunicación JSON) que quedan como activo de DTIC para futuros proyectos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# ANEXO TÉCNICO
|
||||||
|
|
||||||
|
## A. Inventario de Nodos
|
||||||
|
|
||||||
|
### A.1 Servidor Hipervisor: srv-dasu
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|:---|:---|
|
||||||
|
| **Rol** | Hipervisor Proxmox VE 9.1 |
|
||||||
|
| **IP LAN** | 192.168.1.13 (DHCP ISP) |
|
||||||
|
| **IP VPN** | 100.112.46.104 (Tailscale) |
|
||||||
|
| **Bridge** | vmbr0 sobre enp33s0 |
|
||||||
|
| **RAM** | 15 GB total / ~4 GB libre post-VMs |
|
||||||
|
| **Disco** | 94 GB (68% uso, thin provisioned) |
|
||||||
|
| **Acceso remoto** | Tailscale + SSH |
|
||||||
|
|
||||||
|
### A.2 Servidor SQL: dasu-sql4 (VM 106)
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|:---|:---|
|
||||||
|
| **Rol** | SQL Server 2019 Enterprise |
|
||||||
|
| **SO** | Windows Server 2022 (Español) |
|
||||||
|
| **IP LAN** | 192.168.1.11 (DHCP ISP) |
|
||||||
|
| **IP VPN** | 100.107.15.82 (Tailscale — inestable) |
|
||||||
|
| **Workgroup** | DASUTEN |
|
||||||
|
| **Zona Horaria** | Argentina Standard Time (UTC-3) — configurada 15/04/2026 |
|
||||||
|
| **RAM** | 4 GB |
|
||||||
|
| **vCPU** | 2 |
|
||||||
|
| **Disco 1 (sata0)** | 60 GB — Sistema Operativo |
|
||||||
|
| **Disco 2 (sata1)** | 120 GB — Datos SQL (GPT/NTFS "SQLData") |
|
||||||
|
| **Estructura SQL** | F:\DATA, F:\LOG, F:\BACKUP, F:\TEMPDB |
|
||||||
|
| **Autenticación** | Mixta (SQL Auth + Windows) |
|
||||||
|
| **Puerto SQL** | TCP 1433 |
|
||||||
|
| **SSH** | Puerto 7022 (OpenSSH Server, arranque automático) |
|
||||||
|
| **Servicios auto-start** | MSSQLSERVER, sshd |
|
||||||
|
|
||||||
|
### A.3 PC Física Cliente: dasu-pc
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|:---|:---|
|
||||||
|
| **Rol** | Estación de trabajo DASUTEN |
|
||||||
|
| **Patrimonio** | 32120 |
|
||||||
|
| **Ubicación** | Oficina DASUTEN (externa a Facultad) |
|
||||||
|
| **SO** | Windows 10 Pro |
|
||||||
|
| **Workgroup** | DASUTEN |
|
||||||
|
| **IP VPN** | 100.119.233.11 (Tailscale) |
|
||||||
|
| **SSH** | UTNLR@100.119.233.11:7022 |
|
||||||
|
| **CPU** | AMD Ryzen 5 4600G @ 4.30GHz (6 núcleos) |
|
||||||
|
| **RAM** | 8 GB DDR4-3200 |
|
||||||
|
| **Placa Base** | Asus Prime A320M-K |
|
||||||
|
| **Usuarias** | Andrea Almirón (`aalmiron`), Romina Molina (`rmolina`) |
|
||||||
|
| **Directorio DASUTEN** | C:\SysDasuten |
|
||||||
|
| **Config conexión** | C:\SysDasuten\Sistema\Kermet.ini |
|
||||||
|
|
||||||
|
### A.4 VM Cliente de Pruebas: dasu-pcv (VM 107) — Test-Bed
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|:---|:---|
|
||||||
|
| **Rol** | Entorno de pruebas / fallback temporal |
|
||||||
|
| **SO** | Windows 10 |
|
||||||
|
| **Workgroup** | DASUTEN |
|
||||||
|
| **SSH** | Puerto 7022 |
|
||||||
|
| **Estado** | Operativa (usada como fallback vía TeamViewer) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## B. Configuración del Sistema DASUTEN
|
||||||
|
|
||||||
|
### B.1 Archivo de conexión: Kermet.ini
|
||||||
|
|
||||||
|
Parámetros clave configurados para la operación sin Domain Controller:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
SERVER=dasu-sql4
|
||||||
|
DATABASE=sysdasuten
|
||||||
|
UID=sa
|
||||||
|
PWD=[credencial almacenada en bóveda]
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Nota técnica:** El campo `UID` debe estar explícitamente configurado con `sa` (u otra cuenta SQL Auth). Si se deja vacío, el sistema DASUTEN (Visual FoxPro) asume autenticación Windows integrada (SSPI/Kerberos), lo cual falla en entorno Workgroup sin Domain Controller.
|
||||||
|
|
||||||
|
### B.2 Directorio del sistema
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\SysDasuten\
|
||||||
|
├── Sistema\ → Ejecutables VFP + Kermet.ini
|
||||||
|
├── Plantillas\ → Templates Word/Excel para impresión
|
||||||
|
└── [otros directorios de la aplicación]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## C. Base de Datos: sysdasuten
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|:---|:---|
|
||||||
|
| **Nombre** | sysdasuten |
|
||||||
|
| **Motor** | SQL Server 2019 Enterprise |
|
||||||
|
| **Tamaño backup comprimido** | ~1.33 GB |
|
||||||
|
| **Nivel de compatibilidad** | 100 |
|
||||||
|
| **Recovery model** | FULL |
|
||||||
|
| **Integridad (DBCC CHECKDB)** | ✅ Sin errores |
|
||||||
|
| **Tiempo de restauración** | 26 segundos (354 MB/s) |
|
||||||
|
| **Origen de datos** | srvv-fenix (servidor legacy de Facultad) |
|
||||||
|
| **Último refresco** | 13/04/2026 |
|
||||||
|
|
||||||
|
### C.1 Ruta de transferencia del backup (LEGACY — reemplazado por pipeline)
|
||||||
|
|
||||||
|
```
|
||||||
|
srvv-fenix (Origen)
|
||||||
|
│ [BACKUP DATABASE ... WITH COMPRESSION]
|
||||||
|
│ → E:\BK_SQL\sysdasuten_compressed_ADN.bak (~1.33 GB)
|
||||||
|
↓
|
||||||
|
srv-ns8 (Tránsito)
|
||||||
|
│ [SMB/smbget vía dominio]
|
||||||
|
│ → /var/tmp/
|
||||||
|
↓
|
||||||
|
srv-dasu (Tránsito)
|
||||||
|
│ [SCP vía Tailscale]
|
||||||
|
↓
|
||||||
|
dasu-sql4 (Destino)
|
||||||
|
│ [RESTORE DATABASE ... WITH REPLACE]
|
||||||
|
└ → F:\BACKUP\ → F:\DATA\ + F:\LOG\
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## G. Pipeline de Backups (NUEVO — 2026-04-14)
|
||||||
|
|
||||||
|
### G.1 Arquitectura de Drones Atómicos
|
||||||
|
|
||||||
|
El pipeline de backups está compuesto por **6 drones atómicos** que se ejecutan secuencialmente, comunicándose entre sí mediante archivos JSON.
|
||||||
|
|
||||||
|
| Dron | Script | Nodo | Función | Output JSON |
|
||||||
|
|------|--------|------|---------|-------------|
|
||||||
|
| 1 | `dasuten_exportar_srvv-fenix.rb` | srvv-fenix | BACKUP DATABASE TO X:\ | `/tmp/dron_export_output.json` |
|
||||||
|
| 2 | `dasuten_transferir_srvv-fenix-srv-ns8.rb` | srv-ns8 | Copy X:\ → /var/tmp | `/tmp/dron_transfer_output.json` |
|
||||||
|
| 3 | `dasuten_upload_srv-ns8-drive.rb` | srv-ns8 | rclone → Google Drive | `/tmp/dron_upload_output.json` |
|
||||||
|
| 4 | `dasuten_download_drive-dasu-sql4.rb` | dasu-sql4 | Invoke-WebRequest ← Drive | `/tmp/dron_download_output.json` |
|
||||||
|
| 5 | `dasuten_restaurar_dasu-sql4.rb` | dasu-sql4 | RESTORE DATABASE | `/tmp/dron_restore_output.json` |
|
||||||
|
| 6 | `dasuten_verificar-integridad_dasu-sql4.rb` | dasu-sql4 | DBCC CHECKDB | `/tmp/dron_verify_output.json` |
|
||||||
|
|
||||||
|
### G.2 Flujo Completo (Semanal)
|
||||||
|
|
||||||
|
```
|
||||||
|
srvv-fenix (SQL Server)
|
||||||
|
↓ BACKUP DATABASE TO DISK = 'X:\' WITH COMPRESSION
|
||||||
|
X:\sysdasuten_FULL_YYYYMMDD_HHMMSS.bak (~1.3 GB)
|
||||||
|
↓ Copy local (share → /var/tmp)
|
||||||
|
srv-ns8 (/var/tmp/)
|
||||||
|
↓ rclone upload
|
||||||
|
Google Drive (drive_bkps-dasu)
|
||||||
|
↓ HTTP Download (Invoke-WebRequest)
|
||||||
|
dasu-sql4 (F:\BACKUP\)
|
||||||
|
↓ RESTORE DATABASE WITH REPLACE
|
||||||
|
sysdasuten ONLINE
|
||||||
|
↓ DBCC CHECKDB
|
||||||
|
✅ VERIFICADO
|
||||||
|
```
|
||||||
|
|
||||||
|
**Duración total:** ~35-40 minutos
|
||||||
|
|
||||||
|
### G.3 Flujo Diferencial (Diario)
|
||||||
|
|
||||||
|
```
|
||||||
|
srvv-fenix (SQL Server)
|
||||||
|
↓ BACKUP DATABASE TO DISK = 'X:\' WITH DIFFERENTIAL
|
||||||
|
X:\sysdasuten_DIF_YYYYMMDD_HHMMSS.bak (~0.4-10 MB)
|
||||||
|
↓ Copy local (share → /var/tmp)
|
||||||
|
srv-ns8 (/var/tmp/)
|
||||||
|
↓ rclone upload
|
||||||
|
Google Drive (drive_bkps-dasu)
|
||||||
|
↓ HTTP Download (Invoke-WebRequest)
|
||||||
|
dasu-sql4 (F:\BACKUP\)
|
||||||
|
↓ RESTORE DATABASE WITH DIFFERENTIAL
|
||||||
|
sysdasuten ONLINE
|
||||||
|
↓ DBCC CHECKDB
|
||||||
|
✅ VERIFICADO
|
||||||
|
```
|
||||||
|
|
||||||
|
**Duración total:** ~5-10 minutos
|
||||||
|
|
||||||
|
### G.4 Comando de Ejecución
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pipeline completo (semanal - lunes 08:00)
|
||||||
|
./adn/tools/run bkps run dasuten_full
|
||||||
|
|
||||||
|
# Pipeline diferencial (diario - lun-vie 08:00)
|
||||||
|
./adn/tools/run bkps run dasuten_diferencial
|
||||||
|
```
|
||||||
|
|
||||||
|
### G.5 Módulo Común: DasuExecutor
|
||||||
|
|
||||||
|
Para eliminar código repetido, se creó el módulo `adn/tools/cli/drones/lib/dasu_executor.rb` que centraliza:
|
||||||
|
|
||||||
|
| Función | Propósito |
|
||||||
|
|---------|-----------|
|
||||||
|
| `execute_ps_on_dasu(ps_script)` | Ejecuta PowerShell en dasu-sql4 vía SSH |
|
||||||
|
| `read_json_from_dasu(path)` | Lee y parsea JSON desde dasu-sql4 |
|
||||||
|
| `execute_ps_on_fenix(client, ps_script)` | Ejecuta PowerShell en fenix vía xp_cmdshell |
|
||||||
|
| `file_exists_on_fenix?(client, path)` | Verifica existencia de archivo en fenix |
|
||||||
|
| `get_file_info_on_fenix(client, path)` | Obtiene tamaño y existencia de archivo |
|
||||||
|
|
||||||
|
**Beneficio:** -52% de líneas de código en los drones refactorizados.
|
||||||
|
|
||||||
|
### G.6 Lecciones Aprendidas
|
||||||
|
|
||||||
|
| Lección | Impacto |
|
||||||
|
|---------|---------|
|
||||||
|
| Google Drive confirmation page | Archivos >100MB requieren extraer parámetros `id`, `uuid`, `confirm` del HTML |
|
||||||
|
| dasu-sql4 sleep mode | VM entra en suspensión — acceso vía relay SSH desde srv-dasu |
|
||||||
|
| Base64 encoding | Scripts PowerShell se codifican en base64 para transporte SSH |
|
||||||
|
| Un solo destino (X:) | Eliminado paso intermedio E:\BK_SQL\sysdasuten\ — menos puntos de falla |
|
||||||
|
| Módulo común | 6 drones, 1 solo patrón de ejecución remota |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## D. VMs Legacy Preservadas (Apagadas)
|
||||||
|
|
||||||
|
| VM ID | Nombre | Rol anterior | Estado |
|
||||||
|
|:---:|:---|:---|:---:|
|
||||||
|
| 100 | dasu-srvv-dc | Domain Controller AD DS | 🛑 Apagada |
|
||||||
|
| 101 | dasu-srvv-sql | SQL Server (dominio) | 🛑 Apagada |
|
||||||
|
| 102 | dasu-pcv | PC cliente (dominio) | 🛑 Apagada |
|
||||||
|
| 103 | dasu-sql2 | SQL Server prueba v1 | 🛑 Descartada |
|
||||||
|
| 104 | dasu-pcv2 | PC prueba v1 | 🛑 Descartada |
|
||||||
|
|
||||||
|
> Las VMs legacy (100-102) se mantienen apagadas como respaldo. Pueden reactivarse en caso de necesitar un rollback al enfoque con Domain Controller.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E. Accesos Remotos Configurados
|
||||||
|
|
||||||
|
| Nodo | Método | Detalle |
|
||||||
|
|:---|:---|:---|
|
||||||
|
| srv-dasu | Tailscale SSH | 100.112.46.104 |
|
||||||
|
| dasu-sql4 | SSH (LAN) | 192.168.1.11:7022 |
|
||||||
|
| dasu-sql4 | Tailscale | 100.107.15.82 (inestable) |
|
||||||
|
| dasu-pc | Tailscale SSH | 100.119.233.11:7022 |
|
||||||
|
| dasu-pc | AnyDesk | Instalado (GUI) |
|
||||||
|
| dasu-pcv | TeamViewer | Activo (workaround temporal) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F. Cuenta de Gestión VPN
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|:---|:---|
|
||||||
|
| **Proveedor** | Tailscale |
|
||||||
|
| **Cuenta** | pcdasu0@frlr.utn.edu.ar |
|
||||||
|
| **Nodos vinculados** | srv-dasu, dasu-sql4, dasu-pc, dasu-pcv |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Historial de versiones
|
||||||
|
|
||||||
|
| Versión | Fecha | Cambios |
|
||||||
|
|:---:|:---:|:---|
|
||||||
|
| 0.1 | 13/04/2026 | Versión inicial (fusión de informe de avance + anexo técnico) |
|
||||||
|
| 0.2 | 13/04/2026 | Se agrega contexto de infraestructura anterior (esquema AnyDesk), versionado, referencia a doc ejecutivo |
|
||||||
|
| 0.3 | 14/04/2026 | Impresión resuelta + Pipeline de backups implementado + DasuExecutor |
|
||||||
|
| **1.0** | **15/04/2026** | **COMPLETADO Y VALIDADO** — Zona horaria corregida (UTC-3), validación usuaria Andrea Almirón confirma sistema operativo |
|
||||||
|
|
||||||
|
*Fin del informe técnico.*
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
require_relative 'adn/tools/cli/drones/lib/dasu_executor'
|
||||||
|
|
||||||
|
ps_script = <<~PSCMD
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
Write-Host "=> Creando C:\\rclone"
|
||||||
|
New-Item -ItemType Directory -Path "C:\\rclone" -Force | Out-Null
|
||||||
|
|
||||||
|
Write-Host "=> Descargando rclone desde internet..."
|
||||||
|
Invoke-WebRequest -Uri "https://downloads.rclone.org/rclone-current-windows-amd64.zip" -OutFile "C:\\rclone\\rclone.zip"
|
||||||
|
|
||||||
|
Write-Host "=> Descomprimiendo rclone..."
|
||||||
|
Expand-Archive -Path "C:\\rclone\\rclone.zip" -DestinationPath "C:\\rclone\\extracted" -Force
|
||||||
|
|
||||||
|
$exe = Get-ChildItem -Path "C:\\rclone\\extracted" -Recurse -Filter "rclone.exe" | Select-Object -First 1
|
||||||
|
Copy-Item $exe.FullName -Destination "C:\\rclone\\rclone.exe" -Force
|
||||||
|
|
||||||
|
Write-Host "=> Limpiando basurilla..."
|
||||||
|
Remove-Item "C:\\rclone\\extracted" -Recurse -Force
|
||||||
|
Remove-Item "C:\\rclone\\rclone.zip" -Force
|
||||||
|
|
||||||
|
Write-Host "=> Escribiendo archivo de configuracion rclone.conf..."
|
||||||
|
$confBody = @"
|
||||||
|
[rmonla-GDrive]
|
||||||
|
type = drive
|
||||||
|
scope = drive
|
||||||
|
token = {"access_token":"ya29.a0Aa7MYirskUiCqAyDAbZH3e5Z7d4qYmRV-DeuBwfAOIm5Gy9MhYtgb0jRjF85i1rks4jchG0FQRXQeY5_FXrbUW5AFqENLq6DfwMJjXoPG0DueLKDrlaH_5EMS7yWM7o_UkOfzrysJxlmfW6JaoWf3hx2TMD3zu6tJlxBsKey2mtJN7gIWYcqeTCotg-YmFcTP5bAbbZm6AaCgYKAaYSARASFQHGX2MiBBuiVNhLdkRtfs4ib1TY2g0209","token_type":"Bearer","refresh_token":"1//0hHqACkNyse9FCgYIARAAGBESNwF-L9Ir_E_IDEhK7SZOgaqwao8Hv3C3gGttRi5FljpNYTc8fDuPTW438sgShcUGsIHQ3-pOU9g","expiry":"2026-04-15T09:50:40.008914201-03:00"}
|
||||||
|
"@
|
||||||
|
[System.IO.File]::WriteAllText("C:\\rclone\\rclone.conf", $confBody)
|
||||||
|
|
||||||
|
Write-Host "=> Finalizado exitosamente. Verificando version:"
|
||||||
|
& C:\\rclone\\rclone.exe --version
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
puts DasuExecutor.execute_ps_on_dasu(ps_script)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Archivo: docs.conf
|
||||||
|
# Ubicación: /servicios/nginx/conf.d/docs.conf
|
||||||
|
# Sirve la documentación de referencia de GEMMA (Protocolos, Guías, etc.)
|
||||||
|
# bajo /docs/<tipo>/<ambito>/<slug> — render on-demand vía gemma-docs-web.service
|
||||||
|
# (WEBrick en 127.0.0.1:4567, ver gemma/tools/docs_web/server.rb). Sin .html
|
||||||
|
# pre-generado: el .md es la única fuente, cada request lo renderiza al vuelo.
|
||||||
|
# (Fragmento sin server block - se incluye desde ns8.conf)
|
||||||
|
|
||||||
|
location /docs/ {
|
||||||
|
proxy_pass http://127.0.0.1:4567/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user