[A04.P005] DASUTEN: Pipeline refactorizado + Documentación + Informes

Refactorización del pipeline de backups DASUTEN:

1. Flujo simplificado (un solo destino):
   - Backup directo a X:\ (share srv-ns8)
   - Eliminado paso intermedio E:\BK_SQL\sysdasuten\
   - Reducido de 3 ubicaciones a 2 (X: → /var/tmp/)

2. Módulo común DasuExecutor (lib/dasu_executor.rb):
   - Centraliza ejecución PowerShell en dasu-sql4
   - Centraliza verificación de archivos en fenix
   - Elimina código repetido en 6 drones (-52% líneas)

3. Drones refactorizados:
   - dasuten_exportar_srvv-fenix.rb (FULL → X:)
   - dasuten_exportar_diferencial_srvv-fenix.rb (DIF → X:)
   - dasuten_transferir_srvv-fenix-srv-ns8.rb (simplificado)
   - dasuten_restaurar_dasu-sql4.rb (usa DasuExecutor)
   - dasuten_restaurar_diferencial_dasu-sql4.rb (usa DasuExecutor)
   - dasuten_download_drive-dasu-sql4.rb (usa DasuExecutor)
   - dasuten_verificar-integridad_dasu-sql4.rb (usa DasuExecutor)

4. Integración BKPs mantenida:
   - T8-T15: Tareas individuales
   - C7: dasuten_full (pipeline semanal)
   - C8: dasuten_diferencial (pipeline diario)

5. Documentación actualizada:
   - A04.P006_Backups-DASUTEN.md: flujo simplificado + módulo común
   - Lecciones aprendidas agregadas

6. Nuevos archivos:
   - Informes técnicos DASUTEN (docs/informes/2026-04_DASUTEN-migracion/)
   - A01.P011/P012: Documentación de drones y pipeline
   - proc_linux.rb, ops.rb: Utilitarios del ecosistema

7. Scripts legacy (para referencia):
   - orquestador_pipeline.rb, orquestador_pipeline_diferencial.rb
   - dasuten_upload_srv-ns8-drive.rb
