Files
dtic-DIIAA/docs/ambito/dtic-ADN/A01.P009_Evolucion-Dron-ADN.md
T
Ricardo MonlaandClaude Opus 4.6 4a0cdd883e [DRON] Heartbeat con output acumulado en Bitácora Web
Mejora en el sistema de latidos de los drones para mostrar el progreso
acumulado en lugar de solo la última línea.

Cambios realizados:
- ejecutor.rb: Acumulación de output en array accumulated_out[]
- actualizar_heartbeat_web(): Muestra últimas 15 líneas en bloque ```text
- A01.P009: Documentada Fase F1.5b con ejemplo técnico
- A04.P006: Actualizado plan DASUTEN con mejora y lección aprendida

Beneficio: Visibilidad completa del progreso de drones multi-paso
(ej: [1/3], [2/3], [3/3] en pipelines de backup/restore)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 10:45:11 -03:00

941 lines
41 KiB
Markdown

# A01.P009 - Evolución del Ecosistema ADN al Concepto de Dron
> **Estado**: 🟢 Fase 2 en progreso
> **Pertenece a**: [A01 - Ecosistema ADN](./A01_dtic-ADN.md)
> **Fecha**: 2026-04-09
> **Responsable**: Lic. Ricardo MONLA
> **Versión**: 3.5 — Fase 2: rclone atómico planificado + Heartbeat acumulado (2026-04-15)
---
## 📈 Progreso
```
Fase 0: ██████████ 100% Infraestructura de Datos (tablas internas) ✅
Fase 1: ██████████ 100% Atomicidad (bugs corregidos) ✅
Fase 1.5: ██████████ 100% Visibilidad Bitácora Web + Corrección Bugs ✅
Fase 1.5b: ██████████ 100% Heartbeat con Output Acumulado ✅ (2026-04-15)
Fase 2: ████████░░ 80% Composición — Orquestación de Flujos 🔧
Fase 3: ░░░░░░░░░░ 0% Planificación — Drones Programados
Fase 4: ░░░░░░░░░░ 0% Inteligencia de Colmena
Fase 5: ░░░░░░░░░░ 0% Observabilidad — Dashboard y Métricas
```
---
## 📋 Resumen Ejecutivo
Este plan define la evolución del ecosistema ADN hacia una arquitectura de **Inteligencia de Colmena** basada en **AtomicDrones** — drones atómicos especializados que, compuestos, forman una maquinaria autónoma integral.
**Filosofía de Diseño:**
| Principio | Descripción |
|-----------|-------------|
| **🧬 AtomicDrones** | Cada dron hace UNA cosa y la hace bien. Sin monolitos. |
| **🤖 Inteligencia de Colmena** | La solución emerge de la composición de drones simples. |
| **⚙️ Engranajes Especializados** | Drones ejecutores, vigilantes, sanadores, planificadores. |
| **🚫 No FrankenDrones** | Rechazar drones que intentan hacer todo mal. |
**Concepto de Dron ADN:**
- **Atómico**: Una responsabilidad única (Single Responsibility)
- **Componible**: Se ensambla con otros drones para soluciones complejas
- **Auto-bitácora**: Registra inicio/fin automáticamente
- **Observable**: Estado visible vía `dron flota` y dashboard web
- **Efímero**: Nace, ejecuta, muere (sin estado persistente innecesario)
---
## 🎯 Objetivo
Evolucionar el ecosistema ADN hacia una **colonia de AtomicDrones** especializados que, compuestos, resuelven problemas complejos mediante inteligencia de colmena.
**Meta:** Reducir la fricción operativa mediante drones atómicos que se especializan en una tarea única y se componen para flujos complejos.
---
## 🐝 Tipos de AtomicDrones (Especialización)
Cada tipo de dron tiene una única responsabilidad. La composición de múltiples drones resuelve problemas complejos.
| Tipo | Símbolo | Responsabilidad | Ejemplo |
|------|---------|-----------------|---------|
| **Ejecutor** | `🛠️` | Ejecuta un comando y reporta resultado | `dron_ejecutor` |
| **Vigilante** | `👁️` | Monitorea estado de otros drones | `dron_vigilante` |
| **Sanador** | `🩹` | Repara drones zombies/fallidos | `dron_sanador` |
| **Planificador** | `📅` | Lanza drones según cron | `dron_planificador` |
| **Mensajero** | `📨` | Notifica resultados (Slack, email) | `dron_mensajero` |
| **Orquestador** | `🎼` | Compone múltiples drones en flujo | `dron_orquestador` |
### Ejemplo de Composición (Inteligencia de Colmena)
**Problema:** Backup nocturno de SQL + sync a nube + notificación
**Solución FrankenDron (❌ rechazar):**
```ruby
# Un solo dron que hace todo (monolito frágil)
dron_backup_nocturno.rb # 500 líneas, 7 responsabilidades
```
**Solución AtomicDrones (✅ adoptar):**
```bash
# Orquestador compone 4 drones especializados
dron_orquestador \
--step "dron_ejecutor --cmd 'vzdump 103'" \
--step "dron_ejecutor --cmd 'rclone sync /bkps oneDrive:'" \
--step "dron_vigilante --check exit_code" \
--step "dron_mensajero --notify slack 'Backup OK'"
```
---
## 📡 Medio de Comunicación: Bitácora Web como Registro Central
La **Bitácora Web** es el medio de comunicación donde los drones dejan registro de su actividad. El sistema sigue el principio **DB-First** con capas de visibilidad.
### Principio de Codificación: Atomisidad
**Atomisidad** es el principio de código compartido que aplica "Menos es Más" a la implementación de drones:
```
┌─────────────────────────────────────────────────────────────┐
│ Dron::Base (módulo compartido) │
│ - logger: Logger único para todos los drones │
│ - db_query: Ejecución de SQL con resultados │
│ - db_exec: Ejecución de SQL sin resultados │
│ - formato_duracion: Utilitario común │
│ - slice_safe: Manejo seguro de strings │
│ - blank?: Verificación de nil/empty │
└─────────────────────────────────────────────────────────────┘
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Ejecutor │ │ Vigilante │ │ Sanador │
│ extend Base │ │ extend Base │ │ extend Base │
└──────────────┘ └──────────────┘ └──────────────┘
```
**Beneficios:**
- Un solo lugar para corregir errores comunes
- ~40% menos líneas de código
- Nuevos drones heredan funcionalidad automáticamente
### Arquitectura de Registros
```
┌─────────────────────────────────────────────────────────────────┐
│ DRONES (Ejecutores, Vigilantes, Sanadores, etc.) │
│ Cada dron deja registro al ejecutar │
└────────────┬────────────────────────────────────────────────────┘
│ registra
┌─────────────────────────────────────────────────────────────────┐
│ CAPA 1: Tablas Internas (NO visibles directamente) │
│ - bitacoras.dron_logs: Logs detallados de cada dron │
│ - bitacoras.dron_avances: Progreso paso-a-paso │
│ - bitacoras.dron_metricas: Métricas de rendimiento │
│ - bitacoras.dron_errores: Errores y reintentos │
└────────────┬────────────────────────────────────────────────────┘
│ dron_vigilante consolida
┌─────────────────────────────────────────────────────────────────┐
│ CAPA 2: Bitácora Web (Resumen visible) │
│ - events: Eventos consolidados por flujo/orquestación │
│ - Descripción: Resumen legible del progreso │
│ - Estado: ⏳ En ejecución | ✅ Completado | ❌ Fallido │
│ - URL: http://localhost:5174/bitacoras/ │
└─────────────────────────────────────────────────────────────────┘
```
### Tablas Internas (Esquema `bitacoras`)
```sql
-- Logs detallados de cada dron (NO visible en web directamente)
CREATE TABLE bitacoras.dron_logs (
id SERIAL PRIMARY KEY,
dron_id VARCHAR(50) NOT NULL, -- ej: dron_083045_123
flujo_id VARCHAR(50), -- ID de orquestación (si aplica)
tipo VARCHAR(20) NOT NULL, -- ejecutor|vigilante|sanador|planificador|mensajero
estado VARCHAR(20) NOT NULL, -- pending|running|completed|failed|zombie
cmd TEXT, -- Comando ejecutado
output TEXT, -- Salida del comando
exit_code INTEGER, -- Código de retorno
started_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP,
heartbeat TIMESTAMP DEFAULT NOW(), -- Último heartbeat
reintentos INTEGER DEFAULT 0,
metadata JSONB DEFAULT '{}'::jsonb -- Metadata adicional
);
-- Avances paso-a-paso (para flujos multi-paso)
CREATE TABLE bitacoras.dron_avances (
id SERIAL PRIMARY KEY,
dron_id VARCHAR(50) NOT NULL,
paso INTEGER NOT NULL, -- Número de paso en flujo
descripcion TEXT NOT NULL, -- Qué se está haciendo
estado VARCHAR(20) NOT NULL, -- pending|running|completed|failed
started_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP,
metadata JSONB DEFAULT '{}'::jsonb
);
-- Métricas de rendimiento (agregaciones)
CREATE TABLE bitacoras.dron_metricas (
id SERIAL PRIMARY KEY,
fecha DATE NOT NULL,
dron_tipo VARCHAR(20) NOT NULL,
total_ejecuciones INTEGER DEFAULT 0,
completados INTEGER DEFAULT 0,
fallidos INTEGER DEFAULT 0,
zombies INTEGER DEFAULT 0,
tiempo_promedio INTERVAL,
reintentos_total INTEGER DEFAULT 0,
UNIQUE(fecha, dron_tipo)
);
-- Índices para consultas eficientes
CREATE INDEX idx_dron_logs_estado ON bitacoras.dron_logs(estado);
CREATE INDEX idx_dron_logs_heartbeat ON bitacoras.dron_logs(heartbeat);
CREATE INDEX idx_dron_logs_flujo ON bitacoras.dron_logs(flujo_id);
CREATE INDEX idx_dron_avances_dron ON bitacoras.dron_avances(dron_id);
```
### Dron Vigilante: El Monitor de Flotas
El **`dron_vigilante`** es responsable de:
1. **Escanear flota**: Lee `bitacoras.dron_logs` para detectar drones activos
2. **Detectar zombies**: `heartbeat > 5min` → marca como zombie
3. **Registrar avances**: Inserta en `bitacoras.dron_avances` el progreso
4. **Consolidar resumen**: Crea/actualiza evento en `bitacoras.events` (visible en web)
5. **Alertar anomalías**: Si detecta >5 fallos en 1h → notifica
```ruby
# Ejemplo: dron_vigilante escanea y registra avances
class DronVigilante
def escanear_flota
drones_activos = DronDB.where("estado = 'running' AND heartbeat < ?", 5.minutes.ago)
drones_activos.each do |dron|
# Marcar como zombie
dron.update(estado: 'zombie')
# Registrar en avances
DronAvance.create(
dron_id: dron.id,
paso: -1,
descripcion: "Dron detectado como zombie (heartbeat > 5min)",
estado: 'failed'
)
# Consolidar resumen en events (visible en Bitácora Web)
consolidar_resumen(dron.flujo_id)
end
end
def consolidar_resumen(flujo_id)
# Obtener todos los logs del flujo
logs = DronLog.where(flujo_id: flujo_id)
# Calcular estado consolidado
estado = if logs.all? { |l| l.estado == 'completed' }
'completed'
elsif logs.any? { |l| l.estado == 'failed' }
'failed'
else
'running'
end
# Actualizar/crear evento resumen (visible en Bitácora Web)
Evento.find_or_create_by(flujo_id: flujo_id).update(
descripcion: "Flujo #{flujo_id}: #{logs.count} drones - #{estado}",
estado: estado
)
end
end
```
### Bitácora Web: Lo que el Usuario Ve
La **Bitácora Web** (`http://localhost:5174/bitacoras/`) muestra:
| Columna | Contenido |
|---------|-----------|
| **ID** | ID del evento resumen |
| **I** | Hora de inicio del flujo |
| **F** | Hora de fin (si completado) |
| **Descripción** | Resumen consolidado (ej: "Backup nocturno: 4 drones completados") |
| **J** | Modo (P=Presencial, R=Remoto, A=Automático) |
| **E** | Estado (⏳, ✅, ❌) |
| **Acc.** | Acciones (ver detalle, reintentar) |
**Al hacer clic en "ver detalle"**:
- Muestra tabla con drones individuales y su estado
- Muestra avances paso-a-paso del flujo
- Muestra métricas de rendimiento (tiempo, reintentos)
---
## 📊 Estado Actual (Línea Base — Auditoría 2026-04-09)
### Herramientas Existentes
| Herramienta | Ubicación | Tipo | Estado |
|-------------|-----------|------|--------|
| `dron.rb` | `adn/tools/cli/dron.rb` | Dispatcher | ✅ Refactorizado |
| `dron/ejecutor.rb` | `adn/tools/cli/dron/ejecutor.rb` | Ejecutor atómico | ⚠️ Bugs en bitácora web |
| `dron/vigilante.rb` | `adn/tools/cli/dron/vigilante.rb` | Vigilante atómico | ⚠️ Bugs en consolidación |
| `dron/sanador.rb` | `adn/tools/cli/dron/sanador.rb` | Sanador atómico | ⚠️ Bugs en reintentos |
| `dron/bitacora.rb` | `adn/tools/cli/dron/bitacora.rb` | Bitácora atómica | ✅ Implementado |
| `dron/base.rb` | `adn/tools/cli/dron/base.rb` | Módulo compartido | ✅ Implementado |
| `dron_db.rb` | `adn/tools/db/core/dron_db.rb` | Acceso a DB | ⚠️ 3 bugs críticos |
| `dispatcher.rb` | `adn/tools/cli/dron/dispatcher.rb` | Routing subcomandos | ⚠️ 1 bug crítico |
### Capacidades Actuales
```bash
# Ejecutor (lanzar tarea con auto-bitácora)
./adn/tools/run dron lanzar --evento AUTO --nota "Backup" -- <cmd>
# Vigilante (ver flota)
./adn/tools/run dron flota
./adn/tools/run dron salud
./adn/tools/run dron estado <dron_id>
# Sanador (health check + limpiar + reintentar)
./adn/tools/run dron sanear --dry-run
./adn/tools/run dron limpiar
./adn/tools/run dron reintentar
```
### 🐛 Deuda Técnica Detectada (Auditoría 2026-04-09)
#### Bugs Críticos (crashes directos)
| # | Archivo | Línea | Bug | Impacto |
|---|---------|-------|-----|---------|
| B1 | `dispatcher.rb` | 214 | `BitacorasDB::BitacoraDB.ejecutar()` — método inexistente (debe ser `.with_connection { \|db\| db.execute() }`) | `dron estado <ID>` crashea con NoMethodError |
| B2 | `dron_db.rb` | 178-179 | `incrementar_reintentos` hace doble `.first` (`.first.to_h` → Hash, luego `.first` sobre Hash = par `[k,v]`) | Sanador crashea al reintentar drones |
| B3 | `dron_db.rb` | 231-232 | `obtener_metricas` mismo patrón de doble `.first` | Consulta de métricas crashea |
#### Bugs Medios (fallan en ciertos flujos)
| # | Archivo | Línea | Bug | Impacto |
|---|---------|-------|-----|---------|
| B4 | `dron_db.rb` | 337-345 | `limpiar_antiguos` — DELETE sin RETURNING intenta leer `['count']` inexistente | Limpieza reporta 0 siempre |
| B5 | `dron_db.rb` | 253 | `actualizar_metricas` — pasa Float donde PG espera INTERVAL | Error de tipo al guardar métricas |
| B6 | `sanador.rb` | 27 | `Time.now - dron['completed_at']` — el campo viene como String, no Time | TypeError al calcular backoff |
| B7 | `sanador.rb` | 41 | `Dron::Ejecutor.lanzar(...)` sin `require_relative 'ejecutor'` | NameError si se llama directamente |
#### Bugs Bajos (edge cases)
| # | Archivo | Línea | Bug | Impacto |
|---|---------|-------|-----|---------|
| B8 | `sanador.rb` | 41 | `dron.dig('metadata', 'nota')` — metadata es String JSON, no Hash | Nota de reintento siempre nil |
| B9 | `ejecutor.rb` | 42-75 | Timeout nunca se aplica (falta `Timeout.timeout()` wrapping) | Drones largos nunca se frenan |
| B10 | `vigilante.rb` | 141,143 | `BitacorasDB::BitacoraDB.crear_evento(...)` como método de clase (es de instancia) | Consolidación de flujos falla silenciosamente |
| B11 | `ejecutor.rb` | 82-131 | `registrar_bitacora` con rescue genérico silencia todos los errores | Fallos de bitácora web invisibles |
| B12 | `dron.rb` / `base.rb` | 30 / 27 | Logger: `.instance` (singleton) vs `.new` (instancia separada) | Logs inconsistentes |
### 🔍 Brecha Principal: Visibilidad en Bitácora Web
**Problema crítico**: Los drones escriben en tablas internas (`dron_logs`) pero la **Bitácora Web** (`bitacoras.events`) no refleja su actividad.
```
CAPA 1 (dron_logs) CAPA 2 (events → Web)
┌─────────────────┐ ┌─────────────────┐
│ ✅ Drones │ ──?──→ │ ❌ La web no ve │
│ escriben acá │ │ nada │
└─────────────────┘ └─────────────────┘
La flecha "vigilante consolida"
NO FUNCIONA por bugs B1, B10, B11
```
**Causas raíz:**
1. El heartbeat solo actualiza `dron_logs.heartbeat` (tabla interna), nunca `events`
2. El ejecutor tiene lógica de bitácora web pero falla silenciosamente (B11)
3. El vigilante llama métodos de clase inexistentes (B10)
4. Coexiste un sistema antiguo de scripts `.sh` (que sí funcionaba) con el nuevo Ruby (que no)
### Limitaciones Futuras (post-corrección)
| # | Limitación | Tipo | Impacto | Prioridad |
|---|------------|------|---------|-----------|
| 1 | Sin orquestación de flujos | Orquestador | No hay composición multi-paso | Media |
| 2 | Sin planificación (cron) | Planificador | Requiere lanzamiento manual | Alta |
| 3 | Sin notificaciones | Mensajero | Alertas manuales | Media |
| 4 | Sin dashboard web dedicado | Observabilidad | Solo CLI + Bitácora Web | Baja |
---
## 🗺️ Fases de Implementación
### Fase 0: Infraestructura de Datos (Dron 0.5) — ✅ COMPLETA
**Objetivo:** Crear tablas internas para registro detallado de drones.
| ID | Tarea | Descripción | Estado |
|:--:|-------|-------------|--------|
| F0.T1 | Migración `003_dron_logs.sql` | Tabla `bitacoras.dron_logs` | ✅ Completa |
| F0.T2 | Migración `004_dron_avances.sql` | Tabla `bitacoras.dron_avances` | ✅ Completa |
| F0.T3 | Migración `005_dron_metricas.sql` | Tabla `bitacoras.dron_metricas` | ✅ Completa |
| F0.T4 | `adn/tools/db/core/dron_db.rb` | Acceso a datos de drones | ✅ Completa |
| F0.T5 | Hooks DB-First | Auto-registro en dron_logs al lanzar | ✅ Completa |
**Criterio de éxito:** ✅ Drones registran actividad en tablas internas sin tocar events directamente.
---
### Fase 1: Atomicidad — Separación de Responsabilidades (Dron 1.0) — ⚠️ CON DEUDA TÉCNICA
**Objetivo:** Refactorizar el monolito `dron.rb` en módulos atómicos especializados.
| ID | Tarea | Tipo de Dron | Estado |
|:--:|-------|--------------|--------|
| F1.T1 | `dron/ejecutor.rb` | Ejecutor — lanza y monitorea comando | ⚠️ Bugs B9, B11 |
| F1.T2 | `dron/vigilante.rb` | Vigilante — escanea flota, detecta zombies | ⚠️ Bug B10 |
| F1.T3 | `dron/sanador.rb` | Sanador — re-intenta, limpia, notifica fallos | ⚠️ Bugs B6, B7, B8 |
| F1.T4 | `dron/bitacora.rb` | Registrador — auto-bitácora (inicio/fin) | ✅ Completa |
| F1.T5 | `dron/dispatcher.rb` | Dispatcher — routing de subcomandos | ⚠️ Bug B1 |
| F1.T6 | `dron/base.rb` | Módulo compartido (atomisidad) | ✅ Completa |
| F1.T7 | `dron_db.rb` | Capa de datos para drones | ⚠️ Bugs B2, B3, B4, B5 |
**Criterio de éxito original:** ✅ Cada módulo tiene una única responsabilidad. ~40% menos código.
**Criterio revisado:** ⚠️ La estructura atómica es correcta pero **12 bugs** impiden operación confiable. Corregidos en Fase 1.5.
---
### Fase 1.5: Visibilidad en Bitácora Web + Corrección de Bugs (Dron 1.1) — ✅ COMPLETA
**Objetivo:** Que los drones sean **visibles desde la Bitácora Web** y corregir toda la deuda técnica de Fase 1.
**Problema que resuelve:** Actualmente los drones son "fantasmas" — ejecutan tareas pero el usuario no puede ver su progreso desde la interfaz web principal. Solo escriben en tablas internas (`dron_logs`) pero nunca actualizan `bitacoras.events` de forma confiable.
**Lo que el usuario debería ver en Bitácora Web:**
```
┌─────┬───────┬───────┬─────────────────────────────────────────────┬───┬───┐
│ ID │ I │ F │ Descripción │ J │ E │
├─────┼───────┼───────┼─────────────────────────────────────────────┼───┼───┤
│1420 │ 02:00 │ │ 🛸 Backup nocturno (dron_020000_123) │ A │ ⏳│
│ │ │ │ ├─ Ejecutando: vzdump 103 --mode stop │ │ │
│ │ │ │ └─ ❤️ Heartbeat: hace 45s │ │ │
├─────┼───────┼───────┼─────────────────────────────────────────────┼───┼───┤
│1419 │ 01:30 │ 01:45 │ 🛸 Sync rclone (dron_013000_456) — ✅ 15m │ A │ ✅│
│ │ │ │ └─ Aterrizaje limpio — 2.3GB sincronizados │ │ │
└─────┴───────┴───────┴─────────────────────────────────────────────┴───┴───┘
```
| ID | Tarea | Descripción | Estado |
|:--:|-------|-------------|--------|
| F1.5.T1 | Corregir bugs críticos | Bugs B1, B2, B3 — crashes directos en dispatcher y dron_db | ✅ |
| F1.5.T2 | Corregir bugs medios | Bugs B4, B5, B6, B7 — fallos en sanador y métricas | ✅ |
| F1.5.T3 | Simplificar `registrar_bitacora` | Reescribir el método del ejecutor: crear evento → heartbeat → cierre limpio | ✅ |
| F1.5.T4 | Heartbeat visible en web | Que el heartbeat cada 30s actualice `events.descripcion` con progreso | ✅ |
| F1.5.T5 | Eliminar sistema dual `.sh`/Ruby | `copiloto.rb` y `novato.rb` deprecados, `tmp/dron/` limpiado | ✅ |
| F1.5.T6 | Corregir bugs bajos | Bugs B8-B12 — edge cases y consistencia | ✅ |
| F1.5.T7 | Heartbeat con output acumulado | Mostrar últimas 15 líneas del output acumulado en lugar de solo la última línea | ✅ (2026-04-15) |
**Flujo corregido del Ejecutor:**
```
Ejecutor.lanzar(cmd, nota)
├─ 1. Registrar en dron_logs (tabla interna) ──→ DronDB
├─ 2. Crear evento en events (Bitácora Web) ──→ 🌐 VISIBLE
├─ 3. Ejecutar comando con heartbeat thread:
│ loop cada 30s:
│ ├─ Actualizar dron_logs.heartbeat ──→ DronDB
│ └─ Actualizar events.descripcion ──→ 🌐 VISIBLE
├─ 4. Comando finaliza
├─ 5. Registrar fin en dron_logs ──→ DronDB
└─ 6. Cerrar evento en events (estado + hora fin) ──→ 🌐 VISIBLE
```
**Criterio de éxito:**
- Cada dron lanzado aparece inmediatamente en la Bitácora Web como evento `⏳`
- El heartbeat actualiza la descripción del evento en web cada 30 segundos
- Al finalizar, el evento se cierra con `✅` o `❌`, hora de fin, y duración
- `dron estado <ID>`, `dron flota`, y `dron salud` funcionan sin crashes
- Cero bugs críticos, cero bugs medios
### Mejora: Heartbeat con Output Acumulado (2026-04-15)
**Problema:** Los drones que ejecutan pipelines multi-paso (ej: `dasuten_download_drive-dasu-sql4.rb`) muestran progreso tipo `[1/3]`, `[2/3]`, `[3/3]`, pero el heartbeat original solo capturaba la **última línea** (`last_out`), perdiendo el contexto del progreso acumulado.
**Solución:** Modificar `Ejecutor#ejecutar_comando` para acumular todo el output en un array `accumulated_out[]` y pasar el output completo al heartbeat web.
**Cambios en `adn/tools/cli/dron/ejecutor.rb`:**
```ruby
# ANTES (solo última línea):
last_out = lineas.last&.strip || last_out
actualizar_heartbeat_web(..., last_out)
# AHORA (output acumulado completo):
accumulated_out = []
accumulated_out.concat(lineas) # Acumular todas las líneas
actualizar_heartbeat_web(..., accumulated_out.join("\n"))
```
**Cambios en `actualizar_heartbeat_web`:**
```ruby
# ANTES (últimos 80 chars de una línea):
clean_out = last_out.gsub(/[^[:print:]]/, '').strip
descripcion += "\n\n> 🕵️ `#{clean_out[-80..-1] || clean_out}`"
# AHORA (últimas 15 líneas en bloque de código):
clean_out = accumulated_out.gsub(/[^[:print:]\n]/, '').strip
lineas = clean_out.split("\n")
lineas_mostrar = lineas.last(15)
descripcion += "\n\n```text\n#{lineas_mostrar.join("\n")}\n```"
```
**Resultado en Bitácora Web:**
```
🛸 Download desde Google Drive (`dron_152045_789`)
├─ Comando: `ruby dasuten_download_drive-dasu-sql4.rb`
└─ ❤️ 2m 30s — heartbeat #5
```text
[*] Dron: Download desde Google Drive
Nodo: dasu-sql4
Drive ruta: rmonla-GDrive:drive_bkps-dasu/sysdasuten.bak
File ID: 1P0ci...
Destino: F:\BACKUP\sysdasuten.bak
[1/3] Iniciando descarga con Rclone Nativo...
[2/3] Descargando...
[+] Descarga en 45.2s
[3/3] Verificando...
Tamano: 1.33 MB
```
```
**Beneficio:** El usuario puede ver **todo el progreso acumulado** del dron en tiempo real, no solo la última línea. Esto es crítico para pipelines largos donde cada paso tiene su propio progreso numerado.
---
### Fase 2: Composición — Orquestación de Flujos (Dron 1.5)
**Objetivo:** Permitir composición de drones para flujos multi-paso. Aplicar "Menos es Más" a todos los procesadores — **1 Dron = 1 Unidad Atómica**.
| ID | Tarea | Descripción | Estado |
|:--:|-------|-------------|--------|
| F2.T1 | `bkps orquestar` | Orquestación 1-dron-por-archivo (scan → lanzar secuencial) | ✅ |
| F2.T1a | `proc_xen.rb` | `listar()` + `ejecutar_uno()` — 1 dron por `.xva` | ✅ |
| F2.T1b | `proc_proxmox.rb` | `listar()` + `ejecutar_uno()` — 1 dron por set VM | ✅ |
| F2.T1c | `sync_rclone.rb` | `listar()` + `ejecutar_uno()` — 1 dron por directorio de nodo | ⏳ |
| F2.T2 | Condicionales | `--on-error continue\|abort\|retry` + `--retries N` | ✅ |
| F2.T3 | Paralelismo | `--parallel` para pasos independientes | ⏳ |
| F2.T4 | Contexto compartido | Variables entre pasos (`$DRON_OUTPUT_N`) | ⏳ |
#### F2.T1c — Atomización de Rclone (1 dron por nodo)
**Problema actual:**
```bash
# MONOLITO: un solo rclone sync para ~260GB (17 nodos)
rclone sync /mnt/ns8Disco3/.../bkps-SERVIDORes/ rmOneDrive:/.../bkps-SERVIDORes/
# Si falla en el nodo 15 de 17, hay que reiniciar TODO
```
**Solución AtomicDrones:**
```bash
# ATÓMICO: 1 dron por directorio de nodo
# Dron 1: rclone sync .../srvv-DNS/ → rmOneDrive:.../srvv-DNS/ (1.5GB)
# Dron 2: rclone sync .../srvv-KOHA/ → rmOneDrive:.../srvv-KOHA/ (9.7GB)
# ...
# Dron 17: rclone sync .../srv-SysACADWeb/ → rmOneDrive:.../srv-SysACADWeb/ (46GB)
```
**Implementación requerida en `sync_rclone.rb`:**
| Método | Qué hace |
|--------|----------|
| `listar(tarea)` | Escanea subdirectorios del `origen`, retorna lista de `{nombre, ruta, tamaño}` |
| `ejecutar_uno(tarea, directorio)` | `rclone sync origen/NODO/ destino/NODO/` para un solo nodo |
**Inventario de nodos (17 directorios, ~260GB total):**
```
8K pcv-DASU3 474M srvv-SITIO2 17G pcv-DASU2
648M srvv-UPTIME 966M srvv-N8N 18G pcv-SERVIIO
1.5G srvv-DNS 6.9G srvv-SITIO 23G pcv-DASU1
7.5G srvv-DOCs 9.7G srvv-KOHA 27G srvv-DTIC
13G srvv-DATA 14G srvv-SITIO0 37G srv-MauriK
38G srvv-FENIX 46G srv-SysACADWeb
```
**Uso propuesto:**
```bash
# Dry-run: ver nodos pendientes de sync
./adn/tools/run bkps orquestar T5 --dry-run
# Real: 1 dron por nodo, secuencial, con retry ante fallos de red
./adn/tools/run bkps orquestar T5 --on-error retry --retries 3
# Meta-orquestación: el orquestador mismo es un dron
./adn/tools/run dron lanzar --nota "Sync Nube Servidores" -- ./adn/tools/run bkps orquestar T5 --on-error retry
```
**Implementado (F2.T1a, F2.T1b):**
```bash
# Dry-run: ver archivos pendientes
./adn/tools/run bkps orquestar T1 --dry-run
# Real: 1 dron por .xva, secuencial, cada uno visible en Bitácora
./adn/tools/run bkps orquestar T2
# Meta-orquestación: el orquestador mismo es un dron
./adn/tools/run dron lanzar --nota "Orquestar PMOX" -- ./adn/tools/run bkps orquestar T2
```
**Criterio de éxito:** Flujos complejos se expresan como composición de drones simples. Cada procesador (xen, proxmox, rclone) soporta `listar()` + `ejecutar_uno()` para orquestación atómica.
---
### Fase 3: Planificación — Drones Programados (Dron 2.0)
**Objetivo:** Ejecución automática según cron.
| ID | Tarea | Tipo de Dron | Estado |
|:--:|-------|--------------|--------|
| F3.T1 | `dron/planificador.rb` | Planificador — lee cron y lanza drones | ⏳ |
| F3.T2 | Persistencia de schedules | `~/.claude/scheduled_tasks.json` | ⏳ |
| F3.T3 | CLI schedules | `schedule add\|list\|remove` | ⏳ |
| F3.T4 | Integración CronCreate | SDK de Claude Code | ⏳ |
**Criterio de éxito:** Tareas recurrentes se lanzan automáticamente.
---
### Fase 4: Inteligencia de Colmena — Comunicación entre Drones (Dron 3.0)
**Objetivo:** Drones se comunican y coordinan para resolver problemas.
| ID | Tarea | Descripción | Estado |
|:--:|-------|-------------|--------|
| F4.T1 | Cola de eventos | Redis o archivo compartido para eventos | ⏳ |
| F4.T2 | Publicar/ Suscribirse | Drones publican eventos, otros reaccionan | ⏳ |
| F4.T3 | Patrones de reacción | `on failed → sanador`, `on completed → mensajero` | ⏳ |
| F4.T4 | Feromonas digitales | Señales químicas virtuales (`/tmp/dron_pheromones/`) | ⏳ |
**Ejemplo de flujo emergente:**
```
1. Ejecutor falla → publica "failed" en cola
2. Sanador escucha → re-intenta (máx 3 veces)
3. Si falla → publica "critical"
4. Mensajero escucha → notifica por Slack
```
**Criterio de éxito:** Soluciones complejas emergen sin orquestador central.
---
### Fase 5: Observabilidad — Dashboard y Métricas (Dron 4.0)
**Objetivo:** Visibilidad completa de la colonia.
| ID | Tarea | Tipo de Dron | Estado |
|:--:|-------|--------------|--------|
| F5.T1 | API `/api/drones` | Endpoint para listar drones | ⏳ |
| F5.T2 | API `/api/drones/:id/avances` | Endpoint para avances de un dron | ⏳ |
| F5.T3 | Dashboard web | `http://localhost:5174/drones` | ⏳ |
| F5.T4 | Gráficas | Tareas por día, tasa de éxito, tiempos | ⏳ |
| F5.T5 | Alertas proactivas | `dron_vigilante` detecta anomalías | ⏳ |
**Criterio de éxito:** Visibilidad completa vía web dashboard y métricas históricas.
---
## 🏗️ Arquitectura de Colmena
### Diagrama de Flujo (Inteligencia de Colmena)
```
┌──────────────────────────────────────────────────────────────────┐
│ Dron::Base (módulo compartido - Atomisidad) │
│ - logger: Logger único │
│ - db_query/db_exec: Acceso a DB │
│ - utilidades: formato_duracion, slice_safe, blank? │
└─────────────┬────────────────────────────────────────────────────┘
│ extend Base (todos los drones)
┌─────────┼─────────┬─────────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌──────────┐
│EJECUTOR│ │VIGILANTE│ │SANADOR│ │BITÁCORA │
│🛠️ │ │👁️ │ │🩹 │ │📝 │
│lanzar │ │flota │ │sanear │ │iniciar │
│ │ │salud │ │limpiar│ │finalizar │
└───┬────┘ └───┬────┘ └───┬────┘ └────┬─────┘
│ │ │ │
│ │ │ │ registra
│ │ │ ▼
│ │ │ ┌─────────────┐
│ │ │ │bitacoras. │
│ │ │ │events │
│ │ │ └─────────────┘
│ │ │
│ │ └─→ Re-intenta (máx 3, backoff)
│ │
│ └─→ Detecta zombies (heartbeat > 5min)
│ Consolida en events
└─→ Ejecuta cmd
Heartbeat cada 30s
Registra en dron_logs + events
```
### Principios de Diseño
| Principio | Aplicación |
|-----------|------------|
| **Single Responsibility** | Cada dron hace UNA cosa |
| **Composability** | Drones se ensamblan en flujos |
| **Event-Driven** | Comunicación vía cola de eventos |
| **Stateless** | Drones no guardan estado (efímeros) |
| **Observable** | Todo evento se registra en DB |
---
## 🔧 Componentes Implementados
### Base Compartido (`adn/tools/cli/dron/base.rb`) — Atomisidad
| Componente | Propósito |
|------------|-----------|
| `Base.logger` | Logger único para todos los drones |
| `Base.db_query` | Ejecuta SQL y retorna resultados |
| `Base.db_exec` | Ejecuta SQL sin retornar resultados |
| `Base.formato_duracion` | Formatea segundos a "Xmin Ys" |
| `Base.slice_safe` | Slice seguro de strings (evita nil) |
| `Base.blank?` | Verifica si string es nil/vacío |
### Ejecutor (`adn/tools/cli/dron/ejecutor.rb`)
| Componente | Propósito |
|------------|-----------|
| `Ejecutor.lanzar` | Ejecuta comando, heartbeat, reporta resultado |
| `Ejecutor#heartbeat` | Actualiza heartbeat cada 30s |
| `Ejecutor#registrar_bitacora` | Crea/actualiza evento en bitácora |
### Vigilante (`adn/tools/cli/dron/vigilante.rb`)
| Componente | Propósito |
|------------|-----------|
| `Vigilante.escanear_flota` | Lee dron_logs, muestra resumen |
| `Vigilante.salud` | Health check, detecta zombies y anomalías |
| `Vigilante.dashboard` | Dashboard compacto de la flota |
| `Vigilante.consolidar_flujo` | Consolidar estado de flujo en events |
### Sanador (`adn/tools/cli/dron/sanador.rb`)
| Componente | Propósito |
|------------|-----------|
| `Sanador.reintentar_fallidos` | Re-lanza failed (máx 3, backoff exponencial) |
| `Sanador.limpiar_antiguos` | Elimina completados > N días |
| `Sanador.sanear` | Saneamiento completo (reintentar + limpiar) |
### Bitácora (`adn/tools/cli/dron/bitacora.rb`)
| Componente | Propósito |
|------------|-----------|
| `Bitacora.iniciar` | Crea evento `⏳` en bitácora |
| `Bitacora.finalizar` | Actualiza evento con `✅` o `❌` |
| `Bitacora.auto` | Modo AUTO: inicia, ejecuta, finaliza |
### Persistencia
| Archivo/Tabla | Tipo | Propósito | Visibilidad |
|---------|---------|-----------|-------------|
| `bitacoras.dron_logs` | Tabla DB | Logs detallados de cada dron | 🔒 Interna |
| `bitacoras.dron_avances` | Tabla DB | Progreso paso-a-paso de flujos | 🔒 Interna |
| `bitacoras.dron_metricas` | Tabla DB | Agregaciones y estadísticas | 🔒 Interna |
| `bitacoras.events` | Tabla DB | Eventos consolidados (resumen) | ✅ Bitácora Web |
| `/tmp/dron_flota/*.json` | Archivo | Estado de drones activos (runtime) | 🔒 CLI |
| `/tmp/dron_queue/*.json` | Archivo | Cola de eventos (por tipo) | 🔒 Drones |
| `/tmp/dron_pheromones/` | Directorio | Señales químicas virtuales | 🔒 Drones |
| `~/.claude/scheduled_tasks.json` | Archivo | Schedules persistentes (cron) | 🔒 Planificador |
### Modificar
| Archivo | Cambio |
|---------|--------|
| `adn/tools/cli/dron.rb` | Refactorizar → dispatcher que importa módulos |
| `adn/tools/run` | Registrar subcomandos: `dron lanzar`, `dron orquestar`, etc. |
| `adn/tools/db/core/bitacora_db.rb` | Hook para eventos AUTO (dron_bitacora) |
---
## 📝 Comandos por Tipo de Dron
### Ejecutor
```bash
# Lanzar tarea simple (auto-bitácora)
./adn/tools/run dron lanzar --evento AUTO --nota "Backup SQL" -- vzdump 103
# Lanzar sin registro (efímero)
./adn/tools/run dron lanzar --no-bitacora --nota "Test rápido" -- echo hola
# Lanzar con timeout
./adn/tools/run dron lanzar --timeout 300 --nota "Sync larga" -- rclone sync ...
```
### Orquestador (Composición)
```bash
# Flujo secuencial (backup → sync → notificar)
./adn/tools/run dron orquestar \
--step "dron lanzar --nota 'Backup' -- vzdump 103" \
--step "dron lanzar --nota 'Sync' -- rclone sync /bkps oneDrive:" \
--step "dron notificar --msg 'Backup completado'" \
--on-error abort
# Flujo paralelo (múltiples backups simultáneos)
./adn/tools/run dron orquestar \
--parallel \
--step "dron lanzar --nota 'Backup SQL' -- vzdump 103" \
--step "dron lanzar --nota 'Backup DC' -- vzdump 100" \
--step "dron lanzar --nota 'Backup PCV' -- vzdump 104"
```
### Planificador (Cron)
```bash
# Agendar backup nocturno
./adn/tools/run dron schedule add \
--cron "0 2 * * *" \
--cmd "dron lanzar --evento AUTO --nota 'Backup nocturno' -- ./adn/tools/run bkps run C4 --batch"
# Listar schedules
./adn/tools/run dron schedule list
# Eliminar schedule
./adn/tools/run dron schedule remove <id>
```
### Vigilante (Monitoreo)
```bash
# Dashboard de flota
./adn/tools/run dron flota
# Estado detallado de un dron
./adn/tools/run dron estado <tarea_id>
# Métricas históricas
./adn/tools/run dron metricas [--desde 2026-04-01] [--hasta 2026-04-07]
```
### Sanador (Reparación)
```bash
# Health check (detecta zombies)
./adn/tools/run dron salud
# Reintentar fallidos
./adn/tools/run dron sanear --reintentar
# Limpiar completados
./adn/tools/run dron limpiar --mayor-a 24h
```
### Mensajero (Notificaciones)
```bash
# Notificar por Slack
./adn/tools/run dron notificar --slack "#backups" "Backup completado ✅"
# Notificar por email
./adn/tools/run dron notificar --email "admin@frlr.utn.edu.ar" "Alerta de fallo"
```
---
## 🎯 Criterios de Éxito
### Atomicidad (Fase 1)
- ✅ Cada dron tiene una única responsabilidad (Single Responsibility)
- ✅ Sin código duplicado entre módulos
- ✅ Tests independientes por tipo de dron
### Composición (Fase 2)
- ✅ Flujos multi-paso se expresan como composición de drones
- ✅ Orquestador permite secuencial/paralelo/condicional
### Inteligencia de Colmena (Fase 4)
- ✅ Drones se comunican vía cola de eventos
- ✅ Patrones de reacción emergen (failed → sanador → notificar)
- ✅ Sin orquestador central para reacciones automáticas
### Observabilidad (Fase 5)
- ✅ Dashboard web muestra estado en tiempo real
- ✅ Histórico consultable vía CLI y web
- ✅ Alertas proactivas ante anomalías
---
## 📚 Referencias
### Internas (ADN)
- **Herramienta actual**: [`adn/tools/cli/dron.rb`](../../../adn/tools/cli/dron.rb)
- **A01.P006**: [Sensor de Mejora Continua](./A01.P006_Sensor-de-Mejora-Continua.md) — Dron como evolución del sensor
- **A01 (dtic-ADN)**: [Manifiesto de Ámbito](./A01_dtic-ADN.md) — Marco de trabajo
- **adn/06_gobernanza.md**: [Principios Rectores](../../../adn/06_gobernanza.md) — Menos es Más
### Externas (Inspiración)
- **Swarm Intelligence**: Comportamiento emergente en colonias de insectos
- **Unix Philosophy**: "Do one thing and do it well"
- **Microservices**: Servicios pequeños, composables, independientes
- **Event-Driven Architecture**: Comunicación asíncrona vía eventos
- **CronCreate**: SDK de Claude Code para schedules persistentes
### DB-First
- Todas las métricas se almacenan en PostgreSQL (`bitacoras.dron_logs`)
- Dashboard web consume API → DB
---
## 🔄 Armonía Integral
**Última actualización**: 2026-04-15 — Fase 1.5 completada + Heartbeat con output acumulado
| Documento | Estado | Notas |
|-----------|--------|-------|
| `A01_dtic-ADN.md` | 📍 Pendiente | Agregar A01.P009 a tabla de planes |
| `adn/07_proyectos.md` | 📍 Pendiente | Referenciar nuevo plan |
| `docs/contexto/IA.md` | 📍 Pendiente | Actualizar sección de drones |
| `adn/06_gobernanza.md` | ✅ Alineado | Principio "Menos es Más" — AtomicDrones + Atomisidad |
---
## 📦 Entregables
### Fase 1 (Estructura atómica — con deuda técnica)
| Archivo | Líneas | Descripción | Estado |
|---------|--------|-------------|--------|
| `adn/tools/cli/dron/base.rb` | 66 | Módulo compartido (atomisidad) | ✅ |
| `adn/tools/cli/dron/dispatcher.rb` | 284 | Routing de subcomandos | ⚠️ B1 |
| `adn/tools/cli/dron/ejecutor.rb` | 171 | Ejecutor atómico | ⚠️ B9, B11 |
| `adn/tools/cli/dron/vigilante.rb` | 151 | Vigilante atómico | ⚠️ B10 |
| `adn/tools/cli/dron/sanador.rb` | 85 | Sanador atómico | ⚠️ B6, B7, B8 |
| `adn/tools/cli/dron/bitacora.rb` | 76 | Bitácora atómica | ✅ |
| `adn/tools/db/core/dron_db.rb` | 351 | Acceso a datos DB | ⚠️ B2-B5 |
| `adn/tools/db/migrations/003_create_dron_tables.rb` | 232 | Migración DB | ✅ |
### Fase 1.5 (Pendiente — Visibilidad + Corrección)
Entregables esperados:
- Corrección de los 12 bugs detectados
- Heartbeat visible en Bitácora Web
- Eliminación del sistema dual `.sh`/Ruby
- Flujo ejecutor→events→web funcional y probado