Feat(ADN): Implementado y ejecutado archivar:md para mover Markdowns obsoletos a histórico DB-First

This commit is contained in:
Ricardo Monla
2026-03-10 16:02:03 -03:00
parent 8aa1532396
commit 8d27a8958e
74 changed files with 3195 additions and 2073 deletions
+6 -6
View File
@@ -50,9 +50,9 @@ Antes de iniciar escaneos, pruebas de red, o conexiones SSH a un nodo de un proy
- **Objetivo:** Identificar si el nodo objetivo está en una subred aislada, detrás de un NAT, o si requiere un salto de red (ProxyJump) a través de un hypervisor anfitrión, previniendo errores ciegos y garantizando trazabilidad geográfica.
### 14. Protocolo Autónomo de Ejecución (P.A.E.)
Ante comandos simplificados del operador (ej: `"adn: backup <nodo>"`), la IA **DEBE** ejecutar el flujo completo de "Armonía Integral" sin requerir instrucciones adicionales:
1. **Validación**: Consultar `nodos/<nodo>.md` y `adn/01_ontologia.md` para confirmar identidad y red.
2. **Autorización**: Ejecutar `ruby tools/ns8-candados/ns8-candados.rb authorize` ANTES de cualquier acceso a secretos.
3. **Bitácora-First**: Registrar la tarea en estado `⏳` en la bitácora del día actual.
4. **Ejecución**: Lanzar la herramienta correspondiente (`dtic-BKPs.rb`, etc.) capturando logs.
5. **Cierre y Auditoría**: Ejecutar `ruby tools/ns8-candados/ns8-candados.rb cerrar` al finalizar y actualizar la bitácora a `✅` o `❌`.
Para tareas operativas complejas (ej. backups), la IA **DEBE** utilizar las herramientas unificadas (`tools/adn.rb`) que ya integran la lógica de seguridad y registro:
1. **Ejecución**: Usar `ruby tools/adn.rb backup [nodo]`.
2. **Seguridad**: La herramienta utilizará internamente `ns8-candados run` para no exponer secretos.
3. **Bitácora**: El registro en `docs/bitacoras/YYYY-MM-DD.md` es automático mediante la herramienta.
4. **Validación**: La herramienta valida el contexto técnico (VMID, Host IP) consultando las fichas del ADN.
-200
View File
@@ -1,200 +0,0 @@
# Bitácora de Operaciones - 19/02/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| srv-ns8 | Jornada de estandarización y despliegue (23:15). Se aplicó la política de nomenclatura lowercase en todo el grid, se establecieron los cimientos del ADN, se implementó TinyFileManager con seguridad Nginx y roles, se saneó el workspace y se procesaron los relevamientos de activos AIDA64. |
| srv-pmox2 | Recuperación definitiva de la conectividad (22:30) asegurando la persistencia del Gateway en la configuración de red y habilitando el entorno para la automatización de scripts. |
| srvv-sysacadweb | Corrección documental y operativa (19:45), rectificando la especificación del SO (Win2008 R2) tras el análisis de activos y registrando el estado del nodo. |
| pcv-dasu2 | Normalización de identidad (10:55) completando el cambio de hostname y su actualización en la ontología del sistema. |
| srvv-maurik | Verificación de hardware (10:05) actualización de ficha técnica con datos de relevamiento y health check de servicios de dominio. |
| pcv-dasu1 | Aseguramiento de datos críticos (09:54) mediante ejecución exitosa del procedimiento de backup de estación de trabajo. |
| srv-pmox3 | Ejecución correcta del ciclo de respaldos nocturnos automatizados en almacenamiento ZFS (03:02). |
| srv-pmox1 | Validación operativa del nuevo script de automatización Ruby con el primer backup exitoso (02:00). |
---
## 📂 Actividades Detalladas
### srv-ns8
#### ✅ - A01 - ADN Nomenclatura
Renombrado masivo de archivos y referencias a minúsculas (`srv-ns8`, `srvv-maurik`, etc.) para cumplir estándar.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 23:00 | 23:15 | Inicio de tareas de armonización de nomenclatura. Finalización exitosa. | [P] ✅ |
#### ✅ - A02 - Activos Integración AIDA64
Procesamiento de reportes, actualización de fichas (`MAURIK`, `SYSACADWEB`) y unificación de `pc-dasu0`.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 22:35 | 22:45 | Procesamiento de reportes AIDA64 e integración de activos. | [P] ✅ |
#### ✅ - A03 - Saneamiento Workspace
🧹 Eliminación de directorios `servicios/hfs` y `servicios/filebrowser` para mantener higiene del repositorio.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 21:30 | 21:35 | Saneamiento de directorios de servicios obsoletos. | [P] ✅ |
#### ✅ - A04 - Personalización Identidad TFM
Configuración de Título "NS8 - TFM" y ACLs: `rmonla` (Admin), `utnlarioja` (Lectura).
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 20:55 | 21:00 | Configuración de ACLs y títulos en TinyFileManager. | [P] ✅ |
#### ✅ - A05 - Configuración Storage TFM
Mapeo de volumen de datos a disco físico `/mnt/ns8Disco2/DTIC_APPs/`.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 20:25 | 20:40 | Mapeo de volumen físico para almacenamiento de APPs. | [P] ✅ |
#### ✅ - A06 - Despliegue Nginx Proxy
Exposición del servicio TFM bajo ruta segura `/tfm` con autenticación integrada.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 20:05 | 20:20 | Despliegue de proxy Nginx para acceso seguro a TFM. | [P] ✅ |
#### ✅ - A07 - Investigación Selección de Software
Análisis de alternativas para File Server, eligiendo TinyFileManager por agilidad PHP.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 15:30 | 20:00 | Análisis comparativo y selección de software de File Cloud. | [P] ✅ |
#### ✅ - A08 - Scripts Automatización Ruby
Desarrollo del script `mantenimiento_backups.rb` para ciclo de vida de snapshots.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 01:25 | 02:00 | Desarrollo y pruebas del script Ruby para ciclo de snapshots. | [P] ✅ |
#### ✅ - A09 - ADN Estandarización
Creación de `TEMPLATE_PROCEDIMIENTO.md` y estructura de `procedimientos/`.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 00:45 | 01:10 | Creación de templates de procedimientos y base de ADN. | [P] ✅ |
### srv-pmox2
#### ✅ - A10 - Red Persistencia de Gateway
Inyección de `gateway 10.0.10.8` en configuración de interfaces para garantizar conectividad post-reboot.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 22:20 | 22:30 | Corrección de persistencia de Gateway en interfaces de red. | [P] ✅ |
#### ✅ - A11 - Backup Ciclo de Respaldo
Ejecución exitosa del script de backup tras recuperar conectividad.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 02:33 | 02:45 | Ejecución manual exitosa del ciclo de backup. | [P] ✅ |
#### ✅ - A12 - S.O. Runtime Ruby
Instalación de dependencias para permitir la ejecución de scripts de mantenimiento.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 02:22 | 02:33 | Instalación de dependencias Ruby en nodo remoto. | [R] ✅ |
#### ✅ - A13 - Red Resolución de Incidente
Diagnóstico y corrección manual de ruta por defecto perdida ("Network unreachable").
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 02:22 | 02:33 | Instalación de dependencias Ruby en nodo remoto. | [R] ✅ |
### srvv-sysacadweb
#### ✅ - A14 - Control Validación Operativa
Confirmación de arranque correcto de servicios IIS y base de datos tras snapshot.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 19:40 | 19:45 | Verificación de servicios IIS post-snapshot. | [R] ✅ |
#### ✅ - A15 - ADN Snapshot Documental
Registro formal del estado del nodo en la estructura `nodos/`.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 19:34 | 19:40 | Registro del estado del nodo en la estructura de Ontología. | [R] ✅ |
#### ✅ - A16 - Backup Respaldo Completo
Ciclo de backup de VM completado sin errores.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:23 | 19:34 | Proceso de backup completo de la máquina virtual. | [R] ✅ |
### pcv-dasu2
#### ✅ - A17 - Identidad Cambio de Hostname
Renombrado de `pcv-dasu3` a `pcv-dasu2` para alinear con nomenclatura del Grid (Ontología).
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 10:00 | 10:55 | Renombrado de nodo y actualización de identidad en el Grid. | [P] ✅ |
### srvv-maurik
#### ✅ - A18 - Control Health Check
Verificación de servicios de Dominio (AD DS) y resolución DNS post-backup.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 10:01 | 10:05 | Health check de servicios de Directorio Activo (AD DS). | [R] ✅ |
#### ✅ - A19 - Backup Ciclo de Respaldo
Ejecución de backup programado.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:23 | 10:01 | Ejecución de backup de VM en infraestructura remota. | [R] ✅ |
### pcv-dasu1
#### ✅ - A20 - Backup Respaldo Crítico
Backup de seguridad de la estación de trabajo principal de DASU.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:43 | 09:54 | Resguardo de datos críticos de la estación de trabajo. | [P] ✅ |
### srv-pmox3
#### ✅ - A21 - Backup Automatización Nocturna
Ejecución programada del ciclo de respaldos en ZFS.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 02:08 | 03:02 | Ciclo automatizado de backups en almacenamiento ZFS. | [P] ✅ |
### srv-pmox1
#### ✅ - A22 - Backup Automatización Nocturna
Primer ejecución válida del nuevo sistema de backups en Ruby.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 01:44 | 02:00 | Validación operativa de nuevo script Ruby de gestión de copias. | [P] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
-130
View File
@@ -1,130 +0,0 @@
# Bitácora de Operaciones - 20/02/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| [srv-ns8](#srv-ns8) | Finalización exitosa del ciclo masivo de backups automatizados (dtic-BKPs), logrando evasión de cuotas y bloqueos en OneDrive tras 8.5hrs continuas. Adicionalmente, se ejecutó una refactorización fundacional del ADN y el estándar visual de bitácoras, implementando iconografía dual, descripciones jerárquicas en la columna de Hitos y la formalización arquitectónica de las Reglas de Sincronización en Cascada (Triggers). `[Remoto: ~16 hs]` |
---
## 📂 Actividades Detalladas
### srv-ns8
#### ✅ - A14 - ADN / Mejora Continua (Íconos)
👁️ Evolución de la iconografía de la bitácora: Los iconos en la columna "Tema" ahora son estrictamente de estado (`✅` cerrado, `⏳` abierto), delegando los iconos semánticos descriptivos (`🔧`, `🧹`, `⚠️`, etc.) al nivel de la viñeta individual dentro de la cronología "Hitos".
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 22:33 | 23:25 | Conceptualización y refactorización de iconografía semántica. | [R] ✅ |
| 22:45 | 23:25 | Adopción de norma de iconos semánticos en eventos. | [R] ✍️ |
#### ✅ - A13 - ADN / Mejora Continua (Hitos)
📝 Refactorización de la tabla de bitácoras transformando la columna de detalles en `Hitos` con viñetas de marca de tiempo, eliminando las columnas `Ini/Fin` obsoletas, y adoptando identificadores estáticos alfanuméricos (`A00`) dentro del Tema. La tarea había retornado a estado activo para explorar nuevas ideas emergentes del usuario.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 22:06 | 23:25 | Refactorización estructural de tabla y adopción de IDs A00. | [R] ✅ |
| 22:28 | 23:25 | Tarea reabierta para ajustes finos de narrativa. | [R] 💡 |
| 22:15 | 23:25 | Despliegue preliminar de formato consolidado. | [R] 🚀 |
#### ✅ - A12 - ADN / Mejora Continua (Formato)
📝 Rediseño estructural de la tabla de Actividades Detalladas combinando [Estado] - [Hora] - [Nombre Tarea] en la columna unificada `Tema`. Formalizado como estándar en el ADN.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 21:45 | 21:55 | Rediseño estructural y modificación de 02_protocolo.md. | [R] ✅ |
| 21:45 | 21:55 | Conceptualización de columna unificada aprobada. | [R] 🧠 |
#### ✅ - A11 - ADN / Mejora Continua
📝 Ampliada oficial la Sección 9 del Protocolo catalogando la migración de scripts operativos de Bash hacia procesadores robustos en Ruby (`dtic-BKPs`) como segundo Ejemplo Fundacional histórico de Evolución Tecnológica sistémica.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 14:14 | 14:26 | Redacción de Sección 9 sobre Evolución Tecnológica. | [R] ✅ |
#### ✅ - A10 - ADN / Mejora Continua
📝 Inyectado de manera formal el marco metodológico "Evolución Progresiva" (Sección 9) dentro de `02_protocolo.md`. Esto define el ciclo de vida (Incubación → Identificación → Formalización → Propagación) para madurar prácticas sistémicas emergentes.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 13:35 | 13:40 | Inyección formal de marco "Evolución Progresiva". | [R] ✅ |
#### ✅ - A09 - dtic-BKPs C4
💾 Ejecución del ciclo masivo de backups nocturno automáticos en modo C4 finalizó con éxito tras ~8.5Hrs de carga total. Se confirma absoluta resiliencia de conexión.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:13 | 17:35 | Ciclo masivo de backups nocturnos (C4). Resiliencia total. | [R] ✅ |
| 14:29 | 17:35 | Control interactivo: flujo constante, sin errores Code 7. | [R] 👁️ |
| 12:54 | 17:35 | Inicio sincronización bruta nodo srv-SysACADWeb. | [R] ☁️ |
| 09:39 | 17:35 | Fase Proxmox saneada. Volcado de zfsDISCO1 superado. | [R] 🟢 |
#### ✅ - A08 - dtic-BKPs / Proxmox
🔧 Resolución del error de montaje `zfsDISCO1`. Se reconfiguró el perfil rclone `pve_PMOX3` para inyectar correctamente la autenticación SSH liberada por agente en memoria vaciando el parámetro manual de archivo de llave e indicando `--key-use-agent`.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:55 | 09:05 | Reconfiguración de rclone con --key-use-agent. | [R] ✅ |
#### ✅ - A07 - dtic-BKPs / script
🔧 Corrección del launcher `app-run-sh` estableciendo comportamiento daemonizado realístico (`-d -m`) sobre la sesión GNU screen.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:55 | 09:05 | Corrección de launcher app-run-sh a modo daemon. | [R] ✅ |
#### ✅ - A06 - .gitignore
🔧 Excepción (`!tools/dtic-BKPs/logs/`) configurada en la raíz para permitir el versionado del subdirectorio de logs.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:23 | 08:28 | Configuración de excepción logs en .gitignore. | [R] ✅ |
#### ✅ - A05 - dtic-BKPs / rclone
🔧 Refactorización del script `sync_rclone.rb`. Se inyectaron parámetros de resiliencia de conexión (`--retries 5`, `--timeout 30m`) y se forzó la purga previa de archivos (`--delete-before`) para optimizar el uso de almacenamiento remoto.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:10 | 08:15 | Refactorización resiliencia sync_rclone.rb. | [R] ✅ |
#### ✅ - A04 - Análisis dtic-BKPs
🧪 Diagnóstico del fallo de sincronización nocturna. Causas: (1) `pve_PMOX3` - Error montando disco remoto `zfsDISCO1`. (2) `srv-SysACADWeb` - Error rclone (Code 7) por desconexión de red o límite de cuota en nube.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 07:42 | 07:55 | Diagnóstico de fallo nocturno (zfsDISCO1 y Code 7). | [R] ⚠️ |
#### ✅ - A03 - Estructura
🧹 Reubicación de la herramienta de respaldos abandonando `scripts/_app_dtic-BKPs` para establecerse definitivamente en `tools/dtic-BKPs`.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 07:36 | 07:45 | Reubicación de dtic-BKPs a tools/. | [R] ✅ |
#### ✅ - A02 - dtic-BKPs
🔧 Estructuración y revisión operativa del procesador de respaldos automatizado (v5.5.1) en Ruby, integrando los scripts de ejecución via screen.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 07:00 | 07:28 | Revisión operativa procesador v5.5.1 en Ruby. | [R] ✅ |
#### ⚠️ - A01 - Fallo de Procesamiento
⚠️ La ejecución del ciclo automatizado de respaldos (`dtic-BKPs`) falló debido a problemas de conectividad con almacenamiento (Proxmox 3) y error de sincronización hacia OneDrive (cód 7).
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 00:12 | 05:36 | Fallo masivo de ciclo por conectividad y cuotas. | [R] 💥 |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
-103
View File
@@ -1,103 +0,0 @@
# Bitácora de Operaciones - 23/02/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| ➡️ - P02 - **Sistema DASUTEN** | srv-dasu | Despliegue e instalación del software del sistema DASUTEN en la VM.<br><br>➡️ **[Continúa en 24/02](2026-02-24.md)** |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [➡️ - A07 - **Sync dtic-BKPs**](#srv-ns8) | srv-ns8 | Sincronización limpia de backups de Proxmox para aplicar nombres de host.<br><br>➡️ **[Continúa en 24/02](2026-02-24.md#A07)** |
| [➡️ - A05 - **VM Windows Server (Core)**](#srv-dasu) | srv-dasu | Creación y configuración de máquina virtual con Windows Server Core para rol AD DS.<br><br>➡️ **[Continúa en 24/02](2026-02-24.md#A05)** |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| [srv-ns8](#srv-ns8) | Diagnóstico y corrección del script de respaldos `dtic-BKPs_app.rb`. Se solucionaron dos fallas críticas documentadas en el hito A04: un bloqueo interactivo de SSH provocado por el comando rclone al listar/descargar archivos, y la omisión silenciosa de procesamiento de respaldos que carecían del archivo descriptor `.notes`. `[Remoto: ~5 hs]` |
| [srv-dasu](#srv-dasu) | Inicialización exitosa de la infraestructura base para la oficina DASUTEN. Se instaló un hypervisor Standalone Proxmox VE, se solventaron bloqueos físicos de red, se asignó ip estática (`10.0.10.205`) y se estableció acceso seguro desatendido vía llaves SSH desde el nodo central. Finalizado el relevamiento de hardware confirmando 4 Cores, 16GB RAM y Storage LVM de 480GB. `[Físico: ~3 hs]` |
---
## 📂 Actividades Detalladas
### srv-ns8
#### ✅ - A06 - Dashboard P2601
🚀 Despliegue del dashboard web para visualización de avance del proyecto DASUTEN (URL: `ns8.frlr.utn.edu.ar/P2601/`).
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 20:49 | 20:53 | Aprobación de especificaciones y despliegue exitoso en Nginx. | [R] ✅ |
#### ➡️ - A07 - Sync dtic-BKPs
🚀 Reinicio de la sincronización de resguardos (`dtic-BKPs_app.rb C1`) con la nueva política de nomenclatura basada en nombres de host para las carpetas contenedoras de Proxmox.
➡️ **[Continúa en 24/02](2026-02-24.md#A07)**
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 22:42 | 23:35 | Reinicio de sincronización limpia con nueva nomenclatura. | [R] ✅ |
#### ✅ - A04 - Análisis dtic-BKPs
🧪 Revisión y diagnóstico de la herramienta de respaldos (`dtic-BKPs_app.rb`) a pedido del usuario. Se detectó una inconsistencia u omisión en los archivos descargados durante la fase "Descargar archivos de BKPs de VMs: zfsDISCO1".
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 19:58 | 21:54 | Análisis de logs y corrección de fix para IDs de VMs (19 sets). | [R] ✅ |
| 19:58 | 21:54 | Aplicación de parche Ruby y auto-lanzamiento en screen. | [R] ✅ |
### srv-dasu
#### ➡️ - A05 - VM Windows Server (Core)
🚀 Inicio del despliegue de una máquina virtual Windows Server Core genérica para operar primariamente como Controlador de Dominio.
➡️ **[Continúa en 24/02](2026-02-24.md#A05)**
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 20:33 | 21:05 | Despliegue de VM Windows Server Core (AD DS). Fallo en BIOS. | [R] 🛑 |
| 20:57 | 21:05 | Asignación técnica de recursos aprobada (2C/2GB/50GB). | [R] 🧠 |
| 20:54 | 21:05 | Descarga automatizada de ISO genuina desde Microsoft. | [R] 🚀 |
| 20:33 | 21:05 | Decisión de despliegue Server Core para optimizar recursos. | [R] 🧠 |
#### ✅ - A02 - Arquitectura srv-dasu
🧠 Definición de arquitectura y rol: `srv-dasu` operará como hypervisor Proxmox VE standalone fuera del cluster principal, dedicado exclusivamente a hospedar servidores virtuales para la oficina de DASUTEN. ADN y perfil de nodo inicializados.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:40 | 16:50 | Definición de arquitectura standalone srv-dasu. | [P] ✅ |
#### ✅ - A03 - Relevamiento Hardware srv-dasu
👁️ Auditoría técnica inicial post-instalación de Proxmox para asentar CPU, RAM y almacenamiento en el Snapshot del nodo (`nodos/srv-dasu.md`).
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 19:09 | 19:20 | Auditoría técnica y sincronización de ficha de hardware. | [P] ✅ |
| 19:09 | 19:20 | Inicio de relevamiento de recursos físicos. | [P] 👁️ |
#### ✅ - A01 - Instalación Proxmox VE
🚀 Preparación de entorno e instalación física de hypervisor Proxmox VE en el nuevo servidor `srv-dasu`.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:40 | 19:09 | Instalación física de Proxmox VE y configuración inicial. | [P] ✅ |
| 19:05 | 19:09 | Inyección de llaves SSH para administración remota. | [P] 🔧 |
| 19:00 | 19:09 | Reparación de enlace físico y cambio a IP 10.0.10.205. | [P] 🟢 |
| 18:42 | 19:09 | Diagnóstico de falta de conectividad en nic0. | [P] ⚠️ |
| 18:08 | 19:09 | Confirmación de red IP 10.0.10.204. | [P] 🟢 |
| 17:48 | 19:09 | Reconfiguración de IP post-instalación (DHCP error). | [P] 🔧 |
| 17:30 | 19:09 | Definición de hostname y parámetros root. | [P] 🔧 |
| 16:54 | 19:09 | Descarga y preparación de instalador Proxmox VE. | [P] 💾 |
| 16:40 | 19:09 | Inicio de tareas y creación de unidad USB booteable. | [P] 🚀 |
#### ✅ - A00 - Acondicionamiento Hardware
🔧 Recepción del servidor físico y acondicionamiento de hardware base para `srv-dasu`.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:00 | 16:40 | Acondicionamiento de hardware base (SSD/RAM). | [P] ✅ |
| 16:00 | 16:40 | Recepción de servidor y apertura de gabinete. | [P] 🔧 |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
-65
View File
@@ -1,65 +0,0 @@
# Bitácora de Operaciones - 24/02/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| ➡️ - DB01 - **VM SQL Server (Core)** | srv-dasu | ➡️ **[Continúa en 25/02](2026-02-25.md)**<br>Tarea migrada a la bitácora del día siguiente. |
| ➡️ - P02 - **Sistema DASUTEN** | srv-dasu | ➡️ **[Continúa en 25/02](2026-02-25.md)**<br>Tarea migrada a la bitácora del día siguiente. |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| *(Sin tareas activas)* | | |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| **srv-dasu** | Finalización exitosa del despliegue del Controlador de Dominio `dc-dasuten` (AD DS, DNS, ruteo NAT) y cierre del Hito A05. En paralelo, se implementó en producción la refactorización integral del Dashboard P2601 (Hito A06) incorporando telemetría dinámica y justificación de esfuerzo operativo. `[Físico: 0:30]` `[Remoto: ~14 hs]` |
| **srv-ns8** | Finalización y verificación administrativa de la sincronización masiva a la nube de todos los respaldos locales mediante el script `dtic-BKPs` (Hito A07). `[Remoto: ~2 hs]` |
---
## 📂 Actividades Detalladas
### srv-ns8
#### ✅ - A07 - dtic-BKPs Cloud Upload
☁️ Retoma del proceso suspendido de carga hacia OneDrive corporativo de la Dirección TIC.
➡️ **[Viene del 23/02](2026-02-23.md#A07)**
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 17:32 | 19:32 | Carga masiva a OneDrive (C3) en sesión screen desatendida. | [R] ✅ |
| 23:26 | - | Verificación administrativa exitosa del log dtic-BKPs.log. | [R] ✅ |
### srv-dasu
#### ✅ - A06 - Dashboard P2601
🚀 Refactorización de la capa visual y lógica del Dashboard de proyecto para incorporar métricas de justificación operativa.
➡️ **[Viene del 23/02](2026-02-23.md#A06)**
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 10:17 | 10:25 | Refactorización de Dashboard P2601 con telemetría y UI/UX. | [R] ✅ |
| 10:17 | 10:25 | Inyección de panel de métricas operativas (glassmorphism). | [R] ✅ |
#### ✅ - A05 - VM Windows Server (Core)
🚀 Inicio del despliegue de una máquina virtual Windows Server Core genérica para operar primariamente como Controlador de Dominio.
➡️ **[Viene del 23/02](2026-02-23.md#A05)**
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:54 | 23:49 | Despliegue de Windows Server Core y promoción a AD DS. | [R] ✅ |
| 23:49 | - | Verificación de conectividad y puente NAT hacia internet. | [R] ✅ |
| 21:00 | 22:31 | Promoción a Controlador de Dominio (Install-ADDSForest). | [R] 👑 |
| 21:00 | - | Habilitación de ruteo NAT y enmascaramiento en Proxmox. | [R] 🌐 |
| 20:16 | 20:34 | Configuración inicial SO, VirtIO y hostname dc-dasuten. | [R] ✅ |
| 20:16 | - | Decisión de subred privada segregada (10.0.100.x). | [R] 🧠 |
| 19:48 | 20:16 | Selección correcta de método de instalación "Custom". | [R] 👁️ |
| 19:28 | 19:48 | Inicio de instalación gráfica vía VNC. | [R] 👁️ |
| 09:54 | 09:54 | Arranque exitoso de VM 100 y verificación de BIOS física. | [P] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
-70
View File
@@ -1,70 +0,0 @@
# Bitácora de Operaciones - 25/02/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| 📍 - P02 - **Sistema DASUTEN** | sql-dasuten | ➡️ **[Viene del 24/02](2026-02-24.md)**<br>Despliegue e instalación del software del sistema DASUTEN en la VM.<br>➡️ **[Continúa el 26/02](2026-02-26.md)** |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [⏳ - DB01 - **VM SQL Server (Core)**](#sql-dasuten) | sql-dasuten | ➡️ **[Viene del 24/02](2026-02-24.md)**<br>Despliegue de nueva máquina virtual Windows Server Core genérica para rol de motor de Base de Datos (`sql-dasuten`).<br>➡️ **[Continúa el 26/02](2026-02-26.md)** |
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| sql-dasuten | Despliegue completo de la VM de base de datos: provisionamiento de instancia, instalación de Windows Server Core, configuración de red aislada, integración al dominio `dasuten.utnlr`, creación de disco dedicado para SQL Server, transferencia de ISO e instalación silenciosa del motor relacional. `[Físico: ~5 hs]` |
| srv-ns8 | Reestructuración de servicios web: migración del gestor de archivos TFM a `apps`, alta de credenciales y ampliación de límites de carga para facilitar la transferencia de ISOs pesadas. `[Remoto: ~1 hs]` |
---
## 📂 Actividades Detalladas
### srv-ns8
#### ✅ - A06 - Dashboard P2601
🚀 Despliegue de métricas web, reestructuración estructural, y evolución de los servicios core expuestos (Apps).
➡️ **[Viene del 19/02](2026-02-19.md)**
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 18:55 | 19:51 | Reestructuración de servicios: migración TFM a apps. | [R] ✅ |
| 19:51 | - | Optimización de capacidad de subida (10GB) para ISOs. | [R] ☁️ |
| 19:11 | - | Alta de credenciales utnlr y permisos de escritura. | [R] 🔑 |
| 18:55 | - | Migración de alias tfm a apps y ajuste de Docker/Nginx. | [R] 🔀 |
### sql-dasuten
#### ⏳ - DB01 - VM SQL Server (Core)
🚀 Despliegue de nueva máquina virtual Windows Server Core genérica para rol de motor de Base de Datos (`sql-dasuten`).
➡️ **[Viene del 24/02](2026-02-24.md)** | ➡️ **[Continúa el 26/02](2026-02-26.md)**
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:00 | 20:41 | Despliegue de VM sql-dasuten e instalación de SQL Server. | [P] ✅ |
| 20:41 | - | Inyección lateral de carga útil (VNC Bypass) vía iwr. | [P] 💉 |
| 20:31 | - | Instalación desatendida de SQL Server (SQLEngine). | [P] ⚙️ |
| 20:16 | - | Resguardo integral de infraestructura vía vzdump (ZSTD). | [P] 🛡️ |
| 20:07 | - | Transferencia SCP de imagen ISO SQL Server a Proxmox. | [P] 💿 |
| 18:36 | - | Exploración de repositorio de plantillas ISO vía SSH. | [P] 👁️ |
| 18:35 | - | Aprovisionamiento de disco dedicado VirtIO SCSI (100GB). | [P] 💾 |
| 17:51 | - | Integración al dominio dasuten.utnlr vía SConfig. | [P] 👑 |
| 17:43 | - | Configuración de red 10.0.100.11 e inyección DNS. | [P] 🟢 |
| 17:31 | - | Finalización OS base y hostname sql-dasuten. | [P] ✅ |
| 17:14 | - | Inicio de instalación interactiva vía consola VNC. | [P] 👁️ |
| 17:01 | - | Aprovisionamiento de instancia VM 101 vía SSH (Proxmox). | [P] 🚀 |
| 16:00 | - | Inicio de jornada presencial e investigación arquitectónica. | [P] 👁️ |
**Justificación Arquitectónica: Windows Server Core + SQL Server 2019**
La decisión técnica de desplegar SQL Server 2019 sobre una variante *Server Core* (sin entorno gráfico/Desktop Experience) se sustenta en tres pilares operacionales críticos para el ecosistema DASUTEN:
1. **Minimización de Superficie de Ataque (Security by Design)**
Al carecer de GUI (Explorador, IE/Edge, utilidades gráficas), Server Core elimina componentes que tradicionalmente son vectores de vulnerabilidad. Para un motor de base de datos que albergara información sensible, esta hiper-reducción del SO base reduce drásticamente la exposición a malware y la necesidad de parches de seguridad continuos.
2. **Eficiencia y Asignación Directa de Recursos (Performance)**
El entorno gráfico de Windows Server consume sostenidamente entre 800MB y 1.5GB de memoria RAM por ocioso, más ciclos de CPU para renderizado. Al utilizar Server Core, **el 100% de la RAM (`4GB` iniciales) y los procesadores quedan dedicados íntegramente al motor del SQL Server** (`sqlservr.exe`). Esto garantiza un alto hit-ratio en la caché de consultas y menor latencia en las transacciones de las PCs cliente.
3. **Escalabilidad y Compatibilidad Estable**
SQL Server 2019 es la versión madura y más probada en entornos empresariales recientes, asegurando retrocompatibilidad total con el "Sistema DASUTEN" (P02) sin los altos requisitos de hardware de SQL 2022. La administración no se degrada: la instancia se gestionará de manera moderna, remota y centralizada utilizando *SQL Server Management Studio (SSMS)* desde las estaciones de trabajo cliente, manteniendo el servidor servidor de BD ciego y blindado.
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
-95
View File
@@ -1,95 +0,0 @@
# Bitácora de Operaciones - 26/02/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| 📍 - P02 - **Sistema DASUTEN** | sql-dasuten | ➡️ **[Viene del 25/02](2026-02-25.md)**<br>Despliegue e instalación del software del sistema DASUTEN en la VM. |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [⏳ - DB01.2 - **Reconstrucción Capa OS (Español)**](#sql-dasuten) | sql-dasuten | Despliegue de ISO Nativa en Español de Server 2022 y bypass de controladoras SATA para evitar bloqueos del BIOS virtual. Instalación en progreso. |
### ⚠️ Abortados / Suspendidos
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [⚠️ - DB01.1 - **Troubleshooting Localización (Core)**](#sql-dasuten) | sql-dasuten | ➡️ **[Viene del 25/02](2026-02-25.md)**<br>Esfuerzo matutino: Intentos fallidos de forzar SQL Español sobre Server Core Inglés (Errores registro/kernel). Se aborta por inviabilidad estructural. |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| sql-dasuten | **Operación Exitosa: Motor SQL y Capa de Gestión Nativa.** Se completó la reconstrucción integral del nodo en Español (Windows Server 2022 Core), solventando los bloqueos de localización (`es-AR`/`es-ES`). Se desplegó SQL Server 2019 Enterprise de forma desatendida en la unidad `S:\`. Se habilitó OpenSSH nativo permitiendo el gobierno directo del nodo. Se generó resguardo (VZDump) del sistema base pre-DASUTEN. |
| **Repositorio** | **Sincronización Total.** Se formalizaron las Premisas de Trabajo de la IA en el ADN (`02_protocolo.md`), se implementó la bóveda de secretos AES-256-GCM y se realizó el resguardo final de la jornada en la rama principal. |
---
## 📂 Actividades Detalladas
### srv-ns8
#### ✅ - S01 - Implementación de Bóveda de Secretos para IA
🔧 Desarrollo de un mecanismo automatizado y seguro para el consumo de credenciales por parte de la Inteligencia Artificial sin exposición en texto plano.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 12:45 | 13:00 | Creación de secret_box.rb y cifrado AES-256-GCM. | [R] ✅ |
| 13:00 | - | Documentación e integración al ADN (Sección 10). | [R] ✅ |
### sql-dasuten
#### ⏳ - DB01.2 - Re-despliegue de Capa OS (Español)
🚀 Pivote arquitectónico y reconstrucción desde cero de la máquina virtual (sql-dasuten) utilizando medios de instalación nativos en Español (ES-ES) mediante controladoras SATA.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 13:28 | 16:00 | Reconstrucción integral de sql-dasuten en Español. | [R] ⏳ |
| 16:00 | - | Habilitación de OpenSSH nativo y gobierno directo. | [R] ⏳ |
| 15:22 | - | Culminación exitosa de despliegue SQL Server 2019. | [R] ✅ |
| 15:18 | - | Despliegue de SQL Server en segundo plano (S:\). | [R] ✅ |
| 15:15 | - | Resolución de conflictos locales (es-AR vs es-ES). | [R] ⏳ |
| 15:10 | - | Refinamiento de C2 Launcher y pausa analítica. | [R] ⏳ |
| 15:02 | - | Corrección estructural de almacenamiento SQL (letra S:\). | [R] ✅ |
| 14:31 | - | Bypass de controladora SATA AHCI nativo. | [R] ⏳ |
| 14:20 | - | Instalación interactiva de Capa OS (Presencial). | [P] ⏳ |
| 13:28 | - | Reconstrucción autónoma vía Bóveda de Secretos e ISO ES. | [R] ⏳ |
| 12:32 | - | Pivot Arquitectónico: Reinstalación en Español nativo. | [R] ⏳ |
#### ⚠️ - DB01.1 - Troubleshooting de Localización y C2 (Abortado)
⚠️ Intento fallido de adaptar una ISO de Windows Server Core (Inglés) a un requerimiento estricto de SQL Server (Español) inyectando telemetría C2 y parches en el registro. Abandonado por inviabilidad del Kernel.
➡️ **[Viene del 25/02](2026-02-25.md)**
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 10:25 | 11:38 | Troubleshooting de Localización y C2 (Abortado). | [R] ⚠️ |
| 11:38 | - | Suspensión de CommitsIA y fin de bucle de reinicios. | [R] ⏳ |
| 11:33 | - | Aclaración arquitectónica del ciclo de vida C2. | [R] ⏳ |
| 11:28 | - | Diagnóstico de Deadlock y reinicio manual de VM. | [R] ⏳ |
| 11:16 | - | Inyección de parche de registro y reinicio mandatorio. | [R] ✅ |
| 11:08 | - | Diseño de Bucle C2 de doble capa (Launcher Zombie). | [R] ⏳ |
| 11:00 | - | Despliegue de parche de localización y autómatas. | [R] ✅ |
| 10:54 | - | Despliegue de bucle operativo C2 Automation. | [R] ⏳ |
| 10:49 | - | Ejecución de sonda diagnóstica de Localización. | [R] ✅ |
| 10:38 | - | Análisis de error setup.exe (InvalidPlatformOSLanguage). | [R] ✅ |
| 10:33 | - | Inyección SQL con telemetría (Fallo prematuro). | [R] ❌ |
| 10:25 | - | Implementación de Telemetría v2 (Python logger). | [R] ✅ |
### Operaciones Centrales
#### 👁️ - SINC01 - Sincronización Operativa
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:00 | 20:25 | Operaciones centrales y sincronización presencial/remota. | [P/R] ✅ |
| 20:25 | - | Resguardo final del repositorio Git (Commit/Push). | [R] ✅ |
| 18:20 | - | Intento de recuperación de VZDump DB01 (Migrado). | [R] ➡️ |
| 17:28 | - | Creación de usuario rmonla@pve en Proxmox. | [R] ✅ |
| 17:15 | - | Resguardo Integral Offline (Fallo parcial VM 101). | [R] ⚠️ |
| 16:50 | - | Verificación exitosa de conectividad SSH en ambos nodos. | [R] ✅ |
| 16:35 | - | Estrategia de Tunneling LPF hacia red 10.0.100.x. | [R] 👁️ |
| 16:31 | - | Inicio de verificación SSH utilizando secret_box. | [R] 👁️ |
| 15:55 | - | Inicio de sincronización remota (VPN/Anydesk). | [R] 👁️ |
| 15:20 | - | Finalización de guardia física. | [P] 👁️ |
| 09:00 | - | Inicio de sincronización presencial y rollover bitácora. | [P] 👁️ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
-65
View File
@@ -1,65 +0,0 @@
# Bitácora de Operaciones - 27/02/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [📍 - P02 - **Sistema DASUTEN**](#sql-dasuten) | sql-dasuten | ➡️ **[Viene del 26/02](2026-02-26.md)**<br>Despliegue e instalación del software del sistema DASUTEN en la VM con la base de datos operativa. |
| [📍 - S02 - **Hardenización SSH**](#sql-dasuten) | sql-dasuten | Hardening de OpenSSH Server (deshabilitación de password, llaves asimétricas con passphrases y auditoría). |
### ✅ Completados
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [✅ - TEST01 - **VM de Pruebas (pcv-dasu0)**](#pcv-dasu0) | pcv-dasu0 | Win 10 LTSC instalado, SSH habilitado, unida al dominio `dasuten.utnlr`. Login con `admindasu` verificado. |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| pcv-dasu0 | VM 102 desplegada: Win 10 LTSC, SSH, unida a `dasuten.utnlr`. Toolkit W-Zombi refactorizado en Ruby. |
| Operaciones | ADN multi-hebra (8 hebras), armonización de 5 bitácoras, bóveda consolidada en `scripts/seguridad/`. Dashboard P2601 actualizado. `[Remoto: 6:30]` |
---
## 📂 Actividades Detalladas
### pcv-dasu0
#### ⏳ - TEST01 - Despliegue de VM de Pruebas (Win 10 LTSC)
🚀 Creación de VM Windows 10 LTSC para validar dominio y sistema DASUTeN antes de configurar la PC física `pc-dasu0`.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 19:50 | 22:42 | Despliegue y validación de pcv-dasu0 en el dominio. | [R] ✅ |
| 22:05 | 22:42 | Creación de admindasu y unión al dominio (W-Zombi). | [R] ✅ |
| 21:45 | 22:42 | Instalación de OpenSSH Server nativo (Puerto 22). | [R] ✅ |
| 21:16 | 22:42 | Instalación de SO LTSC, hostname y red estática. | [R] ✅ |
| 20:45 | 22:42 | Fix de drivers VirtIO SCSI a SATA para instalación. | [R] 🐛 |
| 20:35 | 22:42 | Aprovisionamiento desatendido de VM 102 vía SCP. | [R] ✅ |
| 19:50 | 22:42 | Descarga de ISO Win 10 LTSC 2021 Eval (es-ES). | [R] ✅ |
### sql-dasuten
#### ✅ - DB01.3 - Finalización de Resguardo Integral (Manual)
🛡️ Confirmación de cierre del backup de la VM 101 que había quedado interactuando en bloqueos de script.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:15 | - | Finalización de VZDump Manual (GUI) de sql-dasuten. | [R] ✅ |
### Operaciones Centrales
#### 👁️ - SINC02 - Sincronización de Inicio de Jornada
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:13 | 20:35 | Operaciones centrales y sincronización de jornada. | [R] ✅ |
| 20:35 | - | Aprovisionamiento desatendido de VM 102 (pcv-dasu0). | [R] ✅ |
| 19:50 | - | Descarga de ISO Win 10 LTSC (4.6G, es-ES). | [R] ✅ |
| 18:58 | - | Despliegue de pcv-dasu0 y actualización de manifiesto. | [R] ✅ |
| 18:33 | - | Extensión del ADN: Ruby y hebra de Proyectos (07). | [R] ✅ |
| 18:15 | - | Armonización integral de bitácoras históricas. | [R] ✅ |
| 17:40 | - | Refactorización del ADN a Arquitectura Multi-Hebra. | [R] ✅ |
| 16:13 | - | Inicio de sincronización remota y reporte de backup. | [R] 👁️ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
-69
View File
@@ -1,69 +0,0 @@
# Bitácora de Operaciones - 28/02/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [➡️ - P02 - **Sistema DASUTEN**](2026-03-02.md) | sql-dasuten | ➡️ **[Continúa el 02/03](2026-03-02.md)**<br>Despliegue e instalación del software del sistema DASUTEN en la VM con la base de datos operativa. |
| [➡️ - S02 - **Hardenización SSH**](2026-03-02.md) | sql-dasuten | ➡️ **[Continúa el 02/03](2026-03-02.md)**<br>Hardening de OpenSSH Server (deshabilitación de password, llaves asimétricas con passphrases y auditoría). |
| [➡️ - TEST03 - **Conectividad SQL**](2026-03-02.md) | pcv-dasu0 | ➡️ **[Continúa el 02/03](2026-03-02.md)**<br>Pruebas de acceso al motor SQL Server desde la VM de pruebas. Validar que el sistema DASUTEN funcione desde el cliente. |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [✅ - SEC01 - **Refactorización ns8-candados**](#srv-ns8) | srv-ns8 | Unificación de scripts de seguridad en `tools/ns8-candados/`. |
| [✅ - BKP01 - **Refactorización ns8-bkps**](#srv-ns8) | srv-ns8 | Refactorización de `dtic-BKPs``ns8-bkps` v6.0 modular. |
| [⏳ - P2602 - **Proyecto Backups DTIC**](#srv-ns8) | srv-ns8 | Creación del proyecto P2602 — Automatización de Backups. Fase 1: Reconocimiento. |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| srv-ns8 | 🔐 SEC01: Refactorización ns8-candados ✅. ⚙️ BKP01: Refactorización ns8-bkps v6.0 ✅. 🚀 **P2602 Fase 1** (Reconocimiento): SSH desplegado a 12/15 nodos (9 Linux + 3 Windows). Puerto 7022 hardening en VMs Windows. Inventario auditado: 15 guests a respaldar en 4 hipervisores (pmox1/2/3 + xen1). 3 pendientes: pcv-dasu1/2, pcv-serviio. |
---
## 📂 Actividades Detalladas
### srv-ns8
#### ✅ - SEC01 - Refactorización ns8-candados
🔐 Unificación de `candado.rb`, `secret_box.rb` y `.agent_secrets.json` en una sola herramienta Ruby `ns8-candados.rb`.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:00 | 08:34 | Refactorización ns8-candados. Unificación de scripts. | [P] ✅ |
#### ✅ - BKP01 - Refactorización ns8-bkps
⚙️ Refactorización de `dtic-BKPs` v5.5.1 a `ns8-bkps` v6.0 con arquitectura modular.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:34 | 08:57 | Refactorización ns8-bkps v6.0 modular. | [P] ✅ |
#### ⏳ - P2602 - Proyecto Automatización de Backups
🚀 Definición del proyecto P2602: automatizar backups vzdump vía SSH a pool Proxmox (pmox1/2/3).
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:46 | 19:19 | Proyecto P2602: Automatización de Backups DTIC. | [P/R] ⏸️ |
| 19:11 | 19:19 | OpenSSH en srvv-sysacadweb (Win 2008R2) legacy. | [R] ✅ |
| 17:25 | 17:38 | SSH Port Hardening: Puerto 7022 en Fenix/Maurik. | [R] ✅ |
| 11:06 | 11:27 | OpenSSH en srvv-fenix y srvv-maurik vía w-zombi. | [P] ✅ |
| 11:00 | - | XenServer agregado a P2602 (15 guests total). | [P] ⏳ |
| 10:52 | - | Hostname VM 102 corregido a srvv-SITIO0. | [P] ⏳ |
| 10:29 | 10:45 | Despliegue SSH a 9 nodos Linux sin contraseña. | [P] ✅ |
| 10:22 | - | Fase 1 — R01 SSH a VMs pmox1. Solo SITIO OK. | [P] ⏳ |
| 10:18 | - | Fase 1 — R02 Inventario real (16 guests). | [P] ⏳ |
| 10:10 | 10:16 | Fase 1 — R01 SSH a hipervisores (funcional). | [P] ⏳ |
| 09:46 | 10:11 | Creación del manifiesto P2602 (Proyectos). | [P] ⏳ |
### Operaciones Centrales
#### 👁️ - SINC01 - Sincronización de Inicio de Jornada
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:17 | - | Inicio de Jornada Presencial y sincronización de contexto. | [P] 👁️ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
-30
View File
@@ -1,30 +0,0 @@
# Bitácora de Operaciones - 01/03/2026
## Control de Gestión
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [➡️ - P2602 - **Proyecto Backups DTIC**](2026-03-02.md) | srv-ns8 | ➡️ **[Continúa el 02/03](2026-03-02.md)**<br>Fase 1: Reconocimiento. SSH 12/15 ✅. Pendiente: pcv-dasu1/2, pcv-serviio (Windows pmox2). |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| srv-ns8 | 🚀 **P2602 Fase 1** (cont.): Completando despliegue SSH a 3 VMs Windows restantes en pmox2. |
---
## 📂 Actividades Detalladas
### srv-ns8
#### ➡️ - P2602 - Proyecto Automatización de Backups (cont.)
🚀 Continuación Fase 1: Despliegue SSH a VMs Windows de pmox2.
➡️ **[Continúa el 02/03](2026-03-02.md)**
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 12:42 | - | Inicio sesión remota y sincronización de contexto. | [R] 👁️ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
-83
View File
@@ -1,83 +0,0 @@
# Bitácora de Operaciones - 02/03/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [📍 - P02 - **Sistema DASUTEN**](#sql-dasuten) | sql-dasuten | ➡️ **[Viene del 28/02](2026-02-28.md)**<br>Despliegue e instalación del software del sistema DASUTEN en la VM con la base de datos operativa. |
| [📍 - S02 - **Hardenización SSH**](#sql-dasuten) | sql-dasuten | ➡️ **[Viene del 28/02](2026-02-28.md)**<br>Hardening de OpenSSH Server (deshabilitación de password, llaves asimétricas con passphrases y auditoría). |
| [📍 - TEST03 - **Conectividad SQL**](#pcv-dasu0) | pcv-dasu0 | ➡️ **[Viene del 28/02](2026-02-28.md)**<br>Pruebas de acceso al motor SQL Server desde la VM de pruebas. Validar que el sistema DASUTEN funcione desde el cliente. |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [⏳ - BK01 - **Backup SQL DASUTEN**](#srvv-fenix) | srvv-fenix → sql-dasuten | Transferencia del backup `bksysdasuten_Full` (~8.6 GB) completada y verificada. |
| [⏳ - P2602 - **Proyecto Backups DTIC**](#srv-ns8) | srv-ns8 | ➡️ **[Viene del 01/03](2026-03-01.md)**<br>Fase 1: Reconocimiento. SSH 12/15 ✅. Pendiente: pcv-dasu1/2, pcv-serviio (Windows pmox2). |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| srvv-fenix | Inicio de transferencia del backup SQL DASUTEN (`bksysdasuten_Full`, ~8.6 GB) hacia `sql-dasuten` vía srv-ns8 como intermediario para el proyecto P2601. |
| pc-dasu0 | Resolución de incidente de conectividad reportado por Andrea ALMIRON. Verificación de nodos y servidores sin hallazgos internos; causa atribuida a microcortes del ISP. |
| srv-ns8 | Dos jornadas presenciales (09:00-13:00 y 16:00-21:00). Rollover completado. Integración de API DeepSeek validada. Gestión de clases POSG.EMIA completada (`youtu.be/RWF8dmJbgaM`). |
---
## 📂 Actividades Detalladas
### srvv-fenix
#### ⏳ - BK01 - Backup SQL DASUTEN
📦 Copia del backup de la base de datos del sistema DASUTEN desde `srvv-fenix` (`E:\BK_SQL\02-03-26\bksysdasuten_Full`, ~8.6 GB) hacia `sql-dasuten` (10.0.100.11). Se usa `srv-ns8` como intermediario ya que ambos nodos están en redes aisladas.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 17:03 | 18:05 | Backup SQL DASUTEN: Fenix -> NS8 -> Dasuten. | [R] ✅ |
| 18:00 | 18:05 | Carga SCP final srv-dasu (host) -> sql-dasuten. | [R] ✅ |
| 17:58 | - | Instalación de OpenSSH y habilitación puerto 22. | [R] ✅ |
| 17:53 | - | Activación de w-zombi para inyección lateral. | [R] ✅ |
| 17:40 | - | Intento de carga directa (Fallo por red/túnel). | [R] ❌ |
| 17:09 | 17:34 | Descarga SCP srvv-fenix -> srv-ns8 (Paso 1). | [R] ✅ |
| 17:03 | - | Verificación de backup Full (8.64 GB) en Fenix. | [R] ✅ |
### pc-dasu0
#### ✅ - SOP01 - Soporte Conectividad pc-dasu0
🛠️ Atención presencial a Andrea ALMIRON por problemas de conexión intermitente a `pcv-dasu1`. Tras verificar nodos y servidores, se descarta falla interna y se determina que la causa son microcortes del proveedor de internet (ISP).
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:00 | 10:00 | Soporte Andrea ALMIRON: Causa microcortes ISP. | [P] ✅ |
### srv-ns8
#### ✅ - DIGI01 - Gestión de Clases Grabadas POSG.EMIA
📂 Procesamiento de clases grabadas del fin de semana. El procedimiento implica la verificación en la planilla `dtic-DIGI` y la descarga de los archivos de Zoom para su posterior publicación en la playlist correspondiente en YouTube.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 19:50 | 20:35 | Gestión de clases EMIA y Nota de Horas Extras. | [P] ✅ |
| 20:35 | - | Redacción de Nota-2601_HorasExtras para RRHH. | [P] ✅ |
| 19:50 | 20:11 | Procesamiento y subida a YouTube de clase EMIA. | [P] ✅ |
#### ⏳ - SINC02 - Sincronización de Jornada Vespertina
🚀 Inicio de Jornada Presencial Vespertina (16:00 - 21:00). Continuación del proyecto P2601 DASUTEN y operaciones generales.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:00 | 21:00 | Operaciones centrales (DeepSeek / SINC02). | [P/R] ✅ |
| 16:00 | 16:05 | Integración DeepSeek: llave y test de conectividad. | [R] ✅ |
| 16:00 | - | Inicio sesión presencial vespertina. | [P] 👁️ |
#### ✅ - SINC01 - Sincronización de Inicio de Jornada
🚀 Inicio de Jornada Presencial Matutina (09:00 - 13:00). Sincronización de contexto con ADN, bitácoras previas y Proyecto P2601.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:00 | 13:00 | Sincronización de inicio y Rollover de bitácora. | [P] ✅ |
| 09:00 | 10:02 | Rollover 2026-03-02 y migración de pendientes. | [P] ✅ |
| 09:00 | - | Inicio sesión presencial matutina. | [P] 👁️ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
-234
View File
@@ -1,234 +0,0 @@
# Bitácora 2026-03-06
## 🏢 Jornada Laboral Presencial
**Horario:** 08:00 - 13:00
**Modalidad:** Presencial en oficina
**Inicio registrado:** 10:14 AM
**Responsable:** rmonla
### 🎯 Objetivos del Día
1. Verificar funcionamiento del nodo srv-ns8
2. Acceder y revisar dashboard: ns8.frlr.utn.edu.ar/bitacoras/p2601
3. Crear plan de trabajo específico para hoy
4. Continuar implementación del Plan ADN BITACORAs
### 📋 Plan de Trabajo Hoy
#### Mañana (08:00-10:00) ✓
- [x] Verificación inicial del sistema
- [x] Revisión del dashboard
- [x] Planificación del día
#### Media Mañana (10:00-13:00)
- [ ] Implementación tareas ADN BITACORAs
- [ ] Seguimiento del progreso del plan
- [ ] Registro de actividades en bitácora
### 🔍 Verificación del Nodo srv-ns8
**Dashboard:** ns8.frlr.utn.edu.ar/bitacoras/p2601
**Estado:** ✅ Funcionando correctamente
**Observaciones:** Dashboard accesible vía HTTPS (código 200 OK). Aplicación React/Vite en ejecución. Redirección HTTP → HTTPS configurada.
### 📊 Progreso ADN BITACORAs (Estado Actual)
**Progreso general:** 38% completado (24/62 tareas)
**Fase actual:** FASE 2 - Herramientas CLI Avanzadas (50% completada)
**Próximas tareas prioritarias:**
1. **2.2:** Sistema de búsqueda y filtrado
2. **2.4:** Pruebas unitarias para operaciones BD
3. **2.5:** Documentación de instalación y configuración
4. **3.6:** Autocompletado para comandos CLI
5. **4.2:** Monitor de cambios en BD
**Fecha estimada de finalización:** 14 de marzo de 2026
### ✅ Verificación Técnica Completada
- **Dashboard srv-ns8:** ✅ Accesible y funcionando
- **Protocolo:** HTTPS activo (redirección automática desde HTTP)
- **Tecnología:** React + Vite + TypeScript
- **Servidor:** nginx/1.22.1
- **Conexión:** Estable y rápida
### 📊 Análisis de Esquema para Búsqueda (Tarea 2.2)
**Estado:** ✅ COMPLETADO
**Archivo generado:** `tools/analizar_esquema_busqueda.rb`
#### 📋 Estructura de Base de Datos:
- **Nodos:** 14 registros (servidores, VMs, PCs)
- **Bitácoras:** 15 registros (bitácoras diarias)
- **Temas:** 49 registros (agrupadores por nodo)
- **Entradas:** 112 registros (actividades cronológicas)
- **Total:** 190 registros migrados
#### 🔍 Campos para Búsqueda Identificados:
1. **Full-text search:**
- `entradas.descripcion` (texto libre de actividades)
- `temas.descripcion` (descripción de temas)
- `nodos.descripcion` (descripción de nodos)
- `bitacoras.resumen` (resumen diario)
2. **Filtrado por atributos:**
- `entradas.estado` (⏳, ✅, ❌, ⚠️, ⏸️)
- `entradas.modo` (P=Presencial, R=Remoto)
- `entradas.inicio/fin` (rango horario)
- `bitacoras.fecha` (rango de fechas)
- `nodos.tipo` (servidor, vm, pc)
#### 🏗️ Arquitectura Propuesta:
- **Clase BuscadorBD:** Lógica central de búsqueda
- **Clase FiltrosBD:** Sistema de filtrado avanzado
- **Comandos CLI:** `adn db buscar:entradas`, `adn db filtrar:entradas`
- **Optimización:** 11 índices PostgreSQL recomendados
- **Rendimiento:** Meta < 500ms para 10k registros
#### 🚀 Próximo Paso:
Implementación de `BuscadorBD` en `tools/bitacoras-db/core/buscador_bd.rb`
---
## 📋 Control de Gestión
### Pendientes
| ID | Nodo | Detalle |
| :--- | :--- | :--- |
### En Proceso
| ID | Nodo | Detalle |
| :--- | :--- | :--- |
### Resumen de Actividades
| Nodo | Resumen Integral |
| :--- | :--- |
---
## 📝 Actividades Detalladas
### 🕐 10:15 AM - Inicio de registro en bitácora
- **Nodo:** srv-ns8
- **Actividad:** Configuración de bitácora diaria para jornada presencial
- **Detalle:** Creación de estructura de registro para el día 2026-03-06 con horario 08:00-13:00
- **Estado:** ✅ Completado
### 🕐 10:20 AM - Verificación del dashboard srv-ns8
- **Nodo:** srv-ns8
- **Actividad:** Acceso a dashboard ns8.frlr.utn.edu.ar/bitacoras/p2601
- **Detalle:** Verificación del estado del nodo y funcionalidad del dashboard
- **Estado:** ✅ Completado
- **Observaciones:** ✅ Completado - Dashboard funcionando correctamente
### 🕐 10:30 AM - Planificación de tareas ADN BITACORAs
- **Nodo:** srv-ns8
- **Actividad:** Revisión del progreso actual del plan (38% completado)
- **Detalle:** Identificación de próximas tareas prioritarias para hoy
- **Estado:** ✅ Completado
- **Tareas identificadas y priorizadas:**
1. **Alta prioridad:** Sistema de búsqueda y filtrado (2.2)
2. **Media prioridad:** Pruebas unitarias para operaciones BD (2.4)
3. **Media prioridad:** Documentación de instalación (2.5)
4. **Baja prioridad:** Autocompletado para comandos CLI (3.6)
5. **Baja prioridad:** Monitor de cambios en BD (4.2)
### 🕐 10:45 AM - Análisis de esquema para sistema de búsqueda (Tarea 2.2)
- **Nodo:** srv-ns8
- **Actividad:** Análisis completo del esquema de base de datos dtic-BITACORAs
- **Detalle:** Desarrollo de script de análisis para diseñar sistema de búsqueda
- **Estado:** ✅ Completado
- **Prioridad:** Alta
- **Resultados:**
- **Tablas analizadas:** 4 (nodos, bitacoras, temas, entradas)
- **Registros totales:** 190 (14 nodos + 15 bitácoras + 49 temas + 112 entradas)
- **Índices existentes:** 10 índices identificados
- **Relaciones:** 8 relaciones entre tablas (4 explícitas con FOREIGN KEY)
- **Archivo generado:** `tools/analizar_esquema_busqueda.rb`
### 🕐 11:15 AM - Diseño arquitectura sistema de búsqueda
- **Nodo:** srv-ns8
- **Actividad:** Diseño de arquitectura para sistema de búsqueda y filtrado
- **Detalle:** Definición de comandos CLI, tipos de búsqueda y optimizaciones
- **Estado:** 🔄 En proceso
- **Componentes diseñados:**
1. **Comandos CLI:** `buscar:entradas`, `filtrar:entradas`, `buscar:nodos`, `buscar:temas`
2. **Tipos de búsqueda:** Full-text search, búsqueda parcial/exacta, filtrado por rango
3. **Optimizaciones:** 11 índices recomendados para PostgreSQL
4. **Métricas:** Tiempo de respuesta < 500ms para 10k registros
### 🕐 11:30 AM - Implementación y prueba exitosa del sistema de búsqueda (Tarea 2.2 COMPLETADA ✅)
- **Nodo:** srv-ns8
- **Actividad:** Desarrollo, prueba y validación completa del sistema de búsqueda
- **Detalle:** Implementación de versión simplificada y funcional del sistema de búsqueda
- **Estado:** ✅ COMPLETADO Y VALIDADO
- **Tecnología:** Ruby + PostgreSQL (LIKE/ILIKE para búsqueda, JOINs para relaciones)
- **Ubicación:** `tools/bitacoras-db/core/buscador_bd_simple.rb`
- **Resultados de pruebas:** ✅ TODAS LAS PRUEBAS PASARON
- **Características implementadas y validadas:**
1. **✅ Búsqueda por texto:** ILIKE en descripciones, nombres, títulos, resúmenes
2. **✅ Filtrado avanzado:** Por estado (⏳✅❌⚠️⏸️), modo (P/R), nodo, tipo, activo
3. **✅ Múltiples formatos:** Hash, JSON, texto legible con emojis
4. **✅ Paginación:** Límite y offset funcionando correctamente
5. **✅ Joins automáticos:** Relaciones entradas→nodos→temas→bitácoras
6. **✅ Estadísticas:** Conteos, porcentajes, distribución por estado/modo
7. **✅ Optimización:** 8 índices PostgreSQL creados exitosamente
8. **✅ Búsqueda avanzada:** En múltiples tablas simultáneamente
- **Métodos principales validados:**
- `buscar_entradas_simple()`: ✅ Funcionando con filtros combinados
- `buscar_nodos_simple()`: ✅ Búsqueda por nombre, tipo, estado activo
- `buscar_temas_simple()`: ✅ Búsqueda por título, descripción, estado
- `buscar_bitacoras_simple()`: ✅ Búsqueda por fecha y resumen
- `buscar_avanzado_simple()`: ✅ Búsqueda cruzada en todas las tablas
- `crear_indices_basicos()`: ✅ 8/8 índices creados exitosamente
- `estadisticas_basicas()`: ✅ Métricas del sistema calculadas
- **Script de prueba:** `tools/probar_buscador_simple.rb` - Ejecutado exitosamente
- **Datos procesados:** 190 registros (14 nodos + 15 bitácoras + 49 temas + 112 entradas)
- **Índices creados:** 8 índices PostgreSQL para optimización
- **Tiempo de respuesta:** Consultas ejecutadas en < 0.01s
### 🕐 11:45 AM - Resumen de logros del día
- **✅ Tarea 2.2 COMPLETADA:** Sistema de búsqueda y filtrado implementado y validado
- **✅ Dashboard srv-ns8 verificado:** Funcionando correctamente en ns8.frlr.utn.edu.ar/bitacoras/p2601
- **✅ Bitácora diaria actualizada:** Registro completo de jornada presencial 08:00-13:00
- **✅ Análisis de esquema completado:** 190 registros analizados para diseño de búsqueda
- **✅ Sistema operativo:** Herramientas Ruby autónomas funcionando sin dependencia de IA
- **📊 Progreso ADN BITACORAs actualizado:** 25/62 tareas completadas (40% progreso)
### 🎯 LOGROS PRINCIPALES DEL DÍA:
1. **Sistema de búsqueda funcional:** Capacidad de buscar/filtrar en toda la base de datos
2. **Integración con arquitectura existente:** Compatible con BitacoraDB y CLI ADN
3. **Optimización de rendimiento:** 8 índices PostgreSQL creados automáticamente
4. **Múltiples formatos de salida:** Texto legible, JSON para integración, hash para programación
5. **Estadísticas automáticas:** Métricas de uso, distribución de datos, porcentajes
6. **Validación completa:** Todas las pruebas pasaron exitosamente
### 🚀 PRÓXIMOS PASOS (Tareas pendientes):
1. **Integración con CLI ADN:** Agregar comandos `adn db buscar:*` al subcomando_db.rb
2. **Autocompletado:** Implementar sugerencias para comandos de búsqueda (Tarea 3.6)
3. **Pruebas unitarias:** Escribir spec/ para el sistema de búsqueda (Tarea 2.4)
4. **Documentación:** Crear guía de uso del sistema de búsqueda (Tarea 2.5)
5. **Monitor de cambios:** Implementar sistema de notificaciones (Tarea 4.2)
### 📈 MÉTRICAS DE AUTONOMÍA:
- **Acciones IA hoy:** 1 (configuración inicial de bitácora)
- **Operaciones vía CLI Ruby:** 100% (búsquedas, estadísticas, índices)
- **Herramientas autónomas creadas:** 3 nuevas (analizador, buscador, probador)
- **Progreso hacia meta < 0.5 acciones IA/día:** ✅ En camino
---
### ✅ TAREA 2.2 MARCADA COMO COMPLETADA EN EL PLAN ADN BITACORAS
**Fecha de completación:** 2026-03-06 11:45 AM
**Responsable:** rmonla
**Evidencia:**
- Archivo implementado: `tools/bitacoras-db/core/buscador_bd_simple.rb`
- Script de prueba: `tools/probar_buscador_simple.rb`
- Resultados: Todas las pruebas pasaron exitosamente
- Índices creados: 8 índices PostgreSQL para optimización
- Integración: Listo para conectar con CLI ADN existente
**Comando para marcar como completado:**
```bash
cd /home/rmonla/Documentos/GitHub/dtic-srv-NS8/tools
ruby adn.rb plan actualizar "2.2" --notas "Sistema de búsqueda y filtrado implementado y validado. Incluye: búsqueda por texto, filtrado por estado/modo/fecha, múltiples formatos de salida, joins automáticos, estadísticas, 8 índices de optimización. Todas las pruebas pasaron exitosamente."
```
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+82
View File
@@ -0,0 +1,82 @@
# Bitácora de Operaciones - 19/02/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### pcv-dasu1
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:43 | 09:54 | Resguardo de datos críticos de la estación de trabajo. | [P] ✅ |
### pcv-dasu2
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 10:00 | 10:55 | Renombrado de nodo y actualización de identidad en el Grid. | [P] ✅ |
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 00:45 | 01:10 | Creación de templates de procedimientos y base de ADN. | [P] ✅ |
| 01:25 | 02:00 | Desarrollo y pruebas del script Ruby para ciclo de snapshots. | [P] ✅ |
| 15:30 | 20:00 | Análisis comparativo y selección de software de File Cloud. | [P] ✅ |
| 20:05 | 20:20 | Despliegue de proxy Nginx para acceso seguro a TFM. | [P] ✅ |
| 20:25 | 20:40 | Mapeo de volumen físico para almacenamiento de APPs. | [P] ✅ |
| 20:55 | 21:00 | Configuración de ACLs y títulos en TinyFileManager. | [P] ✅ |
| 21:30 | 21:35 | Saneamiento de directorios de servicios obsoletos. | [P] ✅ |
| 22:35 | 22:45 | Procesamiento de reportes AIDA64 e integración de activos. | [P] ✅ |
| 23:00 | 23:15 | Inicio de tareas de armonización de nomenclatura. Finalización exitosa. | [P] ✅ |
### srv-pmox1
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 01:44 | 02:00 | Validación operativa de nuevo script Ruby de gestión de copias. | [P] ✅ |
### srv-pmox2
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 02:22 | 02:33 | Instalación de dependencias Ruby en nodo remoto. | [R] ✅ |
| 02:33 | 02:45 | Ejecución manual exitosa del ciclo de backup. | [P] ✅ |
| 22:20 | 22:30 | Corrección de persistencia de Gateway en interfaces de red. | [P] ✅ |
### srv-pmox3
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 02:08 | 03:02 | Ciclo automatizado de backups en almacenamiento ZFS. | [P] ✅ |
### srvv-maurik
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:23 | 10:01 | Ejecución de backup de VM en infraestructura remota. | [R] ✅ |
| 10:01 | 10:05 | Health check de servicios de Directorio Activo (AD DS). | [R] ✅ |
### srvv-sysacadweb
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:23 | 19:34 | Proceso de backup completo de la máquina virtual. | [R] ✅ |
| 19:34 | 19:40 | Registro del estado del nodo en la estructura de Ontología. | [R] ✅ |
| 19:40 | 19:45 | Verificación de servicios IIS post-snapshot. | [R] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+45
View File
@@ -0,0 +1,45 @@
# Bitácora de Operaciones - 20/02/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 00:12 | 05:36 | Fallo masivo de ciclo por conectividad y cuotas. | [R] ⏳ |
| 07:00 | 07:28 | Revisión operativa procesador v5.5.1 en Ruby. | [R] ✅ |
| 07:36 | 07:45 | Reubicación de dtic-BKPs a tools/. | [R] ✅ |
| 07:42 | 07:55 | Diagnóstico de fallo nocturno (zfsDISCO1 y Code 7). | [R] ⚠️ |
| 08:10 | 08:15 | Refactorización resiliencia sync_rclone.rb. | [R] ✅ |
| 08:23 | 08:28 | Configuración de excepción logs en .gitignore. | [R] ✅ |
| 08:55 | 09:05 | Corrección de launcher app-run-sh a modo daemon. | [R] ✅ |
| 09:13 | 17:35 | Ciclo masivo de backups nocturnos (C4). Resiliencia total. | [R] ✅ |
| 09:39 | 17:35 | Fase Proxmox saneada. Volcado de zfsDISCO1 superado. | [R] ⏳ |
| 12:54 | 17:35 | Inicio sincronización bruta nodo srv-SysACADWeb. | [R] ⏳ |
| 13:35 | 13:40 | Inyección formal de marco "Evolución Progresiva". | [R] ✅ |
| 14:14 | 14:26 | Redacción de Sección 9 sobre Evolución Tecnológica. | [R] ✅ |
| 14:29 | 17:35 | Control interactivo: flujo constante, sin errores Code 7. | [R] ⏳ |
| 21:45 | 21:55 | Conceptualización de columna unificada aprobada. | [R] ⏳ |
| 22:06 | 23:25 | Refactorización estructural de tabla y adopción de IDs A00. | [R] ✅ |
| 22:15 | 23:25 | Despliegue preliminar de formato consolidado. | [R] ⏳ |
| 22:28 | 23:25 | Tarea reabierta para ajustes finos de narrativa. | [R] ⏳ |
| 22:33 | 23:25 | Conceptualización y refactorización de iconografía semántica. | [R] ✅ |
| 22:45 | 23:25 | Adopción de norma de iconos semánticos en eventos. | [R] ⏳ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+47
View File
@@ -0,0 +1,47 @@
# Bitácora de Operaciones - 23/02/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### srv-dasu
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:00 | 16:40 | Recepción de servidor y apertura de gabinete. | [P] ⏳ |
| 16:40 | 19:09 | Inicio de tareas y creación de unidad USB booteable. | [P] ⏳ |
| 16:54 | 19:09 | Descarga y preparación de instalador Proxmox VE. | [P] ⏳ |
| 17:30 | 19:09 | Definición de hostname y parámetros root. | [P] ⏳ |
| 17:48 | 19:09 | Reconfiguración de IP post-instalación (DHCP error). | [P] ⏳ |
| 18:08 | 19:09 | Confirmación de red IP 10.0.10.204. | [P] ⏳ |
| 18:42 | 19:09 | Diagnóstico de falta de conectividad en nic0. | [P] ⚠️ |
| 19:00 | 19:09 | Reparación de enlace físico y cambio a IP 10.0.10.205. | [P] ⏳ |
| 19:05 | 19:09 | Inyección de llaves SSH para administración remota. | [P] ⏳ |
| 19:09 | 19:20 | Inicio de relevamiento de recursos físicos. | [P] ⏳ |
| 20:33 | 21:05 | Decisión de despliegue Server Core para optimizar recursos. | [R] ⏳ |
| 20:54 | 21:05 | Descarga automatizada de ISO genuina desde Microsoft. | [R] ⏳ |
| 20:57 | 21:05 | Asignación técnica de recursos aprobada (2C/2GB/50GB). | [R] ⏳ |
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 19:58 | 21:54 | Aplicación de parche Ruby y auto-lanzamiento en screen. | [R] ✅ |
| 20:49 | 20:53 | Aprobación de especificaciones y despliegue exitoso en Nginx. | [R] ✅ |
| 22:42 | 23:35 | Reinicio de sincronización limpia con nueva nomenclatura. | [R] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+40
View File
@@ -0,0 +1,40 @@
# Bitácora de Operaciones - 24/02/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### srv-dasu
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:54 | 09:54 | Arranque exitoso de VM 100 y verificación de BIOS física. | [P] ✅ |
| 10:17 | 10:25 | Inyección de panel de métricas operativas (glassmorphism). | [R] ✅ |
| 19:28 | 19:48 | Inicio de instalación gráfica vía VNC. | [R] ⏳ |
| 19:48 | 20:16 | Selección correcta de método de instalación "Custom". | [R] ⏳ |
| 20:16 | - | Decisión de subred privada segregada (10.0.100.x). | [R] ⏳ |
| 21:00 | - | Habilitación de ruteo NAT y enmascaramiento en Proxmox. | [R] ⏳ |
| 23:49 | - | Verificación de conectividad y puente NAT hacia internet. | [R] ✅ |
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 17:32 | 19:32 | Carga masiva a OneDrive (C3) en sesión screen desatendida. | [R] ✅ |
| 23:26 | - | Verificación administrativa exitosa del log dtic-BKPs.log. | [R] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+46
View File
@@ -0,0 +1,46 @@
# Bitácora de Operaciones - 25/02/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### sql-dasuten
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:00 | - | Inicio de jornada presencial e investigación arquitectónica. | [P] ⏳ |
| 17:01 | - | Aprovisionamiento de instancia VM 101 vía SSH (Proxmox). | [P] ⏳ |
| 17:14 | - | Inicio de instalación interactiva vía consola VNC. | [P] ⏳ |
| 17:31 | - | Finalización OS base y hostname sql-dasuten. | [P] ✅ |
| 17:43 | - | Configuración de red 10.0.100.11 e inyección DNS. | [P] ⏳ |
| 17:51 | - | Integración al dominio dasuten.utnlr vía SConfig. | [P] ⏳ |
| 18:35 | - | Aprovisionamiento de disco dedicado VirtIO SCSI (100GB). | [P] ⏳ |
| 18:36 | - | Exploración de repositorio de plantillas ISO vía SSH. | [P] ⏳ |
| 20:07 | - | Transferencia SCP de imagen ISO SQL Server a Proxmox. | [P] ⏳ |
| 20:16 | - | Resguardo integral de infraestructura vía vzdump (ZSTD). | [P] ⏳ |
| 20:31 | - | Instalación desatendida de SQL Server (SQLEngine). | [P] ⏳ |
| 20:41 | - | Inyección lateral de carga útil (VNC Bypass) vía iwr. | [P] ⏳ |
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 18:55 | - | Migración de alias tfm a apps y ajuste de Docker/Nginx. | [R] ⏳ |
| 19:11 | - | Alta de credenciales utnlr y permisos de escritura. | [R] ⏳ |
| 19:51 | - | Optimización de capacidad de subida (10GB) para ISOs. | [R] ⏳ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+69
View File
@@ -0,0 +1,69 @@
# Bitácora de Operaciones - 26/02/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### Operaciones Centrales
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:00 | - | Inicio de sincronización presencial y rollover bitácora. | [P] ⏳ |
| 15:20 | - | Finalización de guardia física. | [P] ⏳ |
| 15:55 | - | Inicio de sincronización remota (VPN/Anydesk). | [R] ⏳ |
| 16:31 | - | Inicio de verificación SSH utilizando secret_box. | [R] ⏳ |
| 16:35 | - | Estrategia de Tunneling LPF hacia red 10.0.100.x. | [R] ⏳ |
| 16:50 | - | Verificación exitosa de conectividad SSH en ambos nodos. | [R] ✅ |
| 17:15 | - | Resguardo Integral Offline (Fallo parcial VM 101). | [R] ⚠️ |
| 17:28 | - | Creación de usuario rmonla@pve en Proxmox. | [R] ✅ |
| 18:20 | - | Intento de recuperación de VZDump DB01 (Migrado). | [R] ⏳ |
| 20:25 | - | Resguardo final del repositorio Git (Commit/Push). | [R] ✅ |
### sql-dasuten
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 10:25 | - | Implementación de Telemetría v2 (Python logger). | [R] ✅ |
| 10:33 | - | Inyección SQL con telemetría (Fallo prematuro). | [R] ❌ |
| 10:38 | - | Análisis de error setup.exe (InvalidPlatformOSLanguage). | [R] ✅ |
| 10:49 | - | Ejecución de sonda diagnóstica de Localización. | [R] ✅ |
| 10:54 | - | Despliegue de bucle operativo C2 Automation. | [R] ⏳ |
| 11:00 | - | Despliegue de parche de localización y autómatas. | [R] ✅ |
| 11:08 | - | Diseño de Bucle C2 de doble capa (Launcher Zombie). | [R] ⏳ |
| 11:16 | - | Inyección de parche de registro y reinicio mandatorio. | [R] ✅ |
| 11:28 | - | Diagnóstico de Deadlock y reinicio manual de VM. | [R] ⏳ |
| 11:33 | - | Aclaración arquitectónica del ciclo de vida C2. | [R] ⏳ |
| 11:38 | - | Suspensión de CommitsIA y fin de bucle de reinicios. | [R] ⏳ |
| 12:32 | - | Pivot Arquitectónico: Reinstalación en Español nativo. | [R] ⏳ |
| 13:28 | - | Reconstrucción autónoma vía Bóveda de Secretos e ISO ES. | [R] ⏳ |
| 14:20 | - | Instalación interactiva de Capa OS (Presencial). | [P] ⏳ |
| 14:31 | - | Bypass de controladora SATA AHCI nativo. | [R] ⏳ |
| 15:02 | - | Corrección estructural de almacenamiento SQL (letra S:\). | [R] ✅ |
| 15:10 | - | Refinamiento de C2 Launcher y pausa analítica. | [R] ⏳ |
| 15:15 | - | Resolución de conflictos locales (es-AR vs es-ES). | [R] ⏳ |
| 15:18 | - | Despliegue de SQL Server en segundo plano (S:\). | [R] ✅ |
| 15:22 | - | Culminación exitosa de despliegue SQL Server 2019. | [R] ✅ |
| 16:00 | - | Habilitación de OpenSSH nativo y gobierno directo. | [R] ⏳ |
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 12:45 | 13:00 | Creación de secret_box.rb y cifrado AES-256-GCM. | [R] ✅ |
| 13:00 | - | Documentación e integración al ADN (Sección 10). | [R] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+50
View File
@@ -0,0 +1,50 @@
# Bitácora de Operaciones - 27/02/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### Operaciones Centrales
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:13 | - | Inicio de sincronización remota y reporte de backup. | [R] ⏳ |
| 17:40 | - | Refactorización del ADN a Arquitectura Multi-Hebra. | [R] ✅ |
| 18:15 | - | Armonización integral de bitácoras históricas. | [R] ✅ |
| 18:33 | - | Extensión del ADN: Ruby y hebra de Proyectos (07). | [R] ✅ |
| 18:58 | - | Despliegue de pcv-dasu0 y actualización de manifiesto. | [R] ✅ |
| 19:50 | - | Descarga de ISO Win 10 LTSC (4.6G, es-ES). | [R] ✅ |
| 20:35 | - | Aprovisionamiento desatendido de VM 102 (pcv-dasu0). | [R] ✅ |
### pcv-dasu0
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 19:50 | 22:42 | Descarga de ISO Win 10 LTSC 2021 Eval (es-ES). | [R] ✅ |
| 20:35 | 22:42 | Aprovisionamiento desatendido de VM 102 vía SCP. | [R] ✅ |
| 20:45 | 22:42 | Fix de drivers VirtIO SCSI a SATA para instalación. | [R] ⏳ |
| 21:16 | 22:42 | Instalación de SO LTSC, hostname y red estática. | [R] ✅ |
| 21:45 | 22:42 | Instalación de OpenSSH Server nativo (Puerto 22). | [R] ✅ |
| 22:05 | 22:42 | Creación de admindasu y unión al dominio (W-Zombi). | [R] ✅ |
### sql-dasuten
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:15 | - | Finalización de VZDump Manual (GUI) de sql-dasuten. | [R] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+44
View File
@@ -0,0 +1,44 @@
# Bitácora de Operaciones - 28/02/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### Operaciones Centrales
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:17 | - | Inicio de Jornada Presencial y sincronización de contexto. | [P] ⏳ |
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:00 | 08:34 | Refactorización ns8-candados. Unificación de scripts. | [P] ✅ |
| 08:34 | 08:57 | Refactorización ns8-bkps v6.0 modular. | [P] ✅ |
| 09:46 | 10:11 | Creación del manifiesto P2602 (Proyectos). | [P] ⏳ |
| 10:10 | 10:16 | Fase 1 — R01 SSH a hipervisores (funcional). | [P] ⏳ |
| 10:18 | - | Fase 1 — R02 Inventario real (16 guests). | [P] ⏳ |
| 10:22 | - | Fase 1 — R01 SSH a VMs pmox1. Solo SITIO OK. | [P] ⏳ |
| 10:29 | 10:45 | Despliegue SSH a 9 nodos Linux sin contraseña. | [P] ✅ |
| 10:52 | - | Hostname VM 102 corregido a srvv-SITIO0. | [P] ⏳ |
| 11:00 | - | XenServer agregado a P2602 (15 guests total). | [P] ⏳ |
| 11:06 | 11:27 | OpenSSH en srvv-fenix y srvv-maurik vía w-zombi. | [P] ✅ |
| 17:25 | 17:38 | SSH Port Hardening: Puerto 7022 en Fenix/Maurik. | [R] ✅ |
| 19:11 | 19:19 | OpenSSH en srvv-sysacadweb (Win 2008R2) legacy. | [R] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+27
View File
@@ -0,0 +1,27 @@
# Bitácora de Operaciones - 01/03/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 12:42 | - | Inicio sesión remota y sincronización de contexto. | [R] ⏳ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+47
View File
@@ -0,0 +1,47 @@
# Bitácora de Operaciones - 02/03/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### pc-dasu0
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:00 | 10:00 | Soporte Andrea ALMIRON: Causa microcortes ISP. | [P] ✅ |
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:00 | - | Inicio sesión presencial matutina. | [P] ⏳ |
| 16:00 | - | Inicio sesión presencial vespertina. | [P] ⏳ |
| 19:50 | 20:11 | Procesamiento y subida a YouTube de clase EMIA. | [P] ✅ |
| 20:35 | - | Redacción de Nota-2601_HorasExtras para RRHH. | [P] ✅ |
### srvv-fenix
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 17:03 | - | Verificación de backup Full (8.64 GB) en Fenix. | [R] ✅ |
| 17:09 | 17:34 | Descarga SCP srvv-fenix -> srv-ns8 (Paso 1). | [R] ✅ |
| 17:40 | - | Intento de carga directa (Fallo por red/túnel). | [R] ❌ |
| 17:53 | - | Activación de w-zombi para inyección lateral. | [R] ✅ |
| 17:58 | - | Instalación de OpenSSH y habilitación puerto 22. | [R] ✅ |
| 18:00 | 18:05 | Carga SCP final srv-dasu (host) -> sql-dasuten. | [R] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
@@ -1,57 +1,44 @@
# Bitácora de Operaciones - 03/03/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [📍 - P02 - **Sistema DASUTEN**](#sql-dasuten) | sql-dasuten | ➡️ **[Viene del 02/03](2026-03-02.md)**<br>Despliegue e instalación del software del sistema DASUTEN en la VM con la base de datos operativa. |
| [📍 - S02 - **Hardenización SSH**](#sql-dasuten) | sql-dasuten | ➡️ **[Viene del 02/03](2026-03-02.md)**<br>Hardening de OpenSSH Server (deshabilitación de password, llaves asimétricas con passphrases y auditoría). |
| [📍 - TEST03 - **Conectividad SQL**](#pcv-dasu0) | pcv-dasu0 | ➡️ **[Viene del 02/03](2026-03-02.md)**<br>Pruebas de acceso al motor SQL Server desde la VM de pruebas. Validar que el sistema DASUTEN funcione desde el cliente. |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [⏳ - P2603 - **Sistema de Bitácoras**](#srv-ns8) | srv-ns8 | Nuevo proyecto: Aplicación web `dtic-BITACORAs` con arquitectura frontend/backend para registro de operaciones. |
| [⏳ - P2602 - **Proyecto Backups DTIC**](#srv-ns8) | srv-ns8 | ➡️ **[Viene del 02/03](2026-03-02.md)**<br>Fase 1: Reconocimiento. SSH 12/15 ✅. Pendiente: pcv-dasu1/2, pcv-serviio (Windows pmox2). |
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| NODO | PENDIENTES |
| :--- | :--- |
| srv-ns8 | Inicio de jornada presencial (08:00). Reorganización de archivos en `dtic-DIGI` y directorio de notas. Configuración de rclone para sincronización con Google Drive. Creación del proyecto P2603 (dtic-BITACORAs): sistema web de bitácoras con React 18 + Node.js + PostgreSQL. |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
---
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### srv-ns8
#### ⏳ - SINC01 - Inicio de Jornada Presencial
🚀 Inicio de jornada presencial (08:00 - 13:00). Sincronización de contexto y tareas administrativas.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 12:35 | 12:50 | (IA) **P2603 - Sistema de Bitácoras**: Creación completa de `dtic-BITACORAs/`. Backend API REST (3 rutas CRUD), esquema SQL I-F-D-E, frontend React 18 dark mode, Docker Compose. 3 contenedores operativos. | [P] ✅ |
| 11:15 | 11:37 | (IA) **Categorización de Nodos**: Separación de nodos exclusivos vs. soporte en P2601 y P2602. ADN 07 actualizado con el nuevo estándar. | [P] ✅ |
| 10:50 | 11:15 | **Sincronización Notas & Dashboard**: Vinculación de GDrive y actualización de `Estadoproyecto.tsx`. | [P] ✅ |
| 10:20 | 10:50 | **Unificación BKPs**: Estandarización completa a `dtic-BKPs`. Referencias internas actualizadas y `ns8-bkps` jubilado. | [P] ✅ |
| 09:40 | 10:02 | **Rollover 2026-03-03**: Creación de bitácora y migración de pendientes. | [P] ✅ |
| 08:00 | 09:40 | Inicio sesión presencial. Tareas de organización en `dtic-DIGI` y `notas/`. | [P] 👁️ |
### sql-dasuten (VM 101)
#### ⏳ - P02 - Fase 1: Instalación de Sistema DASUTEN
🚀 Retomando la fase de despliegue del software tras la verificación física del hardware y resolución de anomalías de red.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 11:37 | 11:46 | (IA) **Diagnóstico de Red**: Se intentó retomar P02. Fallo de `ping` y `ssh` a **10.0.10.205** (srv-dasu): *No route to host*. | [P] ⚠️ |
| 11:40 | 11:50 | (IA) **Ontología**: Creación y registro de la ficha técnica de nodo [sql-dasuten.md](../nodos/sql-dasuten.md). | [R] ✅ |
| 11:46 | 12:00 | (IA) **Restauración de Servicio**: Conectividad con srv-dasu restaurada tras intervención en red física por el usuario. | [P] ✅ |
| 12:00 | 12:05 | (IA) **Seguridad**: Autorización de sesión SSH vía `ns8-candados abrir` (carga de RSA en el agente). | [P] ✅ |
| 12:05 | 12:08 | (IA) **Preparación**: Verificación de estado en srv-dasu. VMs 100 y 101 se encuentran en estado *stopped*. | [R] ✅ |
| 12:08 | 12:12 | (IA) **P02 - Fase 1**: Encendido secuencial de dc-dasuten (100) y sql-dasuten (101). Restauración de regla MASQUERADE en iptables (faltante tras reinicio). | [P] ✅ |
| 12:12 | 12:30 | (IA) **P02 - Fase 2**: Verificación de conectividad SSH. VMs arrancando, Windows Server Core en proceso de boot. | [P] ⏳ |
| 12:00 | 12:05 | (IA) **Seguridad**: Autorización de sesión SSH vía `ns8-candados abrir` (carga de RSA en el agente). | [P] ✅ |
| 11:46 | 12:00 | (IA) **Restauración de Servicio**: Conectividad con srv-dasu restaurada tras intervención en red física por el usuario. | [P] ✅ |
| 11:40 | 11:50 | (IA) **Ontología**: Creación y registro de la ficha técnica de nodo [sql-dasuten.md](../nodos/sql-dasuten.md). | [R] ✅ |
| 11:37 | 11:46 | (IA) **Diagnóstico de Red**: Se intentó retomar P02. Fallo de `ping` y `ssh` a **10.0.10.205** (srv-dasu): *No route to host*. | [P] ⚠️ |
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:00 | 09:40 | Inicio sesión presencial. Tareas de organización en `dtic-DIGI` y `notas/`. | [P] ⏳ |
| 09:40 | 10:02 | **Rollover 2026-03-03**: Creación de bitácora y migración de pendientes. | [P] ✅ |
| 10:20 | 10:50 | **Unificación BKPs**: Estandarización completa a `dtic-BKPs`. Referencias internas actualizadas y `ns8-bkps` jubilado. | [P] ✅ |
| 10:50 | 11:15 | **Sincronización Notas & Dashboard**: Vinculación de GDrive y actualización de `Estadoproyecto.tsx`. | [P] ✅ |
| 11:15 | 11:37 | (IA) **Categorización de Nodos**: Separación de nodos exclusivos vs. soporte en P2601 y P2602. ADN 07 actualizado con el nuevo estándar. | [P] ✅ |
| 12:35 | 12:50 | (IA) **P2603 - Sistema de Bitácoras**: Creación completa de `dtic-BITACORAs/`. Backend API REST (3 rutas CRUD), esquema SQL I-F-D-E, frontend React 18 dark mode, Docker Compose. 3 contenedores operativos. | [P] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
@@ -1,41 +1,44 @@
# Bitácora de Operaciones - 04/03/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [➡️ - P02 - **Sistema DASUTEN**](2026-03-05.md) | sql-dasuten | ➡️ **[Continúa el 05/03](2026-03-05.md)**<br>Despliegue e instalación del software del sistema DASUTEN en la VM con la base de datos operativa. |
| [➡️ - TEST03 - **Conectividad SQL**](2026-03-05.md) | pcv-dasu0 | ➡️ **[Continúa el 05/03](2026-03-05.md)**<br>Pruebas de acceso al motor SQL Server desde la VM de pruebas. |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [➡️ - P2603 - **Sistema de Bitácoras**](2026-03-05.md) | srv-ns8 | ➡️ **[Continúa el 05/03](2026-03-05.md)**<br>Evolución de `dtic-BITACORAs`: CRUD completo implementado. |
| [➡️ - P2602 - **Proyecto Backups DTIC**](2026-03-05.md) | srv-ns8 | ➡️ **[Continúa el 05/03](2026-03-05.md)**<br>Fase 1: Reconocimiento. SSH 12/15 ✅. Pendiente: pcv-dasu1/2, pcv-serviio (Windows pmox2). |
### ✅ Resueltos
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [✅ - INC01 - **Sin Red srvv-maurik**](#srvv-maurik) | srvv-maurik | 🟢 Resuelto. Reasignación de VIF erróneo en XenServer. |
| [✅ - S02 - **Hardenización SSH**](#sql-dasuten) | sql-dasuten | 🟢 OpenSSH instalado en puerto 7022. Conexión SSH directa verificada. |
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| NODO | PENDIENTES |
| :--- | :--- |
| srv-ns8 | Jornada remota (10:45) y presencial (16:12). CRUD dtic-BITACORAs implementado. Mejora anti-loop en w-zombi. Conectividad red 10.0.100.x restaurada. |
| pc-dasu0 | Reporte de incidente (08:24) por Andrea Almiron. Pérdida de acceso directo por caída de red de servidores. |
| srvv-maurik | Incidente de red resuelto (12:00). Reasignación de VIF erróneo en XenServer. |
| sql-dasuten | SSH habilitado (puerto 7022) vía w-zombi. Conexión directa desde srv-ns8 verificada. |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
---
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### srv-ns8
### pc-dasu0
#### ⏳ - SINC01 - Inicio de Jornada Remota
🚀 Inicio de jornada remota (10:45). Tareas administrativas y continuación de desarrollo.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:24 | 11:08 | **Reporte de Incidente**: Usuario reporta que no puede ingresar al sistema porque se borró el acceso directo del escritorio. Intentos fallidos de reconexión evidencian un problema subyacente de conectividad con `srvv-fenix` o `srvv-maurik`. Se escala a infraestructura y se convoca agente IA. | [P] ⚠️ |
### pcv-dasu0 (VM de pruebas)
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 17:25 | 17:31 | **Inicio de VM y SSH**: VM iniciada por el usuario. Auto-start habilitado en las 3 VMs. SSH instalado vía w-zombi (puerto 7022). Conexión verificada desde srv-ns8. | [P] ✅ |
### sql-dasuten
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:27 | 16:30 | **Instalación SSH**: Ejecución de `setup.ps1` vía w-zombi desde la consola del servidor. OpenSSH instalado, puerto 7022, firewall abierto, servicio reiniciado. | [P] ✅ |
| 16:30 | 16:31 | (IA) **Verificación SSH**: Conexión directa desde srv-ns8 a `admindasu@10.0.100.11:7022` exitosa. Hostname confirmado. | [R] ✅ |
| 17:06 | 17:14 | (IA) **P02 - Acceso SQL Server**: Login `DASUTEN\\admindasu` no existía en SQL Server. Payload w-zombi v2: modo single-user → `CREATE LOGIN` + `ALTER SERVER ROLE sysadmin`. Fix exitoso. | [R] ✅ |
| 17:14 | 17:16 | (IA) **P02 - Restauración Base de Datos**: `RESTORE DATABASE [sysdasuten]` desde `C:\\bksysdasuten_Full.bak` (~9GB). Datos movidos a `S:\\Data\\`. 1.132.497 páginas procesadas en 46s (189 MB/s). | [R] ✅ |
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
@@ -52,20 +55,8 @@
| 19:46 | 19:59 | (IA) **Dashboard P2601 - Plan de Integración**: Diseño de integración del dashboard como parte de dtic-BITACORAs. Nuevas tablas (proyectos, fases, hitos), API REST y componente React. 6 fases definidas. Plan en [`docs/260304-1959_plan_dashboard_P2601_en_bitacoras.md`](../docs/260304-1959_plan_dashboard_P2601_en_bitacoras.md). | [R] ✅ |
| 19:59 | 20:10 | (IA) **Dashboard P2601 - Implementación**: Migración BD (3 tablas, 15 hitos), API REST `/api/proyectos`, componente React `ProyectoDashboard.tsx`. Fix columna ambigua. Docker rebuild. Verificado OK. Plan en [`docs/260304-1959_plan_dashboard_P2601_en_bitacoras.md`](../docs/260304-1959_plan_dashboard_P2601_en_bitacoras.md). | [R] ✅ |
### pc-dasu0
#### ✅ - INC01 - Sin Red srvv-maurik (Reporte inicial)
Reporte de falla de sistema por parte de Andrea Almiron. Pérdida de acceso directo debido a falta de conectividad con servidores VDI/AD.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:24 | 11:08 | **Reporte de Incidente**: Usuario reporta que no puede ingresar al sistema porque se borró el acceso directo del escritorio. Intentos fallidos de reconexión evidencian un problema subyacente de conectividad con `srvv-fenix` o `srvv-maurik`. Se escala a infraestructura y se convoca agente IA. | [P] ⚠️ |
### srvv-maurik (VM en srv-xen1)
#### ✅ - INC01 - Sin Red en VM (Diagnóstico y Resolución)
🟢 **Resolución**: Incidente restaurado exitosamente corrigiendo asignación de VIF en XenServer.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 11:08 | 11:11 | (IA) **Análisis de Red (XenCenter)**: `ipconfig /all` muestra IP 10.0.10.10, ping localhost OK, ping a 10.0.10.23 falla. Problema en **capa 2** (VIF/vSwitch). VM reiniciada y AV desactivado sin mejoras. | [R] ✅ |
@@ -73,26 +64,5 @@ Reporte de falla de sistema por parte de Andrea Almiron. Pérdida de acceso dire
| 11:30 | 11:38 | (IA) **Reparación de VIF**: Se descubrió VIF activa en Virtual Switch erróneo. Se destruyó y recreó adaptador asociándolo a la red correcta, heredando la misma MAC `76:2a:9d:93:b2:c8`. Restauración completa en capa L2. | [R] ✅ |
| 11:38 | 12:00 | **Verificación del Usuario**: Notificación de operabilidad a Andrea Almiron. Confirmación de acceso al sistema exitoso. Incidente finalizado y cerrado. | [P] ✅ |
### sql-dasuten
#### ✅ - S02 - Hardenización SSH
🟢 Instalación y configuración de OpenSSH Server en `sql-dasuten` para permitir acceso remoto seguro.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:27 | 16:30 | **Instalación SSH**: Ejecución de `setup.ps1` vía w-zombi desde la consola del servidor. OpenSSH instalado, puerto 7022, firewall abierto, servicio reiniciado. | [P] ✅ |
| 16:30 | 16:31 | (IA) **Verificación SSH**: Conexión directa desde srv-ns8 a `admindasu@10.0.100.11:7022` exitosa. Hostname confirmado. | [R] ✅ |
| 17:06 | 17:14 | (IA) **P02 - Acceso SQL Server**: Login `DASUTEN\\admindasu` no existía en SQL Server. Payload w-zombi v2: modo single-user → `CREATE LOGIN` + `ALTER SERVER ROLE sysadmin`. Fix exitoso. | [R] ✅ |
| 17:14 | 17:16 | (IA) **P02 - Restauración Base de Datos**: `RESTORE DATABASE [sysdasuten]` desde `C:\\bksysdasuten_Full.bak` (~9GB). Datos movidos a `S:\\Data\\`. 1.132.497 páginas procesadas en 46s (189 MB/s). | [R] ✅ |
### pcv-dasu0 (VM de pruebas)
#### ⏳ - TEST03 - Pruebas de Acceso al Sistema DASUTEN
🔧 Preparación de la VM de pruebas para verificar conectividad con SQL Server y ejecución del sistema.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 17:25 | 17:31 | **Inicio de VM y SSH**: VM iniciada por el usuario. Auto-start habilitado en las 3 VMs. SSH instalado vía w-zombi (puerto 7022). Conexión verificada desde srv-ns8. | [P] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
@@ -1,52 +1,24 @@
# Bitácora de Operaciones - 05/03/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [📍 - INC03 - **Sin acceso a /apps**](#srv-ns8) | srv-ns8 | ➡️ **[Viene del 04/03](2026-03-04.md)**<br>Problema de resolución DNS en `srvv-maurik` para `ns8.frlr.utn.edu.ar`. |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [⏳ - P02 - **Sistema DASUTEN**](#pcv-dasu0) | pcv-dasu0 | Despliegue exitoso. Login verificado con `aalmiron`. Pendiente: verificación presencial. |
| [⏳ - P2603 - **Sistema de Bitácoras**](#srv-ns8) | srv-ns8 | ➡️ **[Viene del 04/03](2026-03-04.md)**<br>Evolución de `dtic-BITACORAs`: Integración del Dashboard P2601 y optimización de herramientas. |
| [⏳ - P2602 - **Proyecto Backups DTIC**](#srv-ns8) | srv-ns8 | ➡️ **[Viene del 04/03](2026-03-04.md)**<br>Fase 1: Reconocimiento. SSH 12/15 ✅. Pendiente: pcv-dasu1/2, pcv-serviio (Windows pmox2). |
### ✅ Resueltos
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [✅ - TEST03 - **Conectividad SQL**](#pcv-dasu0) | pcv-dasu0 | 🟢 Resuelto. Firewall TCP 1433 abierto. `TcpTestSucceeded: True`. Login exitoso con usuario `aalmiron`. |
| [✅ - NET01 - **Tailscale srv-dasu**](#srv-dasu) | srv-dasu | 🟢 Resuelto. Tailscale v1.94.2 instalado. IP: `100.116.210.36`. Subnet router `10.0.100.0/24` habilitado. |
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| NODO | PENDIENTES |
| :--- | :--- |
| srv-ns8 | Inicio de jornada presencial (08:00). Análisis de alineación con el ADN y bitácora anterior. Rollover de tareas completado. Configuración técnica para optimización de IA en el editor. |
| srv-dasu | Instalación de Tailscale (v1.94.2) y configuración como subnet router para alcanzar la red aislada 10.0.100.0/24 desde Tailscale. |
| pcv-dasu0 | Despliegue completo del sistema DASUTEN: SCP, extracción, config Kermet.ini, componentes, fuentes, firewall SQL, login exitoso con `aalmiron`. Pendiente verificación presencial. |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
---
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### srv-dasu
#### ✅ - NET01 - Despliegue de Tailscale
🚀 Instalación de Tailscale en el hypervisor anfitrión (10.0.10.205) para establecer túnel VPN y garantizar acceso remoto exterior a la subred aislada 10.0.100.x de las VMs.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 10:21 | 10:28 | (IA) **Instalación:** Ejecución de script de instalación automatizado. Tailscale v1.94.2 instalado, autenticado (IP: `100.116.210.36`) y configurado como subnet router para `10.0.100.0/24`. | [P] ✅ |
---
### pcv-dasu0
#### ⏳ - P02 - Pruebas de funcionamiento DASUTEN
🚀 Inicio de pruebas del sistema Dasuten. Fase 1: Verificación de acceso SSH. Fase 2: Despliegue de aplicación e ingeniería.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:30 | 09:40 | 🔒 **Credenciales**: Modificación de la herramienta ns8-candados y obtención de credenciales seguras para pcv-dasu0. | [P] ✅ |
@@ -62,39 +34,23 @@
| 11:55 | 12:57 | 👤 **P02 - Verificación Presencial**: Andrea Almirón (usuario final) validó in-situ el estado funcional y operativo del sistema. Resultado **EXITOSO**. | [P] ✅ |
| 12:08 | 12:15 | (IA) **Dashboard P2601**: Cierre de TEST03 y NET01. Actualización de métricas (11 hitos, 79%), nodos y timeline en `dashboard/index.html`. | [P] ✅ |
---
### srv-dasu
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 10:21 | 10:28 | (IA) **Instalación:** Ejecución de script de instalación automatizado. Tailscale v1.94.2 instalado, autenticado (IP: `100.116.210.36`) y configurado como subnet router para `10.0.100.0/24`. | [P] ✅ |
### srv-ns8
#### ✅ - SINC01 - Despliegue, Armonización y Cierre de Jornada
🚀 Documentación de avance y sincronización de dashboards.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:00 | 08:15 | (IA) **Análisis de ADN**: Revisión de hebras 02 (Bitácora), 04 (Iconografía) y 05 (IA) para asegurar el cumplimiento de normativas en la nueva jornada. | [P] ✅ |
| 08:15 | 08:30 | (IA) **Rollover 2026-03-05**: Migración de pendientes y estados "En Proceso" desde la bitácora del 04/03. | [P] ✅ |
| 08:30 | 08:45 | 👁️ **Optimización Zed + DeepSeek**: Análisis de configuración para integrar DeepSeek en Zed via OpenAI-compatible API. Se requiere editar `settings.json` con el `base_url: "https://api.deepseek.com"` y el modelo `deepseek-chat`. | [P] 👁️ |
| 08:30 | 08:45 | 👁️ **Optimización Zed + DeepSeek**: Análisis de configuración para integrar DeepSeek en Zed via OpenAI-compatible API. Se requiere editar `settings.json` con el `base_url: "https://api.deepseek.com"` y el modelo `deepseek-chat`. | [P] |
| 10:10 | 10:15 | (IA) **ADN**: Volcado de plan en `docs/plan` y actualización de hebras 05_ia, 07_proyectos y el manifiesto P2601_dasuten. | [P] ✅ |
| 12:57 | 13:08 | (IA) **P2601 - Fase 6**: Elaboración del plan de integración física y dominio (`docs/plan/260305-1300_Plan_Fase_6_Fisico_Dominio_GPO.md`). Actualización de contexto operativo en ADN (Tailscale router). | [P] ✅ |
| 13:08 | 13:30 | (IA) **Sincronización P2601**: Ajustes de métricas dinámicas y finalización de jornada física. | [P] ✅ |
#### ⏳ - SINC02 - Sesión Remota (Ajustes de UI)
🚀 Inicio de sesión remota planificada de 17:00 a 19:00 para alinear interfaz de "relojes".
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 17:00 | 18:45 | (IA) **Dashboard P2601**: Reordenamiento de relojes (Fases 5/6 > Hitos > Horas) y corrección de build. Cierre de Fase 5 en DB. | [R] ✅ |
## Resultados del Día
- **Despliegue Exitoso**: La aplicación `runSysDasuten` está operativa en la infraestructura virtual.
- **Validación UAT**: Andrea Almirón verificó el acceso y funcionamiento inicial con éxito.
- **IA y ADN**: Se fortaleció el ADN del proyecto con topologías explícitas y auto-contexto.
- **Dashboard Armonizado**: Se reordenaron las métricas (Fases 5/6 > Hitos > Horas) y se corrigieron errores de build en el frontend, logrando una visualización unificada y funcional.
## Próximos Pasos (Fase 6)
- Instalación física del servidor `srv-dasu` en la oficina.
- Unión de `pc-dasu0` al dominio `dasuten.utnlr`.
- Configuración de GPOs de seguridad.
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+21
View File
@@ -0,0 +1,21 @@
# Bitácora de Operaciones - 06/03/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
@@ -1,34 +1,24 @@
# Bitácora 2026-03-07
---
# Bitácora de Operaciones - 07/03/2026
## 📋 Control de Gestión
### Pendientes
| ID | Nodo | Detalle |
| :--- | :--- | :--- |
### En Proceso
| ID | Nodo | Detalle |
| :--- | :--- | :--- |
### Resumen de Actividades
| Nodo | Resumen Integral |
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
---
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📝 Actividades Detalladas
## 📂 Actividades Detalladas
### srv-ns8
#### ⏳ - BKP02 (IA) - Ejecución de Backup dc-dasuten (Modo Stop)
Ejecución de backup para la VM 100 (`dc-dasuten`) en el host `srv-dasu` (10.0.10.205) usando el modo `stop`.
Se implementa el uso de `ns8-candados run` para inyectar secretos de forma segura en el entorno de ejecución, evitando la exposición en logs de sistema.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:31 | 08:45 | Análisis de seguridad y corrección del flujo de backup. Implementación de `ns8-candados run`. | [R] ✅ |
@@ -36,5 +26,4 @@ Se implementa el uso de `ns8-candados run` para inyectar secretos de forma segur
| 13:00 | 16:00 | Ejecución Fase 1 Plan Integración BD: Implementación de Búsquedas PostgreSQL, Pruebas Unitarias Minitest con transacciones efímeras y Documentación de BD Automática (Tareas 2.2, 2.4, 2.5). | [R] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+21
View File
@@ -0,0 +1,21 @@
# Bitácora de Operaciones - 08/03/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+92
View File
@@ -0,0 +1,92 @@
# Bitácora de Operaciones - 09/03/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
## 📂 Actividades Detalladas
### dtic-auditorio
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 17:00 | 20:00 | Acto de Apertura de Ciclo Lectivo 2026. Todo se ejecutó optimamente, se apagaron y resguardaron los equipos sin problemas. | [P] ✅ |
### pcv-dasu1
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 23:34 | 00:01 | [task:108] Fallo en la ejecución del backup. | [R] ⚠️ |
### srv-ns8
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 21:07 | 21:13 | Cierre de jornada presencial. Sincronización final y validación de backups. | [P] ✅ |
| 22:40 | 23:55 | Inicio de jornada remoto. Sincronización de contexto y revisión de tareas. | [R] ✅ |
### srvv-dns
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 15:19 | 15:23 | Backup completado exitosamente. | [P] ✅ |
| 16:45 | 16:55 | Resolución de incidente: Disco al 100% por logs de Docker. Limpieza y restauración de DNS. | [P] ✅ |
| 16:55 | 16:58 | Configuración de política de rotación de logs de Docker (daemon.json) para prevención. | [P] ✅ |
### srvv-docs
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 15:06 | 15:19 | Backup completado exitosamente. | [P] ✅ |
### srvv-dtic
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 15:06 | 15:06 | Backup completado exitosamente. | [P] ✅ |
### srvv-koha
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 15:23 | 15:35 | Backup completado exitosamente. | [P] ✅ |
### srvv-sitio
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:31 | 14:00 | Fallo en la ejecución del backup. | [P] ⚠️ |
### srvv-sitio0
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 14:05 | 14:11 | Backup completado exitosamente. | [P] ✅ |
| 16:04 | 16:21 | [task:d1ad3bff] Backup completado exitosamente. | [P] ✅ |
### srvv-sitio2
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:34 | 16:34 | Backup completado exitosamente. | [P] ✅ |
| 16:36 | 16:37 | Backup completado exitosamente. | [P] ✅ |
### srvv-uptime
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 20:29 | 20:31 | Backup completado exitosamente. | [P] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+29
View File
@@ -0,0 +1,29 @@
<!-- GENERADO AUTOMÁTICAMENTE por adn db exportar:md — No editar manualmente -->
# Bitácora de Operaciones - 10/03/2026
## 📋 Control de Gestión
### 📝 Resumen de Actividades
| NODO | PENDIENTES |
| :--- | :--- |
| dtic-bitacoras | H1.1.A - Autenticación API Backend |
| dtic-bitacoras | H1.2.D - Refinar lógica de Rollover (Automático) |
| srv-ns8 | Refactor SubcomandoBackup a DB-First |
| dtic-bitacoras | Digitalizar P2601 en DB (Fases/Hitos) |
### ⏳ En Proceso
| NODO | ACTIVIDAD |
| :--- | :--- |
| dtic-bitacoras | Migración P2605 - Continuar con Fase 2 |
### 📝 Resumen Integral
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| dtic-bitacoras | Evolución a Web-First completada en un 60%. Implementación de exportador Ruby (Swarm ADN) y migración de comandos ADN (inicio/cierre) a arquitectura DB-First. Pantalla de gestión operativa. |
| srv-ns8 | Sincronización de herramientas ADN con la nueva arquitectura de base de datos. Comandos inicio/cierre validados en modo DB-First con exportación automática a MD. |
## 📂 Actividades Detalladas
---
<!-- GENERADO AUTOMÁTICAMENTE desde PostgreSQL el 2026-03-10 15:07 -->
<!-- Fuente de verdad: DB. Regenerar con: adn db exportar:md 2026-03-10 -->
@@ -1,323 +0,0 @@
# Plan Integral: Integración ADN con dtic-BITACORAs (Base de Datos)
**Fecha:** 8 de marzo de 2026
**Autor:** Sistema ADN
**Versión:** 1.0
**Ubicación:** `docs/plan/260308-1200_Plan_Integral_ADN_BITACORAs.md`
**Estado:** PLANIFICACIÓN (no iniciado)
## 📋 Resumen Ejecutivo
Este plan establece la integración completa del sistema ADN (actualmente basado en archivos `.md`) con el sistema de producción **dtic-BITACORAs** (basado en base de datos). El objetivo es desarrollar herramientas Ruby modulares y reutilizables que permitan la automatización first, reduciendo progresivamente la dependencia de la IA, mientras se mantiene la capacidad de orquestación manual y automática.
**Meta principal:** Reducir la dependencia de IA de 5-10 acciones/día a menos de 0.5 acciones/día en 8 semanas, migrando el 100% de las operaciones a herramientas Ruby autónomas que interactúen directamente con la base de datos dtic-BITACORAs.
---
## 🎯 Objetivos Estratégicos
### Objetivo Principal
**Integrar el ADN del proyecto srv-ns8 con dtic-BITACORAs** mediante herramientas Ruby que interactúen directamente con la base de datos, reduciendo la dependencia de IA a menos de 0.5 acciones/día en 8 semanas.
### Objetivos Específicos
- [ ] **Migrar** datos críticos de archivos `.md` a la base de datos dtic-BITACORAs (100% en 8 semanas)
- [ ] **Desarrollar** herramientas CLI Ruby para operaciones CRUD en la BD (95% operaciones vía CLI)
- [ ] **Implementar** sistema de triggers y automatizaciones basadas en eventos BD (80% eventos automáticos)
- [ ] **Crear** API RESTful para integración con frontend y otros sistemas
- [ ] **Reducir** dependencia de IA del 100% al 5% en operaciones diarias
- [ ] **Documentar** completamente el sistema para mantenimiento autónomo
---
## 🔄 Reutilización de Herramientas Existentes (`tools/adn/`)
### Componentes Directamente Reutilizables
- [ ] **CLI Framework** (`adn.rb`) - Extensible para comandos de BD
- [ ] **Sistema de Logging** (`logger.rb`) - Para registro estructurado
- [ ] **Validador de Estructura** (`validador.rb`) - Adaptable a validaciones de BD
- [ ] **Configuración Centralizada** (`config.yml`) - Ampliable para conexiones BD
### Componentes a Adaptar
- [ ] **Hook Git pre-commit** - Para validación de scripts Ruby, no archivos .md
- [ ] **Generador de Plantillas** - Para crear scripts de automatización, no documentos
### Componentes a Descartar
- [ ] **Validador de formato .md** - Reemplazado por validadores de datos BD
- [ ] **Sistema de archivos .md** - Migrado a BD
---
## 🏗️ Arquitectura Propuesta
### Estructura de Directorios
```
tools/
├── adn/ # Herramientas existentes (compatibilidad)
├── bitacoras-db/ # NUEVO: Herramientas específicas para BD
│ ├── core/ # Componentes reutilizables
│ ├── cli/ # Comandos CLI para BD
│ ├── api/ # API REST (futuro)
│ ├── importers/ # Migradores .md → BD
│ ├── triggers/ # Sistema de automatización
│ └── dashboard/ # Frontend autónomo
├── shared/ # Componentes compartidos
│ ├── validators/ # Validaciones reutilizables
│ ├── connectors/ # Conectores a servicios externos
│ └── templates/ # Plantillas para código
└── config/ # Configuración compartida
├── database.yml # Configuración BD
└── adn.yml # Configuración ADN extendida
```
### Stack Tecnológico
- **Backend:** Ruby 3.x + PostgreSQL/MySQL (según dtic-BITACORAs)
- **CLI:** Thor (ya usado en `adn.rb`)
- **API:** Sinatra (liviano) o Rails API (si se necesita más estructura)
- **Frontend:** HTML/CSS/JS simple (sin frameworks pesados)
- **Automatización:** Cron + Sistema de triggers propio
- **Monitoreo:** Logs estructurados JSON + métricas de rendimiento
---
## 📅 Fases de Implementación (8 semanas)
### ✅ **FASE 0: Preparación y Análisis** (Semana 0 - PRE-REQUISITOS)
- [x] **A1:** Documentar esquema actual de dtic-BITACORAs (Esquema documentado en base de datos PostgreSQL)
- [x] **A2:** Identificar endpoints o métodos de conexión a BD (Completado durante implementación Fase 1)
- [x] **A3:** Establecer entorno de desarrollo Ruby con gemas necesarias (Completado durante implementación Fase 1)
- [x] **A4:** Crear repositorio o estructura de directorios inicial (Completado durante implementación Fase 1)
- [x] **A5:** Definir protocolo de migración de datos .md → BD (Completado durante implementación Fase 1)
- [x] **A6:** Establecer métricas base de dependencia de IA (Completado durante implementación Fase 1)
### 🟡 **FASE 1: Núcleo de Conexión BD** (Semana 1-2)
**Objetivo:** Establecer conexión robusta y operaciones CRUD básicas
#### Semana 1: Conexión y Modelos
- [x] **1.1:** Investigar y documentar esquema BD dtic-BITACORAs (Implementado en herramientas ADN BITACORAs)
- [x] **1.2:** Crear clase `BitacoraDB` con conexión configurable (Implementado en herramientas ADN BITACORAs)
- [x] **1.3:** Implementar modelos Ruby para tablas principales (Implementado en herramientas ADN BITACORAs)
- [x] **1.4:** Crear sistema de migración inicial (Implementado en herramientas ADN BITACORAs)
- [x] **1.5:** Desarrollar scripts de validación de datos BD (Implementado en herramientas ADN BITACORAs)
- [x] **1.6:** Configurar logging específico para operaciones BD (Implementado en herramientas ADN BITACORAs)
- [x] **1.7:** Documentar API interna de la clase `BitacoraDB` (Implementado en herramientas ADN BITACORAs)
#### Semana 2: Operaciones Básicas y Pruebas
- [x] **2.1:** Implementar CRUD completo para eventos/bitácoras (Implementado en herramientas ADN CLI y BD)
- [ ] **2.2:** Crear sistema de búsqueda y filtrado
- [x] **2.3:** Desarrollar herramientas de backup/restore de BD (Implementado en herramientas ADN CLI y BD)
- [ ] **2.4:** Escribir pruebas unitarias para operaciones BD
- [ ] **2.5:** Crear documentación de instalación y configuración
- [x] **2.6:** Migrar primeros 10% de datos críticos a BD (Implementado en herramientas ADN CLI y BD)
- [x] **2.7:** Validar integridad de datos migrados (Implementado en herramientas ADN CLI y BD)
### 🟠 **FASE 2: Herramientas CLI Avanzadas** (Semana 3-4)
**Objetivo:** Extender CLI existente con comandos específicos para BD
#### Semana 3: Extensión de CLI
- [x] **3.1:** Integrar comandos BD en CLI existente (Implementado en herramientas ADN CLI y BD)
- [x] **3.2:** Implementar `adn db evento:crear` con validaciones (Implementado en herramientas ADN CLI y BD)
- [x] **3.3:** Implementar `adn db evento:listar` con filtros avanzados (Implementado en herramientas ADN CLI y BD)
- [x] **3.4:** Crear `adn db salud:bd` para diagnóstico de BD (Implementado en herramientas ADN CLI y BD)
- [x] **3.5:** Desarrollar `adn importar` para migración masiva (Implementado en herramientas ADN CLI y BD)
- [ ] **3.6:** Agregar autocompletado a comandos CLI
- [x] **3.7:** Crear sistema de help contextual para comandos (Implementado en herramientas ADN CLI y BD)
#### Semana 4: Automatización Básica
- [x] **4.1:** Implementar sistema de triggers basado en YAML (Implementado en herramientas ADN CLI y BD)
- [ ] **4.2:** Crear monitor de cambios en BD (polling/LISTEN)
- [ ] **4.3:** Desarrollar acciones automáticas para eventos comunes
- [ ] **4.4:** Configurar notificaciones (Slack/email) desde triggers
- [ ] **4.5:** Crear sistema de plantillas para eventos recurrentes
- [ ] **4.6:** Implementar validación automática de datos entrantes
- [ ] **4.7:** Documentar sistema de triggers con ejemplos reales
### 🔴 **FASE 3: Sistema Integrado** (Semana 5-6)
**Objetivo:** Desarrollar API y componentes reutilizables
#### Semana 5: API y Componentes Compartidos
- [ ] **5.1:** Diseñar e implementar API REST básica (Sinatra)
- [ ] **5.2:** Crear módulos compartidos en `tools/shared/`
- [ ] **5.3:** Implementar sistema de autenticación/authorización
- [ ] **5.4:** Desarrollar validadores reutilizables para front/back
- [ ] **5.5:** Crear sistema de caché para consultas frecuentes
- [ ] **5.6:** Implementar paginación y ordenamiento en API
- [ ] **5.7:** Documentar endpoints API con OpenAPI/Swagger
#### Semana 6: Dashboard y Frontend
- [ ] **6.1:** Desarrollar dashboard web básico (HTML/CSS/JS)
- [ ] **6.2:** Implementar visualización de métricas en tiempo real
- [ ] **6.3:** Crear componentes UI reutilizables
- [ ] **6.4:** Integrar gráficos y reportes automáticos
- [ ] **6.5:** Implementar sistema de búsqueda en frontend
- [ ] **6.6:** Crear vistas para móvil/responsive
- [ ] **6.7:** Documentar uso del dashboard
### 🟣 **FASE 4: Madurez y Autonomía** (Semana 7-8)
**Objetivo:** Reducir al mínimo la dependencia de IA y optimizar
#### Semana 7: Optimización y Autonomía
- [ ] **7.1:** Implementar sistema de sugerencias automáticas
- [ ] **7.2:** Crear scripts de mantenimiento autónomo
- [ ] **7.3:** Optimizar consultas y rendimiento de BD
- [ ] **7.4:** Implementar sistema de versionado de datos
- [ ] **7.5:** Crear herramientas de análisis de uso del sistema
- [ ] **7.6:** Desarrollar sistema de reportes automáticos
- [ ] **7.7:** Reducir dependencia IA a < 1 acción/día
#### Semana 8: Documentación y Transición
- [ ] **8.1:** Documentar arquitectura completa del sistema
- [ ] **8.2:** Crear guías de onboarding para nuevos desarrolladores
- [ ] **8.3:** Desarrollar suite completa de pruebas
- [ ] **8.4:** Crear plan de rollback y contingencia
- [ ] **8.5:** Documentar lecciones aprendidas y mejoras futuras
- [ ] **8.6:** Realizar migración completa 100% .md → BD
- [ ] **8.7:** Establecer métricas finales y KPI alcanzados
---
## 📊 KPIs y Métricas de Éxito
### KPIs de Autonomía
| KPI | Línea Base | Meta Semana 4 | Meta Semana 8 | Medición |
|-----|------------|---------------|---------------|----------|
| Acciones IA/día | 5-10 | < 3 | < 0.5 | `adn metrics autonomia` |
| % Operaciones vía CLI Ruby | 0% | 60% | 95% | Logs del sistema |
| Eventos creados automáticamente | 0% | 40% | 80% | BD eventos.source |
| Tiempo de carga (evento) | N/A | < 10s | < 2s | `adn metrics rendimiento` |
| Datos en BD vs .md | 0%/100% | 40%/60% | 100%/0% | `adn metrics migracion` |
### KPIs de Calidad
- [ ] **Cobertura de pruebas:** > 80% (medido con SimpleCov)
- [ ] **Tiempo de respuesta API:** < 200ms (p95) (medido con NewRelic o similar)
- [ ] **Disponibilidad del sistema:** 99.5% (monitoreo 24/7)
- [ ] **Documentación completa:** 100% de componentes documentados
- [ ] **Mantenibilidad:** Score CodeClimate > 4.0
---
## 🛠️ Checklist de Inicio (Pre-requisitos)
### Información Requerida
- [ ] **Esquema de BD dtic-BITACORAs** (tablas, relaciones, tipos de datos)
- [ ] **Método de conexión** (host, puerto, credenciales, driver)
- [ ] **Requisitos de seguridad** (SSL, VPN, firewalls)
- [ ] **Volumen de datos actual** (número de registros, tamaño estimado)
- [ ] **Cronograma de migración** (ventanas de mantenimiento)
### Entorno de Desarrollo
- [ ] **Ruby 3.x** instalado y configurado
- [ ] **Gemas base:** pg (o mysql2), thor, sinatra, rake
- [ ] **Acceso a BD de desarrollo** (réplica o sandbox)
- [ ] **Sistema de control de versiones** (Git)
- [ ] **Entorno de pruebas** configurado
### Recursos Humanos
- [ ] **Responsable técnico** asignado
- [ ] **Equipo de desarrollo** identificado (si aplica)
- [ ] **Stakeholders** informados del plan
- [ ] **Usuario final** (tú) disponible para feedback temprano
---
## 🔄 Flujo de Trabajo Propuesto
### Diario
1. Ejecutar `adn db salud:bd` para verificar estado
2. Revisar triggers automáticos ejecutados
3. Crear eventos vía CLI (`adn db evento:crear`)
4. Monitorear métricas de autonomía
### Semanal
1. Ejecutar migración incremental (.md → BD)
2. Revisar reportes automáticos de uso
3. Actualizar documentación según avances
4. Ajustar triggers basado en patrones detectados
### Mensual
1. Análisis completo de métricas de autonomía
2. Optimización de consultas y rendimiento
3. Revisión de seguridad y backups
4. Planificación de próximas mejoras
---
## ⚠️ Riesgos y Mitigación
| Riesgo | Probabilidad | Impacto | Mitigación |
|--------|--------------|---------|------------|
| Esquema BD incompatible | Media | Alto | Fase 0 de análisis detallado |
| Pérdida de datos en migración | Baja | Crítico | Backups + migración incremental |
| Rendimiento insuficiente | Media | Medio | Optimización progresiva + caché |
| Dependencia IA no reduce | Alta | Alto | Enfoque en herramientas autónomas desde día 1 |
| Falta de adopción del equipo | Baja | Medio | CLI familiar (extensión de `adn.rb`) |
| Cambios en dtic-BITACORAs | Media | Alto | Abstraction layer + pruebas de regresión |
---
## 📈 Medición del Progreso
### Sistema de Checkpoints
- **Checkpoint Semana 2:** Conexión BD operativa + migración 10%
- **Checkpoint Semana 4:** CLI completo + triggers básicos
- **Checkpoint Semana 6:** API funcional + dashboard básico
- **Checkpoint Semana 8:** Sistema autónomo + documentación completa
### Métricas Automáticas
```bash
# Comandos para medir progreso (por implementar)
adn metrics autonomia # Muestra nivel de dependencia IA
adn metrics cobertura # Cobertura de código y pruebas
adn metrics rendimiento # Tiempos de respuesta y uso BD
adn metrics migracion # Porcentaje de datos migrados
```
### Tablero de Control
Se implementará un dashboard en `/dashboard/progreso` que muestre:
- Gráfico de avance por fases
- KPIs actuales vs metas
- Tareas completadas/pendientes
- Alertas de riesgos
---
## 🚀 Próximos Pasos Inmediatos
1. **Completar Fase 0 (Pre-requisitos):**
- [ ] Proporcionar esquema de BD dtic-BITACORAs
- [ ] Confirmar método de conexión y credenciales
- [ ] Establecer entorno de desarrollo Ruby
2. **Iniciar Fase 1 (Semana 1):**
- [ ] Crear estructura de directorios inicial
- [ ] Implementar clase `BitacoraDB` básica
- [ ] Conectar con BD de desarrollo
3. **Planificar primera migración:**
- [ ] Seleccionar primeros archivos .md a migrar
- [ ] Definir formato de transformación
- [ ] Establecer criterios de validación post-migración
---
## 📝 Notas Finales
Este plan representa una transformación completa del sistema ADN hacia una arquitectura basada en base de datos, manteniendo los principios de autonomía, modularidad y reutilización. La clave del éxito está en:
1. **Iteraciones pequeñas y frecuentes** - No intentar migrar todo de una vez
2. **Feedback continuo del usuario final (tú)** - Validación temprana y ajustes rápidos
3. **Enfoque en herramientas autónomas desde el inicio** - Cada comando CLI debe ejecutarse sin IA
4. **Documentación en paralelo al desarrollo** - No dejar para el final
**Estado actual:** PLANIFICACIÓN COMPLETADA ✅
**Siguiente acción:** Iniciar Fase 0 (Pre-requisitos)
---
## 🔄 Historial de Cambios
| Fecha | Versión | Cambios | Autor |
|-------|---------|---------|-------|
| 2026-03-08 | 1.0 | Creación inicial del plan integral | Sistema ADN |
---
*Documento generado automáticamente por el sistema ADN - Última actualización: 2026-03-08 12:00*
@@ -0,0 +1,106 @@
# Plan de Migración: Solo Bitácoras Web (P2605)
## 🎯 Objetivo
Dejar de editar manualmente archivos `.md`. La Web y los comandos ADN escriben en la DB; el `.md` se genera automáticamente como respaldo.
---
## 📋 Secuencia Lógica de Hitos
> La numeración refleja dependencias reales: cada hito requiere los anteriores.
### Etapa A — La Web puede reemplazar al MD (Entrada de datos)
Sin esto, seguís dependiendo del archivo para registrar actividad.
| # | Hito | Qué resuelve | Estado |
| :---: | :--- | :--- | :---: |
| A1 | **Esquema DB: gestion + resumen_nodos** | Tablas para Pendientes, En Proceso y Resumen Integral | ✅ |
| A2 | **API: endpoints gestion y resumen** | Backend para crear/editar/eliminar desde la Web | ✅ |
| A3 | **UI: Panel de Gestión** | Editar Pendientes y En Proceso desde el Dashboard | ✅ |
| A4 | **UI: Editor Resumen Integral** | Editar resumen por nodo desde el Dashboard | ✅ |
| A5 | **UI: Formulario de Entradas** | Crear entradas I-F-D-E desde la Web | ✅ |
| A6 | **Rollover automático** | Pendientes activos pasan al día siguiente (vía `activo=true`) | ✅ |
**Resultado:** La Web ya puede hacer todo lo que antes se hacía a mano en el MD.
---
### Etapa B — Los comandos ADN hablan con la DB (Automatización)
Sin esto, `adn inicio` y `adn cierre` siguen escribiendo en el `.md`.
| # | Hito | Qué resuelve | Estado |
| :---: | :--- | :--- | :---: |
| B1 | **Refactor inicio.rb → DB-First** | `adn inicio de jornada` escribe en DB, no en MD | ✅ |
| B2 | **Refactor cierre.rb → DB-First** | `adn cierre de jornada` escribe en DB, no en MD | ✅ |
| B3 | **Refactor backup → DB** | Registrar eventos de backup como entradas en DB | ✅ |
**Resultado:** Los comandos del enjambre ya no tocan archivos `.md`.
---
### Etapa C — El MD se genera desde la DB (Respaldo)
Recién ahora tiene sentido generar el MD, porque la DB **ya es** la fuente de verdad.
| # | Hito | Qué resuelve | Estado |
| :---: | :--- | :--- | :---: |
| C1 | **Exportador Ruby: md_exporter.rb** | `adn db exportar:md FECHA` genera el MD desde la DB | ✅ |
| C2 | **Exportación masiva** | `adn db exportar:md --todo` regenera todos los MDs | ✅ |
**Resultado:** Los archivos `.md` son un producto generado, no una fuente de entrada.
---
### Etapa D — Verificación y cierre (Confianza)
Asegurar que no se perdió nada en la transición.
| # | Hito | Qué resuelve | Estado |
| :---: | :--- | :--- | :---: |
| D1 | **Migración histórica** | `adn db migrar:md --todo` importa todo el historial | ✅ |
| D2 | **Verificación de cobertura** | `adn db verificar` audita MD ↔ DB | ✅ |
| D3 | **Desactivar edición manual de MDs** | Header `<!-- GENERADO AUTOMÁTICAMENTE -->` en MDs exportados | ✅ |
---
## 🐝 Herramientas del Enjambre ADN
```
# Flujo diario (DB-First)
./tools/adn/run inicio de jornada presencial|remoto
./tools/adn/run cierre de jornada presencial|remoto
# Importar MD existente → DB
./tools/adn/run db migrar:md FECHA [--sobrescribir]
./tools/adn/run db migrar:md --todo [--sobrescribir]
# Exportar DB → MD (respaldo)
./tools/adn/run db exportar:md FECHA
./tools/adn/run db exportar:md --todo
# Auditoría
./tools/adn/run db verificar
./tools/adn/run db estadisticas
```
---
## 📊 Estado Actual — COMPLETO ✅
**Ya no es necesario editar MDs manualmente.** Todas las etapas están completas.
| Etapa | Progreso |
| :--- | :---: |
| A — Web reemplaza al MD | 6/6 ✅ |
| B — ADN habla con DB | 3/3 ✅ |
| C — MD se genera desde DB | 2/2 ✅ |
| D — Verificación y cierre | 3/3 ✅ |
### Notas
- Cobertura de migración: 88.9% (16/18 bitácoras con entradas). Las 2 restantes son días sin actividades detalladas.
- Selector "Modo de Jornada" en la UI queda como mejora futura (no bloquea el flujo actual).
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+2 -1
View File
@@ -4,7 +4,8 @@
- **Hostname**: `dc-dasuten.dasuten.utnlr`
- **Rol Inicial**: Controlador de Dominio Principal (AD DS / DNS)
- **Estado**: ✅ Operativo a nivel servicio
- **Padre/Host**: `srv-dasu` (VM ID: 100)
- **Padre/Host**: `srv-dasu`
- **VMID**: `100`
## Hardware (Virtual)
- **Hypervisor**: Proxmox VE 9.1
@@ -1,8 +1,8 @@
# Ficha de Nodo: prueba-automatizacion
# Ficha de Nodo: dtic-bitacoras
| Atributo | Valor |
| :--- | :--- |
| **Hostname** | `prueba-automatizacion` |
| **Hostname** | `dtic-bitacoras` |
| **IP LAN** | `10.0.10.X` |
| **Sistema Operativo** | *Especificar* |
| **Rol** | *Describir función* |
@@ -21,7 +21,7 @@
- **Monitorización**: *Herramientas de monitoreo*
## Dependencias
- *Listar nodos/proyectos dependientes*
- [P2605](../proyectos/P2605_Migracion_Solo_Bitacoras_Web.md): Proyecto de Migración a Solo Bitácoras Web.
## Historial de Cambios Relevantes
- 2026-03-05: Ficha creada automáticamente
- 2026-03-10: Ficha creada automáticamente
+5
View File
@@ -8,6 +8,11 @@
- **Sistema Operativo**: Windows 10
- **Conectividad**: Tailscale (`100.100.145.51`) - ✅ ONLINE
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox2`
- **VMID**: `108`
- **Plataforma**: Proxmox VE
## Servicios
- **Sistema DASU**: Aplicativo legacy/interno.
- **AnyDesk**: Servidor de acceso (Escuchando conexiones LAN/Tailscale).
+5
View File
@@ -9,6 +9,11 @@
| **Rol Principal** | Sistema DASU / Atención al Usuario |
| **Estado** | 🟢 Online |
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox2`
- **VMID**: `109`
- **Plataforma**: Proxmox VE
## Descripción
Máquina virtual encargada del procesamiento de datos de DASUTEN. Recientemente renombrada de `pcv-dasu3` a `pcv-dasu2` para estandarización del Grid.
+5 -3
View File
@@ -8,11 +8,13 @@
| :--- | :--- |
| **Hostname** | pcv-SERVIIO |
| **VMID** | 110 |
| **Hipervisor** | srv-pmox2 (`10.0.10.202`) |
| **Tipo** | VM (QEMU) |
| **OS** | Windows 10 |
| **IP** | DHCP |
| **Estado** | ✅ Running |
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox2`
- **VMID**: `110`
- **Plataforma**: Proxmox VE
| **Rol** | Streaming de propaganda por TV vía Serviio |
## Descripción
+5
View File
@@ -7,6 +7,11 @@
- **Estado**: 🟢 Online
- **OS**: Debian 12 (Bookworm)
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox1` (10.0.10.201)
- **VMID**: `112`
- **Plataforma**: Proxmox VE
## Hardware
- **CPU**: (Virtual - KVM)
- **RAM**: 4 GB (3.8Gi Total)
+5
View File
@@ -7,6 +7,11 @@
- **Estado**: 🟢 Online
- **OS**: Debian 12 (Bookworm)
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox1` (10.0.10.201)
- **VMID**: `104`
- **Plataforma**: Proxmox VE
## Hardware
- **CPU**: (Virtual - KVM)
- **RAM**: 4 GB (3.8Gi Total)
+5
View File
@@ -7,6 +7,11 @@
- **Estado**: 🟢 Online
- **OS**: Debian 12 (Bookworm)
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox1` (10.0.10.201)
- **VMID**: `103`
- **Plataforma**: Proxmox VE
## Hardware
- **CPU**: (Virtual - KVM)
- **RAM**: 4 GB (3.8Gi Total)
+5
View File
@@ -7,6 +7,11 @@
- **Estado**: 🟢 Online
- **OS**: Debian 11 (Bullseye)
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox2` (10.0.10.202)
- **VMID**: `105`
- **Plataforma**: Proxmox VE
## Hardware
- **CPU**: (Virtual - KVM)
- **RAM**: 2 GB (1.9Gi Total)
+5
View File
@@ -8,6 +8,11 @@
- **Estado**: 🟢 Online
- **OS**: Ubuntu Linux
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox1` (10.0.10.201)
- **VMID**: `101`
- **Plataforma**: Proxmox VE
## Hardware (Virtual)
- **CPU**: Common KVM processor (Virtualizado)
- **RAM**: 4.8 GB Total (~900MB Usados)
+5
View File
@@ -7,6 +7,11 @@
- **Estado**: 🟢 Online
- **OS**: Debian 12 (Bookworm)
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox1` (10.0.10.201)
- **VMID**: `102`
- **Plataforma**: Proxmox VE
## Hardware
- **CPU**: (Pendiente de validación - VM en Proxmox/KVM)
- **RAM**: 3 GB (2.9Gi Total)
+5
View File
@@ -8,6 +8,11 @@
- **Tipo**: Contenedor LXC (Proxmox)
- **OS**: Debian 12 (Bookworm)
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox3`
- **VMID**: `113`
- **Plataforma**: Proxmox VE
## Hardware
- **CPU**: (Compartido LXC)
- **RAM**: 2 GB (2.0Gi Total)
+5
View File
@@ -8,6 +8,11 @@
- **Tipo**: Contenedor LXC (Proxmox)
- **OS**: Debian 12 (Bookworm)
## Datos de Virtualización
- **Host Anfitrión**: `srv-pmox3`
- **VMID**: `106`
- **Plataforma**: Proxmox VE
## Hardware
- **CPU**: (Compartido LXC)
- **RAM**: 1 GB (1.0Gi Total)
+94
View File
@@ -0,0 +1,94 @@
server {
server_name ns8.frlr.utn.edu.ar 100.88.252.26 190.114.205.18 srvns8.tail982245.ts.net;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
location / {
proxy_pass http://127.0.0.1:3001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Portainer
location /portainer/ {
proxy_pass http://127.0.0.1:9000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# SysACAD
location /sysacad/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Bitácoras
location /bitacoras/ {
proxy_pass http://127.0.0.1:5173/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Dashboard P2601
location /P2601/ {
try_files $uri $uri/ =404;
}
# APPs (TinyFileManager)
location /apps/ {
client_max_body_size 20G;
proxy_pass http://127.0.0.1:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = ns8.frlr.utn.edu.ar) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name ns8.frlr.utn.edu.ar 100.88.252.26 190.114.205.18 srvns8.tail982245.ts.net 10.0.10.8;
location /apps/ {
client_max_body_size 20G;
proxy_pass http://127.0.0.1:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /s.txt {
root /var/www/html;
}
location / {
return 404; # managed by Certbot
}
}
+66 -43
View File
@@ -1,11 +1,45 @@
server {
server_name ns8.frlr.utn.edu.ar 100.88.252.26 190.114.205.18 srvns8.tail982245.ts.net;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
index index.html index.htm;
# -------------------------
# CONFIGURACIÓN BASE PROXY
# -------------------------
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
# Websocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# -------------------------
# SERVICIOS
# -------------------------
# Servicio principal
location / {
proxy_pass http://127.0.0.1:3001/;
proxy_pass http://127.0.0.1:3001;
}
# APIDS
location = /apids {
return 301 /apids/;
}
location /apids/ {
proxy_pass http://127.0.0.1:7071/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@@ -15,71 +49,61 @@ server {
# Portainer
location /portainer/ {
proxy_pass http://127.0.0.1:9000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# SysACAD
location /sysacad/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Bitácoras
location /bitacoras/ {
proxy_pass http://127.0.0.1:5173/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Dashboard P2601
location /P2601/ {
try_files $uri $uri/ =404;
}
# APPs (TinyFileManager)
# TinyFileManager
location /apps/ {
client_max_body_size 20G;
proxy_pass http://127.0.0.1:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
# -------------------------
# OPTIMIZACIONES
# -------------------------
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering off;
# -------------------------
# SSL
# -------------------------
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($host = ns8.frlr.utn.edu.ar) {
return 301 https://$host$request_uri;
} # managed by Certbot
# -------------------------
# HTTP -> HTTPS REDIRECT
# -------------------------
server {
listen 80;
server_name ns8.frlr.utn.edu.ar 100.88.252.26 190.114.205.18 srvns8.tail982245.ts.net 10.0.10.8;
location /apps/ {
client_max_body_size 20G;
proxy_pass http://127.0.0.1:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /s.txt {
@@ -87,8 +111,7 @@ server {
}
location / {
return 404; # managed by Certbot
return 301 https://$host$request_uri;
}
}
}
+94
View File
@@ -0,0 +1,94 @@
server {
server_name ns8.frlr.utn.edu.ar 100.88.252.26 190.114.205.18 srvns8.tail982245.ts.net;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
location / {
proxy_pass http://127.0.0.1:3001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Portainer
location /portainer/ {
proxy_pass http://127.0.0.1:9000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# SysACAD
location /sysacad/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Bitácoras
location /bitacoras/ {
proxy_pass http://127.0.0.1:5173/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Dashboard P2601
location /P2601/ {
try_files $uri $uri/ =404;
}
# APPs (TinyFileManager)
location /apps/ {
client_max_body_size 20G;
proxy_pass http://127.0.0.1:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = ns8.frlr.utn.edu.ar) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name ns8.frlr.utn.edu.ar 100.88.252.26 190.114.205.18 srvns8.tail982245.ts.net 10.0.10.8;
location /apps/ {
client_max_body_size 20G;
proxy_pass http://127.0.0.1:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /s.txt {
root /var/www/html;
}
location / {
return 404; # managed by Certbot
}
}
@@ -0,0 +1,94 @@
server {
server_name ns8.frlr.utn.edu.ar 100.88.252.26 190.114.205.18 srvns8.tail982245.ts.net;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
location / {
proxy_pass http://127.0.0.1:3001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Portainer
location /portainer/ {
proxy_pass http://127.0.0.1:9000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# SysACAD
location /sysacad/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Bitácoras
location /bitacoras/ {
proxy_pass http://127.0.0.1:5173/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Dashboard P2601
location /P2601/ {
try_files $uri $uri/ =404;
}
# APPs (TinyFileManager)
location /apps/ {
client_max_body_size 20G;
proxy_pass http://127.0.0.1:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = ns8.frlr.utn.edu.ar) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name ns8.frlr.utn.edu.ar 100.88.252.26 190.114.205.18 srvns8.tail982245.ts.net 10.0.10.8;
location /apps/ {
client_max_body_size 20G;
proxy_pass http://127.0.0.1:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /s.txt {
root /var/www/html;
}
location / {
return 404; # managed by Certbot
}
}
@@ -0,0 +1,94 @@
server {
server_name ns8.frlr.utn.edu.ar 100.88.252.26 190.114.205.18 srvns8.tail982245.ts.net;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
location / {
proxy_pass http://127.0.0.1:3001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Portainer
location /portainer/ {
proxy_pass http://127.0.0.1:9000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# SysACAD
location /sysacad/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Bitácoras
location /bitacoras/ {
proxy_pass http://127.0.0.1:5173/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Dashboard P2601
location /P2601/ {
try_files $uri $uri/ =404;
}
# APPs (TinyFileManager)
location /apps/ {
client_max_body_size 20G;
proxy_pass http://127.0.0.1:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ns8.frlr.utn.edu.ar/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = ns8.frlr.utn.edu.ar) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name ns8.frlr.utn.edu.ar 100.88.252.26 190.114.205.18 srvns8.tail982245.ts.net 10.0.10.8;
location /apps/ {
client_max_body_size 20G;
proxy_pass http://127.0.0.1:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /s.txt {
root /var/www/html;
}
location / {
return 404; # managed by Certbot
}
}
View File
@@ -42,15 +42,45 @@ module.exports = (pool) => {
ORDER BY e.inicio DESC
`, [bitacora.id]);
// Obtener gestión asociada a esta bitácora (fija) + activos globales (rollover)
const { rows: gestion } = await pool.query(`
SELECT g.*, n.nombre as nodo_nombre
FROM bitacoras.gestion g
LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id
WHERE g.bitacora_id = $1 OR g.activo = true
ORDER BY g.tipo, g.orden
`, [bitacora.id]);
// Obtener resúmenes por nodo
const { rows: resúmenes } = await pool.query(`
SELECT r.*, n.nombre as nodo_nombre
FROM bitacoras.resumen_nodos r
JOIN bitacoras.nodos n ON r.nodo_id = n.id
WHERE r.bitacora_id = $1
`, [bitacora.id]);
// Agrupar por nodo
const porNodo = {};
entradas.forEach(e => {
const nodo = e.nodo_nombre || 'sin-nodo';
if (!porNodo[nodo]) porNodo[nodo] = { nodo: nodo, ip: e.nodo_ip, entradas: [] };
if (!porNodo[nodo]) porNodo[nodo] = { nodo: nodo, ip: e.nodo_ip, entradas: [], resumen: '' };
porNodo[nodo].entradas.push(e);
});
res.json({ ...bitacora, nodos: Object.values(porNodo) });
// Adjuntar resúmenes a la agrupación por nodo
resúmenes.forEach(r => {
if (porNodo[r.nodo_nombre]) {
porNodo[r.nodo_nombre].resumen = r.resumen;
} else {
porNodo[r.nodo_nombre] = { nodo: r.nodo_nombre, entradas: [], resumen: r.resumen };
}
});
res.json({
...bitacora,
nodos: Object.values(porNodo),
gestion: gestion
});
});
// GET /api/bitacoras/:fecha/export — Exportar a Markdown
@@ -61,17 +91,41 @@ module.exports = (pool) => {
const bitacora = bResult.rows[0];
const { rows: entradas } = await pool.query(`
SELECT e.*, n.nombre as nodo_nombre
FROM bitacoras.entradas e
LEFT JOIN bitacoras.nodos n ON e.nodo_id = n.id
WHERE e.bitacora_id = $1
ORDER BY n.nombre, e.inicio DESC
`, [bitacora.id]);
SELECT e.*, n.nombre as nodo_nombre FROM bitacoras.entradas e
LEFT JOIN bitacoras.nodos n ON e.nodo_id = n.id
WHERE e.bitacora_id = $1 ORDER BY n.nombre, e.inicio ASC
`, [bitacora.id]);
const { rows: gestion } = await pool.query(`
SELECT g.*, n.nombre as nodo_nombre FROM bitacoras.gestion g
LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id
WHERE g.bitacora_id = $1 OR g.activo = true ORDER BY g.tipo, g.orden
`, [bitacora.id]);
const { rows: resumenes } = await pool.query(`
SELECT r.*, n.nombre as nodo_nombre FROM bitacoras.resumen_nodos r
JOIN bitacoras.nodos n ON r.nodo_id = n.id WHERE r.bitacora_id = $1
`, [bitacora.id]);
// Generar Markdown compatible con ADN
const [y, m, d] = fecha.split('-');
let md = `# Bitácora de Operaciones - ${d}/${m}/${y}\n\n`;
md += `## 📂 Actividades Detalladas\n\n`;
let md = `# Bitácora ${fecha}\n\n---\n\n## 📋 Control de Gestión\n\n### Pendientes\n\n| ID | Nodo | Detalle |\n| :--- | :--- | :--- |\n`;
gestion.filter(g => g.tipo === 'pendiente').forEach(g => {
md += `| ${g.id} | ${g.nodo_nombre || '—'} | ${g.detalle} |\n`;
});
md += `\n### En Proceso\n\n| ID | Nodo | Detalle |\n| :--- | :--- | :--- |\n`;
gestion.filter(g => g.tipo === 'proceso').forEach(g => {
md += `| ${g.id} | ${g.nodo_nombre || '—'} | ${g.detalle} |\n`;
});
md += `\n### Resumen de Actividades\n\n| Nodo | Resumen Integral |\n| :--- | :--- |\n`;
resumenes.forEach(r => {
md += `| ${r.nodo_nombre} | ${r.resumen} |\n`;
});
md += `\n---\n\n## 📝 Actividades Detalladas\n\n`;
const porNodo = {};
entradas.forEach(e => {
@@ -82,18 +136,18 @@ module.exports = (pool) => {
Object.entries(porNodo).forEach(([nodo, entries]) => {
md += `### ${nodo}\n\n`;
md += `| I | F | Descripción | E |\n`;
md += `| :--- | :--- | :--- | :--- |\n`;
entries.forEach(e => {
// Agrupar por tema si es necesario, o lista simple
const inicio = e.inicio ? e.inicio.substring(0, 5) : '-';
const fin = e.fin ? e.fin.substring(0, 5) : '-';
const ia = e.es_ia ? '(IA) ' : '';
const estado = `[${e.modo}] ${e.estado}`;
md += `| ${inicio} | ${fin} | ${ia}${e.descripcion} | ${estado} |\n`;
md += `| ${inicio} | ${fin} | ${e.descripcion} | ${e.modo} | ${e.estado} |\n`;
});
md += '\n';
});
md += `\n---\n<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->\n`;
res.set('Content-Type', 'text/markdown');
res.send(md);
});
@@ -31,6 +31,7 @@ module.exports = (pool) => {
UPDATE bitacoras.entradas SET inicio=$1, fin=$2, descripcion=$3, estado=$4, modo=$5, es_ia=$6
WHERE id=$7 RETURNING *
`, [inicio, fin, descripcion, estado, modo, es_ia, id]);
if (rows.length === 0) return res.status(404).json({ error: 'Entrada no encontrada' });
res.json(rows[0]);
});
@@ -0,0 +1,53 @@
module.exports = (pool) => {
const router = require('express').Router();
// GET /api/gestion — Listar activos (para rollover) o por bitácora
router.get('/', async (req, res) => {
const { bitacora_id, activos } = req.query;
let query = 'SELECT g.*, n.nombre as nodo_nombre FROM bitacoras.gestion g LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id';
const params = [];
if (activos === 'true') {
query += ' WHERE g.activo = true';
} else if (bitacora_id) {
query += ' WHERE g.bitacora_id = $1';
params.push(bitacora_id);
}
query += ' ORDER BY g.tipo, g.orden, g.created_at DESC';
const { rows } = await pool.query(query, params);
res.json(rows);
});
// POST /api/gestion — Crear nuevo pendiente/proceso
router.post('/', async (req, res) => {
const { tipo, nodo_id, detalle, estado, bitacora_id, orden } = req.body;
const { rows } = await pool.query(
'INSERT INTO bitacoras.gestion (tipo, nodo_id, detalle, estado, bitacora_id, orden) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *',
[tipo, nodo_id || null, detalle, estado || '⏳', bitacora_id, orden || 0]
);
res.status(201).json(rows[0]);
});
// PUT /api/gestion/:id — Actualizar
router.put('/:id', async (req, res) => {
const { id } = req.params;
const { tipo, nodo_id, detalle, estado, activo, orden } = req.body;
const { rows } = await pool.query(
'UPDATE bitacoras.gestion SET tipo=$1, nodo_id=$2, detalle=$3, estado=$4, activo=$5, orden=$6, updated_at=CURRENT_TIMESTAMP WHERE id=$7 RETURNING *',
[tipo, nodo_id || null, detalle, estado, activo !== undefined ? activo : true, orden || 0, id]
);
if (rows.length === 0) return res.status(404).json({ error: 'No encontrado' });
res.json(rows[0]);
});
// DELETE /api/gestion/:id
router.delete('/:id', async (req, res) => {
const { rowCount } = await pool.query('DELETE FROM bitacoras.gestion WHERE id = $1', [req.params.id]);
if (rowCount === 0) return res.status(404).json({ error: 'No encontrado' });
res.json({ message: 'Eliminado' });
});
return router;
};
@@ -0,0 +1,32 @@
module.exports = (pool) => {
const router = require('express').Router();
// GET /api/resumen — Listar resúmenes por bitácora
router.get('/', async (req, res) => {
const { bitacora_id } = req.query;
if (!bitacora_id) return res.status(400).json({ error: 'Falta bitacora_id' });
const { rows } = await pool.query(
'SELECT r.*, n.nombre as nodo_nombre FROM bitacoras.resumen_nodos r JOIN bitacoras.nodos n ON r.nodo_id = n.id WHERE r.bitacora_id = $1',
[bitacora_id]
);
res.json(rows);
});
// POST /api/resumen — Upsert resumen por nodo
router.post('/', async (req, res) => {
const { bitacora_id, nodo_id, resumen } = req.body;
const { rows } = await pool.query(
`INSERT INTO bitacoras.resumen_nodos (bitacora_id, nodo_id, resumen)
VALUES ($1, $2, $3)
ON CONFLICT (bitacora_id, nodo_id)
DO UPDATE SET resumen = EXCLUDED.resumen, updated_at = CURRENT_TIMESTAMP
RETURNING *`,
[bitacora_id, nodo_id, resumen]
);
res.status(201).json(rows[0]);
});
return router;
};
@@ -29,6 +29,8 @@ app.use('/api/nodos', require('./routes/nodos')(pool));
app.use('/api/bitacoras', require('./routes/bitacoras')(pool));
app.use('/api/entradas', require('./routes/entradas')(pool));
app.use('/api/proyectos', require('./routes/proyectos')(pool));
app.use('/api/gestion', require('./routes/gestion')(pool));
app.use('/api/resumen', require('./routes/resumen')(pool));
// Error handler
app.use((err, req, res, next) => {
@@ -20,6 +20,7 @@ interface NodoGroup {
nodo: string;
ip: string;
entradas: Entrada[];
resumen?: string;
}
interface Bitacora {
@@ -27,6 +28,16 @@ interface Bitacora {
fecha: string;
resumen: string;
nodos: NodoGroup[];
gestion: GestionItem[];
}
interface GestionItem {
id: number;
tipo: 'pendiente' | 'proceso';
nodo_nombre: string;
detalle: string;
estado: string;
activo: boolean;
}
interface Nodo {
@@ -85,6 +96,8 @@ function App() {
const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]);
const [bitacora, setBitacora] = useState<Bitacora | null>(null);
const [nodos, setNodos] = useState<Nodo[]>([]);
const [proyectos, setProyectos] = useState<{ id: number, codigo: string, nombre: string }[]>([]);
const [selectedProyecto, setSelectedProyecto] = useState<string>('P2601');
const [loading, setLoading] = useState(true);
// Form: nueva entrada
@@ -116,6 +129,71 @@ function App() {
const [nodoTipo, setNodoTipo] = useState('servidor');
const [nodoDesc, setNodoDesc] = useState('');
// === HELPERS ===
const renderDescription = (text: string) => {
if (!text) return '';
// Regex para links markdown: [texto](url)
const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
const parts: (string | JSX.Element)[] = [];
let lastIndex = 0;
let match;
while ((match = markdownLinkRegex.exec(text)) !== null) {
if (match.index > lastIndex) {
parts.push(text.substring(lastIndex, match.index));
}
const label = match[1];
let url = match[2];
// Si es un link a un proyecto, interceptamos para navegación SPA
if (url.startsWith('../proyectos/')) {
const projectFile = url.split('/').pop() || '';
const projectCodeMatch = projectFile.match(/^(P\d{4})/);
if (projectCodeMatch) {
const projectCode = projectCodeMatch[1];
parts.push(
<a
key={match.index}
href="#"
onClick={(e) => {
e.preventDefault();
setSelectedProyecto(projectCode);
changeTab('proyecto');
}}
className="desc-link"
title={`Ver plan ${projectCode} en la web`}
>
{label}
</a>
);
lastIndex = markdownLinkRegex.lastIndex;
continue;
}
// Si no tiene el formato PXXXX, fallback a link estático
url = '/bitacoras/docs/proyectos/' + projectFile;
}
parts.push(
<a key={match.index} href={url} target="_blank" rel="noopener noreferrer" className="desc-link">
{label}
</a>
);
lastIndex = markdownLinkRegex.lastIndex;
}
if (lastIndex < text.length) {
parts.push(text.substring(lastIndex));
}
return parts.length > 0 ? <>{parts}</> : text;
};
// === FETCH ===
const fetchBitacora = useCallback(async () => {
setLoading(true);
@@ -134,8 +212,70 @@ function App() {
} catch (err) { console.error('Error:', err); }
};
const fetchProyectos = async () => {
try {
const res = await fetch(`${API_URL}/proyectos`);
const data = await res.json();
setProyectos(data);
if (data.length > 0) {
// Al cargar por primera vez, si no hay P2601 explícito o se quiere forzar al último
setSelectedProyecto(prev => prev === 'P2601' && prev ? data[data.length - 1].codigo : prev || data[data.length - 1].codigo);
}
} catch (err) { console.error('Error:', err); }
};
const addGestion = async (tipo: 'pendiente' | 'proceso') => {
if (!bitacora) return;
const detalle = prompt(`Nuevo ${tipo}:`);
if (!detalle) return;
await fetch(`${API_URL}/gestion`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tipo, detalle, bitacora_id: bitacora.id, estado: '⏳'
})
});
fetchBitacora();
};
const deleteGestion = async (id: number) => {
if (!confirm('¿Eliminar item de gestión?')) return;
await fetch(`${API_URL}/gestion/${id}`, { method: 'DELETE' });
fetchBitacora();
};
const updateResumenNodo = async (nodoNombre: string, current: string) => {
if (!bitacora) return;
const newResumen = prompt(`Resumen Integral para ${nodoNombre}:`, current);
if (newResumen === null) return;
const nodo = nodos.find(n => n.nombre === nodoNombre);
if (!nodo) return;
await fetch(`${API_URL}/resumen`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
bitacora_id: bitacora.id,
nodo_id: nodo.id,
resumen: newResumen
})
});
fetchBitacora();
};
const navigateProyecto = (direction: number) => {
if (proyectos.length === 0) return;
const currentIndex = proyectos.findIndex(p => p.codigo === selectedProyecto);
let nextIndex = currentIndex + direction;
if (nextIndex < 0) nextIndex = proyectos.length - 1;
if (nextIndex >= proyectos.length) nextIndex = 0;
setSelectedProyecto(proyectos[nextIndex].codigo);
};
useEffect(() => { fetchBitacora(); }, [fetchBitacora]);
useEffect(() => { fetchNodos(); }, []);
useEffect(() => { fetchNodos(); fetchProyectos(); }, []);
// === ENTRADAS CRUD ===
const handleSubmit = async (e: React.FormEvent) => {
@@ -276,7 +416,7 @@ function App() {
className={`btn btn-sm ${tab === 'proyecto' ? 'btn-primary' : ''}`}
onClick={() => changeTab('proyecto')}
style={tab !== 'proyecto' ? { background: 'var(--bg-card)', color: 'var(--text)', border: '1px solid var(--border)' } : {}}
><i className="fas fa-project-diagram"></i> P2601</button>
><i className="fas fa-project-diagram"></i> Planes</button>
{tab === 'bitacora' && (
<a href={`${API_URL}/bitacoras/${fecha}/export`} target="_blank" rel="noopener"
className="btn btn-sm btn-primary">
@@ -308,11 +448,11 @@ function App() {
{/* Stats */}
<div className="stats-bar">
<div className="stat-card">
<div className="stat-value">{totalEntradas}</div>
<div className="stat-value">{bitacora?.nodos?.reduce((s, n) => s + n.entradas.length, 0) || 0}</div>
<div className="stat-label">Entradas</div>
</div>
<div className="stat-card">
<div className="stat-value">{totalNodos}</div>
<div className="stat-value">{bitacora?.nodos?.length || 0}</div>
<div className="stat-label">Nodos Activos</div>
</div>
<div className="stat-card">
@@ -325,6 +465,58 @@ function App() {
</div>
</div>
{/* =================== CONTROL DE GESTIÓN =================== */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.5rem', marginBottom: '2rem' }}>
<div className="card">
<div className="card-header" style={{ borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between' }}>
<span><i className="fas fa-list-ul" style={{ color: '#fbbf24' }}></i> Pendientes</span>
<button className="btn btn-sm" onClick={() => addGestion('pendiente')} style={{ padding: '0.1rem 0.4rem', fontSize: '0.7rem' }}><i className="fas fa-plus"></i></button>
</div>
<div style={{ padding: '0.5rem', minHeight: '60px' }}>
<table style={{ width: '100%', fontSize: '0.85rem' }}>
<tbody>
{bitacora?.gestion?.filter(g => g.tipo === 'pendiente').map(g => (
<tr key={g.id} style={{ borderBottom: '1px solid var(--border-light)' }}>
<td style={{ padding: '0.4rem', color: 'var(--text-secondary)', width: '20%' }}>{g.nodo_nombre || '—'}</td>
<td style={{ padding: '0.4rem' }}>{g.detalle}</td>
<td style={{ padding: '0.4rem', textAlign: 'right', width: '60px' }}>
<div style={{ display: 'flex', gap: '0.3rem', alignItems: 'center' }}>
<span>{g.estado}</span>
<button className="btn btn-sm btn-danger" onClick={() => deleteGestion(g.id)} style={{ padding: '0 0.2rem', fontSize: '0.6rem' }}><i className="fas fa-trash"></i></button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className="card">
<div className="card-header" style={{ borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between' }}>
<span><i className="fas fa-spinner" style={{ color: '#3b82f6' }}></i> En Proceso</span>
<button className="btn btn-sm" onClick={() => addGestion('proceso')} style={{ padding: '0.1rem 0.4rem', fontSize: '0.7rem' }}><i className="fas fa-plus"></i></button>
</div>
<div style={{ padding: '0.5rem', minHeight: '60px' }}>
<table style={{ width: '100%', fontSize: '0.85rem' }}>
<tbody>
{bitacora?.gestion?.filter(g => g.tipo === 'proceso').map(g => (
<tr key={g.id} style={{ borderBottom: '1px solid var(--border-light)' }}>
<td style={{ padding: '0.4rem', color: 'var(--text-secondary)', width: '20%' }}>{g.nodo_nombre || '—'}</td>
<td style={{ padding: '0.4rem' }}>{g.detalle}</td>
<td style={{ padding: '0.4rem', textAlign: 'right', width: '60px' }}>
<div style={{ display: 'flex', gap: '0.3rem', alignItems: 'center' }}>
<span>{g.estado}</span>
<button className="btn btn-sm btn-danger" onClick={() => deleteGestion(g.id)} style={{ padding: '0 0.2rem', fontSize: '0.6rem' }}><i className="fas fa-trash"></i></button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
{/* New Entry Form */}
<div className="card" style={{ marginBottom: '2rem' }}>
<div className="card-header"><i className="fas fa-plus-circle"></i> Nueva Entrada</div>
@@ -362,12 +554,22 @@ function App() {
{nodoGroup.ip && <span className="nodo-ip">({nodoGroup.ip})</span>}
<span className="badge badge-gradient" style={{ marginLeft: 'auto' }}>{nodoGroup.entradas.length}</span>
</div>
<div style={{ padding: '0.8rem 1rem', background: 'rgba(102, 126, 234, 0.05)', borderBottom: '1px solid var(--border)', fontSize: '0.85rem', color: 'var(--text-secondary)' }}>
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
<i className="fas fa-quote-left" style={{ opacity: 0.5 }}></i>
<div style={{ fontStyle: 'italic', flex: 1 }}>{nodoGroup.resumen || 'Sin resumen integral registrado.'}</div>
<button className="btn btn-sm" onClick={() => updateResumenNodo(nodoGroup.nodo, nodoGroup.resumen || '')} style={{ padding: '0.1rem 0.4rem', fontSize: '0.7rem' }} title="Editar Resumen"><i className="fas fa-pen"></i></button>
</div>
</div>
<table className="table-ifde">
<thead>
<tr>
<th className="col-time">I</th>
<th className="col-time">F</th>
<th>Descripción</th>
<th className="col-j-header" style={{ width: '35px', textAlign: 'center' }}>J</th>
<th className="col-status">E</th>
<th style={{ width: '80px' }}>Acc.</th>
</tr>
@@ -381,20 +583,21 @@ function App() {
<td><input type="time" value={editFin} onChange={ev => setEditFin(ev.target.value)} className="edit-input" /></td>
<td>
<input type="text" value={editDesc} onChange={ev => setEditDesc(ev.target.value)} className="edit-input" style={{ width: '100%' }} />
<div style={{ display: 'flex', gap: '0.3rem', marginTop: '0.3rem', alignItems: 'center' }}>
<select value={editEstado} onChange={ev => setEditEstado(ev.target.value)} className="edit-input">
<option value="⏳"></option><option value="✅"></option>
<option value="⚠️"></option><option value="❌"></option><option value="👁️">👁</option>
</select>
<select value={editModo} onChange={ev => setEditModo(ev.target.value)} className="edit-input">
<option value="P">[P]</option><option value="R">[R]</option>
</select>
<label style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', display: 'flex', alignItems: 'center', gap: '0.2rem' }}>
<input type="checkbox" checked={editEsIa} onChange={ev => setEditEsIa(ev.target.checked)} /> IA
</label>
</div>
<label style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', display: 'flex', alignItems: 'center', gap: '0.2rem', marginTop: '0.3rem' }}>
<input type="checkbox" checked={editEsIa} onChange={ev => setEditEsIa(ev.target.checked)} /> IA
</label>
</td>
<td style={{ textAlign: 'center' }}>
<select value={editModo} onChange={ev => setEditModo(ev.target.value)} className="edit-input" style={{ width: '45px', padding: '0.35rem 0.2rem' }}>
<option value="P">P</option><option value="R">R</option><option value="S">S</option>
</select>
</td>
<td style={{ textAlign: 'center' }}>
<select value={editEstado} onChange={ev => setEditEstado(ev.target.value)} className="edit-input" style={{ width: '45px', padding: '0.35rem 0.2rem' }}>
<option value="⏳"></option><option value="✅"></option>
<option value="⚠️"></option><option value="❌"></option><option value="👁️">👁</option>
</select>
</td>
<td></td>
<td>
<div style={{ display: 'flex', gap: '0.2rem' }}>
<button className="btn btn-sm btn-success" onClick={saveEdit} title="Guardar"><i className="fas fa-check"></i></button>
@@ -409,9 +612,10 @@ function App() {
<td className="col-time">{e.fin ? e.fin.substring(0, 5) : '—'}</td>
<td className="col-desc">
{e.es_ia && <span className="ia-tag">IA</span>}
{e.descripcion}
{renderDescription(e.descripcion)}
</td>
<td className="col-status">[{e.modo}] {e.estado}</td>
<td style={{ textAlign: 'center', fontWeight: 600, color: 'var(--text-secondary)' }}>{e.modo}</td>
<td className="col-status">{e.estado}</td>
<td>
<div style={{ display: 'flex', gap: '0.2rem' }}>
<button className="btn btn-sm" onClick={() => startEdit(e)} title="Editar"
@@ -518,7 +722,38 @@ function App() {
{/* =================== TAB PROYECTO =================== */}
{tab === 'proyecto' && (
<ProyectoDashboard codigo="P2601" />
<>
<div className="date-selector" style={{ marginBottom: '1.5rem', justifyContent: 'center' }}>
<button className="btn btn-sm btn-primary" onClick={() => navigateProyecto(-1)}>
<i className="fas fa-chevron-left"></i>
</button>
<span style={{ fontSize: '0.85rem', fontWeight: 600, color: 'var(--text-secondary)' }}>
<i className="fas fa-project-diagram"></i> Plan:
</span>
<select
value={selectedProyecto}
onChange={e => setSelectedProyecto(e.target.value)}
style={{
background: 'var(--bg-card)',
color: 'var(--text)',
border: '1px solid var(--border)',
padding: '0.4rem 1rem',
borderRadius: '8px',
fontSize: '0.9rem',
fontWeight: 600,
minWidth: '200px'
}}
>
{proyectos.map(p => (
<option key={p.id} value={p.codigo}>{p.codigo} - {p.nombre}</option>
))}
</select>
<button className="btn btn-sm btn-primary" onClick={() => navigateProyecto(1)}>
<i className="fas fa-chevron-right"></i>
</button>
</div>
<ProyectoDashboard codigo={selectedProyecto} />
</>
)}
</main>
</div>
@@ -61,6 +61,7 @@ body {
font-weight: 700;
background: var(--gradient);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
display: flex;
align-items: center;
@@ -160,7 +161,7 @@ body {
}
.table-ifde .col-status {
width: 80px;
width: 45px;
text-align: center;
}
@@ -178,6 +179,23 @@ body {
margin-right: 0.3rem;
}
.desc-link {
color: var(--primary);
text-decoration: none;
font-weight: 600;
background: rgba(102, 126, 234, 0.1);
padding: 0.1rem 0.4rem;
border-radius: 4px;
transition: var(--transition);
border-bottom: 1px solid transparent;
}
.desc-link:hover {
background: var(--primary);
color: white;
text-decoration: none;
}
/* Nodo section */
.nodo-section {
margin-bottom: 2rem;
@@ -337,6 +355,7 @@ body {
font-weight: 700;
background: var(--gradient);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
@@ -23,6 +23,9 @@ interface Fase {
descripcion: string;
fecha_inicio: string | null;
fecha_fin: string | null;
objetivo?: string;
desarrollo?: string;
logros?: string;
hitos: Hito[] | null;
}
@@ -49,6 +52,13 @@ interface Proyecto {
nombre: string;
estado: string;
descripcion: string;
detalles_json: {
contexto?: string[];
objetivos?: string[];
responsables?: string[];
beneficiarios_directos?: string[];
beneficiarios_indirectos?: string[];
} | null;
fases: Fase[];
metricas: Metricas;
nodos: NodoInfo[];
@@ -116,48 +126,14 @@ function DonutChart({ segments, size = 140, strokeWidth = 18, centerLabel, cente
);
}
/* ═══════════ CONTENIDO ENRIQUECIDO POR FASE ═══════════ */
const faseDetalles: Record<number, { objetivo: string; desarrollo: string; logros: string }> = {
1: {
objetivo: 'Preparar el equipo central que alojará todos los servicios de DASUTeN, garantizando que opere de forma independiente a los sistemas de la Facultad.',
desarrollo: 'Se instaló y configuró el servidor dedicado en la oficina de DASUTeN, creando un entorno de trabajo completamente separado de la red universitaria para mayor seguridad y control.',
logros: 'Servidor propio operativo y listo para funcionar. Entorno aislado que brinda independencia total respecto a los sistemas de la Facultad.'
},
2: {
objetivo: 'Establecer un sistema centralizado de identificación de usuarios y permisos de acceso, propio de la oficina DASUTeN.',
desarrollo: 'Se configuró un servicio de gestión de identidades que permite controlar quién accede a cada equipo y recurso dentro de la red de DASUTeN.',
logros: 'Sistema de usuarios operativo. Cada persona accede con credenciales propias. Se simplifica la administración de permisos y la seguridad de acceso.'
},
3: {
objetivo: 'Instalar el sistema de base de datos necesario y recuperar toda la información del sistema DASUTeN desde el respaldo existente.',
desarrollo: 'Se preparó un entorno dedicado para la base de datos, se transfirió el respaldo completo (~9 GB de información) y se restauró verificando su integridad.',
logros: 'Toda la información de gestión de DASUTeN fue recuperada exitosamente. La base de datos está operativa y accesible desde la red interna.'
},
4: {
objetivo: 'Habilitar la administración remota segura de todos los equipos y generar respaldos de seguridad como punto de recuperación.',
desarrollo: 'Se activó el acceso remoto seguro en todos los equipos y se generaron copias de respaldo completas para poder restaurar el sistema ante cualquier eventualidad.',
logros: 'Posibilidad de gestionar todos los equipos sin necesidad de presencia física. Respaldos de seguridad disponibles ante cualquier imprevisto.'
},
5: {
objetivo: 'Analizar la aplicación de gestión DASUTeN y ponerla en funcionamiento sobre la nueva infraestructura virtual.',
desarrollo: 'Configuración de Kermet.ini, instalación de componentes de sistema, fuentes y apertura de firewall SQL. Pruebas de conectividad exitosas.',
logros: 'Despliegue operativo verificado con login exitoso. Validación presencial (UAT) realizada con Andrea Almirón satisfactoriamente.'
},
6: {
objetivo: '🚀 Poner en funcionamiento el servidor físico en la oficina, integrar clientes al dominio y configurar políticas de seguridad.',
desarrollo: 'Traslado físico del servidor Proxmox, unión de la PC pc-dasu0 al dominio dasuten.utnlr y despliegue de GPOs de seguridad.',
logros: 'Hitos pendientes: Instalación física, AD-Join, GPO de seguridad y mapeo de unidades de red.'
}
};
/* ═══════════ MAIN COMPONENT ═══════════ */
export default function ProyectoDashboard({ codigo = 'P2601' }: { codigo?: string }) {
const [proyecto, setProyecto] = useState<Proyecto | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [vista, setVista] = useState<Vista>('gerencial');
const [expandedFases, setExpandedFases] = useState<Set<number>>(new Set());
const [expandedGerFases, setExpandedGerFases] = useState<Set<number>>(new Set());
const [expandedHitos, setExpandedHitos] = useState<Set<number>>(new Set());
const [descExpanded, setDescExpanded] = useState(false);
useEffect(() => { fetchProyecto(); }, [codigo]);
@@ -185,14 +161,47 @@ export default function ProyectoDashboard({ codigo = 'P2601' }: { codigo?: strin
});
};
const toggleGerFase = (num: number) => {
setExpandedGerFases(prev => {
const toggleHito = (id: number) => {
setExpandedHitos(prev => {
const next = new Set(prev);
next.has(num) ? next.delete(num) : next.add(num);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
};
// Actualizar Estado de Hito
const handleEstadoClick = async (h: Hito) => {
const nextEstado = h.estado === '📋' ? '⏳' : h.estado === '⏳' ? '✅' : '📋';
try {
const res = await fetch(`${API_URL}/proyectos/hitos/${h.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ estado: nextEstado })
});
if (res.ok) fetchProyecto();
} catch (e) { console.error('Error al actualizar estado', e); }
};
// Actualizar Horas
const handleHorasClick = async (h: Hito, tipo: 'P' | 'R') => {
const actual = tipo === 'P' ? h.horas_presencial : h.horas_remoto;
const imputadas = prompt(`Horas ${tipo === 'P' ? 'Presenciales' : 'Remotas'} para hito: ${h.id_hito}\n(Ej: 1.5, 0.5 o dejar vacío)`, String(actual));
if (imputadas === null) return; // Canceló
const num = parseFloat(imputadas.replace(',', '.'));
const valorFinal = isNaN(num) ? 0 : num;
try {
const body = tipo === 'P' ? { horas_presencial: valorFinal } : { horas_remoto: valorFinal };
const res = await fetch(`${API_URL}/proyectos/hitos/${h.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (res.ok) fetchProyecto();
} catch (e) { console.error('Error al actualizar horas', e); }
};
if (loading) return <div style={{ textAlign: 'center', padding: '3rem', color: 'var(--text-muted)' }}>Cargando proyecto...</div>;
if (error || !proyecto) return <div style={{ textAlign: 'center', padding: '3rem', color: '#ef4444' }}>Error: {error}</div>;
@@ -232,38 +241,14 @@ export default function ProyectoDashboard({ codigo = 'P2601' }: { codigo?: strin
<p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', maxWidth: '600px', margin: '0 auto 0.8rem' }}>
{proyecto.descripcion}
</p>
{/* Toggle vista */}
<div style={{
display: 'inline-flex', borderRadius: '8px', overflow: 'hidden',
border: '1px solid var(--border)', background: 'rgba(255,255,255,0.03)'
}}>
<button
onClick={() => setVista('gerencial')}
style={{
padding: '6px 16px', fontSize: '0.75rem', fontWeight: 600, border: 'none', cursor: 'pointer',
background: vista === 'gerencial' ? 'linear-gradient(135deg, #38bdf8, #818cf8)' : 'transparent',
color: vista === 'gerencial' ? '#fff' : 'var(--text-muted)', transition: 'all 0.2s ease'
}}
><i className="fas fa-chart-pie" style={{ marginRight: '5px' }}></i>Resumen</button>
<button
onClick={() => setVista('tecnica')}
style={{
padding: '6px 16px', fontSize: '0.75rem', fontWeight: 600, border: 'none', cursor: 'pointer',
background: vista === 'tecnica' ? 'linear-gradient(135deg, #38bdf8, #818cf8)' : 'transparent',
color: vista === 'tecnica' ? '#fff' : 'var(--text-muted)', transition: 'all 0.2s ease'
}}
><i className="fas fa-list-alt" style={{ marginRight: '5px' }}></i>Detalle Técnico</button>
</div>
</div>
{/* ═══════════ VISTA GERENCIAL ═══════════ */}
{vista === 'gerencial' && (
<>
{/* Descripción del Proyecto — desplegable */}
<div className="card" style={{ padding: 0, overflow: 'hidden', marginBottom: '1.5rem' }}>
<div
onClick={() => setDescExpanded(prev => !prev)}
{/* ═══════════ VISTA UNIFICADA ═══════════ */}
{/* Descripción del Proyecto — desplegable */}
<div className="card" style={{ padding: 0, overflow: 'hidden', marginBottom: '1.5rem' }}>
<div
onClick={() => setDescExpanded(prev => !prev)}
style={{
padding: '0.8rem 1rem', cursor: 'pointer', display: 'flex',
alignItems: 'center', justifyContent: 'space-between',
@@ -280,48 +265,49 @@ export default function ProyectoDashboard({ codigo = 'P2601' }: { codigo?: strin
</div>
{descExpanded && (
<div style={{ padding: '0 1rem 1rem', lineHeight: 1.6, fontSize: '0.82rem', color: 'var(--text)' }}>
<p style={{ margin: '0 0 0.8rem' }}>
<strong>DASUTeN</strong> es la obra social que brinda cobertura a empleados, docentes y alumnos de la
Universidad Tecnológica Nacional. Su oficina en la Facultad Regional La Rioja depende
operativamente de la <strong>sede central en Buenos Aires</strong>.
</p>
<p style={{ margin: '0 0 0.8rem' }}>
Este proyecto tiene como objetivo <strong>dotar a la oficina DASUTeN de su propia infraestructura
tecnológica independiente</strong>, operando bajo un entorno virtualizado (Proxmox) con acceso remoto seguro vía Tailscale.
</p>
{proyecto.detalles_json?.contexto?.map((p, i) => (
<p key={i} style={{ margin: '0 0 0.8rem' }} dangerouslySetInnerHTML={{ __html: p.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>') }} />
))}
<div style={{
display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)',
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
gap: '1.2rem', marginTop: '1rem'
}}>
{proyecto.detalles_json?.objetivos && (
<div>
<div style={subheadStyle}>🎯 Objetivos y Alcance</div>
<ul style={listStyle}>
{proyecto.detalles_json.objetivos.map((o, i) => <li key={i}>{o}</li>)}
</ul>
</div>
)}
<div>
<div style={subheadStyle}>🎯 Objetivos y Alcance</div>
<ul style={listStyle}>
<li>Sistema de gestión operando desde servidor propio</li>
<li>Independencia total de los equipos de la Facultad</li>
<li>Oficina autosuficiente con su propia red y respaldos</li>
<li>Servidor dedicado en oficina DASUTeN</li>
<li>Red interna propia y segura</li>
<li>Computadora de escritorio como acceso al sistema</li>
</ul>
</div>
<div>
<div style={subheadStyle}>👥 Responsables y Beneficiarios</div>
<ul style={listStyle}>
<li><strong>Ejecución:</strong> Lic. Ricardo Monla</li>
<li><strong>Supervisión:</strong> Dirección de TIC</li>
</ul>
<div style={{ fontSize: '0.7rem', color: '#38bdf8', fontWeight: 600, marginTop: '0.4rem', marginBottom: '0.2rem' }}>Usuarios directos:</div>
<ul style={listStyle}>
<li>Andrea Almirón Administrativa DASUTeN</li>
<li>Romina Molina Administrativa</li>
<li>Dra. Eugenia Riveros Auditora Médica</li>
</ul>
<div style={{ fontSize: '0.7rem', color: '#38bdf8', fontWeight: 600, marginTop: '0.4rem', marginBottom: '0.2rem' }}>Beneficiarios indirectos:</div>
<ul style={listStyle}>
<li>Personal Docente</li>
<li>Personal No Docente</li>
<li>Alumnos</li>
</ul>
{proyecto.detalles_json?.responsables && (
<>
<div style={subheadStyle}>👥 Responsables</div>
<ul style={listStyle}>
{proyecto.detalles_json.responsables.map((r, i) => (
<li key={i} dangerouslySetInnerHTML={{ __html: r.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>') }} />
))}
</ul>
</>
)}
{proyecto.detalles_json?.beneficiarios_directos && (
<>
<div style={{ fontSize: '0.7rem', color: '#38bdf8', fontWeight: 600, marginTop: '0.4rem', marginBottom: '0.2rem' }}>Usuarios directos:</div>
<ul style={listStyle}>
{proyecto.detalles_json.beneficiarios_directos.map((b, i) => <li key={i}>{b}</li>)}
</ul>
</>
)}
{proyecto.detalles_json?.beneficiarios_indirectos && (
<>
<div style={{ fontSize: '0.7rem', color: '#38bdf8', fontWeight: 600, marginTop: '0.4rem', marginBottom: '0.2rem' }}>Beneficiarios indirectos:</div>
<ul style={listStyle}>
{proyecto.detalles_json.beneficiarios_indirectos.map((b, i) => <li key={i}>{b}</li>)}
</ul>
</>
)}
</div>
</div>
</div>
@@ -383,100 +369,6 @@ export default function ProyectoDashboard({ codigo = 'P2601' }: { codigo?: strin
</div>
</div>
{/* Fases con descripciones expandibles */}
<SectionTitle title="📊 Fases del Proyecto" />
{proyecto.fases.map(fase => {
const hitos = fase.hitos || [];
const faseCompletos = hitos.filter(h => h.estado === '✅').length;
const faseProgreso = hitos.length > 0 ? Math.round((faseCompletos / hitos.length) * 100) : 0;
const hrsTotal = hitos.reduce((s, h) => s + h.horas_presencial + h.horas_remoto, 0);
const isExpanded = expandedGerFases.has(fase.numero);
const detalle = faseDetalles[fase.numero];
return (
<div key={fase.id} className="card" style={{
marginBottom: '0.6rem', padding: 0, overflow: 'hidden', position: 'relative'
}}>
{/* Barra de progreso de fondo */}
<div style={{
position: 'absolute', top: 0, left: 0, bottom: 0, width: `${faseProgreso}%`,
background: fase.estado === '✅' ? 'rgba(34,197,94,0.06)' : 'rgba(56,189,248,0.04)',
transition: 'width 0.4s', pointerEvents: 'none'
}} />
{/* Header clickeable */}
<div
onClick={() => toggleGerFase(fase.numero)}
style={{
position: 'relative', display: 'flex', justifyContent: 'space-between',
alignItems: 'center', padding: '0.8rem 1rem', cursor: 'pointer'
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.7rem' }}>
<span style={{
fontSize: '1.3rem', width: '36px', height: '36px', display: 'flex',
alignItems: 'center', justifyContent: 'center', borderRadius: '8px',
background: fase.estado === '✅' ? 'rgba(34,197,94,0.12)' :
fase.estado === '⏳' ? 'rgba(234,179,8,0.12)' : 'rgba(255,255,255,0.05)'
}}>{estadoIcon(fase.estado)}</span>
<div>
<div style={{ fontWeight: 600, fontSize: '0.88rem' }}>
F{fase.numero} · {fase.nombre}
</div>
{!isExpanded && fase.descripcion && (
<div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginTop: '2px' }}>
{fase.descripcion}
</div>
)}
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem', flexShrink: 0 }}>
{hrsTotal > 0 && (
<span style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>{hrsTotal.toFixed(1)}h</span>
)}
<div style={{
minWidth: '52px', textAlign: 'right', fontWeight: 700, fontSize: '0.85rem',
color: faseProgreso === 100 ? '#22c55e' : faseProgreso > 0 ? '#38bdf8' : 'var(--text-muted)'
}}>{faseProgreso}%</div>
<span style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>{isExpanded ? '▾' : '▸'}</span>
</div>
</div>
{/* Detalle expandible */}
{isExpanded && detalle && (
<div style={{
position: 'relative', borderTop: '1px solid var(--border)',
padding: '0.8rem 1rem',
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: '0.8rem'
}}>
<FaseDetailCol icon="🎯" title="Objetivo" text={detalle.objetivo} />
<FaseDetailCol icon="⚙️" title="Desarrollo" text={detalle.desarrollo} />
<FaseDetailCol icon="✨" title="Logros" text={detalle.logros} />
</div>
)}
</div>
);
})}
</>
)}
{/* ═══════════ VISTA TÉCNICA ═══════════ */}
{vista === 'tecnica' && (
<>
{/* Métricas numéricas */}
<div style={{
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(130px, 1fr))',
gap: '0.8rem', marginBottom: '1.5rem'
}}>
<MetricCard label="Fases" value={`${fasesCompletas + fasesEnCurso}/${proyecto.fases.length}`} color="#38bdf8" />
<MetricCard label="Hitos Comp." value={m.hitos_completados} color="#22c55e" />
<MetricCard label="Hitos Pend." value={m.hitos_pendientes} color="#eab308" />
<MetricCard label="Hrs Físicas" value={`${hrsP.toFixed(1)}h`} color="#f97316" />
<MetricCard label="Esfuerzo Total" value={`${totalH.toFixed(1)}h`} color="#a78bfa" />
</div>
{/* Barra progreso */}
<div style={{ marginBottom: '1.5rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem', marginBottom: '0.3rem' }}>
@@ -547,6 +439,22 @@ export default function ProyectoDashboard({ codigo = 'P2601' }: { codigo?: strin
transition: 'width 0.4s'
}} />
</div>
{/* Resumen Táctico de Logros y Objetivos (Merged from Gerencial) */}
{expanded && (fase.objetivo || fase.desarrollo || fase.logros) && (
<div style={{
position: 'relative', borderTop: '1px solid var(--border)',
background: 'rgba(255,255,255,0.01)',
padding: '0.8rem 1rem',
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: '0.8rem'
}}>
{fase.objetivo && <FaseDetailCol icon="🎯" title="Objetivo" text={fase.objetivo} />}
{fase.desarrollo && <FaseDetailCol icon="⚙️" title="Desarrollo" text={fase.desarrollo} />}
{fase.logros && <FaseDetailCol icon="✨" title="Logros" text={fase.logros} />}
</div>
)}
{/* Tabla expandible de hitos */}
{expanded && hitos.length > 0 && (
<div style={{ padding: '0.5rem 1rem 0.8rem' }}>
<table className="table-ifde" style={{ fontSize: '0.8rem' }}>
@@ -562,26 +470,61 @@ export default function ProyectoDashboard({ codigo = 'P2601' }: { codigo?: strin
</tr>
</thead>
<tbody>
{hitos.map(h => (
<tr key={h.id}>
<td><code style={{ fontSize: '0.72rem', color: '#38bdf8' }}>{h.id_hito}</code></td>
<td>
<strong>{h.titulo}</strong>
{h.descripcion && <div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginTop: '2px' }}>{h.descripcion}</div>}
</td>
<td style={{ fontSize: '0.72rem', color: 'var(--text-muted)' }}>
{h.fecha ? new Date(h.fecha + 'T12:00:00').toLocaleDateString('es-AR', { day: '2-digit', month: '2-digit' }) : '—'}
</td>
<td style={{ textAlign: 'center', color: h.horas_presencial > 0 ? '#f97316' : 'var(--text-muted)' }}>
{h.horas_presencial > 0 ? `${h.horas_presencial}h` : '—'}
</td>
<td style={{ textAlign: 'center', color: h.horas_remoto > 0 ? '#a78bfa' : 'var(--text-muted)' }}>
{h.horas_remoto > 0 ? `${h.horas_remoto}h` : '—'}
</td>
<td style={{ fontSize: '0.72rem' }}>{h.nodo_nombre || '—'}</td>
<td style={{ textAlign: 'center' }}>{estadoIcon(h.estado)}</td>
</tr>
))}
{hitos.map(h => {
const isHitoExpanded = expandedHitos.has(h.id);
return (
<React.Fragment key={h.id}>
<tr
style={{ cursor: h.descripcion ? 'pointer' : 'default' }}
onClick={() => { if(h.descripcion) toggleHito(h.id) }}
title={h.descripcion ? "Click para expandir/colapsar descripción" : ""}
>
<td onClick={(e) => e.stopPropagation()}><code style={{ fontSize: '0.72rem', color: '#38bdf8' }}>{h.id_hito}</code></td>
<td>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<strong>{h.titulo}</strong>
{h.descripcion && <span style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginLeft: '8px' }}>{isHitoExpanded ? '▾' : '▸'}</span>}
</div>
</td>
<td style={{ fontSize: '0.72rem', color: 'var(--text-muted)' }} onClick={(e) => e.stopPropagation()}>
{h.fecha ? new Date(h.fecha + 'T12:00:00').toLocaleDateString('es-AR', { day: '2-digit', month: '2-digit' }) : '—'}
</td>
<td
style={{ textAlign: 'center', color: h.horas_presencial > 0 ? '#f97316' : 'var(--text-muted)', cursor: 'pointer' }}
onClick={(e) => { e.stopPropagation(); handleHorasClick(h, 'P'); }}
title="Click para editar horas presenciales"
>
{h.horas_presencial > 0 ? `${h.horas_presencial}h` : '—'}
</td>
<td
style={{ textAlign: 'center', color: h.horas_remoto > 0 ? '#a78bfa' : 'var(--text-muted)', cursor: 'pointer' }}
onClick={(e) => { e.stopPropagation(); handleHorasClick(h, 'R'); }}
title="Click para editar horas remotas"
>
{h.horas_remoto > 0 ? `${h.horas_remoto}h` : '—'}
</td>
<td style={{ fontSize: '0.72rem' }} onClick={(e) => e.stopPropagation()}>{h.nodo_nombre || '—'}</td>
<td
style={{ textAlign: 'center', cursor: 'pointer' }}
onClick={(e) => { e.stopPropagation(); handleEstadoClick(h); }}
title="Click para cambiar estado"
>
{estadoIcon(h.estado)}
</td>
</tr>
{isHitoExpanded && h.descripcion && (
<tr style={{ background: 'rgba(255,255,255,0.02)' }}>
<td></td>
<td colSpan={6} style={{ padding: '0.6rem 1rem', fontSize: '0.78rem', color: 'var(--text-muted)' }}>
<div style={{ borderLeft: '2px solid #38bdf8', paddingLeft: '0.8rem', whiteSpace: 'pre-wrap' }}>
{h.descripcion}
</div>
</td>
</tr>
)}
</React.Fragment>
);
})}
</tbody>
</table>
</div>
@@ -589,11 +532,9 @@ export default function ProyectoDashboard({ codigo = 'P2601' }: { codigo?: strin
</div>
);
})}
</>
)}
<div style={{ textAlign: 'center', color: 'var(--text-muted)', fontSize: '0.7rem', marginTop: '1.5rem', paddingTop: '0.8rem', borderTop: '1px solid var(--border)' }}>
P2601 Infraestructura DASUTEN · UTN FRLR · Dashboard integrado en dtic-BITACORAs
Dashboard unificado integrado en dtic-BITACORAs
</div>
</div>
);
+477 -65
View File
@@ -16,15 +16,22 @@ module ADN
@opciones = {
modo: defaults['modo'] || 'stop',
storage: defaults['storage'] || 'local',
compress: defaults['compress'] || 'gzip'
compress: defaults['compress'] || 'gzip',
retencion: defaults['retencion_dias'] || 6
}
end
def ejecutar
parser = OptionParser.new do |opts|
opts.banner = "Uso: ./tools/adn/run backup <nodo> [OPCIONES]"
opts.banner = "Uso: ./tools/adn/run backup <nodo|estados> [OPCIONES]"
opts.separator ""
opts.separator "Ejecuta un backup remoto siguiendo el protocolo ADN"
opts.separator "Ejecuta un backup remoto o consulta el estado actual."
opts.separator ""
opts.separator "Acciones:"
opts.separator " <nodo> Ejecutar backup del nodo especificado"
opts.separator " estados Ver estado actual de los backups en Proxmox"
opts.separator " sanear Limpiar backups antiguos según retención"
opts.separator " predeterminar Configurar valores por defecto"
opts.separator ""
opts.separator "Opciones:"
opts.on("--mode MODO", "Modo de backup (stop, snapshot, suspend. Default: #{@opciones[:modo]})") do |m|
@@ -48,18 +55,21 @@ module ADN
end
if @args.empty?
@logger.error("Se requiere nombre de nodo")
@logger.error("Se requiere nombre de nodo o acción")
puts parser
exit 1
end
if @args[0] == 'predeterminar'
case @args[0]
when 'predeterminar'
comando_predeterminar(@args[1..-1])
return
when 'estados', 'estado'
comando_estados
when 'sanear'
comando_sanear
else
ejecutar_backup(@args[0])
end
nodo = @args[0]
ejecutar_backup(nodo)
end
private
@@ -89,21 +99,41 @@ module ADN
candados_path = File.join(ADN::PROJECT_ROOT, 'tools', 'ns8-candados', 'ns8-candados.rb')
# Ejecutar con ns8-candados run
# Nota: el comando 'authorize' debe haberse ejecutado antes o lo incluimos
puts "#{Color::DIM}🔓 Abriendo candado para sesión de backup...#{Color::RESET}"
# Ejecutar con ns8-candados autorizando primero
system("ruby #{candados_path} authorize > /dev/null 2>&1")
success = system("ruby #{candados_path} run admindasu SSHPASS '#{cmd_seguro}'")
task_id = nil
full_output = ""
# Usar popen para leer la salida en tiempo real
@logger.info("Conectando y monitoreando salida de Proxmox...")
IO.popen("ruby #{candados_path} run admindasu SSHPASS '#{cmd_seguro}' 2>&1") do |pf|
pf.each_line do |line|
# Imprimir flujo para el usuario
puts " #{Color::DIM}#{line.strip}#{Color::RESET}"
full_output += line
# Si detectamos el Task ID al inicio, lo registramos de inmediato
if task_id.nil? && match = line.match(/started backup task '([^']+)'/)
task_id = match[1]
@logger.info("Task ID detectado: #{task_id}. Registrando en bitácora...")
registrar_task_id_bitacora(nodo, task_id)
end
end
end
success = $?.success?
# 4. Finalizar y cerrar
system("ruby #{candados_path} cerrar > /dev/null 2>&1")
if success
@logger.exito("Backup de #{nodo} completado exitosamente.")
registrar_fin_bitacora(nodo, true)
registrar_fin_bitacora(nodo, true, task_id)
else
@logger.error("La ejecución del backup falló.")
registrar_fin_bitacora(nodo, false)
registrar_fin_bitacora(nodo, false, task_id)
exit 1
end
end
@@ -116,10 +146,7 @@ module ADN
end
contenido = File.read(ruta_nodo)
# Buscamos VMID (puede decir "VM ID: 100" o "vmid: 100" o "- **VMID**: `100`")
vmid = contenido.match(/(?:VM ID|vmid|VMID)\*\*?:?\s*`?(\d+)`?/i)&.[](1)
# Buscamos el host (especificamente el link o nombre entre backticks)
host_name = contenido.match(/(?:Padre\/Host|Host Anfitrión|Anfitrión|Host)\*\*:?\s*`([^`]+)`/i)&.[](1)
unless vmid && host_name
@@ -132,11 +159,8 @@ module ADN
host_ip = nil
if File.exist?(ruta_host)
c_host = File.read(ruta_host)
# IP en tabla: | IP | `10.0.10.1` |
host_ip ||= c_host.match(/\|\s*IP[^\|]*\|\s*`([^`]+)`\s*\|/)&.[](1)
# IP en lista: - **IP**: `10.0.10.1`
host_ip ||= c_host.match(/IP\*\*:?\s*`([^`]+)`/)&.[](1)
# IP simple: IP: 10.0.10.1
host_ip ||= c_host.match(/IP:\s*([\d\.]+)/)&.[](1)
end
@@ -145,63 +169,76 @@ module ADN
return nil
end
if host_ip && host_ip.include?('/')
host_ip = host_ip.split('/')[0]
end
host_ip = host_ip.split('/')[0] if host_ip.include?('/')
{ vmid: vmid, host_name: host_name, host_ip: host_ip }
end
def registrar_inicio_bitacora(nodo, datos)
fecha = Date.today.iso8601
ruta = File.join(ADN::BITACORAS_DIR, "#{fecha}.md")
return unless File.exist?(ruta)
require_relative '../../bitacoras-db/core/bitacora_db'
hora = Time.now.strftime('%H:%M')
# Si ya existe la sección del nodo, añadimos la línea. Si no, creamos la sección.
contenido = File.read(ruta)
entrada = <<~ENTRADA
BitacorasDB::BitacoraDB.with_connection do |db|
# Asegurar bitácora
bitacora = db.find_bitacora_by_fecha(Date.today)
bitacora ||= db.create_bitacora(fecha: Date.today)
### #{nodo}
# Buscar nodo en DB
nodo_db = db.find_nodo_by_nombre(nodo)
#### ⏳ - BKP-ADN - Backup Autónomo (Protocolo ADN)
Ejecución de backup gestionada por `tools/adn.rb` para la VM #{datos[:vmid]} en el host #{datos[:host_name]}.
hora = Time.now.strftime('%H:%M')
entrada = db.create_entrada(
inicio: hora,
descripcion: "Inicio de backup remoto vía SSH para VM #{datos[:vmid]} en #{datos[:host_name]}.",
estado: '⏳',
modo: 'R',
bitacora_id: bitacora['id'],
nodo_id: nodo_db ? nodo_db['id'] : nil
)
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| #{hora} | | Inicio de backup remoto vía SSH (Seguro). | [R] ⏳ |
ENTRADA
unless contenido.include?("### #{nodo}")
# Insertar antes del footer de la IA
nuevo_contenido = contenido.sub(/<!-- 🤖 PREMISAS/, "#{entrada}\n\n<!-- 🤖 PREMISAS")
File.write(ruta, nuevo_contenido)
# Guardar ID para actualizar después
@backup_entrada_id = entrada['id'].to_i
@backup_bitacora_id = bitacora['id'].to_i
end
rescue => e
@logger.error("Error registrando inicio en DB: #{e.message}")
end
def registrar_fin_bitacora(nodo, exito)
fecha = Date.today.iso8601
ruta = File.join(ADN::BITACORAS_DIR, "#{fecha}.md")
return unless File.exist?(ruta)
def registrar_task_id_bitacora(nodo, task_id)
return unless @backup_entrada_id
hora = Time.now.strftime('%H:%M')
require_relative '../../bitacoras-db/core/bitacora_db'
BitacorasDB::BitacoraDB.with_connection do |db|
db.update_entrada(@backup_entrada_id, {
descripcion: "[task:#{task_id[0..7]}] Backup remoto en ejecución."
})
end
rescue => e
@logger.error("Error registrando task_id en DB: #{e.message}")
end
def registrar_fin_bitacora(nodo, exito, task_id = nil)
return unless @backup_entrada_id
require_relative '../../bitacoras-db/core/bitacora_db'
require_relative '../../bitacoras-db/exporters/md_exporter'
hora_fin = Time.now.strftime('%H:%M')
estado = exito ? '✅' : '⚠️'
desc = exito ? 'Backup completado exitosamente.' : 'Fallo en la ejecución del backup.'
contenido = File.read(ruta)
# Actualizar la línea de tiempo
linea_nueva = "| #{hora} (Fin) | | #{desc} | [R] #{estado} |"
# Buscamos la tarea BKP-ADN
nuevo_contenido = contenido.gsub(/#### ⏳ - BKP-ADN/, "#### #{estado} - BKP-ADN")
nuevo_contenido = nuevo_contenido.gsub(/\| \d{2}:\d{2} \| \| Inicio de backup remoto.*/, linea_nueva)
File.write(ruta, nuevo_contenido)
desc = "[task:#{task_id[0..7]}] #{desc}" if task_id
BitacorasDB::BitacoraDB.with_connection do |db|
db.update_entrada(@backup_entrada_id, {
fin: hora_fin,
descripcion: desc,
estado: estado
})
end
rescue => e
@logger.error("Error registrando fin en DB: #{e.message}")
end
def comando_predeterminar(args)
if args.length < 2
puts "#{Color::YELLOW}Uso: adn backup predeterminar <parametro> <valor>#{Color::RESET}"
@@ -210,12 +247,11 @@ module ADN
end
param = args[0].downcase
valor = args[1].downcase
# Normalizar parámetro (ej: 'modo' -> 'modo', 'storage' -> 'storage')
valor = args[1]
param = 'modo' if param == 'mode'
param = 'retencion_dias' if param == 'edad'
unless ['modo', 'storage', 'compress'].include?(param)
unless ['modo', 'storage', 'compress', 'retencion_dias'].include?(param)
@logger.error("Parámetro no predeterminable: #{param}")
exit 1
end
@@ -224,5 +260,381 @@ module ADN
@logger.exito("Valor predeterminado establecido: backup.#{param} = #{valor}")
puts "#{Color::GREEN}✓ Se ha predeterminado el #{param} a '#{valor}' para futuros backups.#{Color::RESET}"
end
def comando_estados
@logger.info("Consultando estado de backups en cluster Proxmox")
# Lista de hosts a consultar
hosts = [
{ nombre: 'srv-pmox1', ip: '10.0.10.201' },
{ nombre: 'srv-dasu', ip: '10.0.10.205' }
]
puts "\n#{Color::BOLD}🔍 Estado de Backups en Tiempo Real (Proxmox)#{Color::RESET}"
puts "" * 60
candados_path = File.join(ADN::PROJECT_ROOT, 'tools', 'ns8-candados', 'ns8-candados.rb')
# Asegurar autorización general para el lote
system("ruby #{candados_path} authorize > /dev/null 2>&1")
all_tasks_accumulated = []
hosts.each do |h|
print "📡 Conectando a #{h[:nombre]} (#{h[:ip]})... "
# Intentamos obtener tareas
cmd = "/usr/bin/pvesh get /cluster/tasks --output-format json"
cmd_seguro = "ssh -o StrictHostKeyChecking=no root@#{h[:ip]} '#{cmd}'"
# Ejecutamos vía candados run
output = `ruby #{candados_path} run admindasu SSHPASS '#{cmd_seguro}' 2>&1`
if output.include?('[')
begin
# Extraer el JSON
json_str = output[output.index('[')..output.rindex(']')]
tasks_host = JSON.parse(json_str)
all_tasks_accumulated.concat(tasks_host)
# Filtrar tareas vzdump activas de ESTE host para mostrar
tasks_activas = tasks_host.select { |t| t['type'] == 'vzdump' && t['endtime'].nil? }
puts "#{Color::GREEN}OK#{Color::RESET}"
if tasks_activas.empty?
puts " #{Color::DIM}Sin tareas de backup activas en este host.#{Color::RESET}"
else
tasks_activas.each do |t|
msg = t['status'] || 'ejecutando'
print "#{Color::CYAN}#{t['id']}#{Color::RESET} "
puts "[#{msg}] (Iniciado: #{Time.at(t['starttime']).strftime('%H:%M:%S')})"
end
end
rescue => e
puts "#{Color::YELLOW}JSON ERROR#{Color::RESET}"
puts " #{Color::DIM}Error parseando respuesta: #{e.message}#{Color::RESET}"
end
else
puts "#{Color::RED}ERROR#{Color::RESET}"
if output.include?('Permission denied')
puts " #{Color::DIM}Error de permisos/candado puesto.#{Color::RESET}"
else
puts " #{Color::DIM}No se detectaron tareas activas o el host no respondió.#{Color::RESET}"
end
end
end
system("ruby #{candados_path} cerrar > /dev/null 2>&1")
# 2. Sincronización Automática: Auto-reconciliación y Migración
fecha_hoy = Date.today.iso8601
archivo_md = File.join(ADN::BITACORAS_DIR, "#{fecha_hoy}.md")
if File.exist?(archivo_md)
print "#{Color::DIM}🔄 Reconciliando tareas pendientes... #{Color::RESET}"
auto_reconciliar_bitacora(all_tasks_accumulated)
puts "#{Color::DIM}OK#{Color::RESET}"
print "#{Color::DIM}🔄 Sincronizando bitácora digital... #{Color::RESET}"
# Ejecutamos la migración de forma silenciosa
cmd_migrar = "#{File.join(ADN::PROJECT_ROOT, 'tools/adn/run')} db migrar:md #{fecha_hoy} --sobrescribir > /dev/null 2>&1"
system(cmd_migrar)
puts "#{Color::DIM}OK#{Color::RESET}"
end
# 3. Historial de la DB (Sincronizado)
puts "\n#{Color::BOLD}📅 Historial de hoy (Sincronizado)#{Color::RESET}"
puts "" * 60
db_tool = "./tools/adn/run"
# Buscamos por "backup" o "BKP"
system("#{db_tool} db evento:listar --desde '#{fecha_hoy}' --buscar 'ackup'")
puts "\n#{Color::DIM}Para historial completo use: ./tools/adn/run db evento:listar --desde '#{fecha_hoy}'#{Color::RESET}"
end
def comando_sanear
dias = @opciones[:retencion].to_i
storage = @opciones[:storage]
@logger.info("Iniciando saneamiento de backups (Retención: #{dias} días en '#{storage}')")
# 1. Obtener lista de VMs directamente de Proxmox para nombres precisos
host = { nombre: 'srv-pmox1', ip: '10.0.10.201' }
candados_path = File.join(ADN::PROJECT_ROOT, 'tools', 'ns8-candados', 'ns8-candados.rb')
system("ruby #{candados_path} authorize > /dev/null 2>&1")
@logger.info("Consultando inventario de VMs en Proxmox...")
cmd_vms = "/usr/bin/pvesh get /cluster/resources --type vm --output-format json"
output_vms = `ruby #{candados_path} run admindasu SSHPASS 'ssh -o StrictHostKeyChecking=no root@#{host[:ip]} "#{cmd_vms}"' 2>/dev/null`
vmid_map = cargar_mapa_vmid_nodos # Cargar local primero
begin
if output_vms.include?('[')
json_vms = output_vms[output_vms.index('[')..output_vms.rindex(']')]
JSON.parse(json_vms).each do |vm|
id = vm['vmid'].to_s
# Priorizar nombre de Proxmox si el local no existe o es genérico
vmid_map[id] = vm['name'] if vm['name'] && (!vmid_map[id] || vmid_map[id] == id)
end
end
rescue => e
@logger.advertencia("No se pudo completar el mapa de VMs desde Proxmox: #{e.message}")
end
# 2. Obtener lista de archivos en el storage
# Intentar con varios hosts del cluster por si uno está caído
ruta_dump = "/mnt/pve/#{storage}/dump"
@logger.info("Listando archivos en #{storage}...")
output_ls = ""
[ '10.0.10.201', '10.0.10.202', '10.0.10.203' ].each do |ip|
cmd_ls = "ls -1 --full-time #{ruta_dump}/vzdump-*"
res = `ruby #{candados_path} run admindasu SSHPASS 'ssh -o StrictHostKeyChecking=no root@#{ip} "#{cmd_ls}"' 2>&1`
unless res.include?('No such file') || res.include?('Connection refused')
output_ls = res
break
end
end
if output_ls.empty? || output_ls.include?('No such file')
@logger.error("No se encontró la ruta de backups en ningún host: #{ruta_dump}")
system("ruby #{candados_path} cerrar > /dev/null 2>&1")
exit 1
end
# 3. Filtrar archivos por antigüedad
ahora = Time.now
limite = ahora - (dias * 24 * 60 * 60)
backups_agrupados = {}
output_ls.split("\n").each do |linea|
next if linea.include?('OPTIONS]') || linea.empty?
partes = linea.split(/\s+/)
next if partes.length < 9
fecha_str = "#{partes[5]} #{partes[6]}"
ruta_archivo = partes[8..-1].join(' ')
nombre = File.basename(ruta_archivo)
fingerprint = nombre.sub(/\.(log|vma\.zst|tar\.zst|vma\.zst\.notes|tar\.zst\.notes)$/, '')
begin
mtime = Time.parse(fecha_str)
if mtime < limite
vmid = fingerprint.match(/vzdump-(?:qemu|lxc)-(\d+)-/)&.[](1)
nodo_humano = vmid ? vmid_map[vmid] : "ID:#{vmid}"
backups_agrupados[fingerprint] ||= {
fingerprint: fingerprint,
fecha: mtime,
vmid: vmid,
nodo: nodo_humano,
archivos: []
}
backups_agrupados[fingerprint][:archivos] << { ruta: ruta_archivo, nombre: nombre }
end
rescue
next
end
end
if backups_agrupados.empty?
puts "#{Color::GREEN}✓ No se encontraron backups mayores a #{dias} días en #{storage}.#{Color::RESET}"
system("ruby #{candados_path} cerrar > /dev/null 2>&1")
return
end
# Convertir a array ordenado por fecha
lista_final = backups_agrupados.values.sort_by { |b| b[:fecha] }
# 4. Mostrar lista con nuevo formato solicitado
puts "\n#{Color::BOLD}🗑️ Propuesta de Saneamiento de Backups#{Color::RESET} (Retención: #{dias} días)"
puts "" * 90
puts "#{Color::BOLD}ID Nombre_VMID_Fecha Componentes detectados#{Color::RESET}"
puts "" * 90
lista_final.each_with_index do |b, idx|
num = (idx + 1).to_s.rjust(2, '0')
fecha_corta = b[:fecha].strftime('%Y-%m-%d')
# Formato solicitado: XX - VMNombre_VMid_Fecha
entrada_label = "#{num} - #{Color::BOLD}#{Color::CYAN}#{b[:nodo]}_#{b[:vmid]}_#{fecha_corta}#{Color::RESET}".ljust(60)
tipos = b[:archivos].map { |a| a[:nombre].split('.').last }.uniq.join(", ")
total_archivos = "(#{b[:archivos].length} arch.)"
puts "#{entrada_label} #{Color::DIM}#{tipos.ljust(15)} #{total_archivos}#{Color::RESET}"
end
puts "" * 90
puts "Total detectado: #{lista_final.length} backups completos (#{backups_agrupados.values.sum { |b| b[:archivos].length }} archivos en total)."
puts "\n#{Color::BOLD}Acciones:#{Color::RESET}"
puts " • Escriba #{Color::BOLD}'S'#{Color::RESET} para borrar TODOS los backups listados."
puts " • Escriba #{Color::BOLD}excluir 1, 3-5#{Color::RESET} para conservar backups específicos."
puts " • Escriba #{Color::BOLD}'N'#{Color::RESET} para cancelar."
print "\n👉 #{Color::BOLD}¿Qué deseas hacer?#{Color::RESET}: "
input = STDIN.gets.chomp.strip.downcase
if input == 'n' || input == ''
puts "#{Color::YELLOW}Operación cancelada.#{Color::RESET}"
system("ruby #{candados_path} cerrar > /dev/null 2>&1")
exit
end
# Procesar exclusiones
seleccionados = lista_final.dup
if input.start_with?('excluir')
exclusiones = []
patron = input.sub('excluir', '').strip
patron.split(/[\s,]+/).each do |p|
next if p.empty?
if p.include?('-')
inicio, fin = p.split('-').map(&:to_i)
exclusiones.concat((inicio..fin).to_a)
else
exclusiones << p.to_i
end
end
seleccionados = lista_final.reject.with_index { |_, idx| exclusiones.include?(idx + 1) }
if seleccionados.empty?
puts "#{Color::YELLOW}Has excluido todos los backups. Nada que hacer.#{Color::RESET}"
system("ruby #{candados_path} cerrar > /dev/null 2>&1")
exit
end
puts "#{Color::CYAN} Has excluido #{lista_final.length - seleccionados.length} backups. Procediendo con #{seleccionados.length}...#{Color::RESET}"
elsif input != 's'
puts "#{Color::RED}Opción no válida. Cancelando.#{Color::RESET}"
system("ruby #{candados_path} cerrar > /dev/null 2>&1")
exit
end
# 5. Borrado final
total_a_borrar = seleccionados.sum { |b| b[:archivos].length }
puts "\n#{Color::BOLD}🗑️ Borrando #{total_a_borrar} archivos asociados a #{seleccionados.length} backups...#{Color::RESET}"
seleccionados.each_with_index do |b, i|
puts "\n #{Color::BOLD}[#{i+1}/#{seleccionados.length}] Procesando #{b[:nodo]}...#{Color::RESET}"
b[:archivos].each do |a|
print " Eliminando #{a[:nombre]}... "
del_cmd = "rm -f '#{a[:ruta]}'"
del_seguro = "ssh -o StrictHostKeyChecking=no root@#{host[:ip]} '#{del_cmd}'"
system("ruby #{candados_path} run admindasu SSHPASS '#{del_seguro}' > /dev/null 2>&1")
puts "#{Color::GREEN}OK#{Color::RESET}"
end
end
@logger.exito("Saneamiento completado. Se limpiaron #{seleccionados.length} sets de backup.")
system("ruby #{candados_path} cerrar > /dev/null 2>&1")
end
def cargar_mapa_vmid_nodos
mapa = {}
Dir.glob(File.join(ADN::NODOS_DIR, "*.md")).each do |f|
next if File.directory?(f)
begin
contenido = File.read(f)
# Buscar nombre en h1 o metadatos
nombre = File.basename(f, ".md")
vmid = contenido.match(/(?:VM ID|vmid|VMID)\*\*?:?\s*`?(\d+)`?/i)&.[](1)
mapa[vmid.to_s] = nombre if vmid
rescue
next
end
end
mapa
end
def auto_reconciliar_bitacora(all_tasks)
fecha = Date.today.iso8601
ruta = File.join(ADN::BITACORAS_DIR, "#{fecha}.md")
return unless File.exist?(ruta)
contenido = File.read(ruta)
vmid_map = cargar_mapa_vmid_nodos
# Encontrar todas las filas que tienen un reloj de arena
lineas = contenido.lines
nodo_actual = nil
lineas.each do |linea|
if linea.start_with?('### srvv-')
nodo_actual = linea.sub('### ', '').strip
next
end
# Si es una fila de tabla en progreso (⏳)
if nodo_actual && linea.include?('|') && linea.include?('⏳')
match_hora = linea.match(/\|\s*(\d{2}:\d{2})\s*\|/)
next unless match_hora
hora_inicio_str = match_hora[1]
vmid = vmid_map.key(nodo_actual)
next unless vmid
# Buscar última tarea vzdump de este VMID que ya terminó
hora_inicio_t = Time.parse("#{fecha} #{hora_inicio_str}") rescue nil
tarea = all_tasks.select { |t|
vmid_task = (t['vmid'] || t['id']).to_s
t['type'] == 'vzdump' &&
vmid_task == vmid &&
!t['endtime'].nil? &&
(hora_inicio_t ? Time.at(t['starttime']) >= (hora_inicio_t - 3600) : true)
}.max_by { |t| t['endtime'] }
if tarea
exito = (tarea['status'] == 'OK')
hora_fin = Time.at(tarea['endtime']).strftime('%H:%M')
@logger.info("Auto-cerrando backup de #{nodo_actual} (Task ID: #{tarea['id']})")
cerrar_entrada_especifica(nodo_actual, hora_inicio_str, exito, tarea['id'], hora_fin)
end
end
end
end
def cerrar_entrada_especifica(nodo, hora_ini, exito, task_id, hora_fin)
fecha = Date.today.iso8601
ruta = File.join(ADN::BITACORAS_DIR, "#{fecha}.md")
return unless File.exist?(ruta)
estado_ico = exito ? '✅' : '⚠️'
desc = exito ? 'Backup completado exitosamente.' : 'Fallo en la ejecución del backup.'
desc = "[task:#{task_id[0..7]}] #{desc}" if task_id
linea_nueva = "| #{hora_ini} | #{hora_fin} | #{desc} | R | #{estado_ico} |"
contenido = File.read(ruta)
# Buscamos la fila específica que coincida con el nodo Y la hora de inicio
# Capturamos el bloque del nodo para evitar errores si otros nodos tienen la misma hora
regex_seccion = /(### #{Regexp.escape(nodo)}.*?)(?=^###\s|<!--|\z)/m
seccion = contenido.match(regex_seccion)&.[](0)
unless seccion
return
end
# Regex para la fila exacta que tenga la hora de inicio Y un reloj ⏳ en la última columna
regex_fila = /\|\s*#{Regexp.escape(hora_ini)}\s*\|\s*\|\s*[^|]*\|\s*[^|]*\|\s*[^|]*⏳[^|]*\|/
if seccion.match?(regex_fila)
@logger.exito("Reconciliado: #{nodo} (#{hora_ini} -> #{hora_fin})")
nueva_seccion = seccion.sub(regex_fila, linea_nueva)
# Si ya no quedan ⏳ en esta sección, actualizamos su icono #### ⏳ -> ✅
# Buscamos cualquier reloj de arena que quede en la sección
unless nueva_seccion.include?('⏳')
nueva_seccion.gsub!(/#### [⏳⏳] - BKP-ADN/, "#### #{estado_ico} - BKP-ADN")
end
nuevo_contenido = contenido.gsub(seccion, nueva_seccion)
File.write(ruta, nuevo_contenido)
end
end
end
end
+57
View File
@@ -86,6 +86,8 @@ module ADN
comando_estadisticas
when 'backup'
comando_backup(sub_args)
when 'archivar:md'
comando_archivar_md
else
@logger.error("Subcomando desconocido: #{subcomando}")
@@ -1007,6 +1009,60 @@ module ADN
# Comando: archivar:md - Mover bitácoras y proyectos MD importados/obsoletos a carpetas _hist
def comando_archivar_md
require 'fileutils'
puts "#{Color::CYAN}🧹 Iniciando limpieza y archivado de archivos Markdown...#{Color::RESET}"
BitacorasDB::BitacoraDB.with_connection do |db|
bitacoras_dir = defined?(ADN::BITACORAS_DIR) ? ADN::BITACORAS_DIR : File.expand_path('../../../docs/bitacoras', __dir__)
proyectos_dir = File.expand_path('../../../docs/proyectos', __dir__)
hist_bitacoras = File.join(bitacoras_dir, '_hist')
hist_proyectos = File.join(proyectos_dir, '_hist')
FileUtils.mkdir_p(hist_bitacoras)
FileUtils.mkdir_p(hist_proyectos)
# 1. Archivar Bitácoras Diarias
puts "\\n#{Color::BOLD}>> Verificando Bitácoras Diarias...#{Color::RESET}"
bitacoras_db = db.send(:execute, "SELECT fecha FROM bitacoras.bitacoras").map { |row| row['fecha'] }
Dir.glob(File.join(bitacoras_dir, '*.md')).each do |md_path|
next if File.basename(md_path) == 'README.md'
fecha_md = File.basename(md_path, '.md')
if bitacoras_db.include?(fecha_md)
dest = File.join(hist_bitacoras, File.basename(md_path))
FileUtils.mv(md_path, dest)
puts " #{Color::GREEN}✓ Archivada:#{Color::RESET} #{fecha_md}.md"
else
puts " #{Color::YELLOW}⚠ No en DB (se omite):#{Color::RESET} #{fecha_md}.md"
end
end
# 2. Archivar Proyectos
puts "\\n#{Color::BOLD}>> Verificando Proyectos MD...#{Color::RESET}"
proyectos_db = db.send(:execute, "SELECT codigo FROM bitacoras.proyectos").map { |row| row['codigo'] }
Dir.glob(File.join(proyectos_dir, '*.md')).each do |md_path|
next if File.basename(md_path) == 'README.md'
# Extraer el código PXXXX del principio del archivo, asumiendo formato PXXXX_Titulo.md o PXXXX.md
codigo_match = File.basename(md_path).match(/^(P\d+)/)
if codigo_match && proyectos_db.include?(codigo_match[1])
dest = File.join(hist_proyectos, File.basename(md_path))
FileUtils.mv(md_path, dest)
puts " #{Color::GREEN}✓ Archivado Proyecto:#{Color::RESET} #{File.basename(md_path)}"
else
puts " #{Color::YELLOW}⚠ Proyecto no registrado en DB o formato no válido:#{Color::RESET} #{File.basename(md_path)}"
end
end
puts "\\n#{Color::GREEN}✅ Limpieza Markdown finalizada. Entorno 100% DB-First fortalecido.#{Color::RESET}"
end
end
# ─── MÉTODOS AUXILIARES ──────────────────────────────────────────────
# Carga la configuración desde config.yml
@@ -1037,6 +1093,7 @@ module ADN
puts " #{Color::CYAN}nodo:crear [OPCIONES]#{Color::RESET} Crear nuevo nodo (servidor/VM/PC)"
puts " #{Color::CYAN}nodo:listar [OPCIONES]#{Color::RESET} Listar nodos del sistema"
puts " #{Color::CYAN}estadisticas#{Color::RESET} Mostrar estadísticas del sistema"
puts " #{Color::CYAN}archivar:md#{Color::RESET} Mueve los archivos .md migrados al archivo histórico"
puts ""
puts "#{Color::BOLD}Ejemplos:#{Color::RESET}"
puts " ./tools/adn/run db salud:bd"
+2
View File
@@ -63,6 +63,8 @@ backup:
comprimir: true
defaults:
modo: stop
storage: zfsDISCO1
retencion_dias: '6'
evolucion:
propuestas_directorio: "../../docs/plan"
periodo_revision_horas: 48
+207
View File
@@ -0,0 +1,207 @@
# frozen_string_literal: true
require 'date'
require 'time'
require 'json'
require_relative 'configurador'
module ADN
class Conciliador
def initialize(logger)
@logger = logger
@candados_path = File.join(ADN::PROJECT_ROOT, 'tools', 'ns8-candados', 'ns8-candados.rb')
end
def ejecutar(silencioso: false)
@silencioso = silencioso
@logger.info("Iniciando saneamiento preventivo de pendientes...") unless @silencioso
# 1. Reconciliar backups de ayer y hoy
conciliar_backups_recientes
# 2. Traspasar pendientes no resueltos de ayer a hoy
traspasar_pendientes_ayer_a_hoy
end
private
def conciliar_backups_recientes
# Revisamos ayer y hoy
[Date.today - 1, Date.today].each do |fecha_obj|
fecha = fecha_obj.iso8601
ruta = File.join(ADN::BITACORAS_DIR, "#{fecha}.md")
next unless File.exist?(ruta)
# Si el archivo tiene relojes de arena (⏳)
contenido = File.read(ruta)
if contenido.include?('⏳')
@logger.info(" Revisando tareas pendientes del #{fecha}...") unless @silencioso
procesar_reconciliacion(fecha, ruta)
end
end
end
def procesar_reconciliacion(fecha, ruta)
# Obtener tareas de Proxmox
tasks = obtener_tareas_proxmox
return if tasks.empty?
vmid_map = cargar_mapa_vmid_nodos
contenido = File.read(ruta)
lineas = contenido.lines
nodo_actual = nil
cambio = false
lineas.each do |linea|
if linea.start_with?('### ') && !linea.include?('|')
nodo_actual = linea.sub('### ', '').strip
next
end
if nodo_actual && linea.include?('|') && linea.include?('⏳')
match_hora = linea.match(/\|\s*(\d{2}:\d{2})\s*\|/)
next unless match_hora
hora_inicio_str = match_hora[1]
vmid = vmid_map.key(nodo_actual)
next unless vmid
# Buscar tarea coincidente
hora_inicio_t = Time.parse("#{fecha} #{hora_inicio_str}") rescue nil
tarea = tasks.select { |t|
vmid_task = (t['vmid'] || t['id']).to_s
t['type'] == 'vzdump' &&
vmid_task == vmid &&
(t['status'] == 'OK' || !t['endtime'].nil?) &&
(hora_inicio_t ? Time.at(t['starttime']) >= (hora_inicio_t - 3600) : true)
}.max_by { |t| t['endtime'] || 0 }
if tarea && tarea['endtime']
exito = (tarea['status'] == 'OK')
hora_fin = Time.at(tarea['endtime']).strftime('%H:%M')
task_id = tarea['id']
@logger.exito(" ✨ Tarea auto-resuelta: #{nodo_actual} (#{hora_inicio_str} -> #{hora_fin})") unless @silencioso
# Aplicar cambio en el contenido
estado_ico = exito ? '✅' : '⚠️'
desc = exito ? 'Backup completado exitosamente.' : 'Fallo en la ejecución del backup.'
desc = "[task:#{task_id[0..7]}] #{desc}"
linea_nueva = "| #{hora_inicio_str} | #{hora_fin} | #{desc} | R | #{estado_ico} |\n"
# Reemplazo cuidadoso
regex_fila = /^\|\s*#{Regexp.escape(hora_inicio_str)}\s*\|\s*\|\s*[^|]*\|\s*[^|]*\|\s*[^|]*⏳[^|]*\|/
# Buscar el bloque del nodo
regex_seccion = /(### #{Regexp.escape(nodo_actual)}.*?)(?=\n###\s|<!--|\z)/m
contenido.sub!(regex_seccion) do |seccion|
nueva_seccion = seccion.sub(regex_fila, linea_nueva)
# Si ya no quedan ⏳ en esta sección, actualizamos su icono principal
unless nueva_seccion.include?('⏳')
nueva_seccion.gsub!(/#### [⏳⏳] - BKP-ADN/, "#### #{estado_ico} - BKP-ADN")
end
nueva_seccion
end
cambio = true
end
end
end
if cambio
File.write(ruta, contenido)
# Sincronizar con DB
system("#{File.join(ADN::PROJECT_ROOT, 'tools/adn/run')} db migrar:md #{fecha} --sobrescribir > /dev/null 2>&1")
end
end
def traspasar_pendientes_ayer_a_hoy
ayer = (Date.today - 1).iso8601
hoy = Date.today.iso8601
ruta_ayer = File.join(ADN::BITACORAS_DIR, "#{ayer}.md")
ruta_hoy = File.join(ADN::BITACORAS_DIR, "#{hoy}.md")
return unless File.exist?(ruta_ayer) && File.exist?(ruta_hoy)
cont_ayer = File.read(ruta_ayer)
cont_hoy = File.read(ruta_hoy)
# 1. Extraer sección Pendientes de ayer
pendientes_ayer = extraer_tabla_seccion(cont_ayer, '### Pendientes')
return if pendientes_ayer.empty?
# 2. Filtrar los que ya están en hoy (evitar duplicados)
nuevos_pendientes = pendientes_ayer.reject do |p|
cont_hoy.include?(p[:detalle])
end
return if nuevos_pendientes.empty?
# 3. Insertar en hoy
filas_nuevas = nuevos_pendientes.map { |p| "| #{p[:id]} | #{p[:nodo]} | #{p[:detalle]} |" }.join("\n")
# Regex más flexible para encontrar la tabla de pendientes en hoy
regex_tabla = /(### Pendientes\s*\n\s*\| ID \| Nodo \| Detalle \|\s*\n\s*\| :--- \| :--- \| :--- \|)/
if cont_hoy.match?(regex_tabla)
@logger.exito(" 🚀 Traspasando #{nuevos_pendientes.length} pendientes de ayer a hoy...")
cont_hoy.sub!(regex_tabla) { "#{$1}\n#{filas_nuevas}" }
File.write(ruta_hoy, cont_hoy)
system("#{File.join(ADN::PROJECT_ROOT, 'tools/adn/run')} db migrar:md #{hoy} --sobrescribir > /dev/null 2>&1")
end
end
def extraer_tabla_seccion(contenido, encabezado)
# Busca el bloque que empieza con el encabezado y captura todas las líneas que empiezan con |
regex = /#{Regexp.escape(encabezado)}[^\n]*\n+((?:\|[^\n]*\n?)+)/m
match = contenido.match(regex)
return [] unless match
filas = match[1].strip.split("\n")
return [] if filas.length < 3 # Necesitamos al menos Cabecera, Separador y 1 Fila
# Saltamos las primeras dos líneas (Cabecera y Separador)
filas[2..-1].map do |f|
parts = f.split('|').map(&:strip).reject(&:empty?)
next if parts.length < 3
{ id: parts[0], nodo: parts[1], detalle: parts[2] }
end.compact
end
def obtener_tareas_proxmox
return @tasks if @tasks
system("ruby #{@candados_path} authorize > /dev/null 2>&1")
@tasks = []
[ '10.0.10.201', '10.0.10.202', '10.0.10.203' ].each do |ip|
cmd = "pvesh get /cluster/tasks --output-format json"
output = `ruby #{@candados_path} run admindasu SSHPASS 'ssh -o StrictHostKeyChecking=no root@#{ip} "#{cmd}"' 2>/dev/null`
if output.include?('[')
begin
json_str = output[output.index('[')..output.rindex(']')]
@tasks = JSON.parse(json_str)
break
rescue
next
end
end
end
system("ruby #{@candados_path} cerrar > /dev/null 2>&1")
@tasks
end
def cargar_mapa_vmid_nodos
mapa = {}
Dir.glob(File.join(ADN::NODOS_DIR, "*.md")).each do |f|
nombre = File.basename(f, ".md")
cont = File.read(f)
vmid = cont.match(/(?:VM ID|vmid|VMID)\*\*?:?\s*`?(\d+)`?/i)&.[](1)
mapa[vmid.to_s] = nombre if vmid
end
mapa
end
end
end
+2 -2
View File
@@ -116,8 +116,8 @@ ejecutar_validacion() {
local output
if output=$("${ADN_CLI}" validador --fix 2>&1); then
# El validador exitó con éxito
if echo "$output" | grep -q "Con errores: 0"; then
if echo "$output" | grep -q "Con advertencias: 0"; then
if echo "$output" | grep -qE "Con errores:.*0"; then
if echo "$output" | grep -qE "Con advertencias:.*0"; then
log_exito "Validación del ADN completada sin errores ni advertencias."
else
log_advertencia "Validación del ADN completada con advertencias (revisa arriba)."
+37 -1
View File
@@ -53,6 +53,9 @@ require_relative 'comando/backup'
require_relative 'comando/sync'
require_relative 'comando/triggers'
require_relative 'comando/conocimiento'
require_relative 'comando/cierre'
require_relative 'comando/inicio'
require_relative 'core/conciliador'
# ─── Colores para terminal ───────────────────────────────────────────────
module Color
@@ -707,6 +710,8 @@ class SubcomandoAyuda
#{Color::YELLOW}generar proyecto <código>#{Color::RESET} Generar manifiesto de proyecto
#{Color::YELLOW}backup <nodo> [--mode MODO]#{Color::RESET} Ejecutar backup seguro de un nodo
#{Color::YELLOW}sync [ruta|fecha]#{Color::RESET} Sincronizar con API dtic-BITACORAs
#{Color::YELLOW}inicio de jornada [presencial|remoto]#{Color::RESET} Inicio de jornada laboral
#{Color::YELLOW}cierre de jornada [presencial|remoto]#{Color::RESET} Cierre de jornada laboral
#{Color::YELLOW}triggers [opciones]#{Color::RESET} Gestión del motor de triggers
#{Color::YELLOW}ayuda [subcomando]#{Color::RESET} Mostrar esta ayuda o ayuda específica
@@ -794,6 +799,10 @@ class ADNCLI
end
subcomando = ARGV[0]
# Saneamiento preventivo de pendientes (con cooldown de 5 minutos para performance)
ejecutar_saneamiento_preventivo(subcomando)
args = ARGV[1..-1] || []
case subcomando
@@ -809,12 +818,16 @@ class ADNCLI
ADN::SubcomandoConocimiento.new(args, @logger).ejecutar
when 'db'
ADN::SubcomandoDB.new(args, @logger).ejecutar
when 'backup'
when 'backup', 'backups'
ADN::SubcomandoBackup.new(args, @logger).ejecutar
when 'sync'
ADN::SubcomandoSync.new(args, @logger).ejecutar
when 'triggers'
ADN::SubcomandoTriggers.new(args, @logger).ejecutar
when 'cierre'
ADN::SubcomandoCierre.new(args, @logger).ejecutar
when 'inicio'
ADN::SubcomandoInicio.new(args, @logger).ejecutar
when 'ayuda', 'help', '-h', '--help'
SubcomandoAyuda.new(args, @logger).ejecutar
when 'evolucion'
@@ -842,6 +855,29 @@ class ADNCLI
puts "#{Color::DIM}#{e.backtrace.first}#{Color::RESET}" if ENV['ADN_DEBUG']
exit 1
end
private
def ejecutar_saneamiento_preventivo(subcomando)
# No sanear si es ayuda o db (para evitar bucles o lentitud en info rápida)
return if ['ayuda', 'help', 'db', 'salud', '-h', '--help'].include?(subcomando)
archivo_cooldown = "/tmp/adn_sanity_cooldown"
cooldown_segundo = 300 # 5 minutos
if File.exist?(archivo_cooldown)
last_run = File.mtime(archivo_cooldown)
return if (Time.now - last_run) < cooldown_segundo
end
# Ejecutar conciliación
begin
# Tocamos el archivo primero para evitar que otras instancias concurrentes lo lancen
FileUtils.touch(archivo_cooldown)
ADN::Conciliador.new(@logger).ejecutar(silencioso: true)
rescue => e
@logger.advertencia("No se pudo completar el saneamiento preventivo: #{e.message}")
end
end
end
# ─── Punto de entrada ───────────────────────────────────────────────────
+72
View File
@@ -0,0 +1,72 @@
# frozen_string_literal: true
# tools/bitacoras-db/cli/exportar_todo.rb
# ==========================================
# Exporta TODAS las bitácoras de la DB a archivos .md
# Uso: ruby tools/bitacoras-db/cli/exportar_todo.rb [--dir DIRECTORIO]
# — o vía enjambre —
# ./tools/adn/run db exportar:md --todo
require 'date'
require 'optparse'
require_relative '../core/bitacora_db'
require_relative '../exporters/md_exporter'
module BitacorasDB
module CLI
class ExportarTodo
def initialize(bitacoras_dir, logger = nil)
@bitacoras_dir = bitacoras_dir
@logger = logger || default_logger
@resultados = { ok: 0, error: 0 }
end
def ejecutar
@logger.info("Exportando todas las bitácoras de DB a MD...")
BitacorasDB::BitacoraDB.with_connection do |db|
bitacoras = db.list_bitacoras(limit: 500)
if bitacoras.empty?
@logger.info("No hay bitácoras en la base de datos.")
return
end
exporter = BitacorasDB::Exporters::MDExporter.new(db, @logger)
bitacoras.each do |b|
fecha = b['fecha'].to_s
fecha = fecha.split('T').first if fecha.include?('T')
archivo = File.join(@bitacoras_dir, "#{fecha}.md")
if exporter.exportar_bitacora(fecha, archivo)
@resultados[:ok] += 1
else
@resultados[:error] += 1
end
end
end
@logger.info("📊 Exportación masiva completada:")
@logger.info(" ✅ Exitosas: #{@resultados[:ok]}")
@logger.info(" ❌ Errores: #{@resultados[:error]}") if @resultados[:error] > 0
end
private
def default_logger
Class.new do
def info(msg); puts "[INFO] #{msg}"; end
def error(msg); puts "[ERROR] #{msg}"; end
def exito(msg); puts "[OK] #{msg}"; end
end.new
end
end
end
end
# Ejecución directa
if __FILE__ == $0
bitacoras_dir = File.expand_path('../../../docs/bitacoras', __dir__)
BitacorasDB::CLI::ExportarTodo.new(bitacoras_dir).ejecutar
end
@@ -0,0 +1,121 @@
# frozen_string_literal: true
# tools/bitacoras-db/cli/verificar_migracion.rb
# ================================================
# Verifica que todas las bitácoras MD existentes estén correctamente
# migradas en la base de datos. Reporta faltantes y discrepancias.
#
# Uso:
# ruby tools/bitacoras-db/cli/verificar_migracion.rb
# — o vía enjambre —
# ./tools/adn/run db verificar
require 'date'
require_relative '../core/bitacora_db'
module BitacorasDB
module CLI
class VerificarMigracion
COLORES = {
reset: "\e[0m", bold: "\e[1m", red: "\e[31m",
green: "\e[32m", yellow: "\e[33m", cyan: "\e[36m", dim: "\e[2m"
}.freeze
def initialize(bitacoras_dir)
@bitacoras_dir = bitacoras_dir
@resultados = { ok: [], sin_entradas: [], no_migrada: [], sin_md: [] }
end
def ejecutar
puts "#{COLORES[:cyan]}🔍 Verificación de migración MD ↔ DB#{COLORES[:reset]}\n\n"
archivos_md = Dir.glob(File.join(@bitacoras_dir, '*.md')).sort
puts "#{COLORES[:dim]}Archivos MD encontrados: #{archivos_md.size}#{COLORES[:reset]}"
BitacorasDB::BitacoraDB.with_connection do |db|
# 1. Validar cada MD contra la DB
archivos_md.each do |archivo|
fecha = File.basename(archivo, '.md')
bitacora = db.find_bitacora_by_fecha(fecha)
unless bitacora
@resultados[:no_migrada] << fecha
next
end
entradas = db.execute(
"SELECT COUNT(*) as total FROM bitacoras.entradas WHERE bitacora_id = $1",
[bitacora['id']]
)
total = entradas.first['total'].to_i
if total > 0
@resultados[:ok] << { fecha: fecha, entradas: total }
else
@resultados[:sin_entradas] << fecha
end
end
# 2. Bitácoras en DB que no tienen MD
todas_db = db.list_bitacoras(limit: 500)
fechas_md = archivos_md.map { |a| File.basename(a, '.md') }
todas_db.each do |b|
f = b['fecha'].to_s
f = f.split('T').first if f.include?('T') # por si viene con timestamp
@resultados[:sin_md] << f unless fechas_md.include?(f)
end
end
mostrar_resultados
end
private
def mostrar_resultados
puts "\n#{COLORES[:bold]}📊 Resultado de verificación:#{COLORES[:reset]}\n"
# OK
puts "#{COLORES[:green]}✅ Migradas correctamente: #{@resultados[:ok].size}#{COLORES[:reset]}"
@resultados[:ok].each do |r|
puts " #{COLORES[:dim]}#{r[:fecha]} (#{r[:entradas]} entradas)#{COLORES[:reset]}"
end
# Sin entradas
if @resultados[:sin_entradas].any?
puts "\n#{COLORES[:yellow]}⚠ En DB pero sin entradas: #{@resultados[:sin_entradas].size}#{COLORES[:reset]}"
@resultados[:sin_entradas].each { |f| puts " #{f}" }
end
# No migradas
if @resultados[:no_migrada].any?
puts "\n#{COLORES[:red]}❌ No migradas (MD existe, DB no): #{@resultados[:no_migrada].size}#{COLORES[:reset]}"
@resultados[:no_migrada].each { |f| puts " #{f}" }
puts " #{COLORES[:dim]}Ejecuta: ./tools/adn/run db migrar:md FECHA#{COLORES[:reset]}"
end
# Sin MD
if @resultados[:sin_md].any?
puts "\n#{COLORES[:cyan]} En DB sin archivo MD: #{@resultados[:sin_md].size}#{COLORES[:reset]}"
@resultados[:sin_md].each { |f| puts " #{f}" }
puts " #{COLORES[:dim]}Ejecuta: ./tools/adn/run db exportar:md FECHA#{COLORES[:reset]}"
end
total = @resultados[:ok].size + @resultados[:sin_entradas].size
total_md = @resultados[:ok].size + @resultados[:sin_entradas].size + @resultados[:no_migrada].size
pct = total_md > 0 ? ((@resultados[:ok].size.to_f / total_md) * 100).round(1) : 0
puts "\n#{COLORES[:bold]}📈 Cobertura: #{pct}% (#{@resultados[:ok].size}/#{total_md})#{COLORES[:reset]}"
if @resultados[:no_migrada].empty? && @resultados[:sin_entradas].empty?
puts "#{COLORES[:green]}#{COLORES[:bold]}🎉 ¡Migración completa! Todas las bitácoras están en la DB.#{COLORES[:reset]}"
end
end
end
end
end
# Ejecución directa
if __FILE__ == $0
bitacoras_dir = File.expand_path('../../../docs/bitacoras', __dir__)
BitacorasDB::CLI::VerificarMigracion.new(bitacoras_dir).ejecutar
end
+130
View File
@@ -0,0 +1,130 @@
# frozen_string_literal: true
require 'date'
module BitacorasDB
module Exporters
class MDExporter
def initialize(db, logger = nil)
@db = db
@logger = logger || default_logger
end
def exportar_bitacora(fecha, ruta_destino)
@logger.info("Exportando bitácora #{fecha} a #{ruta_destino}...")
bitacora = @db.find_bitacora_by_fecha(fecha)
unless bitacora
@logger.error("No se encontró la bitácora para la fecha: #{fecha}")
return false
end
bitacora_id = bitacora['id']
# Obtener datos estructurados (incluyendo lo nuevo)
entradas = @db.execute("
SELECT e.*, n.nombre as nodo_nombre
FROM bitacoras.entradas e
LEFT JOIN bitacoras.nodos n ON e.nodo_id = n.id
WHERE e.bitacora_id = $1
ORDER BY n.nombre, e.inicio ASC
", [bitacora_id])
gestion = @db.execute("
SELECT g.*, n.nombre as nodo_nombre
FROM bitacoras.gestion g
LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id
WHERE g.bitacora_id = $1 OR g.activo = true
ORDER BY g.tipo, g.orden
", [bitacora_id]) rescue [] # Por si la tabla no existe aún en otros entornos
resumenes = @db.execute("
SELECT r.*, n.nombre as nodo_nombre
FROM bitacoras.resumen_nodos r
JOIN bitacoras.nodos n ON r.nodo_id = n.id
WHERE r.bitacora_id = $1
", [bitacora_id]) rescue []
# Generar contenido Markdown
contenido = generar_markdown(fecha, bitacora['resumen'], gestion, resumenes, entradas)
File.write(ruta_destino, contenido)
@logger.info("✅ Exportación completada con éxito.")
true
rescue => e
@logger.error("Error exportando bitácora: #{e.message}")
false
end
private
def generar_markdown(fecha, resumen_gen, gestion, resumenes, entradas)
y, m, d = fecha.to_s.split('-')
md = String.new("<!-- GENERADO AUTOMÁTICAMENTE por adn db exportar:md — No editar manualmente -->\n")
md << "# Bitácora de Operaciones - #{d}/#{m}/#{y}\n\n"
if gestion && gestion.ntuples > 0
md << "## 📋 Control de Gestión\n\n"
pendientes = gestion.select { |g| g['tipo'] == 'pendiente' }
if pendientes.any?
md << "### 📝 Resumen de Actividades\n" # Formato ADN
md << "| NODO | PENDIENTES |\n| :--- | :--- |\n"
pendientes.each do |p|
md << "| #{p['nodo_nombre'] || '—'} | #{p['detalle']} |\n"
end
md << "\n"
end
en_proceso = gestion.select { |g| g['tipo'] == 'proceso' }
if en_proceso.any?
md << "### ⏳ En Proceso\n"
md << "| NODO | ACTIVIDAD |\n| :--- | :--- |\n"
en_proceso.each do |p|
md << "| #{p['nodo_nombre'] || '—'} | #{p['detalle']} |\n"
end
md << "\n"
end
end
if resumenes && resumenes.ntuples > 0
md << "### 📝 Resumen Integral\n"
md << "| NODO | RESUMEN INTEGRAL |\n| :--- | :--- |\n"
resumenes.each do |r|
md << "| #{r['nodo_nombre']} | #{r['resumen']} |\n"
end
md << "\n"
end
md << "## 📂 Actividades Detalladas\n\n"
por_nodo = entradas.group_by { |e| e['nodo_nombre'] || 'sin-nodo' }
por_nodo.each do |nodo, entries|
md << "### #{nodo}\n\n"
md << "| I | F | Descripción | E |\n"
md << "| :--- | :--- | :--- | :--- |\n"
entries.each do |e|
inicio = e['inicio'] ? e['inicio'][0..4] : '-'
fin = e['fin'] ? e['fin'][0..4] : '-'
ia = e['es_ia'] == 't' ? '(IA) ' : ''
estado = "[#{e['modo']}] #{e['estado']}"
md << "| #{inicio} | #{fin} | #{ia}#{e['descripcion']} | #{estado} |\n"
end
md << "\n"
end
md << "---\n"
md << "<!-- GENERADO AUTOMÁTICAMENTE desde PostgreSQL el #{Time.now.strftime('%Y-%m-%d %H:%M')} -->\n"
md << "<!-- Fuente de verdad: DB. Regenerar con: adn db exportar:md #{fecha} -->\n"
md
end
def default_logger
Class.new do
def info(msg); puts "[INFO] #{msg}"; end
def error(msg); puts "[ERROR] #{msg}"; end
end.new
end
end
end
end
+100 -38
View File
@@ -31,6 +31,7 @@
require 'date'
require 'time'
require 'yaml'
require 'digest'
module BitacorasDB
module Importers
@@ -43,14 +44,16 @@ module BitacorasDB
'⏸️' => '⏸️',
'⏳' => '⏳',
'👁️' => '⏳', # Sincronización mapea a ⏳
'➡️' => '⏳' # Tarea continúa en otro día
'➡️' => '⏳', # Tarea continúa en otro día
'🎭' => '✅' # Evento/Servicio
}.freeze
# Modos posibles en archivos .md
MODOS_MD = {
'[P]' => 'P', # Presencial
'[R]' => 'R', # Remoto
'[P/R]' => 'P' # Presencial/Remoto -> Presencial por defecto
'[P/R]' => 'P', # Presencial/Remoto -> Presencial por defecto
'[S]' => 'S' # Servicio (No nodo técnico)
}.freeze
# Tipos de nodo inferidos del nombre
@@ -61,6 +64,7 @@ module BitacorasDB
/^pcv-/ => 'vm',
/^pc-/ => 'pc',
/^srvv-/ => 'vm',
/^dtic-/ => 'servicio',
/-pmox\d+$/ => 'hipervisor',
/-xen\d+$/ => 'hipervisor'
}.freeze
@@ -264,7 +268,7 @@ module BitacorasDB
end
# Detectar inicio de tabla de entradas
if linea.match?(/^\| I \| F \| Descripción \| E \|$/)
if linea.match?(/^\| I \| F \| Descripción \| (?:J \| )?E \|$/)
en_tabla_entradas = true
encabezados_tabla = linea.split('|').map(&:strip).reject(&:empty?)
entradas_tabla = []
@@ -343,20 +347,32 @@ module BitacorasDB
# Parsea línea de tema (ej: "✅ - SEC01 - Refactorización ns8-candados")
def parsear_tema(linea)
# Patrón: estado - id - título (id puede contener puntos, como ADN01.1)
match = linea.match(/^([✅❌⚠️⏸️⏳👁️➡️]+)\s*-\s*([A-Z0-9.]+)\s*-\s*(.+)$/)
return nil unless match
# Intentar formato estándar: estado - id - título (ej: ✅ - ADN01 - Título)
match_std = linea.match(/^([^\s-]+)\s*-\s*([A-Z0-9.]+)\s*-\s*(.+)$/)
if match_std
return {
id_tema: match_std[2],
titulo: match_std[3].strip,
estado: ESTADOS_MD[match_std[1]] || '⏳',
descripcion: match_std[3].strip
}
end
estado = match[1]
id_tema = match[2]
titulo = match[3].strip
# Intentar formato simplificado: estado - título (ej: 🎭 - Servicio Auditorio)
match_simple = linea.match(/^([^\s-]+)\s*-\s*(.+)$/)
if match_simple
titulo = match_simple[2].strip
# Generar un ID determinista basado en el título para estabilidad
id_gen = "GEN" + Digest::MD5.hexdigest(titulo)[0..5].upcase
return {
id_tema: id_gen,
titulo: titulo,
estado: ESTADOS_MD[match_simple[1]] || '⏳',
descripcion: titulo
}
end
{
id_tema: id_tema,
titulo: titulo,
estado: ESTADOS_MD[estado] || '⏳',
descripcion: titulo # Por defecto, usar título como descripción
}
nil
end
# Parsea líneas de tabla de entradas
@@ -370,6 +386,7 @@ module BitacorasDB
when 'i' then columnas[:inicio] = idx
when 'f' then columnas[:fin] = idx
when 'descripción' then columnas[:descripcion] = idx
when 'j' then columnas[:jornada] = idx
when 'e' then columnas[:estado] = idx
end
end
@@ -379,28 +396,39 @@ module BitacorasDB
next if linea.match?(/^[\|\s:-]+$/) # Solo pipes, espacios, guiones y dos puntos
next if linea.match?(/^\|?\s*:?-+:?\s*\|/) # Separadores con :---, ---, :-:, etc.
celdas = linea.split('|').map(&:strip).reject(&:empty?)
# Limpiar pipes iniciales y finales antes de split
linea_limpia = linea.strip.gsub(/^\||\|$/, '')
celdas = linea_limpia.split('|').map(&:strip)
next if celdas.length < 3 # Mínimo: inicio, descripción, estado
# Validar que la celda de inicio no sea un separador
# Validar que la celda de inicio tenga contenido
valor_inicio = celdas[columnas[:inicio]]
next if valor_inicio.nil? || valor_inicio.match?(/^:?-+:?$/)
next if valor_inicio.nil? || valor_inicio.empty? || valor_inicio.match?(/^:?-+:?$/)
fin_valor = nil
if columnas[:fin] && celdas[columnas[:fin]] != '-'
fin_valor = celdas[columnas[:fin]]
if columnas[:fin] && !celdas[columnas[:fin]].nil?
raw_fin = celdas[columnas[:fin]].strip
fin_valor = raw_fin if !raw_fin.empty? && raw_fin != '-'
end
entrada = {
inicio: celdas[columnas[:inicio]],
fin: fin_valor,
inicio: valor_inicio.gsub(/\s*\(.*?\)\s*/, '').strip,
fin: fin_valor ? fin_valor.gsub(/\s*\(.*?\)\s*/, '').strip : nil,
descripcion: celdas[columnas[:descripcion]],
estado_modo: celdas[columnas[:estado]]
}
# Parsear estado y modo (ej: "[P] ✅")
if entrada[:estado_modo]
estado_modo = parsear_estado_modo(entrada[:estado_modo])
# Parsear estado y modo
if columnas[:jornada] && columnas[:estado]
# Nuevo formato: J y E separados
# Unimos temporalmente para usar parsear_estado_modo existente
campo_combinado = "[#{celdas[columnas[:jornada]]}] #{celdas[columnas[:estado]]}"
estado_modo = parsear_estado_modo(campo_combinado)
entrada[:estado] = estado_modo[:estado]
entrada[:modo] = estado_modo[:modo]
elsif columnas[:estado]
# Formato viejo o solo E
estado_modo = parsear_estado_modo(celdas[columnas[:estado]])
entrada[:estado] = estado_modo[:estado]
entrada[:modo] = estado_modo[:modo]
end
@@ -456,6 +484,16 @@ module BitacorasDB
bitacora_id = bitacora['id']
# 1b. Si se solicita sobrescribir, limpiar entradas previas de esta bitácora
if opciones[:sobrescribir]
@logger.info("Limpiando datos previos de la bitácora #{estructura[:fecha]}...")
# Borrar entradas primero (FK)
@db.execute("DELETE FROM bitacoras.entradas WHERE bitacora_id = $1", [bitacora_id])
# Borrar temas (FK)
@db.execute("DELETE FROM bitacoras.temas WHERE bitacora_id = $1", [bitacora_id])
@estadisticas[:datos_limpiados] = true
end
# 2. Procesar cada nodo
estructura[:nodos].each do |nombre_nodo, datos_nodo|
# Buscar o crear nodo
@@ -505,19 +543,43 @@ module BitacorasDB
# Validar campos requeridos
next unless entrada_md[:inicio] && entrada_md[:descripcion]
# Crear entrada en BD
@db.create_entrada(
inicio: entrada_md[:inicio],
fin: entrada_md[:fin],
descripcion: entrada_md[:descripcion],
estado: entrada_md[:estado] || '⏳',
modo: entrada_md[:modo] || 'P',
es_ia: false, # Las importadas de .md no son IA
tema_id: tema_id,
bitacora_id: bitacora_id,
nodo_id: nodo_id
)
@estadisticas[:entradas_creadas] += 1
# Intentar buscar entrada existente para evitar duplicados (Upsert)
# Buscamos por: bitacora_id, nodo_id y hora de inicio (única para un nodo en un día)
entradas_nodo = @db.list_entradas(bitacora_id: bitacora_id, nodo_id: nodo_id, limit: 100)
# Normalizar hora para comparación
inicio_norm = @db.send(:parse_time, entrada_md[:inicio])
existente = entradas_nodo.find { |e| e['inicio'] == inicio_norm }
begin
if existente
# Actualizar si ya existe
@db.update_entrada(existente['id'], {
fin: entrada_md[:fin],
descripcion: entrada_md[:descripcion],
estado: entrada_md[:estado] || '⏳',
modo: entrada_md[:modo] || 'P'
})
@estadisticas[:entradas_actualizadas] ||= 0
@estadisticas[:entradas_actualizadas] += 1
else
# Crear si no existe
@db.create_entrada(
inicio: entrada_md[:inicio],
fin: entrada_md[:fin],
descripcion: entrada_md[:descripcion],
estado: entrada_md[:estado] || '⏳',
modo: entrada_md[:modo] || 'P',
es_ia: false,
tema_id: tema_id,
bitacora_id: bitacora_id,
nodo_id: nodo_id
)
@estadisticas[:entradas_creadas] += 1
end
rescue => e
@logger.error("Error procesando entrada '#{entrada_md[:inicio]} - #{entrada_md[:descripcion]}': #{e.message}")
end
end
end
end
+35 -2
View File
@@ -19,6 +19,7 @@
# encrypt <txt> Cifrar texto plano
# decrypt <tok> Descifrar payload
# get <clave> Obtener secreto de la bóveda (salida: solo el valor)
# run <cl> <vr> <cmd> Ejecutar comando con secreto en variable de entorno <vr>
# set <clave> Guardar secreto (pide input interactivo, sin eco)
# rm <clave> Eliminar secreto de la bóveda
# list Listar claves disponibles en la bóveda
@@ -203,6 +204,33 @@ def cmd_get(cripto, boveda, clave)
puts cripto.decrypt(token)
end
def cmd_run(cripto, boveda, clave, env_var, comando)
abort "#{C::RED}✗ Falta nombre de clave.#{C::RESET}" if clave.nil?
abort "#{C::RED}✗ Falta nombre de variable de entorno.#{C::RESET}" if env_var.nil?
abort "#{C::RED}✗ Falta comando a ejecutar.#{C::RESET}" if comando.nil? || comando.empty?
verificar_sesion!
token = boveda.get(clave)
abort "#{C::RED}✗ Clave '#{clave}' no encontrada.#{C::RESET}" if token.nil?
secret = cripto.decrypt(token)
registrar_acceso('RUN', "Clave: #{clave}, Comando: #{comando.is_a?(Array) ? comando.join(' ') : comando}")
# Ejecutar el comando inyectando la variable
ENV[env_var] = secret
begin
# Si comando es un string, lo ejecutamos vía shell, si es array, directo
if comando.is_a?(Array)
system(*comando)
else
system(comando)
end
exit $?.exitstatus || 0
ensure
ENV.delete(env_var)
end
end
def cmd_set(cripto, boveda, clave)
abort "#{C::RED}✗ Falta nombre de clave.#{C::RESET}" if clave.nil?
$stderr.print "#{C::YELLOW}🔑 Valor para '#{clave}': #{C::RESET}"
@@ -243,6 +271,7 @@ def cmd_help
encrypt <texto> Cifrar texto plano token
decrypt <token> Descifrar token texto plano
get <clave> Obtener secreto de la bóveda
run <cl> <vr> <cmd> Ejecutar comando con secreto en variable <vr>
set <clave> Guardar secreto (input seguro)
rm <clave> Eliminar secreto
list Listar claves disponibles
@@ -251,7 +280,10 @@ def cmd_help
# Autorizar para operar
ruby ns8-candados.rb authorize
#{C::DIM}# Usar en scripts (salida limpia)#{C::RESET}
#{C::DIM}# Usar de forma segura inyectando en el entorno (recomendado)#{C::RESET}
ruby ns8-candados.rb run admindasu PASS "sshpass -e ssh user@host"
#{C::DIM}# Usar en scripts (salida limpia - menos seguro)#{C::RESET}
PASS=$(ruby ns8-candados.rb get admindasu)
sshpass -p "$PASS" ssh user@host "cmd"
HELP
@@ -266,13 +298,14 @@ when 'authorize'
autorizar!
when 'cerrar'
cerrar_sesion!
when 'encrypt', 'decrypt', 'get', 'set', 'rm', 'list'
when 'encrypt', 'decrypt', 'get', 'run', 'set', 'rm', 'list'
cripto = Cripto.new(cargar_master_key)
boveda = Boveda.new(BOVEDA)
case comando
when 'encrypt' then cmd_encrypt(cripto, arg)
when 'decrypt' then cmd_decrypt(cripto, arg)
when 'get' then cmd_get(cripto, boveda, arg)
when 'run' then cmd_run(cripto, boveda, arg, ARGV[2], ARGV[3..].join(' '))
when 'set' then cmd_set(cripto, boveda, arg)
when 'rm' then cmd_rm(boveda, arg)
when 'list' then cmd_list(boveda)