This commit is contained in:
Ricardo Monla
2026-04-14 20:06:06 -03:00
parent bc1f42db1f
commit 892019fa22
59 changed files with 5438 additions and 42 deletions
@@ -0,0 +1,368 @@
# A01.P011 — Drones de Operaciones: Actualización Automatizada de OS Linux
> **Estado**: 🟢 Fase 2 en progreso
> **Pertenece a**: [A01 — Ecosistema ADN](./A01_dtic-ADN.md)
> **Relacionado**: [A01.P009 — Evolución Dron ADN](./A01.P009_Evolucion-Dron-ADN.md)
> **Fecha**: 2026-04-13
> **Responsable**: Lic. Ricardo MONLA
> **Versión**: 3.0
---
## 📈 Progreso
```
Fase 1: ██████████ 100% Parche de Emergencia (hotfix en bkps.rb) ✅
Fase 2: ████████░░ 80% Desacoplamiento — `dron ops` + candados ✅
Fase 2.5: ████████░░ 80% Estandarización de Fichas de Nodos 🔧
Fase 3: ░░░░░░░░░░ 0% Robustez — Pre-flight, Smart Lock, Reboot Check
Fase 4: █████░░░░░ 50% Anillos de Despliegue (--ring habilitado) 🔧
Fase 5: ░░░░░░░░░░ 0% Observabilidad — Métricas por Nodo
```
---
## 📋 Resumen Ejecutivo
Este plan define la evolución de la capacidad de **Operaciones de Infraestructura** dentro del ecosistema ADN, comenzando por la actualización automatizada de sistemas operativos Linux (`apt update/upgrade`) mediante AtomicDrones.
**Problema raíz:** La funcionalidad de actualizar nodos Linux fue injertada como un parche rápido dentro de la herramienta de backups (`bkps.rb` / `proc_linux.rb`), violando el principio de responsabilidad única y generando acoplamiento semántico incorrecto. Actualizar un OS **no es un backup**.
**Visión:** Un nuevo subcomando `dron ops` (operaciones) que reutilice la infraestructura existente de ADN (`NodosInfo`, `dron/ejecutor.rb`, Bitácora Web) para orquestar tareas de mantenimiento de infraestructura de forma atómica, visible y desacoplada del sistema de backups.
---
## 🎯 Objetivo
Crear un sistema de **Drones de Operaciones** dentro del ecosistema ADN que permita:
1. Actualizar sistemas operativos Linux de forma automatizada, desatendida y atómica (1 dron = 1 nodo).
2. Registrar cada operación individualmente en la Bitácora Web con trazabilidad completa.
3. Reutilizar las herramientas ADN existentes (`NodosInfo`, `dron/ejecutor.rb`, `Dron::Base`) sin duplicar código.
4. Desacoplar completamente esta funcionalidad del módulo de backups (`bkps.rb`).
---
## 🔍 Hallazgos y Diagnóstico
### Auditoría del Estado Actual (2026-04-12)
| Hallazgo | Severidad | Detalle |
|----------|:---------:|---------|
| **Acoplamiento anómalo** | ✅ Resuelto | `proc_linux.rb` vivía en `bkps/lib/` — ahora existe `dron/ops.rb` independiente |
| **Duplicación de lógica de nodos** | ✅ Resuelto | `ops.rb` reutiliza `NodosInfo.extraer_metadata()` |
| **Orquestación vía bkps** | ✅ Resuelto | Comando nuevo: `dron ops update_os --target linux` |
| **Bloqueo interactivo** | ✅ Resuelto | Corregido con `DEBIAN_FRONTEND=noninteractive` |
| **Falta de candados** | 🟡 Media | Nodos con auth por password ahora usan `candados run` + `sshpass` |
| **Fichas no estandarizadas** | 🔴 Alta | El parser de `NodosInfo` falla en ~40% de fichas por formato libre |
### Hallazgo Crítico: Fichas de Nodos No Estandarizadas (2026-04-13)
> **Impacto:** El módulo `NodosInfo` detecta usuarios SSH incorrectos ("Requiere", "El", "Configurado", "ssh") porque las fichas `.md` de nodos no siguen un formato uniforme. Cada nodo fue documentado con estructura distinta.
| Nodo | Campo SSH en ficha | Usuario detectado | Correcto |
|------|-------------------|:-----------------:|:--------:|
| srvv-dns | `**SSH**: ssh rmonla@10.0.10.2` | `ssh` → corregido a `rmonla` | ✅ (con fallback) |
| srvv-docs | `**Acceso**: Requiere permisos docker` | `Requiere` → corregido a `root` | ⚠️ (fallback) |
| srvv-sitio0 | `**Acceso**: El usuario rmonla...` | `El` → corregido a `root` | ⚠️ (fallback) |
| srvv-nginx-rm | `\| **SSH** \| Puerto 7022, usuario root` | `ssh` → corregido a `root` | ⚠️ (fallback) |
| srvv-nginx-rm | `\| **Bóveda** \| srvv-nginx-rm:root` | No detectado | ❌ falta formato |
**Causa raíz:** Las fichas `.md` son prosa libre sin campos obligatorios estandarizados. El parser necesita ~15 regex heurísticas para cubrir variantes, y aun así falla.
### Código Duplicado Eliminado (Fase 2)
| Función | Antes (proc_linux.rb) | Ahora (ops.rb) |
|---------|:-------------------:|------------------------|
| Detectar si es Linux | `es_nodo_linux?()` 40 líneas | `NodosInfo.detectar_so() == :linux` |
| Extraer IP | `extraer_info_nodo()` regex | `NodosInfo.detectar_ip()` |
| Extraer usuario SSH | regex custom | `NodosInfo.detectar_usuario()` + sanitización |
| Auth por password | No soportado | `candados run` + `sshpass` via `clave_boveda` |
### Inventario de Flota Linux (13 nodos detectados)
| Nodo | IP | Rol | Host |
|------|-----|-----|------|
| srv-dasu | 10.0.10.X | Servidor físico | — |
| srv-pmox1 | 10.0.10.201 | Hipervisor Proxmox | — |
| srv-pmox2 | 10.0.10.202 | Hipervisor Proxmox | — |
| srv-pmox3 | 10.0.10.203 | Hipervisor Proxmox | — |
| srv-xen1 | 10.0.10.X | Hipervisor XenServer (legacy) | — |
| srvv-data | 10.0.10.X | VM — Datos | srv-pmoxN |
| srvv-dns | 10.0.10.2 | VM — DNS CoreDNS | srv-pmox1 |
| srvv-docs | 10.0.10.X | VM — Documentación | srv-pmoxN |
| srvv-dtic | 10.0.10.X | VM — Servicios DTIC | srv-pmoxN |
| srvv-koha | 10.0.10.X | VM — Biblioteca Koha | srv-pmoxN |
| srvv-sitio0 | 10.0.10.X | VM — Sitio principal | srv-pmoxN |
| srvv-sitio2 | 10.0.10.X | VM — Sitio secundario | srv-pmoxN |
| srvv-uptime | 10.0.10.X | VM — Monitoreo | srv-pmoxN |
---
## 🗺️ Fases de Implementación
### Fase 1: Parche de Emergencia (Hotfix en bkps.rb) — ✅ COMPLETA
**Objetivo:** Lograr que los drones de actualización Linux funcionen mínimamente, aunque acoplados al módulo de backups.
| ID | Tarea | Estado |
|:--:|-------|:------:|
| F1.T1 | Sustituir `apt` por `apt-get` con `DEBIAN_FRONTEND=noninteractive` | ✅ |
| F1.T2 | Agregar `--force-confdef --force-confold` a DPKG | ✅ |
| F1.T3 | Registrar tipo `linux` en `TIPOS` de `bkps.rb` | ✅ |
| F1.T4 | Corregir `ejecutar_uno()` para leer ficha `.md` del nodo individual | ✅ |
| F1.T5 | Instalar gema `net-ssh` en entorno de usuario | ✅ |
| F1.T6 | Corregir verbo en nota de Bitácora: `🐧 Actualizar` | ✅ |
**Criterio de éxito:**`./adn/tools/run bkps orquestar update_linux_nodes --on-error continue` ejecuta 13 drones individuales visibles en Bitácora Web.
```
Fase 1: ██████████ 100% ✅
```
---
### Fase 2: Desacoplamiento — Nuevo CLI `dron ops` — 🔧 EN PROGRESO
**Objetivo:** Extraer la lógica de operaciones de infraestructura fuera de `bkps.rb` y crear un subcomando propio dentro del sistema de Drones ADN.
**Principio:** Reutilizar, no reimplementar. El 80% del código necesario ya existe en ADN Core.
| ID | Tarea | Descripción | Estado |
|:--:|-------|-------------|:------:|
| F2.T1 | Crear `adn/tools/cli/dron/ops.rb` | Módulo de operaciones de infraestructura | ✅ |
| F2.T2 | Integrar `NodosInfo` como scanner | Filtrado por OS, estado, validación de usuario SSH | ✅ |
| F2.T3 | Conectar con `dron/ejecutor.rb` | Cada nodo = 1 dron ejecutor estándar (auto-bitácora) | ✅ |
| F2.T4 | Registrar en `dispatcher.rb` | Case `'ops'``OpsComando.ejecutar(args)` + ayuda CLI | ✅ |
| F2.T5 | Integrar `candados` para auth password | Nodos con `clave_boveda` usan `candados run` + `sshpass` | ✅ |
| F2.T6 | Deprecar `proc_linux.rb` de bkps | Marcar como legacy, redirigir a nuevo módulo | ⏳ |
| F2.T7 | Eliminar `update_linux_nodes` de `bkps.yml` | Limpieza de la configuración de backups | ⏳ |
**Arquitectura implementada:**
```
┌─────────────────────────────────────────────────────────────────┐
│ ./adn/tools/run dron ops update_os --target linux │
└───────────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Dron::Ops (cli/dron/ops.rb) │
│ │
│ 1. Scanner: NodosInfo.extraer_metadata(nodo) para cada *.md │
│ → filtra por OS explícito (Debian/Ubuntu/Proxmox) │
│ → excluye Windows, firmware, cámaras │
│ → excluye nodos con estado 🔴 │
│ → sanitiza usuario SSH (lista negra + fallback user@IP) │
│ │
│ 2. Por cada nodo Linux: │
│ → Si tiene clave_boveda: │
│ candados run <clave> 'sshpass -p $PASS ssh ...' │
│ → Si no: │
│ ssh -i <key> user@ip 'apt-get update && upgrade' │
│ → Dron::Ejecutor.lanzar(cmd, nota) → Bitácora Web │
└─────────────────────────────────────────────────────────────────┘
```
**Uso actual (funcional):**
```bash
# Listar nodos Linux con tipo de auth (dry-run)
./adn/tools/run dron ops update_os --target linux --dry-run
# Ejecutar con continuación ante fallos
./adn/tools/run dron ops update_os --target linux --on-error continue
# Ejecutar solo un nodo específico
./adn/tools/run dron ops update_os --nodo srvv-dns
# Ayuda completa
./adn/tools/run dron ops --help
```
**Criterio de éxito:** ⚠️ Parcial. El comando funciona y los drones son visibles en Bitácora. Pendiente deprecar `proc_linux.rb` y limpiar `bkps.yml` una vez estandarizadas las fichas.
```
Fase 2: ████████░░ 80%
```
---
### Fase 2.5: Estandarización de Fichas de Nodos — 🔧 EN PROGRESO
**Objetivo:** Definir un formato canónico obligatorio para las fichas `nodos/*.md` que elimine la ambigüedad del parser `NodosInfo` y habilite operaciones confiables de toda herramienta ADN.
| ID | Tarea | Descripción | Estado |
|:--:|-------|-------------|:------:|
| F2.5.T1 | Definir formato canónico | Tabla `<!-- META:BEGIN -->` / `<!-- META:END -->` con campos fijos | ✅ |
| F2.5.T2 | Migrar fichas existentes | 32/32 fichas migradas (14 Linux + 2 otros + 16 Windows) | ✅ |
| F2.5.T3 | Actualizar `NodosInfo` | Parser dual: tabla canónica primero, regex fallback | ✅ |
| F2.5.T4 | Simplificar `ops.rb` | Eliminadas ~30 líneas de sanitización y regex de contenido | ✅ |
| F2.5.T5 | Validador de fichas | `./adn/tools/run nodos validar` → reporta fichas no conformes | ⏳ |
| F2.5.T6 | Actualizar `generar nodo` | Que la plantilla del generador use el formato canónico | ⏳ |
**Formato canónico implementado:**
```markdown
# Nodo: <hostname>
<!-- META:BEGIN -->
| Campo | Valor |
|:------|:------|
| **Hostname** | `<hostname>` |
| **IP** | `<ip>` |
| **OS** | Debian 12 (Bookworm) |
| **Estado** | 🟢 Online |
| **Rol** | Descripción breve |
| **SSH** | `user@ip:port` |
| **Auth** | `key` / `password` / `rsa_legacy` |
| **Bóveda** | `clave:candados` (solo si auth=password) |
| **Host** | `host-anfitrión` (solo VMs) |
| **VMID** | `123` (solo VMs) |
| **Ring** | `test` / `standard` / `core` |
<!-- META:END -->
```
**Resultados:**
- 32/32 fichas migradas con tabla canónica
- `NodosInfo` lee tabla canónica en 1 regex (vs ~15 antes)
- `ops.rb` simplificado: de ~65 líneas de scanner a ~20
- Nodos Linux detectados: 14 → 17 (se descubrieron `dtic-bitacoras`, `srv-ns8`, `srvv-sitio`)
- `--ring test|standard|core` habilitado en `dron ops` (Fase 4 parcial)
```
Fase 2.5: ████████░░ 80%
```
---
### Fase 3: Robustez — Pre-flight, Smart Lock, Reboot Check
**Objetivo:** Hacer que cada dron de actualización sea inteligente: que verifique condiciones previas, detecte errores conocidos y reporte el estado post-actualización.
| ID | Tarea | Descripción | Estado |
|:--:|-------|-------------|:------:|
| F3.T1 | Pre-flight: espacio en disco | Abortar si `/` o `/var` están al >95% | ⏳ |
| F3.T2 | Pre-flight: estado del nodo | Saltar nodos con `**Estado**: 🔴` | ⏳ |
| F3.T3 | Smart Lock detection | Detectar `Could not get lock /var/lib/dpkg/lock` y reportar `❌ dpkg bloqueado` | ⏳ |
| F3.T4 | Reboot Required check | Post-upgrade: `[ -f /var/run/reboot-required ]``⚠️ Reboot needed` | ⏳ |
| F3.T5 | Resumen de paquetes actualizados | Capturar cantidad de paquetes actualizados del output de `apt-get` | ⏳ |
**Flujo enriquecido por nodo:**
```
🐧 Dron update_os para srvv-dns
├── 🔍 Pre-flight checks
│ ├── Disco /: 61% ✔
│ ├── Disco /var: OK ✔
│ └── Estado: 🟢 ✔
├── [1/3] apt-get update ✔
├── [2/3] apt-get upgrade -y ✔ (4 paquetes)
├── [3/3] apt-get autoremove -y ✔
├── 🔍 Post-flight checks
│ └── reboot-required: NO ✔
└── ✅ srvv-dns actualizado (47s)
```
**Criterio de éxito:** Cero drones con errores silenciosos. Cada falencia conocida tiene su propio código de diagnóstico en la nota de Bitácora.
```
Fase 3: ░░░░░░░░░░ 0%
```
---
### Fase 4: Anillos de Despliegue (Rings)
**Objetivo:** No actualizar toda la flota de golpe. Dividir los nodos en anillos de criticidad para evitar caídas simultáneas de servicios dependientes.
| ID | Tarea | Descripción | Estado |
|:--:|-------|-------------|:------:|
| F4.T1 | Definir anillos en `nodos/*.md` | Nuevo campo `**Ring**: test|standard|core` | ⏳ |
| F4.T2 | Ring 0 — Test | Nodos de bajo impacto: srvv-uptime, srvv-sitio2 | ⏳ |
| F4.T3 | Ring 1 — Standard | VMs de servicios: srvv-dns, srvv-docs, srvv-koha | ⏳ |
| F4.T4 | Ring 2 — Core | Hipervisores y data: srv-pmox1/2/3, srvv-data | ⏳ |
| F4.T5 | Pausa entre anillos | Esperar confirmación o timeout entre rings | ⏳ |
**Uso propuesto:**
```bash
# Solo nodos de testing
./adn/tools/run dron ops update_os --ring test
# Despliegue completo con pausa entre anillos
./adn/tools/run dron ops update_os --rings all --pause 300
```
**Criterio de éxito:** Nunca se actualizan los 3 hipervisores Proxmox al mismo tiempo. Los nodos de servicio crítico se actualizan después de validar en el ring de test.
```
Fase 4: ░░░░░░░░░░ 0%
```
---
### Fase 5: Observabilidad — Métricas por Nodo
**Objetivo:** Historial de actualizaciones por nodo visible en la Bitácora Web.
| ID | Tarea | Descripción | Estado |
|:--:|-------|-------------|:------:|
| F5.T1 | Registro de versión de OS | Capturar `lsb_release -d` pre y post-update | ⏳ |
| F5.T2 | Historial de updates por nodo | Consulta: "¿Cuándo fue la última actualización de srvv-dns?" | ⏳ |
| F5.T3 | Alerta de nodos atrasados | `dron ops status` → nodos sin update en >30 días | ⏳ |
| F5.T4 | Dashboard de salud de flota OS | Vista consolidada en Bitácora Web | ⏳ |
**Criterio de éxito:** Con un solo comando se puede ver el estado de actualización de toda la flota Linux, detectando nodos atrasados.
```
Fase 5: ░░░░░░░░░░ 0%
```
---
## 📊 Conclusiones
### Lo que funciona hoy (Fases 1 + 2 + 2.5)
- **17 nodos Linux** detectados correctamente (incluidos 3 Proxmox, 1 XenServer, srv-ns8).
- **Desacoplamiento logrado:** `dron ops update_os` es subcomando propio del sistema de Drones ADN.
- **Candados integrado:** `srvv-nginx-rm` y futuros nodos con password usan la bóveda.
- **Fichas estandarizadas:** 32/32 con tabla `META:BEGIN` / `META:END`.
- **Rings habilitados:** `--ring test|standard|core` filtra la flota.
- **Dry-run informativo:** Muestra nodo, IP, usuario, auth type y ring.
### Desbloqueado por la estandarización
- **Parser simplificado:** `NodosInfo` lee tabla canónica con 1 regex (antes ~15).
- **Scanner limpio:** `ops.rb` pasó de ~65 líneas de workarounds a ~20 líneas claras.
- **Nodos descubiertos:** 3 nodos Linux que antes no se detectaban (`dtic-bitacoras`, `srv-ns8`, `srvv-sitio`).
- **Cero falsos positivos:** Cámaras IP, firmware y Windows correctamente excluidos.
### Métricas de referencia
| Métrica | Antes | Ahora |
|---------|:-----:|:-----:|
| Nodos Linux detectados | 14 | **17** |
| Nodos Windows (excluidos) | 15 | 14 |
| Total fichas en `nodos/` | 32 | 32 |
| Fichas con tabla canónica | 0 | **32** (100%) |
| Fichas con parseo regex (fallback) | 32 | **0** |
| Regex en NodosInfo (operativas) | ~15 | **1** (canónica) |
| Nodos con auth `key` | 13 | **16** |
| Nodos con auth `candados` | 0 | **1** (srvv-nginx-rm) |
| Líneas de scanner en `ops.rb` | ~65 | **~20** |
| Falsos positivos en dry-run | ~2 | **0** |
---
## 📎 Referencias
- [A01.P009 — Evolución Dron ADN](./A01.P009_Evolucion-Dron-ADN.md) — Plan maestro de AtomicDrones
- `adn/tools/cli/dron/ops.rb`**Módulo nuevo de operaciones** (Fase 2, activo)
- `adn/tools/cli/dron/dispatcher.rb` — Dispatcher con `ops` registrado
- `adn/tools/cli/dron/ejecutor.rb` — Ejecutor atómico con auto-bitácora
- `adn/tools/cli/nodos/info.rb` — Módulo `NodosInfo` (scanner de fichas)
- `adn/tools/candados/candados.rb` — Bóveda de credenciales
- `adn/tools/bkps/lib/proc_linux.rb` — Implementación legacy (Fase 1, a deprecar)
---
*Plan actualizado 2026-04-13 — Versión 3.0*
@@ -0,0 +1,640 @@
# A01.P012 — Pipeline de Drones para Backup y Restauración de DASUTEN
> **Estado**: 🟢 Pipeline implementado (2026-04-14)
> **Pertenece a**: [A01 — Ecosistema ADN](./A01_dtic-ADN.md)
> **Relacionado**: [A01.P009 — Evolución Dron ADN](./A01.P009_Evolucion-Dron-ADN.md)
> **Caso de Uso**: [A04.P005 — DASUTEN sin DC](../dtic-DASUTEN/A04.P005_DASUTEN-sin-DC.md)
> **Fecha**: 2026-04-14
> **Responsable**: Lic. Ricardo MONLA
> **Versión**: 2.0 — Con verificación de consistencia (DBCC CHECKDB) de BD
---
## 📈 Progreso
```
Fase 0: ██████████ 100% Diseño de Arquitectura de Pipeline ✅
Fase 1: ██████████ 100% Drones Atómicos Fragmentados ✅
Fase 1.5: ██████████ 100% Orquestador con Intercomunicación JSON ✅
Fase 1.7: ██████████ 100% Implementación Real — Pipeline DASUTEN ✅ (2026-04-14)
Fase 1.8: ██████████ 100% Verificación de Consistencia (DBCC CHECKDB) ✅
Fase 1.9: ██████████ 100% Pipeline Diferencial (Refresco diario) ✅
Fase 2: ░░░░░░░░░░ 0% Persistencia en Bitácora — Outputs en DB
Fase 3: ░░░░░░░░░░ 0% Reintentos Automáticos — Retry con backoff
Fase 4: ░░░░░░░░░░ 0% Condicionales — Skip si output indica "ya hecho"
```
---
## 📋 Resumen Ejecutivo
Este plan documenta el **Pipeline de Drones para Backup y Restauración de DASUTEN** — una implementación real donde múltiples drones atómicos se encadenan para formar el flujo de refresco de base de datos (Fase 8b), comunicándose entre sí mediante **archivos JSON** que actúan como medio de intercambio de contexto.
**Caso de uso:** Refresco de la base de datos `sysdasuten` desde `srvv-fenix` (producción) hasta `dasu-sql4` (nuevo servidor), pasando por Google Drive como intermediario.
**Problema resuelto:** Transferir 1.3 GB de backup entre servidores sin dependencia de Tailscale inestable, usando Google Drive como puente y verificando integridad con DBCC CHECKDB.
**Solución:** Un **orquestador** que:
1. Lanza 6 drones atómicos secuencialmente
2. Lee el output JSON de cada dron completado
3. Inyecta el contexto en el siguiente dron
4. Decide continuar o abortar según el resultado
### Convención de Nombres (desde 2026-04-14)
```
dasuten_<verbo>_<origen-destino>.rb
Ejemplos:
- dasuten_exportar_srvv-fenix.rb (acción en nodo específico)
- dasuten_transferir_srvv-fenix-srv-ns8.rb (transferencia entre nodos)
- dasuten_upload_srv-ns8-drive.rb (upload desde nodo a Drive)
```
---
## 🎯 Objetivo
Implementar un **Pipeline de Drones para el refresco de BD de DASUTEN** que permita:
1. **Fragmentar el flujo de backup** en 6 drones atómicos independientes (1 dron = 1 paso)
2. **Intercomunicar drones** mediante archivos JSON estandarizados (`/tmp/dron_*_output.json`)
3. **Orquestar el refresco completo** con un solo comando desde `srv-ns8`
4. **Registrar trazabilidad** de cada paso en la Bitácora Web
5. **Permitir re-ejecución** de pasos individuales sin re-correr todo el pipeline
6. **Verificar integridad** de la BD restaurada con `DBCC CHECKDB`
---
## 🏗️ Arquitectura del Pipeline
### Diagrama de Flujo (6 Drones Atómicos)
```
┌──────────────────────────────────────────────────────────────────────────┐
│ ORQUESTADOR (orquestador_pipeline.rb) │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Contexto Compartido (Hash Ruby) │ │
│ │ - backup_ruta: ruta del archivo .bak │ │
│ │ - drive_ruta: URL de Google Drive │ │
│ │ - estado_integridad: resultado de DBCC CHECKDB │ │
│ │ - duracion_total: suma de duraciones de drones │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
│ 1. Lanza dron
┌─────────────────┐
│ DRON: │ Nodo: srvv-fenix
│ exportar │ Tarea: BACKUP DATABASE WITH COMPRESSION
│ │ Output: /tmp/dron_export_output.json
└─────────────────┘
│ 2. Orquestador lee JSON, extrae backup_ruta
┌─────────────────┐
│ DRON: │ Nodo: srv-ns8
│ transferir │ Tarea: smbclient //fenix/BK_SQL → /var/tmp/
│ │ Output: /tmp/dron_transfer_output.json
└─────────────────┘
│ 3. Orquestador lee JSON, extrae backup_local
┌─────────────────┐
│ DRON: │ Nodo: srv-ns8
│ upload │ Tarea: rclone copy → Google Drive
│ │ Output: /tmp/dron_upload_output.json
└─────────────────┘
│ 4. Orquestador lee JSON, extrae drive_ruta
┌─────────────────┐
│ DRON: │ Nodo: dasu-sql4
│ download │ Tarea: Invoke-WebRequest ← Google Drive
│ │ Output: /tmp/dron_download_output.json
└─────────────────┘
│ 5. Orquestador lee JSON, extrae backup_local
┌─────────────────┐
│ DRON: │ Nodo: dasu-sql4
│ restaurar │ Tarea: RESTORE DATABASE (sin CHECKDB)
│ │ Output: /tmp/dron_restore_output.json
└─────────────────┘
│ 6. Orquestador lee JSON, decide verificar
┌─────────────────┐
│ DRON: │ Nodo: dasu-sql4
│ verificar │ Tarea: DBCC CHECKDB + validaciones
│ │ Output: /tmp/dron_verify_output.json
└─────────────────┘
✅ PIPELINE COMPLETADO (6 drones)
```
---
## 🧩 Drones Atómicos del Pipeline (6 drones)
Cada dron es **independiente**, **autocontenido** y **auto-bitácorado**.
### Dron 1: `dasuten_exportar_srvv-fenix.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `srvv-fenix` |
| **Tarea** | Generar backup comprimido en SQL Server |
| **Comando** | `BACKUP DATABASE sysdasuten TO DISK = ... WITH COMPRESSION` |
| **Output File** | `/tmp/dron_export_output.json` |
| **Output Clave** | `backup_ruta` |
**Output JSON:**
```json
{
"paso": "export_complete",
"backup_ruta": "E:\\BK_SQL\\sysdasuten_compressed_ADN.bak",
"nodo": "srvv-fenix",
"duracion": 45.2,
"timestamp": "2026-04-14T10:00:00-03:00",
"siguiente_paso": "transferir"
}
```
---
### Dron 2: `dasuten_transferir_srvv-fenix-srv-ns8.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `srv-ns8` |
| **Tarea** | Descargar backup desde srvv-fenix vía SMB |
| **Comando** | `smbclient //10.0.10.200/BK_SQL -c "get sysdasuten_compressed_ADN.bak"` |
| **Output File** | `/tmp/dron_transfer_output.json` |
| **Output Clave** | `backup_local` |
**Output JSON:**
```json
{
"paso": "transfer_complete",
"backup_local": "/var/tmp/sysdasuten_compressed_ADN.bak",
"nodo": "srv-ns8",
"duracion": 120.5,
"timestamp": "2026-04-14T10:02:00-03:00",
"siguiente_paso": "upload"
}
```
---
### Dron 3: `dasuten_upload_srv-ns8-drive.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `srv-ns8` |
| **Tarea** | Subir backup a Google Drive vía rclone |
| **Comando** | `rclone copy /var/tmp/... rmonla-GDrive:drive_bkps-dasu/...` |
| **Output File** | `/tmp/dron_upload_output.json` |
| **Output Clave** | `drive_ruta` |
**Output JSON:**
```json
{
"paso": "upload_complete",
"drive_ruta": "rmonla-GDrive:drive_bkps-dasu/sysdasuten_compressed_ADN_20260414_100500.bak",
"drive_url": "https://drive.google.com/drive/folders/1Tgrn2QYyWf0v0eyY1bSGCLlhiqhtIA2n",
"nodo": "srv-ns8",
"duracion": 180.5,
"timestamp": "2026-04-14T10:05:00-03:00",
"siguiente_paso": "download"
}
```
---
### Dron 4: `dasuten_download_drive-dasu-sql4.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `dasu-sql4` |
| **Tarea** | Descargar backup desde Google Drive vía HTTP |
| **Comando** | `Invoke-WebRequest -Uri "https://drive.usercontent.google.com/download?id=..."` |
| **Output File** | `/tmp/dron_download_output.json` |
| **Output Clave** | `backup_local` |
**Output JSON:**
```json
{
"paso": "download_complete",
"backup_local": "F:\\BACKUP\\sysdasuten_compressed_ADN.bak",
"drive_origen": "rmonla-GDrive:drive_bkps-dasu/sysdasuten_compressed_ADN_20260414_100500.bak",
"nodo": "dasu-sql4",
"duracion": 150.3,
"timestamp": "2026-04-14T10:08:00-03:00",
"siguiente_paso": "restaurar_backup"
}
```
---
### Dron 5: `dasuten_restaurar_dasu-sql4.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `dasu-sql4` |
| **Tarea** | Restaurar backup (solo RESTORE, sin CHECKDB) |
| **Comando** | `RESTORE DATABASE ... WITH REPLACE, MOVE ...` |
| **Output File** | `/tmp/dron_restore_output.json` |
| **Output Clave** | `estado_integridad=PENDING_CHECKDB` |
**Output JSON:**
```json
{
"paso": "restore_complete",
"backup_ruta": "F:\\BACKUP\\sysdasuten_compressed_ADN.bak",
"nodo": "dasu-sql4",
"duracion_restore": 0.23,
"estado_integridad": "PENDING_CHECKDB",
"timestamp": "2026-04-14T10:10:00-03:00",
"siguiente_paso": "verificar_integridad"
}
```
---
### Dron 6: `dasuten_verificar-integridad_dasu-sql4.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `dasu-sql4` |
| **Tarea** | Verificar integridad con DBCC CHECKDB |
| **Comando** | `DBCC CHECKDB('sysdasuten') WITH NO_INFOMSGS, ALL_ERRORMSGS` |
| **Output File** | `/tmp/dron_verify_output.json` |
| **Output Clave** | `estado_integridad`, `pipeline_completo` |
**Output JSON:**
```json
{
"paso": "verify_complete",
"nodo": "dasu-sql4",
"duracion_checkdb": 248.56,
"estado_integridad": "OK",
"paginas_verificadas": 1186793,
"timestamp": "2026-04-14T10:14:00-03:00",
"pipeline_completo": true
}
```
---
## 🎼 Orquestador: `orquestador_pipeline.rb`
El orquestador es el **cerebro** del pipeline. No ejecuta tareas directamente, sino que:
1. **Lanza drones** secuencialmente vía `dron lanzar`
2. **Lee outputs JSON** de cada dron completado
3. **Inyecta contexto** en el siguiente dron (vía ENV o argumentos)
4. **Decide continuar** o abortar según exito/fracaso
5. **Registra estado** en la Bitácora Web
### Código del Orquestador (simplificado)
```ruby
#!/usr/bin/env ruby
# adn/tools/cli/drones/orquestador_pipeline.rb
ESTADOS = {
exportar: {
script: 'dasuten_exportar.rb',
nodo: 'srvv-fenix',
nota: '📤 Exportar backup DASUTEN',
output_file: '/tmp/dron_export_output.json',
siguiente: :upload
},
upload: {
script: 'dasuten_upload_drive.rb',
nodo: 'srv-ns8',
nota: '☁️ Upload a Google Drive',
output_file: '/tmp/dron_upload_output.json',
siguiente: :download
},
# ... más pasos
}.freeze
def lanzar_dron(script, nodo, nota, timeout)
cmd = "#{ADN_RUN} dron lanzar --nota #{nota} --nodo #{nodo} --timeout #{timeout} -- ruby #{script}"
exito = system(cmd)
if exito
# Leer output del dron
output_file = ESTADOS[nodo.to_sym][:output_file]
resultado = JSON.parse(File.read(output_file)) if File.exist?(output_file)
return true, resultado
else
return false, {}
end
end
# Ejecutar pipeline
paso_actual = :exportar
contexto = {}
loop do
break unless ESTADOS[paso_actual]
config = ESTADOS[paso_actual]
exito, resultado = lanzar_dron(config[:script], config[:nodo], config[:nota], timeout)
unless exito
puts "❌ Pipeline fallido en paso: #{paso_actual}"
exit 1
end
contexto.merge!(resultado)
if config[:siguiente]
paso_actual = config[:siguiente]
else
puts "✅ Pipeline completado!"
break
end
end
```
---
## 🚀 Uso del Pipeline
### Ejecutar Pipeline Completo
```bash
# Pipeline completo (todos los pasos encadenados)
./adn/tools/run dron lanzar --nota "Pipeline DASUTEN 8b" -- \
ruby adn/tools/cli/drones/orquestador_pipeline.rb
```
### Ejecutar Drones Individualmente
```bash
# Paso 1: Exportar backup
./adn/tools/run dron lanzar --nota "Exportar backup" --nodo srvv-fenix -- \
ruby adn/tools/cli/drones/dasuten_exportar.rb
# Paso 2: Upload a Drive
./adn/tools/run dron lanzar --nota "Upload Drive" --nodo srv-ns8 -- \
ruby adn/tools/cli/drones/dasuten_upload_drive.rb
# Paso 3: Download desde Drive
./adn/tools/run dron lanzar --nota "Download Drive" --nodo dasu-sql4 -- \
ruby adn/tools/cli/drones/dasuten_download_drive.rb
# Paso 4: Restaurar backup
./adn/tools/run dron lanzar --nota "Restaurar backup" --nodo dasu-sql4 -- \
ruby adn/tools/cli/drones/dasuten_restaurar.rb
```
### Ejecutar desde un Paso Específico
```bash
# Comenzar desde upload (saltear export)
./adn/tools/run dron lanzar --nota "Pipeline DASUTEN" -- \
ruby adn/tools/cli/drones/orquestador_pipeline.rb --paso upload
```
### Dry-Run (Simulación)
```bash
# Ver qué haría sin ejecutar
./adn/tools/run dron lanzar --nota "Pipeline DASUTEN" -- \
ruby adn/tools/cli/drones/orquestador_pipeline.rb --dry-run
```
---
## 📊 Ventajas de esta Arquitectura
| Ventaja | Descripción |
|---------|-------------|
| **🧩 Atomicidad** | Cada dron hace UNA cosa. Fácil de testear, debuggear, reemplazar. |
| **🔗 Intercomunicación** | JSON como contrato entre drones. Lenguaje-agnóstico. |
| **🔄 Re-ejecución** | Si falla el paso 3, re-ejecutar solo paso 3 (no todo el pipeline). |
| **👁️ Observabilidad** | Cada dron se registra individualmente en Bitácora Web. |
| **🛡️ Tolerancia a Fallos** | Si un dron falla, el pipeline se detiene (fail-fast). |
| **📈 Escalabilidad** | Agregar nuevos pasos = agregar nuevo dron + registrar en ESTADOS. |
---
## 📁 Estructura de Archivos
```
adn/tools/cli/drones/
├── orquestador_pipeline.rb # Orquestador principal
├── dasuten_exportar.rb # Dron 1: Exportar backup
├── dasuten_upload_drive.rb # Dron 2: Upload a Drive
├── dasuten_download_drive.rb # Dron 3: Download desde Drive
└── dasuten_restaurar.rb # Dron 4: Restaurar backup
/tmp/
├── dron_export_output.json # Output del dron 1
├── dron_upload_output.json # Output del dron 2
├── dron_download_output.json # Output del dron 3
└── dron_restore_output.json # Output del dron 4
```
---
## 🔮 Futuras Mejoras (Fases Pendientes)
### Fase 2: Persistencia en Bitácora
Actualmente los outputs JSON son efímeros (`/tmp/`). La **Fase 2** persistirá los outputs en la Bitácora Web:
```ruby
# En dron_db.rb
CREATE TABLE drone_outputs (
id SERIAL PRIMARY KEY,
dron_id VARCHAR(50) REFERENCES drones(dron_id),
output JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- El orquestador puede leer outputs de la DB en lugar de archivos
output = DronDB.obtener_output(dron_id)
```
### Fase 3: Reintentos Automáticos
```ruby
# Orquestador con retry
def lanzar_dron_con_retry(script, nodo, nota, timeout, max_retries: 3)
max_retries.times do |intento|
exito, resultado = lanzar_dron(script, nodo, nota, timeout)
return true, resultado if exito
puts "⚠ Dron falló (intento #{intento + 1}/#{max_retries})"
sleep(2 ** intento) # Backoff exponencial
end
return false, {}
end
```
### Fase 4: Condicionales de Skip
```ruby
# Skip si ya está hecho
def deberia_ejecutar?(paso, contexto)
case paso
when :upload
# Skip si ya hay backup reciente en Drive
!backup_reciente_en_drive?(contexto[:backup_ruta])
when :restaurar
# Skip si BD ya está actualizada
!bd_actualizada?(contexto[:backup_local])
else
true
end
end
```
---
## 📝 Lecciones Aprendidas
### ✅ Lo que Funcionó
| Lección | Descripción |
|---------|-------------|
| **JSON como contrato** | Simple, legible, lenguaje-agnóstico. Funciona mejor que DB compartida. |
| **Outputs en /tmp** | Fácil de limpiar, no requiere permisos especiales. |
| **Orquestador stateless** | El estado está en los JSONs, no en el orquestador. |
| **Drones auto-bitácora** | Cada dron se registra individualmente en la Bitácora Web. |
| **Google Drive como intermediario** | Elimina dependencia de Tailscale inestable. Download HTTP directo desde dasu-sql4. |
| **Base64 encoding para PowerShell** | Evita problemas de escaping UTF-8 al transportar scripts sobre SSH. |
| **sshpass + ProxyCommand** | Patrón efectivo para acceder a VMs detrás de relay (dasu-sql4 detrás de srv-dasu). |
### 📊 Resultados del Pipeline DASUTEN (2026-04-14)
| Métrica | Valor |
|---------|-------|
| **Archivo transferido** | 1.3 GB (`sysdasuten_compressed_ADN.bak`) |
| **Tiempo total de pipeline** | ~35 minutos |
| **Upload a Drive** | 29 minutos (1736s) — limitado por ancho de banda |
| **Download HTTP** | ~5 minutos — con confirmación de virus manejada |
| **Restore SQL** | 0.23 segundos — MOVE instantáneo |
| **DBCC CHECKDB** | 248.56 segundos — **Integridad: OK** |
| **Throughput efectivo** | ~650 KB/s (limitado por subida a Drive) |
### ⚠️ Lo que Requiere Atención
| Lección | Descripción |
|---------|-------------|
| **Limpieza de archivos** | Los JSONs en `/tmp/` deben limpiarse post-pipeline. |
| **Timeouts por dron** | Cada dron debe tener timeout individual (no todos duran lo mismo). |
| **Manejo de errores** | El orquestador debe capturar stderr de cada dron para debugging. |
### 🔧 Desafíos Superados
| Desafío | Solución |
|---------|----------|
| **Virus confirmation page** | Parseo de HTML con regex PowerShell para extraer `id`, `uuid`, `confirm` |
| **dasu-sql4 sleep mode** | Acceso vía relay SSH desde srv-dasu con `sshpass -e` + ProxyCommand |
| **PowerShell escaping** | Base64 encoding del script completo + heredoc con `'PSCMD'` (sin interpolación) |
| **sqlcmd no disponible en PATH** | Usar ruta completa: `C:\Program Files\...\sqlcmd.exe` |
---
## 🔗 Referencias
- **Ámbito**: [A01_dtic-ADN.md](./A01_dtic-ADN.md)
- **Evolución Dron**: [A01.P009_Evolucion-Dron-ADN.md](./A01.P009_Evolucion-Dron-ADN.md)
- **Drones Ops**: [A01.P011_Drones-Operaciones-Infra.md](./A01.P011_Drones-Operaciones-Infra.md)
- **Caso de Uso**: [A04.P005_DASUTEN-sin-DC.md](../dtic-DASUTEN/A04.P005_DASUTEN-sin-DC.md) — Pipeline implementado para Fase 8b
---
## 📊 Estado del Pipeline DASUTEN (6 drones atómicos)
| Paso | Estado | Dron | Nodo | Output | Duración |
|------|--------|------|------|--------|----------|
| **8b.1** | ✅ Completado | `dasuten_exportar_srvv-fenix.rb` | srvv-fenix | `backup_ruta` | 44.8s |
| **8b.2** | ✅ Completado | `dasuten_transferir_srvv-fenix-srv-ns8.rb` | srv-ns8 | `backup_local` | ~2 min |
| **8b.3** | ✅ Completado | `dasuten_upload_srv-ns8-drive.rb` | srv-ns8 | `drive_ruta` | 1736s (29 min) |
| **8b.4** | ✅ Completado | `dasuten_download_drive-dasu-sql4.rb` | dasu-sql4 | `backup_local` | ~5 min |
| **8b.5** | ✅ Completado | `dasuten_restaurar_dasu-sql4.rb` | dasu-sql4 | `estado_integridad=PENDING` | 0.23s |
| **8b.6** | ✅ Completado | `dasuten_verificar-integridad_dasu-sql4.rb` | dasu-sql4 | `estado_integridad=OK` | 248.56s |
**Pipeline completado exitosamente el 2026-04-14 a las 17:21.**
### Resultados Finales
- **Backup comprimido:** 1.3 GB (`sysdasuten_compressed_ADN.bak`)
- **Google Drive URL:** `rmonla-GDrive:drive_bkps-dasu/sysdasuten_compressed_ADN_20260414_*.bak`
- **Download HTTP directo:** Con confirmación de virus manejada (formulario HTML parsing)
- **Restore:** 0.23 segundos (MOVE a F:\DATA y F:\LOG)
- **DBCC CHECKDB:** 248.56 segundos — **Integridad: OK** (1,186,793 páginas verificadas, sin errores)
- **Duración total del pipeline:** ~35 minutos
---
## 🔄 Pipeline Diferencial (Refresco Diario)
Para refrescos diarios de la base de datos, se recomienda usar **backup diferencial** que solo transfiere los cambios desde el último backup completo.
### Ventajas del Pipeline Diferencial
| Métrica | Completo | Diferencial |
|---------|----------|-------------|
| **Tamaño backup** | 1.3 GB | ~50-100 MB* |
| **Tiempo export** | 45 seg | ~5-10 seg |
| **Tiempo upload** | 29 min | ~2-3 min |
| **Tiempo download** | ~5 min | ~30 seg |
| **Tiempo total** | ~35 min | ~5-10 min |
\* Depende de la cantidad de cambios diarios
### Drones del Pipeline Diferencial
| Paso | Dron | Nodo | Tarea |
|------|------|------|-------|
| **1** | `dasuten_exportar_diferencial_srvv-fenix.rb` | srvv-fenix | `BACKUP DATABASE ... WITH DIFFERENTIAL` |
| **2** | `dasuten_transferir_srvv-fenix-srv-ns8.rb` | srv-ns8 | SMB → `/var/tmp/` |
| **3** | `dasuten_upload_srv-ns8-drive.rb` | srv-ns8 | rclone → Google Drive |
| **4** | `dasuten_download_drive-dasu-sql4.rb` | dasu-sql4 | HTTP ← Google Drive |
| **5** | `dasuten_restaurar_diferencial_dasu-sql4.rb` | dasu-sql4 | `RESTORE ... WITH DIFFERENTIAL` + `RECOVERY` |
| **6** | `dasuten_verificar-integridad_dasu-sql4.rb` | dasu-sql4 | `DBCC CHECKDB` |
### Orquestador Diferencial
```bash
# Ejecutar pipeline diferencial completo
./adn/tools/run dron lanzar --nota "Refresco Diario DASUTEN" -- \
ruby adn/tools/cli/drones/orquestador_pipeline_diferencial.rb
```
### Estrategia Recomendada
| Día | Tipo de Backup | Duración Estimada |
|-----|----------------|-------------------|
| **Lunes** | Completo | ~35 min |
| **Martes** | Diferencial | ~5-10 min |
| **Miércoles** | Diferencial | ~5-10 min |
| **Jueves** | Diferencial | ~5-10 min |
| **Viernes** | Diferencial | ~5-10 min |
| **Sábado** | Diferencial | ~5-10 min |
| **Domingo** | — (sin cambios) | — |
### Consideraciones Importantes
1. **El backup diferencial se basa en el último backup completo**, no en el diferencial anterior
2. **Cada diferencial es acumulativo** — el martes tiene cambios desde lunes, el miércoles tiene cambios desde lunes (no desde martes)
3. **Restaurar diferencial requiere:**
- Último backup completo (ya restaurado en dasu-sql4)
- Backup diferencial más reciente
- `WITH NORECOVERY` para aplicar diferencial, luego `WITH RECOVERY` para poner BD online
---
*Documento creado: 2026-04-14*
*Última actualización: 2026-04-14*
*Versión: 2.1 — Con pipeline diferencial*
@@ -5,8 +5,8 @@
**Código:** A04.P005
**Fecha:** 27 de marzo de 2026
**Autor:** Sistema ADN
**Versión:** 4.0
**Estado:** 🚧 EN EJECUCIÓN (F5 completada — BD restaurada, pendiente validación con PC cliente)
**Versión:** 5.3
**Estado:** 🚧 EN EJECUCIÓN (F8b — Refresco final de BD pendiente)
**Dependencia:** A04.P001 (Red srv-dasu)
## 📊 Progreso General
@@ -17,7 +17,11 @@
- **Fase 4: Nueva VM dasu-sql4** [██████████] 100% (12/12) ✅
- **Fase 5: Migración BD** [██████████] 100% (5/5) ✅
- **Fase 5b: Hardening** [██████████] 100% (4/4) ✅
- **Fase 6: Validación** [█░░░░░░░░░] 9% (1/10) 🚧
- **Fase 6: Validación** [██████████] 100% (10/10)
- **Fase 7: PC Física** [██████████] 100% (9/9) ✅
- **Fase 8: Refresco BD** [██████████] 100% (4/4) ✅
- **Fase 8b: Refresco Final** [██████████] 100% (4/4) ✅ ¡COMPLETADA!
- **Fase 9: Cierre** [░░░░░░░░░░] 0% (0/4) ⏳
## 📋 Resumen Ejecutivo
@@ -169,7 +173,7 @@ Crear dos VMs nuevas (SQL Server + PC cliente) en `srv-dasu`, instalar el sistem
- [x] **6.10:** **Decisión**: ¿Se puede prescindir del DC definitivamente?
-**SÍ** → El esquema Workgroup es 100% viable. Se valida que el cliente DASUTEN funciona modificando el `Kermet.ini`. Con esta prueba exitosa en la máquina virtual cliente (Test-Bed `dasu-pcv`), procedemos oficialmente a desplegar este esquema en Producción Física.
### 🚀 **FASE 7: Migración a Producción (PC Física)** *(A iniciar 2026-04-11)*
### 🚧 **FASE 7: Migración a Producción (PC Física)** *(En curso 2026-04-13)*
> **Contexto:** Habiendo superado con éxito la reingeniería en el entorno controlado de la VM de prueba (`dasu-pcv`), el objetivo ahora es trasladar esta misma configuración (Workgroup + Configuración DB directa) a la PC física real operada por la usuaria de DASUTEN.
@@ -177,20 +181,61 @@ Crear dos VMs nuevas (SQL Server + PC cliente) en `srv-dasu`, instalar el sistem
> **Motivo:** Para que la resolución de nombres NetBIOS funcione de manera más armónica y el firewall perciba los nodos como "Red Privada" simétrica, todos los participantes deben pertenecer estrictamente al Group `DASUTEN`.
- [x] **PC Cliente Virtual (`dasu-pcv`):** Grupo `DASUTEN` asignado previamente. ✅
- [x] **Servidor SQL (`dasu-sql4`):** Modificar grupo de trabajo actual a `DASUTEN`. ✅ (Aplicado remotamente vía SSH)
- [ ] **PC Física (`dasu-pc`):** Se modificará a `DASUTEN` durante el relevamiento.
- [x] **PC Física (`dasu-pc`):** Ya estaba en Workgroup `DASUTEN`. ✅ *(Confirmado 2026-04-13)*
#### 7.2 Relevamiento de la PC Física
- [ ] **Acceso inicial:** Gestionar acceso por AnyDesk/TeamViewer a la PC física de la entidad.
- [ ] **Validar estado actual:** Corroborar si aún sigue atada al dominio antiguo y si la usuaria está utilizando un perfil local o de dominio cacheado para evitar pérdida de archivos al mover a Workgroup.
- [ ] **Configuración DASUTEN previo:** Backup preventivo de su directorio `C:\SysDasuten\Sistema\Kermet.ini`.
#### 7.2 Relevamiento y Despliegue en PC Física *(completado 2026-04-13)*
- [x] **Acceso inicial:** SSH habilitado (`UTNLR@100.119.233.11:7022`). Tailscale y AnyDesk activos. ✅
- [x] **Validar estado actual:** Win10 Pro, Workgroup `DASUTEN`, hostname `dasu-pc`, perfil local `aalmiron`. ✅
- [x] **Despliegue SysDasuten:** `C:\SysDasuten` no existía previamente. Se copió el directorio completo desde `dasu-pcv` (montaje disco VM NTFS → tar.gz → HTTP LAN srv-dasu:8080 → PowerShell download → extracción). ✅
- [x] **Kermet.ini verificado:** `SERVER=dasu-sql4`, `UID=sa`, `DATABASE=sysdasuten`. ✅
- [x] **Acceso directo creado** en el escritorio de `aalmiron`. ✅ *(El .lnk generado por WScript.Shell no funcionó; se recreó manualmente).*
#### 7.2 Migración de Configuración en PC Física
- [ ] **Ajuste Workgroup:** Desvincular del dominio obsoleto y agregar al grupo de trabajo `DASUTEN` sin afectar la información local.
- [ ] **Re-conexión al SQL (Nuevo esquema):** Inyectar el `Kermet.ini` modificado con `UID=sa` y **`SERVER=dasu-sql4`**. *(Nota: Se comprobó que usar el Hostname en lugar de la IP funciona perfectamente gracias a la resolución de nombres del grupo de trabajo compartido, otorgando resiliencia ante rotación del DHCP).*
- [ ] **Pruebas de Usuario Final:** Loguearse al sistema con credenciales productivas y validar.
#### 7.3 Prueba Funcional con Usuaria *(parcial 2026-04-13)*
> **Usuaria:** Andrea Almirón (`aalmiron`)
#### 7.3 Destrucción Ecosistema Antiguo
- [ ] Proceder al apagado seguro de las VMs legacy que sostenían el Active Directory (VM 100, VM 101).
- [x] **Inicio rápido del sistema:** ✅ OK — Arranca sin errores.
- [x] **Acceso a ventana de bonos:** ✅ OK — Datos visibles y navegables.
- [x] **Impresión desde el sistema:** ✅ OK — **RESUELTO (2026-04-14)**. El symlink `C:\Sistema → C:\SysDasuten\Sistema` fue la solución definitiva. Las plantillas Word/Excel se localizan correctamente.
#### 7.4 Workaround Temporario *(resuelto 2026-04-13)*
> Mientras se resuelve el problema de impresión, se solicitó a la usuaria que continúe operando mediante el sistema anterior (vía acceso remoto a `dasu-pcv`).
- [x] **RustDesk reinstalado en dasu-pcv:** ❌ No resolvió inicialmente. Se actualizaron también las claves Kaspersky (antivirus vencido causaba cortes de red). Persistió el problema.
- [x] **Verificación cruzada desde srv-ns8:** ❌ Confirmado: RustDesk no conecta desde ningún nodo → problema con los servidores relay propios de RustDesk (no es local).
- [x] **Solución provisional: TeamViewer instalado** en ambas máquinas (dasu-pcv + dasu-pc). Interconexión configurada y verificada con éxito. ✅
- [x] **Usuaria informada:** Operando en sistema legacy vía TeamViewer mientras finaliza tareas urgentes. ✅
- [x] **HALLAZGO RustDesk (2026-04-13 ~16:00):** Se descubrió que RustDesk implementó una nueva política de validación contra su servidor central que antes no existía. Esta validación era la causa raíz de las fallas de conexión entre nodos. Se verificó reconectando exitosamente `srv-ns8 → dasu-pcv` tras aceptar la nueva validación. ✅
- ⚠️ **Acción pendiente:** Reconfigurar RustDesk en `dasu-pcv` (y `dasu-pc`) para que funcione con la nueva política, restaurando el acceso remoto directo sin depender de TeamViewer.
#### 7.5 Diagnóstico de Impresión — Ingeniería Inversa *(en curso 2026-04-13)*
> **Estrategia:** El sistema DASUTEN (Visual FoxPro) genera documentos de impresión mediante **OLE Automation** con Word/Excel, usando plantillas. Se usa `dasu-pcv` como entorno de diagnóstico para hacer ingeniería inversa.
##### 7.5.1 Ingeniería inversa — Resultados (2026-04-13 ~18:20)
- [x] **Plantillas localizadas:** `C:\SysDasuten\Sistema\Word\` (97 archivos `.doc` — formato Word 97-2003) + `C:\SysDasuten\Sistema\Excel\Recibo.xls`. ✅
- [x] **Kermet.ini verificado:** No contiene rutas a plantillas — solo config de BD. Las rutas se resuelven dentro del ejecutable VFP. ✅
- [x] **EXE VFP analizado:** `DasutenSQL.exe` compilado desde `c:\users\lis\fox\generalprueba\`. No hardcodea rutas a plantillas — las resuelve relativamente a `.\Word\` y `.\Excel\`. ✅
- [x] **Office en dasu-pcv:** Microsoft **Office 2010** (v14.0.6024.1000) — `C:\Program Files (x86)\Microsoft Office\Office14\`. ✅
- [x] **Office en dasu-pc:** Microsoft **Office 2016/365** (Office16) — `C:\Program Files\Microsoft Office\Office16\`. ✅
##### 🔴 CAUSA RAÍZ IDENTIFICADA (2026-04-13 ~18:28)
> **Ruta de plantillas incorrecta.** El archivo `leeme!!!!.txt` del sistema indica que las plantillas deben estar en **`C:\Sistema\Word\`** (ruta legacy hardcodeada en el ejecutable VFP). Sin embargo, en la configuración actual las plantillas están en `C:\SysDasuten\Sistema\Word\`. El directorio `C:\Sistema` **no existe** en ninguno de los dos equipos, por lo que el sistema no encuentra las plantillas al intentar imprimir.
>
> **La versión de Office (2010 vs 2016) es una diferencia pero NO la causa principal.**
##### 7.5.2 Resolución — Symlink *(parcial 2026-04-13, continúa 2026-04-14)*
- [x] **Symlink creado en dasu-pcv:** `mklink /D C:\Sistema C:\SysDasuten\Sistema` → enlace simbólico que redirige `C:\Sistema\` a `C:\SysDasuten\Sistema\`. ✅ Verificado: `C:\Sistema\Word\Consulta.doc` resuelve correctamente.
- [x] **OLE Word funcional en dasu-pcv (Session 0):** `New-Object -ComObject Word.Application` → Word 14.0 responde. ✅ Nota: `Documents.Open()` requiere sesión de escritorio interactiva (no funciona por SSH/Session 0).
##### 7.5.3 Pendiente — Validación interactiva en dasu-pcv *(próxima sesión)*
> ⚠️ **La prueba OLE por SSH (Session 0) no alcanza** — Word no puede abrir documentos sin escritorio activo. Se requiere sesión GUI.
- [x] **Paso 1:** Conectar a `dasu-pcv` vía RustDesk (nueva política) o TeamViewer (ya configurado). ✅
- [x] **Paso 2:** Abrir DASUTEN (`C:\SysDasuten\Sistema\DasutenSQL.exe`) desde el escritorio. ✅
- [x] **Paso 3:** Intentar imprimir (ej. bono de consulta) → symlink resolvió el error de plantillas. ✅
- [x] **Paso 4:** Replicado en `dasu-pc` (PC física). **Impresión funcional en producción.**
#### 7.6 Destrucción Ecosistema Antiguo *(pospuesto → Fase 9)*
> Movido a Fase 9 (Cierre del proyecto) tras validación completa.
### 🔄 **FASE 8: Refresco de BD de Producción Final** *(Orquestación)*
@@ -205,6 +250,62 @@ Crear dos VMs nuevas (SQL Server + PC cliente) en `srv-dasu`, instalar el sistem
- [x] **Restauración Transaccional:** Ejecución de `RESTORE DATABASE ... WITH REPLACE` asegurando el forzado a Single User (ejecutado directo, el Dron arrojó error de parsing UTF8 por los mensajes en ISO-8859-1 de `sqlcmd`). ✅
- [x] **Verificación Post-Restore:** Comprobación `DBCC CHECKDB` certificando la consistencia final de la DB. ✅
### 🚧 **FASE 8b: Refresco Final de BD** *(En curso 2026-04-14)*
> **Contexto:** Con la PC física operativa e impresión funcionando, se requiere un último refresco de la BD desde srvv-fenix para que dasu-sql4 tenga los datos más recientes (transacciones de los últimos días).
>
> **NUEVO ENFOQUE (2026-04-14):** Se reemplaza la cadena de transferencias SMB→SCP→SCP por **Google Drive como medio intermedio**, optimizando el flujo y eliminando dependencias de Tailscale inestable. Se implementa arquitectura de **Drones Atómicos Interconectados** con comunicación vía JSON.
#### Pipeline de transferencia (NUEVO — Google Drive)
```
srvv-fenix (BACKUP WITH COMPRESSION)
↓ SMB (rápido, LAN)
srv-ns8 (/var/tmp/)
↓ rclone (subida)
Google Drive (carpeta compartida drive_bkps-dasu)
↓ HTTP Direct Download (Invoke-WebRequest)
dasu-sql4 (F:\BACKUP\)
↓ RESTORE DATABASE WITH REPLACE
→ sysdasuten ONLINE
```
#### Drones Atómicos Implementados
| Dron | Nodo | Tarea | Output JSON |
|------|------|-------|-------------|
| `dasuten_exportar.rb` | srvv-fenix | BACKUP DATABASE WITH COMPRESSION | `backup_ruta` |
| `dasuten_upload_drive.rb` | srv-ns8 | rclone copy → Google Drive | `drive_ruta`, `drive_url` |
| `dasuten_download_drive.rb` | dasu-sql4 | Invoke-WebRequest ← Google Drive | `backup_local`, `duracion` |
| `dasuten_restaurar.rb` | dasu-sql4 | RESTORE + DBCC CHECKDB | `estado_integridad`, `pipeline_completo` |
#### Orquestador
- **Script:** `adn/tools/cli/drones/orquestador_pipeline.rb`
- **Función:** Lanza drones secuencialmente, lee outputs JSON, inyecta contexto al siguiente dron
- **Comando:** `./adn/tools/run dron lanzar --nota "Pipeline DASUTEN" -- ruby adn/tools/cli/drones/orquestador_pipeline.rb`
#### Estado Actual — Resultados Finales
| Paso | Estado | Duración | Resultado |
|------|--------|----------|-----------|
| **8b.1** | ✅ Completado | 44.8s | Backup generado en `srvv-fenix` |
| **8b.2** | ✅ Completado | 1736s (29 min) | Upload a Google Drive (1.4 GB) |
| **8b.3** | ✅ Completado | ~5 min | Download HTTP directo en `dasu-sql4` |
| **8b.4** | ✅ Completado | 248.79s (4 min) | Restore + DBCC CHECKDB — Integridad: **OK** |
**Pipeline completado exitosamente.** Base de datos `sysdasuten` ONLINE en `dasu-sql4` con datos actualizados al 2026-04-14.
#### Lecciones Técnicas — Google Drive Download
- **Virus confirmation page:** Google Drive muestra página de confirmación para archivos >100MB. Se maneja extrayendo parámetros `id`, `uuid`, `confirm` del HTML y construyendo URL confirmada: `https://drive.usercontent.google.com/download?id=...&confirm=...&uuid=...`
- **dasu-sql4 sleep mode:** La VM entra en suspensión cuando no se usa. Acceso recomendado vía relay SSH desde `srv-dasu` con `sshpass` + ProxyCommand.
- **Base64 encoding:** Scripts PowerShell se codifican en base64 para transporte sobre SSH, evitando problemas de escaping UTF-8.
### ⏳ **FASE 9: Cierre del Proyecto** *(pendiente — tras resolver impresión)*
> **Contexto:** Una vez que la impresión funcione en la PC física y el sistema esté 100% operativo, se procede al cierre formal del proyecto.
- [ ] **9.1:** Validación final completa con usuaria (todos los flujos funcionales incluyendo impresión). ✅/❌
- [ ] **9.2:** Reservar IPs fijas en router ISP para `dasu-sql4` y `dasu-pc` (evitar cambios por DHCP).
- [ ] **9.3:** Apagado seguro de VMs legacy: VM 100 (DC), VM 101 (SQL dominio). Documentar estado final.
- [ ] **9.4:** Actualizar informe ejecutivo (`docs/informes/2026-04_DASUTEN-migracion/01_informe_ejecutivo_DASUTEN.md`) a versión definitiva y elevar a autoridades.
---
| **Red** | Privada 10.0.100.x (gw interno) | DHCP del ISP (gw del ISP) |
| **Autenticación SQL** | Windows Integrated (Kerberos) | SQL Auth (mixta) |
@@ -244,6 +345,8 @@ Crear dos VMs nuevas (SQL Server + PC cliente) en `srv-dasu`, instalar el sistem
- **Python `http.server` NO soporta descargas grandes (~1GB+):** `Invoke-WebRequest` y `Net.WebClient.DownloadFile()` en PowerShell fallan con "conexión terminada inesperadamente" al descargar archivos de ~1.3GB desde `python3 -m http.server`. El módulo es single-threaded y no maneja correctamente respuestas chunked/grandes. **Solución**: instalar OpenSSH Server en la VM destino Windows y transferir vía SCP por la LAN (gigabit), que es nativo y confiable para archivos grandes.
- **W-Zombi se bloquea en comandos largos:** Cuando el agente PS1 ejecuta un comando que tarda minutos (ej. `Add-WindowsCapability`), deja de pollear el relay. No se pierde — al terminar, reanuda automáticamente y recoge el siguiente comando en cola. No reiniciar el agente prematuramente.
- **Scripts wrapper evitan problemas de escape con candados:** Para comandos con comillas complejas (ej. `smbclient -U "user%$PASS"`), crear un script `.sh` local con `File.write`, asignar `chmod 0755`, y pasarlo como argumento a `candados run`. Esto evita el doble/triple escape de comillas.
- **Google Drive como medio de transferencia (2026-04-14):** Para evitar cadenas largas de SCP/SMB con Tailscale inestable, se usa Google Drive como intermediario. **Ventaja:** Download HTTP directo desde dasu-sql4 sin depender de Tailscale. **Desafío:** Archivos >100MB requieren confirmación de virus — se resuelve parseando HTML y extrayendo parámetros `id`, `uuid`, `confirm`.
- **Arquitectura de Drones Atómicos Interconectados:** Cada paso del pipeline es un dron independiente que escribe su output en JSON (`/tmp/dron_*_output.json`). El orquestador lee estos JSONs para pasar contexto al siguiente dron. **Ventajas:** Re-ejecución de pasos individuales, observabilidad en Bitácora Web, acoplamiento mínimo.
## 🔗 Referencias
@@ -259,3 +362,5 @@ Crear dos VMs nuevas (SQL Server + PC cliente) en `srv-dasu`, instalar el sistem
- **DHCP del ISP cambia IPs tras reboot:** `dasu-sql4` obtuvo `.11` en vez de `.26` tras el corte de luz. Verificar siempre la IP actual usando ARP + MAC antes de asumir que la IP anterior sigue vigente.
- **W-Zombi no sobrevive reboots del agente:** El script PS1 debe re-lanzarse manualmente desde la consola Proxmox/noVNC tras un reinicio inesperado de la VM. Considerar crear una Scheduled Task.
- **QEMU Guest Agent no inicia automáticamente:** Tras corte de luz, el servicio `QEMU-GA` quedó caído. Verificar que el servicio esté en `Automatic` startup.
- **DASUTEN hardcodea ruta `C:\Sistema`:** El ejecutable VFP busca plantillas en `C:\Sistema\Word\` y `C:\Sistema\Excel\`, pero la instalación moderna las coloca en `C:\SysDasuten\Sistema\`. **Solución:** `mklink /D C:\Sistema C:\SysDasuten\Sistema` — el symlink es transparente para el ejecutable y no requiere modificación alguna del binario.
- **OLE Automation Word es retrocompatible (Office 2010→2016):** `New-Object -ComObject Word.Application` funciona tanto con Office 14 (2010) como Office 16 (2016). La interfaz COM no cambió — no era necesario downgrade de Office.