docs & tools: Actualización de informes y scripts de migración DASUTEN

This commit is contained in:
Ricardo Monla
2026-04-20 16:25:51 -03:00
parent faf46be84b
commit 65dd92b769
44 changed files with 3822 additions and 568 deletions
+2 -1
View File
@@ -50,9 +50,10 @@ module ADN
nodo = tarea[:nodo] || detectar_nodo(tarea[:id])
nota = tarea[:texto] || "DASUTEN: #{tarea[:id]}"
ambito = tarea[:ambito] || 'dtic-DASUTEN'
# Lanzar dron con bitácora automática
cmd = "#{@dron_runner} dron lanzar --nota #{Shellwords.escape(nota)} --nodo #{Shellwords.escape(nodo)} -- ruby #{script_path}"
cmd = "#{@dron_runner} dron lanzar --nota #{Shellwords.escape(nota)} --nodo #{Shellwords.escape(nodo)} --ambito #{Shellwords.escape(ambito)} -- ruby #{script_path}"
@logger.info("Ejecutando dron: #{nota}")
@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."
+5 -1
View File
@@ -114,7 +114,8 @@ module Dron
evento_id: opciones[:evento_id],
timeout: opciones[:timeout],
flujo_id: opciones[:flujo_id],
nodo: opciones[:nodo]
nodo: opciones[:nodo],
ambito: opciones[:ambito]
)
# Mostrar resultado
@@ -136,6 +137,7 @@ module Dron
timeout: 3600,
flujo_id: nil,
nodo: nil,
ambito: nil,
cmd: nil
}
@@ -153,6 +155,8 @@ module Dron
opciones[:flujo_id] = args.shift
when '--nodo'
opciones[:nodo] = args.shift
when '--ambito'
opciones[:ambito] = args.shift
when '--help', '-h'
Dispatcher.mostrar_ayuda
exit 0
+23 -5
View File
@@ -24,14 +24,14 @@ module Dron
HEARTBEAT_INTERVAL = 30
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}")
inicio = Time.now
# 1. Registrar en tablas internas (dron_logs)
resultado_inicio = ADN::DB::DronDB.registrar_inicio(
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]
@@ -42,7 +42,7 @@ module Dron
)
# 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
resultado = ejecutar_comando(cmd, dron_id, timeout, evento_id, nota, inicio)
@@ -161,10 +161,11 @@ module Dron
# ─── 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
nodo_id = detectar_nodo_id(nodo)
ambito_id = detectar_ambito_id(ambito)
BitacorasDB::BitacoraDB.with_connection do |db|
if evento_id.to_i > 0
@@ -196,12 +197,14 @@ module Dron
estado: '⏳',
modo: 'P',
bitacora_id: bitacora_hoy['id'],
nodo_id: nodo_id
nodo_id: nodo_id,
ambito_id: ambito_id
)
entrada['id']
else
evento = db.crear_evento(
nodo_id: nodo_id,
ambito_id: ambito_id,
descripcion: descripcion,
inicio: Time.now.strftime('%H:%M'),
estado: '⏳',
@@ -307,6 +310,21 @@ module Dron
Base.logger.warn("⚠️ Error detectando nodo: #{e.message}, usando default (1)")
1
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
@@ -5,8 +5,8 @@
# adn/tools/cli/drones/dasuten_download_drive-dasu-sql4.rb
# ==========================================================
# Dron: Download desde Google Drive a dasu-sql4
# Flujo: Google Drive → F:\BACKUP\sysdasuten_compressed_ADN.bak
# Usa: Invoke-WebRequest (sin rclone)
# Flujo: Google Drive → F:\BACKUP\sysdasuten_FULL_YYYYMMDD_HHMMSS.bak
# Usa: rclone copy (mantiene nombre original del backup)
# ==========================================================
require 'time'
@@ -27,9 +27,12 @@ end.parse!(ARGV)
BACKUP_DIR_WINDOWS = "F:\\BACKUP"
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
backup_name = nil
file_id = nil
if opciones[:test_file]
puts "[*] Dron: Download desde Google Drive"
puts " Nodo: dasu-sql4"
@@ -52,10 +55,20 @@ else
if File.exist?(UPLOAD_OUTPUT)
upload_data = JSON.parse(File.read(UPLOAD_OUTPUT))
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)
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}"
puts "[*] Dron: Download desde Google Drive"
puts " Nodo: dasu-sql4"
puts " Drive ruta: #{drive_ruta}"
puts " File ID: #{file_id}"
@@ -66,55 +79,44 @@ else
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
ps_script = <<~PSCMD.strip
$ErrorActionPreference = "Stop"
$BACKUP_DIR = "F:\\BACKUP"
$DOWNLOAD_URL = "#{download_url}"
$BACKUP_NAME = "#{backup_name}"
$LOCAL_BACKUP = "$BACKUP_DIR\\$BACKUP_NAME"
$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 $BACKUP_DIR
Write-Host "[1/3] Iniciando descarga..."
Write-Host " URL: $DOWNLOAD_URL"
Write-Host "[1/3] Iniciando descarga con Rclone Nativo..."
Write-Host " Origen: $REMOTE_RCLONE_PATH"
Write-Host " Destino: $LOCAL_BACKUP"
Write-Host "[2/3] Descargando..."
$start = Get-Date
try {
$response = Invoke-WebRequest -Uri $DOWNLOAD_URL -UseBasicParsing -Headers @{
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
$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] }
# Limpiamos el ultimo archivo varado (por si el anterior dejo su esqueleto de 0 bytes)
if (Test-Path $LOCAL_BACKUP) { Remove-Item $LOCAL_BACKUP -Force }
Write-Host " File ID: $fileId"
Write-Host " UUID: $uuid"
# 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
$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
Write-Host "[+] Descarga en $([math]::Round($dur, 2))s"
} catch {
Write-Host "[!] Error: $_"
if ($LASTEXITCODE -ne 0) {
Write-Host "[!] Error: Falló la descarga de rclone (Código exit: $LASTEXITCODE)"
exit 1
}
$dur = (New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
Write-Host "[+] Descarga en $([math]::Round($dur, 2))s"
Write-Host "[3/3] Verificando..."
if (Test-Path $LOCAL_BACKUP) {
$tam = (Get-Item $LOCAL_BACKUP).Length
@@ -131,6 +133,7 @@ ps_script = <<~PSCMD.strip
$res = @{
paso = "download_complete"
backup_local = $LOCAL_BACKUP
backup_name = $BACKUP_NAME
drive_origen = "Google Drive"
nodo = "dasu-sql4"
duracion = [math]::Round($dur, 2)
@@ -12,6 +12,7 @@
require 'tiny_tds'
require 'time'
require 'json'
require 'base64'
require_relative 'lib/dasu_executor'
# Nombre del archivo con timestamp
@@ -33,6 +34,10 @@ begin
sql_user = 'UTNlarioja\\monlaricardo'
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?
puts "[!] Error: No se pudo obtener credencial de candados"
@@ -47,8 +52,24 @@ begin
)
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:
puts "\n[0/2] Generando backup diferencial en share X:..."
puts "[2/3] Generando backup diferencial en share X:..."
start = Time.now
sql = <<~SQL.strip
@@ -65,19 +86,22 @@ begin
duracion = (Time.now - start).round(2)
puts "[+] Backup diferencial generado en #{duracion} segundos"
# Verificar archivo en share usando módulo común
puts "\n[1/2] Verificando archivo en share..."
file_info = DasuExecutor.get_file_info_on_fenix(client, BACKUP_FILE)
if file_info[:exists]
puts "[+] Archivo verificado: #{BACKUP_FILE} (#{file_info[:size_mb]} MB)"
# Verificar archivo en share directamente en srv-ns8 vía SSH
puts "\n[3/3] Verificando archivo en share..."
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 $?.success? && ls_result.include?(File.basename(backup_unix))
# 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
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
end
# 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
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"
@@ -12,6 +12,7 @@
require 'tiny_tds'
require 'time'
require 'json'
require 'base64'
require_relative 'lib/dasu_executor'
# Nombre del archivo con timestamp
@@ -34,6 +35,10 @@ begin
sql_user = 'UTNlarioja\\monlaricardo'
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?
puts "[!] Error: No se pudo obtener credencial de candados"
@@ -48,13 +53,23 @@ begin
)
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
puts "\n[0/2] Verificando directorio en share..."
client.execute("EXEC master.sys.xp_create_subdir 'X:\\'")
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 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
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)
puts "[+] Backup generado en #{duracion} segundos"
# Verificar archivo en share usando módulo común
puts "[2/2] Verificando archivo en share..."
file_info = DasuExecutor.get_file_info_on_fenix(client, BACKUP_NS8)
if file_info[:exists]
puts "[+] Archivo verificado: #{BACKUP_NS8} (#{file_info[:size_mb]} MB)"
# Verificar archivo en share directamente en srv-ns8 vía SSH
puts "[3/3] Verificando archivo en share..."
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 $?.success? && ls_result.include?(File.basename(backup_unix))
# 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
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
end
@@ -22,48 +22,60 @@ JSON_PATH = 'C:\temp\dron_restore_output.json'
if File.exist?(DOWNLOAD_OUTPUT)
output = JSON.parse(File.read(DOWNLOAD_OUTPUT))
backup_local = output['backup_local']
backup_name = File.basename(backup_local)
LOCAL_BACKUP = "#{BACKUP_DIR_WINDOWS}\\#{backup_name}"
backup_name = output['backup_name'] || File.basename(backup_local)
LOCAL_BACKUP = backup_local # Usar la ruta EXACTA que viene del download
puts "[*] Dron: Restaurar backup DASUTEN"
puts " Nodo: dasu-sql4"
puts " Backup: #{LOCAL_BACKUP} (desde download)"
puts " Nombre: #{backup_name}"
else
LOCAL_BACKUP = "#{BACKUP_DIR_WINDOWS}\\sysdasuten_FULL_*.bak"
puts "[*] Dron: Restaurar backup DASUTEN (buscar más reciente)"
puts " Nodo: dasu-sql4"
puts " Backup: #{LOCAL_BACKUP} (búsqueda por patrón)"
end
puts " Nodo: dasu-sql4"
puts " Backup: #{LOCAL_BACKUP}"
# PowerShell script para restore
ps_script = <<~'PSCMD'.strip
ps_script = <<~PSCMD.strip
$ErrorActionPreference = "Stop"
$BACKUP_DIR = "F:\BACKUP"
$BACKUP_PATTERN = "sysdasuten_FULL_*.bak"
$JSON_PATH = "C:\temp\dron_restore_output.json"
$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"
$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) {
Write-Host "[!] No se encontró backup completo en $BACKUP_DIR"
exit 1
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"
$backupFile = Get-ChildItem "$BACKUP_DIR\\$BACKUP_PATTERN" | Sort-Object Name -Descending | Select-Object -First 1
if (-not $backupFile) {
Write-Host "[!] No se encontró ningún backup en $BACKUP_DIR"
exit 1
}
$LOCAL_BACKUP = $backupFile.FullName
Write-Host " Fallback: $LOCAL_BACKUP"
}
$LOCAL_BACKUP = $backupFile.FullName
Write-Host " Archivo: $LOCAL_BACKUP"
$backupFile = Get-Item $LOCAL_BACKUP
Write-Host " Tamaño: $([math]::Round($backupFile.Length / 1MB, 2)) MB"
Write-Host "[1/1] Ejecutando RESTORE DATABASE..."
$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 = @"
RESTORE DATABASE sysdasuten
FROM DISK = '$LOCAL_BACKUP'
WITH REPLACE,
MOVE 'sysdasuten' TO 'F:\DATA\sysdasuten.mdf',
MOVE 'sysdasuten_log' TO 'F:\LOG\sysdasuten_log.ldf'
"@
MOVE 'sysdasuten' TO 'F:\\DATA\\sysdasuten.mdf',
MOVE 'sysdasuten_log' TO 'F:\\LOG\\sysdasuten_log.ldf'
"@
Write-Host " SQL: $restoreSql"
@@ -82,6 +94,7 @@ ps_script = <<~'PSCMD'.strip
$res = @{
paso = "restore_complete"
backup_ruta = $LOCAL_BACKUP
backup_name = $BACKUP_NAME
nodo = "dasu-sql4"
duracion_total = $duracionRestore
duracion_restore = $duracionRestore
@@ -16,17 +16,28 @@ require 'shellwords'
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'
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))
BACKUP_LOCAL = output['backup_local']
BACKUP_LOCAL = output['backup_local'] || output['backup_ruta_unix']
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
BACKUP_LOCAL = "/var/tmp/sysdasuten_compressed_ADN.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
puts " Nodo: srv-ns8"
+9 -2
View File
@@ -108,8 +108,15 @@ module DasuExecutor
# Ejecutar script
ssh_cmd = "ssh srv-dasu 'bash #{remote_path}'"
output = `#{ssh_cmd}`
output = output.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') if output
output_buffer = ""
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
bash_file.unlink
+78
View File
@@ -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"
+61
View File
@@ -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
+157
View File
@@ -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
+68
View File
@@ -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"
+100
View File
@@ -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"
+106
View File
@@ -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"
+214
View File
@@ -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"
+319
View File
@@ -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
+103
View File
@@ -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"
+70
View File
@@ -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"
+84
View File
@@ -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"
+70
View File
@@ -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"
+88
View File
@@ -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"
+63
View File
@@ -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"
+79
View File
@@ -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"
+28 -20
View File
@@ -4,6 +4,15 @@
DO $$
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)
CREATE TABLE IF NOT EXISTS bitacoras.planes (
id SERIAL PRIMARY KEY,
@@ -38,24 +47,23 @@ BEGIN
-- Mantener fase_id por compatibilidad temporal (se deprecará en el futuro)
-- Los nuevos hitos deben usar plan_id
-- Crear vista resumen_planes para métricas
CREATE OR REPLACE VIEW bitacoras.resumen_planes AS
SELECT
p.id AS plan_id,
p.codigo,
p.titulo,
p.estado,
a.codigo AS ambito_codigo,
a.nombre AS ambito_nombre,
COUNT(h.id) AS total_hitos,
COUNT(CASE WHEN h.estado = '' THEN 1 END) AS hitos_completados,
COALESCE(SUM(h.horas_presencial), 0) AS total_horas_presencial,
COALESCE(SUM(h.horas_remoto), 0) AS total_horas_remoto,
MIN(h.fecha) AS fecha_inicio_real,
MAX(h.fecha) AS fecha_fin_real
FROM bitacoras.planes p
LEFT JOIN bitacoras.ambitos a ON p.ambito_id = a.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;
END $$;
-- Crear vista resumen_planes para métricas (fuera del bloque DO)
CREATE OR REPLACE VIEW bitacoras.resumen_planes AS
SELECT
p.id AS plan_id,
p.codigo,
p.titulo,
p.estado,
a.nombre AS ambito_nombre,
COUNT(h.id) AS total_hitos,
COUNT(CASE WHEN h.estado = '' THEN 1 END) AS hitos_completados,
COALESCE(SUM(h.horas_presencial), 0) AS total_horas_presencial,
COALESCE(SUM(h.horas_remoto), 0) AS total_horas_remoto,
MIN(h.fecha) AS fecha_inicio_real,
MAX(h.fecha) AS fecha_fin_real
FROM bitacoras.planes p
LEFT JOIN bitacoras.ambitos a ON p.ambito_id = a.id
LEFT JOIN bitacoras.hitos h ON p.id = h.plan_id
GROUP BY p.id, p.codigo, p.titulo, p.estado, a.nombre;