Homologación de plataformas de monitoreo
Contexto
Hoy Sergio mantiene inventarios paralelos de dispositivos en al menos tres plataformas, sin sincronización end-to-end:
- UISP — portal SaaS de Ubiquiti donde Sergio agrega/actualiza los equipos UISP-managed (antenas, APs, switches Ubiquiti).
es-antenas-new— plataforma propia de monitoreo (API + dashboard). Tiene su propia tabladispositivosmantenida manualmente.backup-system— pipeline Oxidized para backups de configs de red. Su inventory_merger consume UISP + NetBox y emiterouter.db.
Dolor real (palabras de Sergio): “Hay veces que agrego o actualizo equipos en UISP y no lo actualizo o agrego en es-antenas-new. Quiero que se pueda automatizar de alguna manera y solo tener que agregarlos una vez.”
Objetivo: un solo punto de alta por device — el inventario debe fluir solo a todas las plataformas que lo consumen.
Insight clave (aún por validar con Sergio)
backup-system ya está parcialmente homologado: inventory_merger.py (en VM oxidized) pulla UISP y NetBox cada noche para producir el router.db de Oxidized. Es decir, agregar un device en UISP ya fluye automáticamente a backup-system. El gap principal está entre UISP ↔ es-antenas-new.
backup-system cubre además devices fuera de UISP (servers de telefonía, etc.) vía NetBox con tag backup-enabled — eso queda como caso separado, no afecta el flujo principal.
Tareas pendientes
Fase 0 — Validar el modelo mental con Sergio
- (2026-05-12) Fuente de verdad: UISP, como meta. Estado inicial transicional:
es-antenas-newpuede tener devices propios (huérfanos sin contraparte en UISP) hasta que Sergio los migre uno a uno a UISP. El sync debe respetar esos huérfanos sin tocarlos. - (2026-05-13) Modelo de migración refinado: los huérfanos no se quedan para siempre — se migran activamente a UISP (con aprobación manual de Sergio device por device, batch por enlace). Estado terminal del proyecto: cero huérfanos en
es-antenas-new(entre los administrados). Excepción: devices no-administrados-por-nosotros (proveedores externos, equipos cliente fuera de scope) llevan flagadministrado=falsey no cuentan como pendientes. - (2026-05-12) Sincronización: unidireccional UISP →
es-antenas-new. Bidireccional NO se contempla (Sergio no quiere quees-antenas-newmande devices a UISP en steady state). Devices huérfanos permitidos enes-antenas-newdurante la transición. Excepción transicional (decidida 2026-05-13): existe un flujo one-way inverso (es-antenas-new → UISP) solo durante la fase de migración activa, no en steady state, y solo con aprobación manual por device. - (2026-05-12)
backup-systemya cubre UISP → backups — verificado por SSH read-only aoxidized:inventory_merger.pyexiste en/opt/oxidized-tools/scripts/y se invoca comoExecStartPre=deoxidized.service(no como timer/cron independiente).router.dbtiene 255 devices Ubiquiti, todos coinciden con el formato UISP-managed.- Oxidized config:
interval: 86400(24h entre full runs). - ⚠️ Caveat: el merger solo se ejecuta cuando se reinicia
oxidized.service. Último restart: 2026-05-05 (hace 7 días). Si Sergio agregó un device en UISP la última semana, NO está enrouter.dbtodavía — quedaría stale hasta el próximo restart. Esto deja una mejora natural: agregar uninventory-merger.timer(diario o cada N horas) que corra el merger y hagasystemctl reload oxidizedsirouter.dbcambió. Capturado como mejora del proyectobackup-systemtambién — relevante porque la promesa “agregar en UISP fluye automático” sólo se cumple si la cadencia es razonable.
- (2026-05-12) Otras plataformas externas: confirmado por Sergio — ninguna. Solo las 4 internas (UISP,
es-antenas-new,backup-system,monitoreo-collector). - (2026-05-12)
monitoreo-collectorviene gratis: Sergio confirmó que el agent Go lee la lista de devices a polear desdees-antenas-new. Por lo tanto, homologar UISP →es-antenas-newpropaga automático amonitoreo-collector. No requiere trabajo adicional en este proyecto.
Fase 1 — Sync UISP → es-antenas-new (el core del problema)
Modelo decidido (2026-05-12, refinado 2026-05-13): sync unidireccional UISP → es-antenas-new en steady state. Tres tipos de registros conviven en dispositivos, dos de ellos transicionales:
origen=uisp— vino del sync, lo gestiona el sync. Sergio no edita campos base (name/ip/model/site) directo aquí. Estado terminal.origen=manual— huérfano legacy, lo gestiona Sergio manualmente. El sync no lo toca. Estado transicional: la Fase 1A drena este conjunto hacia cero migrando cada device a UISP con aprobación manual.origen=claimed— empezó manual, después se agregó a UISP (vía Fase 1A o por fuera), el sync hizo match y “lo adoptó” preservando la config local (tiers, labels, plantillas). Estado transicional — eventualmente todos losclaimedse reclasifican comouisppuros una vez consolidados.
Flag adicional (decidido 2026-05-13, ampliado 2026-05-15): dispositivos.administrado boolean (default true). Cuando false, el device es monitoreado por nosotros pero no administrado por nosotros. Casos identificados:
- Equipo cliente / equipo de proveedor externo (caso original).
- Devices en UISP de un partner (caso descubierto 2026-05-15): Sergio los tiene en es-antenas-new pero ya viven en otra instancia de UISP (del socio del jefe). Sergio no los puede agregar a su UISP, pero los quiere seguir monitoreando localmente. Mismo flujo:
origen=manual AND administrado=falsepermanente.
Estos devices:
- Pueden coexistir como
origen=manualpermanentemente — no son huérfanos pendientes. - NO entran al reporte de “drenado a UISP”.
- Si en el futuro Sergio empieza a administrarlos (o el partner los suelta), se flippa el flag y entran al flujo normal.
Tareas concretas:
- (2026-05-15) Agregar columnas a
dispositivos— migration escrita en~/code/es-antenas-new/database/migrations/2026_05_15_180000_agregar_origen_uisp_id_administrado_a_dispositivos.php. ModeloDispositivocon castadministrado => booleanagregado. NO deployado todavía — Sergio decide cuándo:origen(enum: uisp | manual | claimed, defaultmanual).uisp_id(string(36) nullable unique).administrado(boolean, defaulttrue).
- #344 📅 sin-fecha — Investigar API de UISP: endpoints para listar devices (
/nms/api/v2.1/devices), atributos relevantes (id, name, model, ip, site, role), webhook/event stream para alta/baja en tiempo real. - #345 📅 sin-fecha — Diseñar mapping UISP →
dispositivos:- Llave estable: UISP
id(UUID) — no name, no IP, no MAC. UISP no renombra UUIDs. Cuando Sergio renombra el device en UISP, sigue siendo el mismo registro enes-antenas-new. - Atributos sincronizados (sobrescriben en cada sync para
origen=uisp):name,ip,model,site,role. - Atributos nunca tocados por el sync:
tier_default, custom labels, etiquetas de plantillas,ignorar, monitoreos asociados.
- Llave estable: UISP
- #346 📅 sin-fecha — Elegir modo de sync:
- A — Pull periódico (cron, cada N min consulta UISP API y reconcilia).
- B — Push por webhook (UISP llama un endpoint en
es-antenas-newcuando hay alta/baja/cambio). - C — Reusar
inventory_merger.pydel backup-system: que también emita un dump quees-antenas-newconsuma. Evita duplicar la lógica de auth/paginación contra UISP. - Recomendación inicial: C + A (reusar el merger y que
es-antenas-newhaga pull del dump cada hora). Cero código nuevo contra UISP API.
- (2026-05-18) Sync command UISP→es-antenas-new — fase claim escrita.
~/code/es-antenas-new/app/Console/Commands/SyncUisp.phpen branchfeature/sync-uisp. Comandoes:sync-uisp {--dry-run} {--fixture=}. PulaGET /nms/api/v2.1/devicesconx-auth-token(configservices.uisp.{base_url,token,verify_ssl}+ envUISP_BASE_URL/UISP_API_TOKEN/UISP_VERIFY_SSL). Lógica: agrupa UISP por IP, agrupa candidatos locales (origen=manual AND administrado=true AND uisp_id IS NULL) por IP, hace claim 1-a-1 cuando hay match único (setuisp_id+origen='claimed'). Reporta: claims, ambiguos (skip), nuevos en UISP sin contraparte (no inserta — política de altas pendiente), desaparecidos (uisp_id ya no existe en UISP — solo reporta, no soft-delete). Idempotente. Tests Pest entests/Feature/SyncUispTest.php(7 cases: claim ok, dry-run no escribe, IP ambigua se skipea, administrado=false intocable, idempotencia, sin-IP no rompe, fixture faltante falla limpio) — 7/7 verde con SQLite override local. Smoke test contramonitoring-homologation/uisp-devices-2026-05-15.json(375 devices reales) parseó OK, stripeó CIDR (/24etc), claimeó la única IP coincidente. NO commiteado — branchfeature/sync-uispcon 4 archivos staged, Sergio decide cuándo mergear. - (2026-05-18) Devices CSV generator escrito.
~/code/es-antenas-new/app/Console/Commands/ExportarCsvUisp.phpen branchfeature/exportar-csv-uisp. Comandoes:exportar-csv-uispcon--dry-run. Leeorigen=manual AND administrado=true AND uisp_id IS NULL, decide managed vs blackBox porplantilla.nombreexacto (lista canónica: PowerBeam M5, Rocket Prism 5AC, EdgeRouter, Ubiquiti AF60, Ubiquiti AF3X/AF4X/AF5X/AF11/AF24, Ubiquiti AF5XHD). Emite CSV único enstorage/app/uisp-imports/devices-YYYY-MM-DD.csvcon formato del template UISP. InferenciadeviceRole: netonix→switch, edgerouter/erx/mikrotik/accedian→router, resto→wireless. PHP lint OK, artisan list lo registra. NO commiteado todavía — Sergio aprueba después de validar--dry-runen prod. - (2026-05-19) Política de altas implementada — cola de candidatos + UI + correo. Detalle en bitácora 2026-05-19. Decisiones finales: NO se inserta directo a
dispositivos, los nuevos entran a tablauisp_candidatos(estado pendiente|agregado|ignorado); Sergio procesa device por device desde/uisp-candidatos(Agregar pide sitio+plantilla; si IP colisiona con manual sin uisp_id ofrece claim). Aviso vía bandeja UI (badge en sidebar) + correo agrupado por tick asvalencia@e-electrosystems.com, controlado porservices.uisp.notify_start_atpara silenciar snapshot inicial. Commit2f7bb46en master, ya pusheado. - (2026-05-19) Política de bajas implementada — marcado + tab UI + correo (commit a4684aa). Decisión final: solo marcar
eliminado_de_uisp_at, NO silenciar, NO soft-delete. El dispositivo sigue 100% activo en monitoreo. Tab nuevo “Desaparecidos en UISP” en/uisp-candidatoscon acciones Quitar flag / Borrar uisp_id (pasa a manual) / Eliminar (soft-delete). Reaparición en UISP limpia flag automático. Correo agrupado condicionado por el mismonotify_start_atque altas. Badge del sidebar ahora suma pendientes+desaparecidos. Detalle en bitácora 2026-05-19. Nota: el modelo final difiere del doc viejo — no se usaignorar=trueporque esa columna no existe endispositivos. - #347 📅 sin-fecha — Flow de “claim” (edge case): cuando Sergio agrega a UISP un device por fuera de la herramienta (no via Fase 1A) y ya existía como
origen=manualenes-antenas-new:- Match heurístico por IP exacta o nombre similar (Levenshtein < N).
- UI muestra match candidato: “Posible match con device manual
X. ¿Es el mismo? (Sí adopta = pasa aorigen=claimed, preserva config local, setuisp_id/ No = crear nuevo registro UISP separado)”. - Sin auto-claim — siempre confirma Sergio. Evita falsos positivos que mezclen monitoreos de dos antenas distintas.
- Nota: con la Fase 1A funcionando, este flow se vuelve raro — Sergio normalmente migrará desde la propia UI de es-antenas-new, que ya conoce el match.
- (2026-05-13) Reconciliación inicial read-only contra UISP — EJECUTADA. Script
monitoring-homologation/reconcile.pyejecutado contra prod (279 dispositivos vivos en es-antenas-new vs 375 devices en UISP). Output:monitoring-homologation/reconciliation-2026-05-13.{md,csv}. Hallazgos clave:- 82 huérfanos brutos (29% de 279) en es-antenas-new sin contraparte en UISP.
- 54 huérfanos únicos por IP (después de deduplicar) — éste es el número real de devices físicos a migrar a UISP en Fase 1A.
- 117 IPs duplicadas en es-antenas-new (234 rows comparten IP con otro) — patrón: mismo equipo con dos plantillas SNMP distintas (sufijos
-AF11,-Rocket,-SNMP,-Telcel). Limpieza pre-migración es prerequisito útil no bloqueante (sin limpiar, el CSV de Imports cargaría duplicados). - Migración es por sitio, no por device. Sitios completos están huérfanos: Hércules 26/26, Torreón 18/18, Colonia del Valle 10/10, Nexpa 5/5, Tepehuanes 5/5, Barretal 2/2. Sitios ya cubiertos (Chihuahua, Parral): casi 100% en UISP. Implicación operacional: cuando Sergio cree el sitio en UISP, carga sus N devices en batch via CSV Imports — cierra bloques grandes de una.
- 70% del inventario ya en UISP (93 match perfecto + 102 match por IP + 2 por nombre). El sync de Fase 1 (steady state UISP → es-antenas-new) tiene mucha materia prima desde el día 0.
- 0 conflictos (no hay devices en es-antenas-new que matcheen a dos UISPs distintos).
- (2026-05-15) ✅ Limpiar duplicados internos en es-antenas-new — EJECUTADO. Stage 1 + 2 + 3 + manuales: 139 soft-deletes (113 dups + 10 Tier A + 3 manuales + 13 Tier B). 279 → 140 dispositivos vivos. Único duplicado intencional restante: Loma Linda Chihuahua/Parral (equipos físicos distintos, IP coincide entre rangos privados). Bloqueante de Fase 1A cerrado.
- (2026-05-21) [#028] Soft-delete de los 2 Loma Linda (id 26 Chihuahua, id 54 Parral, IP
192.168.46.10) — verificado en prod: ambos ya teníandeleted_at = 2026-05-15 17:12:10desde el barrido del stage 1 del cleanup-duplicados (a pesar de que el doc los listaba como “duplicado intencional restante” — fue captura cruzada por el match por IP). No requiere acción adicional. Cumple la decisión de Sergio “no monitorear en ninguna parte” porquemonitoreo-collectorlee dedispositivosy los soft-deleted quedan fuera del scope por default.
Fase 1A — Migración activa de huérfanos a UISP (cero-huérfanos)
Objetivo: drenar origen=manual AND administrado=true hasta cero. Este es el corazón del cambio decidido el 2026-05-13.
Investigación previa (RESUELTA 2026-05-13):
- (2026-05-13) UISP API write descartado. Sondeé
https://uisp.electrosystemsnet.com/nms/api/v2.1con el token de read-only que ya existe enoxidized:/var/lib/oxidized/credentials.yaml:POST /devices= 404 (ruta no existe).POST/PUT/PATCH /devices/{guid-real}= 404 (update tampoco)./api/internal,v1,v2.0= 404 (no hay otra versión).- Conclusión: la API v2.1 es read-only para devices. Crear con token, imposible, incluso con scope admin (la ruta no existe). Modo A muerto.
- (2026-05-13) Modo elegido: B (CSV Imports vía UI web). Es lo más automatizable que va a haber sin reverse-engineering frágil.
- 📅 sin-fecha — Posible Modo A’ (NO emprender sin discusión): capturar el endpoint interno que la UI usa al “Add Device” (browser DevTools al agregar manual una vez) y replicarlo con session cookie auth desde es-antenas-new. Técnicamente posible, operacionalmente frágil (Ubiquiti puede romperlo sin aviso en cualquier release). Solo considerar si Modo B resulta inviable por algún campo que el CSV no acepte.
Decisión final de modo:
| Modo | Estado | Implementación es-antenas-new | Esfuerzo Sergio |
|---|---|---|---|
| A — API write | ❌ descartado | n/a | n/a |
| B — CSV Imports | ✅ elegido | Botón “Generar CSV de este batch” → descarga → Sergio sube en UISP UI | 1 subida / batch |
| C — Checklist manual | Fallback | UI lista con campos copy-friendly (name/IP/MAC) | 1 alta UI manual / device |
| A’ — UI HTTP scrape | Reserva | Reverse-engineer endpoint privado de UI con session cookie | Solo si B no alcanza |
Una vez resuelto el modo:
- #031 📅 sin-fecha — Reporte de huérfanos en UI (“Devices pendientes de migrar a UISP”):
- Filtro:
origen=manual AND administrado=true. Excluiradministrado=false(no son pendientes). - Agrupar por enlace (no por relevancia/monitoreos — decisión Sergio 2026-05-13: el orden natural de trabajo es por enlace).
- Header con métrica: “X de Y devices migrados — N restantes”.
- Por device: nombre, IP, MAC, enlace, link a “Migrar” (modo A) o “Agregar a CSV batch” (modo B) o “Marcar como agregado manualmente” (modo C).
- Opción “Marcar como
administrado=false” para casos que no aplican (sacarlos del conteo).
- Filtro:
- #349 📅 sin-fecha — Flow de migración por device:
- Sergio aprueba → es-antenas-new hace lo que aplique al modo (POST, append CSV, o instructions).
- Cuando el device aparezca en el próximo sync de UISP, el match (UUID si modo A; heurístico IP/MAC para modo B/C) lo eleva a
origen=claimedautomáticamente, preservando la config local (tiers, plantillas, labels).
- #350 📅 sin-fecha — Métrica de cierre de proyecto:
count(origen=manual AND administrado=true) = 0. Cuando se cumple, Fase 1A queda terminada y Fase 3 (doc) puede arrancar con un estado limpio.
Fase 2 — backup-system superset (devices no-UISP) + cadencia del sync
Estado: mayoritariamente cubierto por la convención existente (NetBox tag backup-enabled). Gap encontrado en la cadencia.
- #351 📅 sin-fecha — Que los devices no-UISP (servers de telefonía, switches no-Ubiquiti, etc.) que Sergio quiere backupear estén entrando a NetBox con el tag
backup-enabled. - #352 📅 sin-fecha — Que el flow de NetBox a Oxidized esté funcionando (ya documentado en backup-system).
- (2026-05-13) Gap del timer CERRADO:
inventory-merger.timerdesplegado enoxidized, corre diario a 03:00 (+jitter). Detalle completo en backup-system bitácora 2026-05-13. La promesa “agregar en UISP fluye automático a los backups” ya es real con delay máximo ~48h (vs indefinido antes). Decisión: el .service NO hace reload/restart de oxidized; el daemon re-lee router.db en su poll cycle natural de 24h.
Fase 3 — Documentación + UX
- #353 📅 sin-fecha — Documentar el flujo final en
~/agy/electrosystems/backup-system/README.mdy/o en el wiki (docs.electrosystemsnet.com) para que la operación quede asentada. - #354 📅 sin-fecha — Considerar un dashboard/vista de “salud del inventario” — cuántos devices en cada plataforma, cuántos sincronizados, cuántos huérfanos.
Match-candidates resuelto (2026-05-15). Estado: 116 administrado=true (98 ya en UISP + 2 sugerencia alta + 16 a importar) + 21 administrado=false (partner Mario: Hércules+Torreón completas). Veracruz desaparecido (device + enlace soft-deleted). CSV de 12 sitios físicos listo (uisp-sites-fisicos-2026-05-15.csv). Próximo: Sergio carga sitios en UISP UI; yo diseño el devices CSV generator + sync UISP→es-antenas.
Notas técnicas
Plataformas en juego — qué tiene cada una hoy
| Plataforma | Rol | Inventario | Source of truth |
|---|---|---|---|
| UISP | NMS Ubiquiti (SaaS) | Devices UISP-managed (antenas, APs, switches Ubiquiti). Sergio agrega manualmente aquí. | UISP mismo |
es-antenas-new | Monitoreo SNMP (API + dashboard) | Tabla dispositivos con monitoreos asociados. Hoy mantenido manualmente. | (actualmente independiente) |
backup-system | Backups de configs de red (Oxidized) | router.db generado por inventory_merger.py desde UISP + NetBox. | UISP (UISP-managed) + NetBox (no-UISP) |
monitoreo-collector | Agent Go en sitios remotos | Lee la lista de devices a polear desde es-antenas-new. Hereda lo que tenga es-antenas-new automáticamente. | es-antenas-new (transitivo desde UISP cuando el sync esté listo) |
| NetBox | IPAM/DCIM | Devices no-UISP con tag backup-enabled. | NetBox |
docs-platform | Wiki | Solo docs textuales, no inventario operacional. | n/a |
Hipótesis de arquitectura objetivo (a confirmar)
UISP (alta única manual)
│
├──► inventory_merger.py (existing) ──► backup-system/oxidized
│
└──► [NUEVO] sync UISP → es-antenas-new
└─ via inventory_merger.py extendido o via API directo
NetBox (devices no-UISP, ej. servers VoIP)
│
└──► inventory_merger.py (existing) ──► backup-system/oxidized
es-antenas-new consume del merger, no de UISP directamente. Beneficio: una sola pieza autenticada contra UISP API, fácil de testear, fácil de operar.
Shape de un device third-party en UISP (referencia para CSV Imports)
Rescatado el 2026-05-13 leyendo un blackBox real (Mimosa “ST Gasachi-Capellina B11”) desde /nms/api/v2.1/devices:
{
"identification": {
"id": "022c4ca5-528b-4ca2-b419-b5dcdcafcb71", // UUID generado por UISP — éste es el uisp_id a guardar
"mac": "20:b5:c7:00:d3:b9",
"name": "ST Gasachi-Capellina B11",
"hostname": "ST Gasachi-Capellina B11",
"displayName": "ST Gasachi-Capellina B11",
"site": { "id": "34291b1d-...", "name": "[CHI] Capellina", "type": "site" },
"model": "UNKNOWN", // siempre UNKNOWN para blackBox
"modelName": "B11", // texto libre — el modelo "humano"
"vendor": null,
"vendorName": "Mimosa", // texto libre — la marca
"type": "blackBox", // discriminador third-party
"role": "wireless", // wireless | router | switch | etc.
"authorized": true,
"serialNumber": null,
"firmwareVersion": null
},
"ipAddress": "...",
"ipAddressList": [...]
}
Campos probables del CSV de Imports (a confirmar cuando Sergio abra el feature en UISP UI): name, mac, ipAddress, site_id (o nombre de site), vendorName, modelName, role. El id lo asigna UISP en la importación — es-antenas-new debe llenarse uisp_id en el sync siguiente, no en la creación.
Riesgos a considerar
- Sobrescribir configuración manual de
es-antenas-new: el dashboard de Sergio tiene config valiosa por device (tiers, plantillas, labels). El sync NO debe tocar eso — solo CRUD del registro base. Slug/ID drift: mitigado al decidir UUID de UISP como llave estable (no nombre, no IP, no MAC).- Falso claim: si el match heurístico por IP/nombre adopta el device equivocado, los monitoreos de dos antenas distintas se mezclarían. Mitigación: confirmación manual obligatoria, sin auto-claim.
- Devices “fantasma”: equipos que existen físicamente pero Sergio no agrega a UISP (por accidente u olvido) — el sync no los va a inventar. Mitigación parcial: reporte de huérfanos visible en UI.
Lock-in transitorio en: mitigado por la decisión 2026-05-13 — la Fase 1A drena activamente la lista hacia cero, con métrica visible. Lo que queda comoorigen=manualmanualpermanente llevaadministrado=falseexplícito y no se cuenta como pendiente.- UISP API throttling: si el portal SaaS rate-limita, planear paginación + backoff. Reusar lo que ya hace el merger evita reimplementar esto.
- (CONFIRMADO 2026-05-13) UISP API write NO disponible: sondeo directo confirmó que
POST/PUT/PATCH /devices/...retornan 404 en v2.1. Modo A descartado. Fase 1A va a depender de CSV Imports vía UI (modo B) con checklist manual (modo C) como fallback. Si CSV Imports falla por algún campo, considerar modo A’ (reverse-engineering del endpoint privado de la UI) pero advertir fragilidad. - (NUEVO 2026-05-13) Confusión por flag
administrado: si Sergio no marcaadministrado=falselos devices de proveedores externos, el conteo “huérfanos pendientes” nunca llega a cero y la métrica de éxito queda rota. Mitigación: durante la migración inicial, agregar paso explícito “marca cuáles devices NO administras” antes de empezar a drenar.
Relación con proyectos existentes
es-antenas-new: receptor del sync. Cambios: schema (origen,uisp_id), CRUD que respete atributos gestionados vs locales, UI de claim, reporte de huérfanos, bandeja de “devices nuevos sin monitoreos asignados”.monitoreo-collector: ✅ se homologa transitivo — ya consume su lista de devices desdees-antenas-new. Cero cambios aquí.backup-system: ya provee la pieza UISP-pull. Probable host de la lógica de export paraes-antenas-new. Requiere además un timer para queinventory_mergercorra periódicamente, no solo en restart.backups-infra: tangencial — comparte la disciplina de tener un inventario unificado para saber qué backupear.docs-platform: receptor final del runbook de homologación.
Bitácora
2026-05-11
- Pidió Sergio: anotar como pendiente importante “homologar las plataformas de monitoreo para que todas compartan información/dispositivos/backups”.
- Hice: capturado en
inbox/2026-05-11.mdmientras se acotaba alcance.
2026-05-12 — proyecto creado
- Pidió Sergio (en respuesta a 5 preguntas):
- Proyecto nuevo, no épico cross-project.
- Plataformas:
es-antenas-new,monitoreo-collector,backup-system,backups-infra,docs-platform+ UISP. - Quiere acoplar UISP con
es-antenas-newybackup-system(este último seguirá teniendo equipos extra como servers de telefonía). - Dolor: doble alta + inconsistencia (agregar en UISP y olvidar en
es-antenas-new). - Objetivo: agregar una sola vez y que se propague automático.
- Hice: creé este archivo de proyecto con prioridad
high. Documenté el insight de quebackup-systemya sincroniza con UISP víainventory_merger.py, por lo que el gap principal es UISP →es-antenas-new. Propuse 3 fases (validación, sync core, doc) y una hipótesis de arquitectura que reusa el merger existente. - Falta: que Sergio valide Fase 0 (en particular: ¿UISP como fuente de verdad? ¿
es-antenas-newsolo subset de UISP o también tiene devices propios?). Sin esa decisión no avanza Fase 1.
2026-05-12 — decisión clave de Fase 0
- Pidió Sergio: “Como meta, me gustaría que UISP fuera la fuente de verdad, pero inicialmente es posible que haya devices propios en
es-antenas-new”. - Hice: cerré 2 de las 4 preguntas de Fase 0. Modelo definido: sync unidireccional UISP →
es-antenas-new, con 3 estados por device (origen=uispgestionado por sync,origen=manualhuérfano legacy intocable,origen=claimedadoptado tras match manual). Documenté en Fase 1:- Schema: columnas nuevas
dispositivos.origen(enum) +dispositivos.uisp_id(UUID nullable único). - Llave estable = UUID de UISP, no nombre / IP / MAC.
- Flow de claim transicional con match heurístico por IP/nombre, confirmación manual obligatoria (riesgo grande de mezclar monitoreos si auto-claim).
- Reporte de huérfanos en UI para que Sergio drene la lista hacia cero — necesario para que la meta “UISP como source of truth” se cumpla con el tiempo, no quede en intención.
- Política de bajas: soft-delete (
ignorar=true+ timestamp), nunca hard-delete automático.
- Schema: columnas nuevas
- Falta:
- Confirmar las otras 2 preguntas de Fase 0 (¿
backup-systemya cubre UISP→backups en práctica? ¿hay otras plataformas externas?). Puedo verificar el primero yo mismo con un SSH read-only aoxidized(revisarrouter.db+ commits recientes ennas:configs.git) — pregunta lista para Sergio si quiere que lo haga. - Definir si las “plantillas de monitoreo automáticas por role/model” entran en este proyecto o si Sergio prefiere que cada alta UISP→es-antenas-new sea revisada manualmente para asignarle monitoreos.
- Confirmar las otras 2 preguntas de Fase 0 (¿
2026-05-12 — verificación de backup-system + cierre de Fase 0
- Pidió Sergio:
- Verificar yo mismo que
backup-systemya sincroniza UISP → backups en práctica (SSH read-only autorizado). - Plantillas automáticas: NO — Sergio asigna monitoreos manualmente después de cada alta.
monitoreo-collectorlee su lista de devices desdees-antenas-new— se homologa transitivo.
- Verificar yo mismo que
- Hice:
- SSH read-only a
oxidized(192.168.20.27). Hallazgos:inventory_merger.pyestá en/opt/oxidized-tools/scripts/y se invoca comoExecStartPre=deoxidized.service.router.dbtiene 255 devices Ubiquiti, todos formato UISP-managed.- Oxidized config:
interval: 86400(24h). - Gap real: último start de
oxidized.servicefue 2026-05-05 (hace 7 días). El merger NO ha corrido desde entonces. Si Sergio agregó devices en UISP la última semana, no están enrouter.db. Capturé como tarea en backup-system: crearinventory-merger.timersystemd para cadencia regular (sugerido: diario 03:00, consystemctl reload oxidizedsi cambiórouter.db).
- Actualicé Fase 1: política de altas sin plantillas automáticas, con bandeja “nuevos sin monitoreos asignados” para que Sergio configure. Decidir mecanismo de aviso al implementar (UI bandeja vs correo).
- Actualicé tabla de plataformas:
monitoreo-collectorsource of truth =es-antenas-new(transitivo de UISP cuando sync esté listo). Aclarado en “Relación con proyectos existentes”: cero cambios necesarios en monitoreo-collector.
- SSH read-only a
- Falta:
- Fase 0 cerrada salvo por confirmar explícitamente con Sergio si hay alguna plataforma externa al workspace (Zabbix, LibreNMS, dashboard de cliente) que también deba homologarse. Si no, listo para arrancar Fase 1.
- Sergio decide cuándo arrancar Fase 1. Probable orden: (a) timer del merger en backup-system → (b) schema changes en es-antenas-new → (c) export del merger consumible por es-antenas-new → (d) reconciliación inicial + flow de claim → (e) UI de huérfanos y bandeja de nuevos sin monitoreos.
2026-05-12 — Fase 0 cerrada, proyecto en paused
- Pidió Sergio: “Ninguna plataforma externa. Pero de momento vamos a dejarlo anotado como pendiente, ya lo retomaré en otra ocasión.”
- Hice: cerré la última pregunta de Fase 0 (sin plataformas externas; solo las 4 internas: UISP +
es-antenas-new+backup-system+monitoreo-collector). Cambiéstatus: active → paused. Conservépriority: high— la prioridad es real, solo no se ejecuta hoy. La planeación queda lista para retomarse sin re-discutir alcance. - Falta: nada hasta que Sergio retome. Cuando lo haga, primer paso recomendado: crear el
inventory-merger.timerenoxidized(tarea ya capturada en backup-system) — es el desbloqueo más barato y entrega valor inmediato (sync UISP→backups deja de ser stale) independiente de Fase 1.
2026-05-13 — cambio de modelo: meta = cero huérfanos, no convivencia permanente
- Pidió Sergio: “Hay equipos que existen en es-antenas-new y no en UISP. Inicialmente quiero que se agreguen automáticamente (con autorización manual) a UISP, y una vez que todos existan en UISP, UISP empieza a ser fuente de verdad. Al final es-antenas-new no va a tener ningún huérfano.” Decisiones complementarias:
- Modo preferido: API. Fallback: checklist para agregar manualmente.
- Confirmado: hay devices que NO administramos (de proveedores externos / clientes) — necesita flag.
- Orden de la migración: batch arbitrario pero agrupado por enlace.
- Métrica de éxito confirmada:
count(origen=manual AND administrado=true) = 0. - Inventario es-antenas-new NO es 100% Ubiquiti — pero UISP sí soporta third-party (Sergio ya tiene Mimosa/Netonix ahí para ping-monitoring). Por tanto la meta de cero huérfanos es alcanzable.
- Hice:
- Investigación pública de UISP API: confirmado que UISP soporta third-party devices y tiene feature de Imports vía CSV (acepta MAC + IP). No pude confirmar públicamente si existe
POST /deviceswrite — la doc definitiva está per-instance enhttps://<uisp-host>/nms/api-docs/. Diseñé el plan para degradar limpio entre 3 modos (A=API write, B=CSV Imports bulk, C=checklist manual). - Agregué a Fase 0 dos decisiones: (a) meta refinada a cero huérfanos, (b) existencia de un flujo inverso transicional es-antenas-new → UISP solo durante migración, no en steady state.
- Refiné el modelo en Fase 1: estados
manualyclaimedahora son explícitamente transicionales; estado terminal es solouisp. Agregué flagdispositivos.administrado(boolean, default true) — los devices conadministrado=falseviven comomanualpermanente sin entrar a la cuenta regresiva. - Schema: 3 columnas nuevas en
dispositivos(era 2):origenenum,uisp_idUUID,administradoboolean. - Reformulé el “flow de claim”: pasa de ser caso normal a edge case (Fase 1A cubre el flujo normal).
- Agregué Fase 1A — Migración activa: investigación previa de API write, tabla de 3 modos (A/B/C), reporte de huérfanos agrupado por enlace (no por relevancia como antes) con métrica visible “X de Y migrados”, flow de migración por device, métrica de cierre del proyecto.
- Riesgos: marqué “Lock-in transitorio en
origen=manual” como mitigado por la nueva Fase 1A. Agregué 2 riesgos nuevos: (a) API write quizá no disponible → degradar a B/C, (b) si Sergio no marcaadministrado=falsecorrectamente, la métrica nunca cierra. - Actualicé
updated: 2026-05-12 → 2026-05-13.
- Investigación pública de UISP API: confirmado que UISP soporta third-party devices y tiene feature de Imports vía CSV (acepta MAC + IP). No pude confirmar públicamente si existe
- Falta:
- Acción pendiente de Sergio (no urgente, proyecto sigue paused): abrir
https://<su-uisp-host>/nms/api-docs/y revisar si existe POST para crear devices third-party. Esa respuesta define modo A vs B. Alternativa: autorizar a Antigravity para hacer curl read-only contra/api-docs/con un token de prueba. - Steady-state queda intacto en su diseño. Cambia principalmente la fase de transición (de “lenta, sin urgencia” a “activa, con métrica de cierre”).
- Cuando Sergio retome, el orden recomendado se actualiza: (1) crear
inventory-merger.timerenoxidized→ (2) investigar/api-docs/de UISP → (3) schema changes en es-antenas-new → (4) export del merger consumible → (5) reconciliación inicial + flagadministradopor device → (6) Fase 1A activa (modo según hallazgo de API) → (7) cierre y doc.
- Acción pendiente de Sergio (no urgente, proyecto sigue paused): abrir
2026-05-13 — sondeo directo de UISP API: modo A descartado, modo B confirmado como path
- Pidió Sergio: “Vamos a ver lo del API de una vez. Puedes verificar la documentación del proyecto de backup-system, ahí ya había token de UISP.”
- Hice:
- Recuperé el token UISP desde
oxidized:/var/lib/oxidized/credentials.yaml(SOPS-encrypted, decrypt en memoria, sin escribir a disco ni a workspace). URL confirmada:https://uisp.electrosystemsnet.com. Header:x-auth-token. - Confirmé que el token funciona:
GET /nms/api/v2.1/devices→ 200. - Sondeé
/nms/api-docs/: requiere session login (cookie de UI admin), no acepta token API → no se puede recuperar el swagger spec con token. - Sondeé directamente POST/PUT/PATCH a múltiples variantes de creación:
/devices,/devices/onboard,/devices/import,/devices/third-party,/devices/black-box,/sites/{id}/devices,/imports,/third-party-devices. Todos 404 (rutas no existen). El 403 inicial en/devices/importresultó ser falso positivo del router tratando “import” como param:id. - Confirmé que
POST/PUT/PATCH /devices/{guid-real}también dan 404 — UISP v2.1 simplemente no expone escritura para devices, ni create ni update. - Verdicto: Modo A (API write) descartado definitivamente. No es un problema de scope del token — la ruta no existe.
- Rescaté el shape JSON de un blackBox third-party real (Mimosa Capellina B11) leyendo de
/devices. Lo documenté en notas técnicas como referencia para construir el CSV de Imports. - Actualicé Fase 1A: modo A marcado como descartado en la tabla; modo B (CSV Imports vía UI web) es ahora el path elegido, con modo C como fallback y un modo A’ (reverse-engineering del endpoint privado de la UI con session cookie) como reserva no recomendada por fragilidad.
- Actualicé sección de riesgos: el riesgo “API write no disponible” pasa de hipotético a confirmado.
- Recuperé el token UISP desde
- Falta:
- Confirmar formato exacto del CSV de Imports — esto requiere que Sergio abra UISP UI → Settings → Imports → Devices y vea el template/sample CSV. Una vez tenido, se diseña la generación del CSV en es-antenas-new para que matchee.
- Considerar si vale el esfuerzo investigar modo A’ (HTTP scrape de la UI). Recomendación: no por ahora — modo B con upload manual del CSV es 1 acción por batch, no por device, lo cual es manejable y robusto.
- Cuando Sergio retome, el orden recomendado se actualiza otra vez: (1) crear
inventory-merger.timer→ (2) conseguir template del CSV de Imports (Sergio en UI UISP) → (3) schema changes en es-antenas-new → (4) export del merger → (5) reconciliación inicial + flagadministrado→ (6) Fase 1A modo B → (7) cierre y doc.
2026-05-13 — inventory-merger.timer desplegado (paso 1 del orden recomendado)
- Pidió Sergio: “vamos a seguirle, a ver cuanto alcanzo a hacer hoy” → luz verde para arrancar con el timer (opción A del menú).
- Hice: desplegué
inventory-merger.service+.timerenoxidized, validé end-to-end. Detalle técnico completo en backup-system bitácora 2026-05-13. Impacto en este proyecto: desbloquea la primera condición operacional de la meta. El sync UISP→backups deja de depender de restarts manuales; ahora corre 1x/día. Cuando arranque Fase 1A (CSV Imports → es-antenas-new), ya no hay un gap conocido entre los dos sistemas. - Falta: seguir con el resto del orden recomendado. Próximo según el plan: o (B) script de reconciliación read-only para tener tamaño real del problema de huérfanos, o (C) schema design de es-antenas-new.
2026-05-15 — Estrategia C aplicada: 21 partner flippeados + Veracruz limpiado + CSV de sitios físicos listo
- Pidió Sergio (decisiones por bloque tras revisar reporte):
- Hércules + Torreón completas son del partner Mario →
administrado=falseen bloque (21 devices). - Devices con enlace “Telcel” en otras regiones (Barretal, Nexpa, Tepehuanes, Jacala) son SUYOS — no del partner. Heurística inicial incorrecta, corregida.
- Veracruz: el único device (Netonix Necoxtla) ya no se monitorea → soft-delete. Sitio Veracruz ya no debe existir.
- Resto de sugerencias OK.
- Hércules + Torreón completas son del partner Mario →
- Hice:
- Flip a administrado=false (vía SQL atomic transaction): 21 devices de Hércules (13) y Torreón (8) marcados como del partner. Verificado:
administrado_true=117,administrado_false=21. - Limpieza de Veracruz: detectado rezago — sitio Veracruz YA estaba soft-deleted desde 2026-04-13, pero el device
id 134 Netonix Necoxtlay el enlaceid 31 Nogalesseguían vivos huérfanos. Soft-delete del device + del enlace para consistencia. Estado final: 116 dispositivos vivos administrado=true. - Re-corrida del match-candidates con estado nuevo: 116 a manejar = 98 ya en UISP + 2 sugerencia alta + 16 sin candidato.
- CSV de sitios físicos generado correctamente:
uisp-sites-fisicos-2026-05-15.csv. 12 sitios nuevos con prefijo[COD]correcto yipAddressespre-cargados para auto-discover en UISP. Coordenadas placeholder cerca de Cd. Juárez (~31.7°N, -106.4°W, con offsets de 0.001° por sitio) porque UISP requiere lat/long obligatorios; cadanotelleva prefijo[PLACEHOLDER GPS]para filtrar y actualizar después con coords reales. La elección de clusterizar en Cd. Juárez (en vez de adivinar coords reales por región Tamaulipas/Hidalgo/Durango) hace que el mapa se vea obviamente incorrecto → la urgencia de actualizar es visible.[BAR](2): Barretal, Union Morales.[COL](3, además del existente “El Gato”): Colonia del Valle, Galeana, Nut.[JCL](1): Jacala.[NEX](3): Cerro, Autopista, Nexpa.[TEP](3): Santiago, Cerro, Tepehuanes (RB).
- Flip a administrado=false (vía SQL atomic transaction): 21 devices de Hércules (13) y Torreón (8) marcados como del partner. Verificado:
- Estado final del proyecto al cierre:
- 137 dispositivos vivos en es-antenas-new = 116 (
administrado=true, a operar vía UISP propia) + 21 (administrado=false, partner Mario). - 98 de 116 ya tienen contraparte en UISP → cero acción manual cuando el sync se construya.
- 2 con sugerencia alta (Netonix El Gato →
[COL] El Gatoscore 100; MT Cliente Villa Ahumada →[VIL] Villa Ahumadascore 60) → confirmar al importar. - 16 huérfanos restantes → cubrir creando los 12 sitios físicos del CSV.
- 137 dispositivos vivos en es-antenas-new = 116 (
- Falta:
- Sergio: cargar
uisp-sites-fisicos-2026-05-15.csven UISP (Settings → Imports → Sites). Editar nombres antes de cargar si no le gustan (especialmente Nexpa/Tepehuanes donde la asignación device→sitio físico es inferencia). - Sergio: confirmar los 2 con sugerencia alta al importar.
- Yo (próxima sesión): diseñar el devices CSV generator que lee
origen=manual AND administrado=true AND no_match_uisp(los 18 que no están en UISP) y emite CSV con creds (Ubiquitiubnt/ubntvs SNMP community). Asignación de sitio físico se hace post-import en UISP UI (CSV no tiene columnasite), pero el reporte de match-candidates ya da el mapping para que sea trivial. - Yo: implementar el sync UISP→es-antenas-new propiamente (script que poll
/devices, matchea por IP/name, hace UPDATE conuisp_id+origen='uisp'oorigen='claimed'). Cierra el ciclo: los 98 ya-en-UISP se auto-pegan, después los 18 importados se auto-pegan.
- Sergio: cargar
2026-05-15 — Aclaración región vs sitio físico + reporte match-candidates (Estrategia C)
- Pidió Sergio (aclaración importante): los “sitios” de es-antenas-new son regiones (Chihuahua, Caborca, Parral, etc.), no sitios físicos. En UISP los sitios físicos llevan prefijo
[COD](ej.[CHI] Capellina). El CSV de sitios que generé antes (Hércules como sitio) no aplica: ya marcado.DESCARTADO.csven el folder. Sergio pidió sugerencias para automatizar/facilitar la migración. - Estrategia elegida (C): match-candidates con validación previa por Sergio. Implementado.
- Hice:
- Audité los sitios UISP vía token api (mismo flujo del 2026-05-13, autorizado explícitamente por Sergio). Dump completo:
monitoring-homologation/uisp-sites-2026-05-15.json(108 sitios). - Mapeo región → prefijo confirmado:
- Chihuahua →
[CHI](45), Parral →[PAR](18), Caborca →[CAB](10), Namiquipa →[NAM](3), Villa Ahumada →[VIL](1), Colonia del Valle →[COL](solo 1: “El Gato”). - Regiones sin equivalente UISP: Hércules, Torreón, Nexpa, Tepehuanes, Barretal, Jacala, Nazareno, Veracruz.
- Otros prefijos UISP no en es-antenas-new:
[ELP](17, El Paso),[JRZ](9, Juárez),[NOG](3, Nogales). - 1 sitio rezago detectado:
*+*+*+[CHI] Cerro Coronel(typo de prefijo).
- Chihuahua →
- Re-pull UISP devices fresco: 375 devices, mismo número que la reconciliación del 2026-05-13.
- Pull dispositivos vivos post-cleanup: 138 con frontmatter (id, nombre, ip, region, enlaces).
- Script de match
match-candidates.pyescrito. Lógica:- Match en UISP por ip+name / ip-only / name-only → “Ya en UISP, sync auto-asigna uisp_id”.
- Heurística por tokens del nombre (quita prefijos de equipo y sufijos de modelo) contra sitios
[REGION_PREFIX]correspondientes. - Score por exactitud, dedup, ordenado.
- Reporte generado:
match-candidates-2026-05-15.md(revisable por sección de confianza) +match-candidates-2026-05-15.csv. - Resultados:
- 98 ya en UISP (36 ip+name, 62 ip o name only): cero acción manual, esperan al sync.
- 2 con sugerencia alta: verificación de un vistazo.
- 0 ambiguos: el split por confianza filtró bien.
- 38 sin candidato: agrupados por región. Todos los del partner UISP (enlaces con “Telcel” en el nombre: Barretal-Union Morales, Nexpa-Autopista, Jacala-Zimapan, Santiago-Tepehuanes) son sospechosos fuertes de
administrado=falseen bloque (~17 devices). Los demás son Hércules (13), Torreón (8), Colonia del Valle (5) — regiones reales que necesitan creación de sitios físicos en UISP Oadministrado=false.
- Audité los sitios UISP vía token api (mismo flujo del 2026-05-13, autorizado explícitamente por Sergio). Dump completo:
- Falta:
- Sergio: revisar
match-candidates-2026-05-15.md, confirmar:- Que los devices con enlace “Telcel” son del partner → marcar
administrado=falseen bloque. - Para Hércules/Torreón/Colonia del Valle/etc.: ¿crear sitios físicos en UISP (nombrar Capellina-style)? ¿O también
administrado=false? - Para los 2 con sugerencia alta: aprobar/corregir.
- Que los devices con enlace “Telcel” son del partner → marcar
- Después: actualizar la DB con los
administrado=falseconfirmados + re-corrermatch-candidates.py(queda 0 huérfanos pendientes de migrar a UISP, o muy pocos). - Diseñar el devices CSV generator final: solo emite para
origen=manual AND administrado=true, sin sitio (asignación post-import en UI UISP) o con sitio físico cuando el reporte lo permita.
- Sergio: revisar
2026-05-15 — Schema migration deployada + sites CSV + caso “partner’s UISP”
- Pidió Sergio:
- Push + migrate del schema.
- Caso nuevo: hay dispositivos en es-antenas-new que ya están en otra instancia de UISP (de un socio del jefe). Sergio NO los puede agregar a su UISP, pero los quiere seguir monitoreando localmente.
- Generar el CSV de sites para importar a UISP (template descargado en
C:\Users\sevao\Downloads\import-sites-example.csv).
- Hice:
- Push + migrate completados — con tropezón documentado:
- Primer
php artisan migratefalló a medias: creó la columnaorigenpero abortó antes deuisp_idyadministradopor permisos del log. Causa raíz: el daily log de Laravel (laravel-2026-05-15.log) lo crea www-data como0644y el userelectrosystems(que corre artisan) no puede escribir aunque esté en grupo www-data. - Fix permanente committeado (
0ddec4f):config/logging.phpconpermission => 0664en el daily channel. Monolog crea futuros archivos comorw-rw-r--. Cero sudo recurrente, cero cambios en logrotate. - Para hoy: Sergio aplicó
sudo chmod g+wal log del día (los siguientes nacen bien por el fix de config). - DROP COLUMN
origenparcial vía mysql (autorización explícita de Sergio), re-run migrate → 3 columnas + fila enmigrationsregistrada. Verificación: 138 dispositivos conorigen=manual,administrado=1,uisp_id=NULL(defaults).
- Primer
- Caso “partner’s UISP” — encaja perfecto con
administrado=false:- Estos devices viven permanentemente como
origen=manual AND administrado=false. NO entran al conteo de huérfanos pendientes. NO se intenta migrarlos a UISP propia. El monitoreo local en es-antenas-new sigue normal. - Diferencia con los otros casos
administrado=false(proveedor externo / equipo cliente): el funcional es idéntico, solo la causa cambia. Mismo flag, mismo flujo. - Pendiente operacional: Sergio identifica cuáles devices son partner-UISP y los marca
administrado=false. Probable batch update por sitio o por nombre/patrón cuando los conozca.
- Estos devices viven permanentemente como
- CSV de sites generado (
monitoring-homologation/uisp-sites-import-2026-05-15.csv) para los 6 sitios huérfanos al 100% según reconciliación 2026-05-13:- Hércules (13 devices, era 26 antes del dedup)
- Torreón (8, era 18)
- Colonia del Valle (5, era 10)
- Nexpa (5)
- Tepehuanes (4, era 5)
- Barretal (2)
- Total 37 devices a migrar a UISP en estos sitios. Restante (54 huérfanos únicos − 37 = ~17) son devices sueltos en sitios mixtos (Chihuahua, Caborca, Parral, Namiquipa, etc.) que ya existen en UISP.
- Solo
namepopulado +notecon conteo recordatorio. UISP no tiene GPS/dirección desdesitiostable (schema solo tiene operacionales). Sergio enriquece en UISP UI post-import si quiere.
- Push + migrate completados — con tropezón documentado:
- Falta:
- Sergio: subir el sites CSV a UISP UI (Settings → Imports → Sites).
- Sergio: identificar y marcar
administrado=falselos devices en partner-UISP (no urgente — pueden flippearse cuando se diseñe la UI o vía SQL ad-hoc). - Siguiente paso de Fase 1A: diseñar el generador del devices CSV en es-antenas-new — leer huérfanos con
origen=manual AND administrado=true, agrupar por sitio, emitir un CSV por sitio con el formato deuisp-imports-template.csv. Cruzar plantillas/credenciales (Ubiquitiubnt/ubntvs SNMP community para third-party).
2026-05-15 — Loma Linda cerrado, schema migration lista, CSV template capturado
- Pidió Sergio:
- Soft-delete los 2 Loma Linda (sitio inexistente, asignación errónea que generó duplicado).
- Arrancar con el punto 1 del orden recomendado: schema migration (
origen+uisp_id+administrado). - CSV template de UISP Imports descargado en
C:\Users\sevao\Downloads\import-devices-example.csv.
- Hice:
- Soft-delete Loma Linda: ids 26 y 54 →
deleted_at = NOW(). Estado final: 138 dispositivos vivos, 0 duplicados, 0 sin enlace. Inventario completamente limpio. - Schema migration escrita:
~/code/es-antenas-new/database/migrations/2026_05_15_180000_agregar_origen_uisp_id_administrado_a_dispositivos.php— agrega 3 columnas con defaults seguros (origen=manual, uisp_id=NULL, administrado=true).down()incluido. PHP lint OK.app/Models/Dispositivo.php— cast'administrado' => 'boolean'agregado en$casts. Modelo ya tenía$guarded = []así que las columnas nuevas son mass-assignable sin tocar nada más.- NO deployado todavía. Working tree pendiente; Sergio decide cuándo correr
php artisan migrateenmonitoreo.
- CSV template capturado desde
/mnt/c/Users/sevao/Downloads/(WSL mount). Copia versionada enmonitoring-homologation/uisp-imports-template.csv. Hallazgos:- Columnas:
ip,username,password,httpsPort,sshPort,hostname,deviceRole,model,snmpCommunity. - Dos modos de row: managed (
ip + username + passwordtípicamenteubnt/ubntpara Ubiquiti) o blackBox (ip + hostname + deviceRole + snmpCommunitypara third-party como Mimosa/Netonix). - NO hay columna
sitenimac. El sitio se asigna antes del upload o post-import en UI. La MAC la descubre UISP por SNMP. - Implicación para Fase 1A: sitios huérfanos al 100% (Hércules 26, Torreón 18, Colonia del Valle 10, Nexpa 5, Tepehuanes 5, Barretal 2) deben crearse en UISP antes de cargar su CSV. Luego un CSV por sitio.
- Columnas:
- Soft-delete Loma Linda: ids 26 y 54 →
- Falta (orden recomendado actualizado):
- Sergio: aprobar
php artisan migrateen prod (la migration es no-destructiva, defaults seguros, reversible condown()). Sin esto el sync no tiene dónde guardaruisp_id/origen. - Sergio: crear los 6 sitios huérfanos al 100% en UISP UI antes de cargar nada.
- Diseñar el generador del CSV en
es-antenas-new: leer huérfanos conorigen=manual AND administrado=true, agrupar por sitio, emitir CSV por sitio. Necesita cruzar plantillas/monitoreos para extraer credenciales (Ubiquiti vs SNMP community). - Sergio carga CSV sitio-por-sitio en UISP UI. Tras cada carga, el sync recoge los nuevos uisp_id y los rows de es-antenas-new pasan a
origen=claimed. - Cuando
count(origen=manual AND administrado=true) = 0, Fase 1A termina.
- Sergio: aprobar
2026-05-15 — cleanup completo (Stage 3 + manuales aplicados)
- Pidió Sergio: “Aplica los 4 manuales y los 13 Tier B”.
- Hice: ejecuté el UPDATE en prod con autorización. Aplicación según las recomendaciones documentadas:
- 3 manuales soft-deleted: id 138 (AP RT2-Dia Bras → renombrado a AP Cerro Pinturas-Dia Bras C5c), id 24 (Netonix RT2 → Netonix Cerro Pinturas), id 52 (Netonix Hermosillo → reemplazo físico por WiTek Hermosillo).
- 1 manual sin tocar: Loma Linda Chihuahua (id=26) y Loma Linda Parral (id=54) — equipos físicos distintos con IP
192.168.46.10colisionando entre rangos privados. Único duplicado intencional restante. - 13 Tier B soft-deleted en bloque: ids 7, 8, 9, 12, 34, 35, 37, 38, 39, 149, 140, 153, 137. Rezago confirmado de sitios viejos (Bocoyna, Creel, San Juanito, Casitas, Concheño, Ocampo, Cerro Cordón Dia Bras, CU NAZAS, Quirey 2, Guachochi).
- Trampa operacional encontrada: el primer intento con UPDATE + SELECTs intermedios + COMMIT en el mismo
.sqlfalló — uno de los SELECTs teníaipambiguo (estaba endy ensvía join consitios), y el cliente mysql aborta el stream al hit del error, lo cual canceló el COMMIT y la transaction rolled back. Re-ejecuté soloSTART TRANSACTION; UPDATE...; COMMIT;sin SELECTs intermedios — committeó limpio. - Estado final post-cleanup completo:
- 140 dispositivos vivos (era 279 al inicio del día — bajaron 139).
- 163 soft-deleted total (24 históricos + 139 del cleanup).
- Solo 1 duplicado restante (Loma Linda dos-sitios, intencional).
- Solo 2 sin enlace (los dos Loma Linda — vigentes pero pendientes de asignación de enlace; queda como pendiente operacional).
- Falta: asignarle enlace a los 2 Loma Linda (tarea menor, depende de Sergio decidir a qué enlace pertenecen). Después: avanzar al siguiente paso del orden recomendado — schema migration (
origen+uisp_id+administrado) endispositivos, y conseguir el template del CSV de UISP Imports. - Cierre del bloqueante: Fase 1A queda lista para arrancar. CSV de Imports no va a cargar duplicados.
2026-05-15 — cleanup committeado en prod (Stage 1 + Stage 2)
- Pidió Sergio: “Ya corrí el SQL en producción”. Primera corrida hizo rollback automático porque el
COMMIT;quedó comentado en mi plan original. Descomenté y Sergio re-corrió. - Hice: verificación read-only post-commit. Estado limpio:
- 156 dispositivos vivos (era 279, −123 esperado: 113 dup + 10 Tier A).
- 147 soft-deleted en total (24 históricos + 123 del cleanup).
- 113 low-ids del Stage 1 soft-deleted ✅
- 10 Tier A soft-deleted ✅
- 4 duplicados restantes = exactamente los 4 manuales esperados (RT2x2, Hermosillo Netonix/WiTek, Loma Linda dos-sitios).
- 18 dispositivos vivos sin enlace = 13 Tier B + 5 rows de los manuales (que también están sin enlace; se resolverán al procesar manuales).
- Lección operacional: plantillas de SQL con
START TRANSACTIONdeben tener COMMIT explícito al final, no comentado, cuando el archivo se ejecuta víamysql < file.sql. Con COMMIT comentado, MySQL hace rollback al cerrar la sesión. (Plantilla del cleanup quedó corregida.) - Falta:
- Decidir los 4 pares manuales (sugerencias documentadas en
cleanup-duplicados-2026-05-15.md):- RT2→Cerro Pinturas x2: confirmar rename → soft-delete 138 y 24.
- Netonix→WiTek Hermosillo: confirmar reemplazo físico → soft-delete 52.
- Loma Linda Chihuahua/Parral: confirmar que son equipos distintos con IP que colisiona → dejar ambos (no es duplicado real).
- Decidir los 13 Tier B (huérfanos sin enlace y sin UISP): en bloque o caso a caso. Si todos son rezago de Bocoyna/Creel/San Juanito/Concheño/Ocampo/etc., un solo UPDATE los cierra.
- Decidir los 4 pares manuales (sugerencias documentadas en
2026-05-15 — validación read-only y ampliación del plan (Tier A + Tier B sin enlace)
- Pidió Sergio: “Antes de ejecutar, valida que los low-ids no estén asignados a ningún enlace. Y muéstrame cuántos dispositivos no están duplicados pero tampoco están en ningún enlace — regla: todos los dispositivos vigentes deben estar asignados a un enlace.”
- Hice (SSH read-only a
monitoreo, credenciales.envsolo en subshell, sin escribirlas al workspace):- Confirmé schema:
dispositivos.deleted_atexiste (Laravel SoftDeletes). Pivotdispositivo_enlace.dispositivo_ides singular (corrección de la query de verificación que tenía condispositivos_iden la primera versión del plan — corregido en el SQL). - Q1 (validación low-ids):
COUNT(DISTINCT d.id) FROM dispositivo_enlace WHERE dispositivo_id IN <113 low_ids> AND d.deleted_at IS NULL→ 0. Ninguno de los 113 low-ids del Stage 1 tiene enlace. Soft-delete totalmente seguro, no hay que tocar el pivot. - Q3 (devices sin enlace y no duplicados): 23 dispositivos vivos sin entrada en
dispositivo_enlacey que no comparten IP con otro vivo. Lista completa encleanup-duplicados-2026-05-15.mdStage 2. - Crucé los 23 contra el snapshot de UISP del 2026-05-13:
- Tier A (10 devices): ya están en UISP (3 ip+name, 5 ip-only, 2 name-only). Soft-delete seguro porque la Fase 1 del sync los re-creará como
origen=uispautomáticamente. Agregados al Stage 2 del SQL para ejecutar junto con los duplicados. - Tier B (13 devices): no están en UISP ni en enlace. Hipótesis: rezago de operaciones viejas — sitios como Bocoyna, Creel, San Juanito, Concheño, Ocampo, Cordón Dia Bras, CU NAZAS, Quirey 2, Guachochi, Casitas. Stage 3 del SQL queda comentado: Sergio confirma si todos son retirados (soft-delete masivo) o si alguno sigue vivo (asignar enlace primero, no borrar).
- Tier A (10 devices): ya están en UISP (3 ip+name, 5 ip-only, 2 name-only). Soft-delete seguro porque la Fase 1 del sync los re-creará como
- Actualicé
cleanup-duplicados-2026-05-15.mdcon la sección “Validaciones contra prod” y Stage 2 (Tier A + Tier B tablas). - Actualicé
cleanup-duplicados-2026-05-15.sql: Stage 2 ejecutable para Tier A; Stage 3 comentado para Tier B; corrección dedispositivo_idsingular; queries de verificación post-cleanup ampliadas (devices vivos totales + sin-enlace residual).
- Confirmé schema:
- Resumen para Sergio (cambio total propuesto):
- Stage 1: 113 soft-deletes (duplicados) — seguro.
- Stage 2: 10 soft-deletes adicionales (Tier A, ya en UISP) — seguro.
- Total auto: 123 soft-deletes. El conteo de devices vivos baja de 279 a 156.
- Stage 3 condicional: si Sergio aprueba Tier B, +13 más → 143. Si decide caso a caso, +N.
- 4 manuales aparte (RT2x2, Hermosillo, Loma Linda).
- Falta del lado de Sergio:
- Revisar
cleanup-duplicados-2026-05-15.mdStage 2 (10 Tier A + 13 Tier B). - Decidir qué hacer con Tier B (en bloque o caso a caso).
- Decidir las 4 manuales.
- Ejecutar el SQL.
- Revisar
2026-05-15 — plan de limpieza de los 117 duplicados internos
- Pidió Sergio: “Quiero avanzar un poco con monitoring-homologation.”
- Hice: atacar el bloqueante de Fase 1A (la limpieza de duplicados) sin tocar DB todavía — análisis local del listado de 117 pares en
reconciliation-2026-05-13.mdpara producir un plan ejecutable que solo necesita aprobación de Sergio.- Categorización automática de los 117 pares:
- 46 con sufijo de modelo en high_id (AF11/Rocket/etc) — Rule A, auto-soft-delete del low_id.
- 52 nombres idénticos en mismo sitio — Rule B, auto-soft-delete del low_id.
- 15 patrón “Telcel” → modelo confirmado (el sufijo Telcel era placeholder) — Rule C, auto-soft-delete del low_id.
- 4 manuales (sacados del auto): RT2→Cerro Pinturas x2 (rename de sitio), Netonix→WiTek Hermosillo (reemplazo físico de equipo), Loma Linda con dos sitios distintos (no es duplicado, son dos equipos con IP que colisiona entre rangos).
- Output al workspace:
monitoring-homologation/cleanup-duplicados-2026-05-15.md— plan narrativo con los 4 manuales explicados, checklist de ejecución, riesgos + mitigación, query de verificación previa dedispositivo_enlace.monitoring-homologation/cleanup-duplicados-2026-05-15.sql— 113 IDs en 3 UPDATEs dentro de unSTART TRANSACTIONconSELECTde verificación al cierre. COMMIT/ROLLBACK manual.
- No ejecuté nada contra prod — el plan es reversible (soft-delete), pero la decisión “aplicar o no, en estos 4 manuales qué quieres” es de Sergio.
- Categorización automática de los 117 pares:
- Falta del lado de Sergio (orden):
- Revisar
cleanup-duplicados-2026-05-15.md— sobre todo la sección “Los 4 casos manuales” y el riesgo de monitoreos/enlaces atados al row viejo. - Correr el SELECT de verificación de
dispositivo_enlaceque aparece en el paso 2 del checklist. Si algún row viejo tiene enlace y su contraparte nueva no, mover eldispositivo_enlace.dispositivos_idantes del UPDATE. - Decidir las 4 acciones manuales (la sugerencia mía está documentada para cada uno).
- Ejecutar el SQL en transaction, verificar, COMMIT.
- Tras COMMIT, el ruido del CSV de UISP Imports cae de 82 a 54 huérfanos. Queda desbloqueada Fase 1A.
- Revisar
2026-05-13 — reconciliación read-only es-antenas-new ↔ UISP (paso 2)
- Pidió Sergio: “Seguimos con B” → luz verde para diagnóstico read-only del problema de huérfanos.
- Hice:
- Localicé es-antenas-new en prod: VM
monitoreo(192.168.20.17), DB MySQL locales_monitoreo. SSH read-only autorizado. Credenciales DB y de SNMP solo en memoria, sin escribir a workspace. - Schema de
dispositivos:nombre,ip(no haymac),sitio_id, soft-deleted viadeleted_at. Tabladispositivo_enlacemany-to-many conenlaces(31 enlaces, 14 sitios). - Dump: 279 dispositivos vivos en es-antenas-new vs 375 devices en UISP (164 blackBox third-party + 211 Ubiquiti).
- Escribí
monitoring-homologation/reconcile.py(matching por IP exacta + nombre normalizado, agrupado por sitio→enlace, detección de duplicados internos). - Output persistido al workspace:
monitoring-homologation/reconciliation-2026-05-13.md+.csv+ el script. - Hallazgos:
- 82 huérfanos brutos (29.4%), pero solo 54 únicos por IP después de deduplicar. Las dos cifras importan: 82 es ruido en el CSV de Imports, 54 es trabajo físico real.
- 117 IPs duplicadas internamente en es-antenas-new (234 rows) — patrón: mismo equipo registrado con dos plantillas distintas (sufijos
-AF11,-Rocket,-SNMP,-Telcel). Sub-finding inesperado pero accionable: sugiere consolidar pre-migración. - Migración es por sitio completo, no por device suelto. Sitios huérfanos al 100%: Hércules (26), Torreón (18), Colonia del Valle (10), Nexpa (5), Tepehuanes (5), Barretal (2). Cuando Sergio cree esos sitios en UISP, carga 26+18+10+5+5+2 = 66 devices en pocos batches.
- 70% del inventario ya en UISP — el sync UISP→es-antenas-new (Fase 1 steady state) tiene mucha materia prima desde día 0.
- Tarea correspondiente marcada
[x]en Fase 1; tarea de “limpiar duplicados internos” agregada como opcional pre-migración.
- Localicé es-antenas-new en prod: VM
- Falta (cuando Sergio retome):
- 🔴 BLOQUEANTE — Limpiar 117 duplicados internos en es-antenas-new. Confirmado por Sergio el 2026-05-13 al cierre: la duplicidad es consecuencia de cambios recientes grandes en es-antenas-new que modificaron la manera de leer los datos; los registros nuevos no reemplazaron a los viejos. Tarea cambiada de opcional → requerida como prerequisito de Fase 1A.
- Conseguir template del CSV de UISP Imports (Sergio en UI: Settings → Imports → Devices).
- Diseñar schema migration de es-antenas-new (paso 3 del orden recomendado).
- Métrica de cierre del proyecto recalibrada:
count(huérfanos únicos administrados=true) = 0. Tamaño inicial: 54 devices. Estimación: 6-7 sitios completos a crear/poblar en UISP + algunos devices sueltos.
2026-05-18 — devices CSV generator escrito (comando artisan es:exportar-csv-uisp)
- Pidió Sergio: “Quiero avanzar en monitoring-homologation”.
- Propuse y Sergio aprobó: comando artisan en es-antenas-new que lee
origen=manual AND administrado=true AND uisp_id IS NULLy emite un CSV único en formato template UISP. Sin endpoint web (sobre-ingeniería), sin script Python (duplica creds). Un solo CSV en vez de uno-por-sitio porque la asignación de sitio físico es post-import en la UI de UISP. - Corrección importante de Sergio (capturada como memoria): la clasificación managed vs blackBox NO viene de
plantilla.lectura_datos(ese campo solo describe qué datos se pueden leer). Viene del nombre exacto de la plantilla. Lista canónica managed:PowerBeam M5,Rocket Prism 5AC,EdgeRouter,Ubiquiti AF60,Ubiquiti AF3X/AF4X/AF5X/AF11/AF24,Ubiquiti AF5XHD. Todo lo demás (Mimosa, Netonix, Planet, ZTE, SNMP genéricas) → blackBox. Memoria del hub:project_es_antenas_plantilla_managed_vs_blackbox.md. - Hice:
- Branch
feature/exportar-csv-uispdesde master en~/code/es-antenas-new. app/Console/Commands/ExportarCsvUisp.php: comando con signaturees:exportar-csv-uisp {--dry-run}. Eager-loadsplantilla:id,nombre+sitio:id,nombre. Match exact case-sensitive contra constantePLANTILLAS_MANAGED.- Para managed: fila CSV con
ip + usuario + contrasena(resto vacío). - Para blackBox: fila CSV con
ip + hostname=nombre + deviceRole + snmpCommunity=contrasena(defaultpublicsi null). - Inferencia de
deviceRole(mejor primera importación que dejar todowireless): netonix→switch, edgerouter/erx/mikrotik/accedian→router, resto→wireless. --dry-runimprime tabla por plantilla con conteo managed/blackBox antes de escribir nada. Sergio valida la categorización en una corrida read-only.- Output al ejecutar sin
--dry-run:storage/app/uisp-imports/devices-YYYY-MM-DD.csvcon header + filas. - PHP lint OK (
php -l).php artisan listregistra el comando. - NO commiteado (espera dry-run en prod por parte de Sergio para confirmar lista de plantillas managed antes de fijarla).
- Branch
- Working tree adicional creado:
~/agy/.claude/settings.jsonconworktree.bgIsolation: "none"(autorizado explícitamente por Sergio) — destraba ediciones in-place del hub para bg sessions futuras, pega con el flujo manual de markdown sin branches por sesión. - Falta del lado de Sergio:
- En prod (VM
monitoreo):git fetch && git checkout feature/exportar-csv-uisp && php artisan es:exportar-csv-uisp --dry-run. Revisar la tabla por plantilla. - Si alguna plantilla está mal categorizada (managed que debió ser blackBox o viceversa), avisarme para ajustar
PLANTILLAS_MANAGED. - Una vez OK, correr sin
--dry-runy subir el CSV resultante en UISP UI (Settings → Imports → Devices). Recordar que antes debe haber cargado eluisp-sites-fisicos-2026-05-15.csvpara que existan los sitios físicos destino. - Aprobar commit del comando + merge a master cuando esté contento.
- En prod (VM
- Próximo paso mío (cuando Sergio retome): implementar el sync UISP→es-antenas-new — script que polea
/nms/api/v2.1/devices(token ya conocido), matchea por IP/name contradispositivos, hace UPDATE poniendouisp_id+origen='uisp'|'claimed'. Cierra el ciclo: los 98 ya-en-UISP se auto-pegan, después los huérfanos importados se auto-pegan también.
2026-05-18 — seed one-shot del uisp_id en los 98 ya-en-UISP, dry-run baja de 116 → 18
- Pidió Sergio: corrió
php artisan es:exportar-csv-uisp --dry-runy vio 116 huérfanos, esperaba ~18. Pidió revisar. - Diagnóstico: el filtro
origen=manual AND administrado=true AND uisp_id IS NULLes correcto, pero al no existir aún el sync UISP→es-antenas-new, ninguno de los 98 que ya viven en UISP tieneuisp_idseteado en la DB. Por tanto el comando los incluía como si fueran huérfanos. - Hice:
- Cruce Python (read-only, local) entre
dispositivos-vivos-2026-05-15.tsv(116 administrado=true) yuisp-devices-2026-05-15.json(375 devices UISP) por IP exacta. - Resultado del cruce: 98 matches (todos ip-only, sin colisiones), 18 no-match, 0 ambiguos. Los 18 = 16 huérfanos sin candidato + 2 con sugerencia alta (
282 Netonix El Gatoque matchea[COL] El Gatopor token, y268 MT Cliente Villa Ahumadaque matchea[VIL] Villa Ahumada); ninguno matchea por IP exacta, por eso quedan en la lista a importar. - Generé
projects/monitoring-homologation/seed-uisp-id-2026-05-18.sql: un solo UPDATE conCASE id WHEN ... THEN '<uisp_uuid>'para los 98, seteauisp_id+origen='claimed'. Transacción explícita, COMMIT no comentado (lección 2026-05-15), comentario por fila conid, nombre, ip ← UISP «name»para revisión visual, SELECT de verificación al final con conteos esperados (98 claimed / 18 huérfanos / 0 inconsistentes). - Reversible documentado:
UPDATE dispositivos SET uisp_id = NULL, origen = 'manual' WHERE id IN (<lista>).
- Cruce Python (read-only, local) entre
- Sergio ejecutó en prod: los conteos cuadraron 98/18/0.
--dry-runahora reporta 18 huérfanos — la expectativa real. - Falta del lado de Sergio:
- Correr el comando sin
--dry-runpara generar el CSV final (18 filas + header). - Subir a UISP UI (precondición:
uisp-sites-fisicos-2026-05-15.csvya cargado, sitios físicos existen como destino). - Aprobar merge de
feature/exportar-csv-uispa master cuando el ciclo cierre.
- Correr el comando sin
2026-05-18 (segunda sesión) — sync UISP→es-antenas-new escrito (fase claim)
- Pidió Sergio: “Quiero seguir con monitoring-homologation”.
- Decisión sin consulta (modo no-stop): arrancar con el
es:sync-uispque quedó como próximo paso mío. Scope acotado a fase claim (matchear por IP y elevar manual→claimed). Refresh de fields paraorigen=uispy política de altas se quedan como pasos siguientes — no urgentes hasta que llegue el siguiente batch importado a UISP. - Hice:
- Branch
feature/sync-uispdesde master en~/code/es-antenas-new. config/services.php: agregado bloqueuispconbase_url/token/verify_ssl..env.example: agregadas las 3 vars con descripción.app/Console/Commands/SyncUisp.php: command con signaturees:sync-uisp {--dry-run} {--fixture=path}. Pula/nms/api/v2.1/devices, agrupa por IP, claim donde único, reporte de ambiguos/nuevos/desaparecidos. CIDR stripping (192.168.x.y/24→192.168.x.y). Idempotente. No-op paraadministrado=false. UPDATE enDB::transaction.--fixture=permite correr contra JSON local sin tocar UISP (útil para test y offline).tests/Feature/SyncUispTest.php: 7 cases Pest (claim, dry-run, ambiguo, administrado=false, idempotencia, sin-IP, fixture-falta). 7/7 verde conDB_CONNECTION=sqlite DB_DATABASE=:memory:(PHP local no tienepdo_mysql, pero en prod corre con MySQL como el resto).- Smoke test contra el JSON real (
monitoring-homologation/uisp-devices-2026-05-15.json, 375 devices): seteó 1 device manual con IP192.168.37.65, dry-run reportó “Claims: 1, Ambiguos: 0, Nuevos: 374, Desaparecidos: 0”. Match correcto alerx-campo69real de UISP, CIDR stripeado bien, preservó nombre local. End-to-end OK. - NO commiteado (regla del hub: no commit en otros repos sin permiso explícito). Branch
feature/sync-uispcon 4 archivos staged.
- Branch
- Falta del lado de Sergio:
- En prod (VM
monitoreo):git fetch && git checkout feature/sync-uisp. SetearUISP_BASE_URL=https://uisp.electrosystemsnet.com+UISP_API_TOKEN=<el de oxidized>en.env. (El token vive enoxidized:/var/lib/oxidized/credentials.yamlSOPS; sacar 1 vez, copiar a.envde monitoreo.) php artisan es:sync-uisp --dry-run. Estado esperado HOY: Claims ≈ 0 (los 98 ya tienenuisp_idpor el seed); Nuevos ≈ 277 (la mayoría de UISP no están en es-antenas o son partner). Si hay desaparecidos, revisar manualmente — no hay política de soft-delete automático todavía.- Si OK, correr sin
--dry-run(no debería tocar nada por ahora). - Después de cargar los 18 huérfanos vía CSV en UISP UI: re-correr
es:sync-uisp— debería elevar esos 18 demanualaclaimedautomáticamente. Ese es el ciclo cerrado que se busca. - Aprobar merge de
feature/sync-uispa master cuando esté contento.
- En prod (VM
- Próximos pasos míos (cuando Sergio retome):
- Fase 1 — política de altas: decidir mecanismo (
origen=uispinsert automático + notificación a Sergio vía correo/bandeja UI “devices nuevos sin monitoreos”). Hoy el comando solo reporta, no inserta. Requiere conversación sobre mecanismo de aviso. - Fase 1 — refresh de fields para
origen=uisp: extender el sync para que cuando una row tengauisp_idy el device UISP cambiename/ip, se actualicen (excepto enorigen=claimedque preserva nombre local). Hoy el comando solo claimea, no refresca. - Política de bajas: marcar
eliminado_de_uisp_atcuando unuisp_iddesaparece de UISP (no hard-delete). Hoy solo reporta. - Schedule: una vez probado, agregar al
app/Console/Kernel.phppara que corra cada N horas (sugerido: cada 1h alineado con el merger de backup-system).
- Fase 1 — política de altas: decidir mecanismo (
2026-05-18 (cuarta) — schedule hourly + logs del sync (commit fc1ea47)
- Pidió Sergio: “Arranca con el schedule” después de validar el dry-run en prod (Claims=0, Nuevos=277, Desaparecidos=0 — los conteos esperados).
- Hice:
routes/console.php:Schedule::command(SyncUisp::class)->hourly()->runInBackground()->withoutOverlapping()->name('sync_uisp')dentro del bloqueif (!config('app.remoto'))— solo corre en monitoreo, no en collectors remotos. Cadencia0 * * * *(al minuto 0 cada hora). Validado conschedule:list: “Next Due: en 23 minutos” desde el commit.SyncUisp.php: agregados 3 Log calls.Log::info('sync_uisp: claims aplicados', ['count','ids'])cuando UPDATE > 0.Log::warning('sync_uisp: devices con uisp_id ya no existen en UISP', ['count','ids'])cuando hay desaparecidos en non-dry-run (silencioso en dry-run para evitar ruido en runs manuales).Log::error('sync_uisp: UISP API no respondió OK', ['status','body_head'])cuando la API devuelve no-2xx. Output al CLI sin cambios.- Tests 7/7 siguen verde (las assertions no dependen de Log).
- Commit
fc1ea47directo en master (per regla del 2026-05-18) +git push origin master.
- Falta del lado de Sergio:
git pullen monitoreo para tener el schedule.- Verificar que el cron del sistema corre
php artisan schedule:runcada minuto (debería estar ya — los otros schedules dependen de eso). - A los 60 min del pull, revisar
storage/logs/laravel-*.logcongrep sync_uisp. Si hay claims aplicados o desaparecidos, queda registro. - Cuando suba los 18 huérfanos a UISP, el siguiente tick horario del sync los va a claimear automáticamente. Sin acción manual.
- Próximos pasos míos: política de bajas (
eliminado_de_uisp_at), refresh de fields paraorigen=uisp, política de altas con notificación. En el orden que Sergio prefiera.
2026-05-18 (tercera) — merge a master + regla: este proyecto va directo en master
- Pidió Sergio: “Puedes hacer merge a master? De ahora en adelante, el trabajo que hagas en este proyecto, que sea en el branch master a menos que yo te indique lo contrario”.
- Hice:
- Commit en
feature/sync-uispcon mensaje detallado del sync command + tests. - Checkout master +
merge --no-ff feature/sync-uisp(mismo patrón que el merge previo de exportar-csv-uisp) + delete del branch. Master adelante 2 commits sin pushear (push sigue requiriendo autorización explícita). - Memoria guardada (
feedback_monitoring_homologation_direct_master.md): de aquí en adelante, trabajo en monitoring-homologation va directo amasteren~/code/es-antenas-new, sin feature branches. Sigue aplicando “no commit/push sin autorización per-sesión” — solo cambia el branch destino cuando hay autorización.
- Commit en
- Falta: Sergio decide cuándo pushear master (lleva 2 commits adelante:
d5721b7sync command +c394b49merge). - Próximo paso mío (cuando Sergio retome): implementar el sync UISP→es-antenas-new como comando recurrente (no one-shot). Reemplaza el
seed-uisp-id-*.sqlcon un proceso continuo. Cuando los 18 sean importados a UISP via CSV, el sync los detecta y los marcaclaimedautomáticamente — cierre del proyecto.
2026-05-19 — política de altas: cola de candidatos + UI + correo (commit 2f7bb46)
- Pidió Sergio: “Vamos uno por uno” sobre los pendientes abiertos del proyecto. Empezamos con política de altas (Fase 1). Autorizó destinatario de correo
svalencia@e-electrosystems.com+ commits y push directo a master per-sesión. - Decisiones de diseño tomadas en sesión:
- Aviso: bandeja en UI + correo agrupado (no solo log). El correo solo se manda 1 vez por tick con la lista de candidatos genuinamente nuevos en ese tick.
- Batch inicial (277 detectados): NO insertar automático a
dispositivos. Los nuevos van a una cola de candidatos (tablauisp_candidatos) y Sergio procesa device por device. Decide Agregar (crea Dispositivo) o Ignorar (no vuelve a aparecer). Evita avalancha de 277 con monitoreos vacíos. - Snapshot inicial silencioso: la primera carga entra como
pendientesin disparar correo. Se controla conUISP_NOTIFY_START_AT— si está vacío o es futuro, correo suprimido. Sergio lo activa cuando quiera empezar a recibir avisos. - Conflicto IP: si una IP del candidato coincide con un
Dispositivomanual sinuisp_id, la UI ofrece claim (setsuisp_id+origen=claimedpreservando la config local) como opción explícita antes de crear duplicado.
- Hice (todo en
~/code/es-antenas-new, master):- Migration
2026_05_19_120000_crear_tabla_uisp_candidatos.php: tabla conuisp_idunique,estadoenum,visto_primera_vez_at,procesado_at,dispositivo_id(FK nullable a dispositivos). - Modelo
App\Models\UispCandidatocon scopespendientes/agregados/ignorados. SyncUispextendido: detecta devices UISP sin contraparte (incluyendo los sin IP) y los upsertea auisp_candidatos.ignoradono se reactiva.pendienterefresca fields sin tocarvisto_primera_vez_at.agregadose skip (ya tiene Dispositivo). Si hay nuevos posteriores anotify_start_at→Mail::to(notify_to)->send(new UispCandidatosNuevos($lista)).--dry-runno upsertea.- Config
services.uisp.notify_to(defaultsvalencia@e-electrosystems.com) +services.uisp.notify_start_at(envUISP_NOTIFY_START_AT). - Mailable + view
App\Mail\UispCandidatosNuevos+resources/views/emails/sync/uisp_candidatos_nuevos.blade.php(markdown mail con tabla + botón link a/uisp-candidatos). - Controller
UispCandidatosControllerconindex(filtros por estado, counts),show(incluyeposiblesClaimpor IP + listas de sitios/plantillas),agregar,ignorar,reactivar. La acción agregar validasitio_id+plantilla_id(required_withoutdispositivo_id). Sidispositivo_idviene → claim; si no → crea Dispositivo conorigen=uisp,usuario/contrasenavacíos (NOT NULL en la tabla, se editan después). 409 si el dispositivo elegido para claim ya tiene uisp_id. - Rutas
/uisp-candidatos[ /{id}, /agregar, /ignorar, /reactivar ]bajoauth. - HandleInertiaRequests comparte
uispCandidatosPendientes(count) como prop global para el badge — try/catch para no romper si la tabla aún no existe. - Páginas Vue
Pages/UispCandidatos/Index.vue(tabs pendiente/agregado/ignorado con counts, búsqueda, acciones Agregar/Ignorar/Reactivar) +Show.vue(detalle del candidato + posibles claims con radio buttons + selectores de sitio+plantilla cuando se crea nuevo). - Badge en
ESLayout.vuesección Administración bajo “Candidatos UISP” con número de pendientes en pill amber-500. - Tests Pest
tests/Feature/UispCandidatosTest.php— 13/13 verde (upsert, ignorado no se reactiva, refresh no mueve timestamp, correo condicionado por notify_start_at, dry-run no upsertea, agregar crea Dispositivo, agregar con dispositivo_id hace claim, ignorar, reactivar, locked enagregado, 409 en uisp_id conflict).
- Migration
- Commit + push:
2f7bb46directo en master, 19 archivos, +1225 líneas. Push aorigin/masterOK. - Falta del lado de Sergio:
git pullen monitoreo +php artisan migratepara aplicarcrear_tabla_uisp_candidatos.- Decidir cuándo activar correo: setear
UISP_NOTIFY_START_ATen.env(formato ISO, p. ej.2026-05-20T00:00:00-06:00para arrancar mañana después del snapshot). Sin esto, correo permanece silenciado. - Tras el primer sync horario post-migración, entrar a
/uisp-candidatosy procesar la cola (~277 items esperados según último dry-run, menos los que ya estén cubiertos por seed/claim previo).
- Próximos pendientes míos (en el orden que Sergio prefiera la próxima vez):
- Política de bajas (
eliminado_de_uisp_aten dispositivos cuando un uisp_id desaparece de UISP, hoy solo se reporta). - Refresh de fields para
origen=uisp(sobrescribirnombre/ip/sitioen cada tick). - Asignar enlace a los 2 Loma Linda (id 26 y 54) — menor.
- Reporte de huérfanos en UI (Fase 1A).
- Política de bajas (
2026-05-20 (segunda) — UI: chip de estado UISP + bloque diagnóstico en Forma (commit 276f765 LOCAL)
- Pidió Sergio: “Agrégalo, solo que me parece que no hay show de dispositivo” — después de confirmar el cierre de Fase 1A (ver bitácora del día anterior). En efecto, no hay Show.vue; sólo Index + Forma (edit/create). La UI elegida:
- Index → DispositivosTablaItem.vue: chip pequeño al lado de la IP con el estado UISP:
UISP(indigo),Manual(amber),Partner(slate),Eliminado UISP(rose). Tooltip muestrauisp_idcompleto. - Forma.vue (edit only): bloque “Estado UISP” read-only con origen / administrado / uisp_id / eliminado_de_uisp_at, justo arriba del botón “Guardar Cambios”.
- Index → DispositivosTablaItem.vue: chip pequeño al lado de la IP con el estado UISP:
- Hice (todo en master,
~/code/es-antenas-new, sin push aún):app/Http/Controllers/DispositivosController.php: agreguéorigen, uisp_id, administrado, eliminado_de_uisp_atal SELECT deindexyedit(sin esto Inertia no los manda al front).resources/js/types.ts: extendíIDispositivocon esos 4 campos opcionales.resources/js/Components/DispositivosTablaItem.vue: agreguécomputed chipUispcon la lógica de estado (eliminado prevalece sobre todo, luego UISP, luego !administrado=Partner, fallback Manual). Render del chip al lado del IP. flex-wrap del contenedor para que no se rompa en mobile.resources/js/Pages/Dispositivos/Forma.vue: bloque<dl>con grid de 2 columnas para los 4 campos UISP. Solo se muestra cuando!crear(). Rose-600 para timestamp de eliminado cuando aplica.tests/Feature/AdministracionDispositivosTest.php: agregué test “index expone columnas UISP” que valida los 4 campos en el payload Inertia del index.- Suite verde: 93/93 tests Pest,
npm run buildOK (vite v6.2.5, sw.mjs 16.53kB).
- Commit local:
276f765en master, 11 archivos, +121/-22 (incluye assets de build). - Push + deploy autorizados (misma sesión):
git push origin master:74d2e83 → 276f765OK.ssh monitoreo "... ./scripts/deploy.sh": deploy OK en 104s. Schedulees:sync-uispconfirmado activo (Next Due: 40 min).- Mismo warning conocido del deploy anterior: “Horizon is inactive / workers systemd activos: 0” — no es regresión, no afecta el sync (corre por schedule, no queue).
- Diseño rechazado / no implementado:
- Show.vue dedicada — no existe en la app, no vale crearla solo para 4 campos (la Forma edit ya es el “show con edición”).
- Filtro en Index por origen/estado UISP — esperar hasta que Sergio confirme que lo necesita. Para diagnóstico puntual basta el chip.
2026-05-20 — Sergio cargó sitios + devices a UISP + activó notify_start_at
- Pidió Sergio: “Ya seteé UISP_NOTIFY_START y ya subí los sitios y devices a UISP”.
- Verifiqué (dry-run remoto desde
~/code/es-antenas-newvíassh monitoreo "... es:sync-uisp --dry-run"):- UISP devices: 401 (vs 401 ayer, sin cambio neto — los nuevos sitios/devices pueden haber entrado y otros desaparecidos, neto 0).
- Claims posibles: 0.
- Ambiguos: 0.
- Nuevos sin contraparte: 286 (idéntico al snapshot de ayer).
- Desaparecidos: 0.
- Lectura:
claims=0después de subir los 18 huérfanos es inesperado. Tres hipótesis a discriminar:- A (deseable): los 18 ya fueron claimed por el tick horario anterior (entre tu carga y mi dry-run pasó ≥1 tick). Quedaron
origen=claimed, sus IPs entran aipsCubiertaspor la rama derowsConUisp, por eso no aparecen como nuevos ni como candidatos a claim. Pero nuevos seguiría en 286 (no bajaría a 268) solo si UISP no le asignó IP a los 18 — entonces seguirían contando como “nuevos” en candidatos a IP=null. Inconsistente. Esta hipótesis quedaría medio refutada. - B: UISP importó los 18 como blackBox pero NO propagó la IP del CSV al campo
ipAddressde/devices. El sync no puede matchear por IP, los 18 quedaron upserteados auisp_candidatosconip=null. Los 18 dispositivos locales siguen siendomanual+uisp_id NULL(siguen en huérfanos). Para claimear, Sergio entra a/uisp-candidatos/{id}y la UI ofrece claim solo si hay match por IP — sin IP, tendría que crear nuevo Dispositivo o ajustar manualmente. - C: el CSV no se importó como esperábamos (algo del formato falló, devices entraron duplicados o con IP distinta).
- A (deseable): los 18 ya fueron claimed por el tick horario anterior (entre tu carga y mi dry-run pasó ≥1 tick). Quedaron
- Acción pedida a Sergio (1 minuto): abrir
/uisp-candidatosy reportar:- Cuántos pendientes muestra el badge/tab.
- Buscar una IP conocida de los 18 (ej. la del dry-run anterior) — ¿aparece en candidatos? ¿con IP o sin IP?
- Notify_start_at activo: el correo está armado. El próximo tick que detecte un device UISP con
visto_primera_vez_at >= notify_start_atva a mandar correo asvalencia@e-electrosystems.com. Si todos los 286 son anteriores a notify_start_at (snapshot inicial), el correo seguirá silenciado. - Falta: Sergio verifica /uisp-candidatos → según hallazgo decidimos si ajustamos el sync (rama de “claim por nombre” como fallback cuando IP no existe en UISP), o si los 18 ya están bien y solo hay que procesar la cola desde la UI.
Verificación cerrada (misma fecha): Sergio buscó 192.168.36.2 Netonix El Gato (huérfano real de los 18) en /uisp-candidatos y NO aparece. ✅ Hipótesis A confirmada: los 18 ya fueron claimed por un tick horario anterior. Quedaron origen=claimed con uisp_id poblado. Los 286 candidatos pendientes que reporta el dry-run son el snapshot de UISP-only legítimo (devices que viven en UISP sin contraparte local, no son los 18 huérfanos).
🎯 Métrica de cierre del proyecto: count(origen=manual AND administrado=true AND uisp_id IS NULL) = 0. Fase 1A operacionalmente cerrada. Lo que queda son:
- Procesar la cola de 286 candidatos UISP-only (decisión de Sergio device por device en
/uisp-candidatos). - Refresh de fields para
origen=uisp(pendiente mío — completa el CRUD del sync). - Asignar enlace a los 2 Loma Linda (id 26 y 54) — menor.
- Fase 3 doc.
2026-05-19 (segunda) — política de bajas: marcado + tab UI + correo (commit a4684aa)
- Pidió Sergio: “Sí, dale al siguiente pendiente” después de cerrar altas. Decisiones de diseño tomadas en sesión:
- Severidad: solo marcar (
eliminado_de_uisp_at). NO silenciar (silenciado=true), NO soft-delete. El dispositivo sigue 100% activo en monitoreo. Sergio revisa y decide. Razón: “más seguro pero más ruido si tardo en revisar” es preferible a apagar monitoreo de un device que solo se borró por accidente en UISP. - Aviso: correo agrupado + bandeja UI (igual modelo que altas, sujeto al mismo
notify_start_at).
- Severidad: solo marcar (
- Aclaración encontrada durante implementación: el doc viejo decía “marcar soft-delete (
ignorar=true+eliminado_de_uisp_at)” peroignorarNO existe en la tabladispositivos— solo está enmonitoreos_dispositivos. La columna análoga endispositivosessilenciado(boolean) que Sergio explícitamente descartó. El modelo final solo usaeliminado_de_uisp_at. - Hice (todo en master,
~/code/es-antenas-new):- Migration
2026_05_19_130000_agregar_eliminado_de_uisp_at_a_dispositivos.php: timestamp nullable después deadministrado. Castdatetimeagregado aDispositivo::$casts. SyncUisp:- Detecta
rowsConUispconeliminado_de_uisp_atya cargado. desaparecidos = rowsConUisp WHERE uisp_id NOT IN UISP.reaparecidos = rowsConUisp WHERE eliminado_de_uisp_at != null AND uisp_id IN UISP.marcarDesaparecidos(): seteaeliminado_de_uisp_at = now()para los que tienen flag NULL. Idempotente. Devuelve solo los recién marcados (para correo).limpiarReaparecidos(): pone flag = NULL + log info.notificarDesaparecidos(): igual al de altas —notify_start_atfuturo o vacío suprime; pasado dispara correo anotify_to.--dry-runsalta tanto marcar como limpiar.
- Detecta
- Mailable
App\Mail\UispDesaparecidos+ viewemails/sync/uisp_desaparecidos.blade.php(tabla con nombre/IP/uisp_id + botón a/uisp-candidatos?estado=desaparecidos). - Controller
UispCandidatosControllerampliado:indexahora aceptaestado=desaparecidos. Cuando aplica, devuelve lista deDispositivo WHERE eliminado_de_uisp_at != nullconsitioeager-loaded. Counts incluyendesaparecidos.- 3 acciones nuevas:
quitarFlagDesaparecido(set flag = NULL),borrarUispId(setuisp_id=null+origen=manual+ flag NULL),eliminarDispositivo(soft-delete).
- Rutas
/uisp-desaparecidos/{dispositivo}/{quitar-flag|borrar-uisp-id|eliminar}bajoauth. - HandleInertiaRequests
uispCandidatosPendientes(badge global) ahora sumapendientes + desaparecidos— un solo número de “issues UISP que requieren atención”. Si las tablas no existen aún, retorna 0 (try/catch). - Page Vue
Pages/UispCandidatos/Index.vueampliada: nuevo tab “Desaparecidos en UISP” con tabla específica (nombre/IP/sitio/origen/timestamp/acciones). Búsqueda compartida funciona en ambos modos. 3 botones por fila: Quitar flag (gris), Borrar uisp_id (amber), Eliminar (rojo), cada uno conconfirm()previo. - Tests Pest
tests/Feature/UispBajasTest.php— 11/11 verde (marcado, idempotencia, reaparición limpia, dry-run no marca, correo NO sin start_at, correo SÍ con start_at pasado, correo NO si solo hay viejos, 3 acciones del controller, rechazo a no-marcados). Suite UISP completa: 31/31 (Sync + Candidatos + Bajas).
- Migration
- Commit + push:
a4684aaen master, 14 archivos, +619 líneas. Push aorigin/masterOK. - Deploy a producción (misma sesión, autorizado por Sergio):
ssh monitoreo "cd /var/www/es-monitoreo && ./scripts/deploy.sh"— script ya existente (commitd07540a).- Aplicó pull (de 2f7bb46 — altas que ya estaba desplegado — a a4684aa), composer install, npm ci/build,
php artisan migrate --force. Migrations 54 (crear_tabla_uisp_candidatos) y 55 (agregar_eliminado_de_uisp_at_a_dispositivos) corrieron OK. php artisan schedule:listconfirma0 * * * * php artisan es:sync-uisp ... Next Due: en 18 minutos.- Validación post-deploy:
php artisan es:sync-uisp --dry-run→ 401 devices UISP recibidos, 0 claims posibles, 286 nuevos en UISP sin contraparte, 0 desaparecidos. El próximo tick horario va a insertar esos 286 enuisp_candidatossilencioso (porqueUISP_NOTIFY_START_AT=2026-05-20T00:00:00-06:00, futuro). - Sergio entrará a
/uisp-candidatosdespués del próximo tick (alrededor de las 19:00 MDT) y verá los 286 esperando decisión. - A partir de mañana 00:00 MDT, cualquier device NUEVO en UISP (post-snapshot) sí disparará correo.
- ⚠️ Observación: el script reportó
Horizon is inactive+workers systemd activos: 0después delhorizon:terminate. No afecta sync_uisp (que va por schedule, no queue), pero si el resto del sistema usa Horizon para jobs, conviene relevantarlo. Dejado anotado, sin acción adicional.
- Próximos pendientes míos (orden sugerido):
- Refresh de fields para
origen=uisp(próximo natural: completa el CRUD del sync — altas, bajas, ya hechos; quedaría updates). Asignar enlace a los 2 Loma Linda→ reemplazado 2026-05-21 por soft-delete de ambos (ver bitácora de ese día).- Reporte de huérfanos en UI (Fase 1A — la parte de migrar activamente los 18 huérfanos a UISP).
- Refresh de fields para
2026-05-21 noche — #029 cerrado: Horizon multi-instance limpiado (de 20 masters → 1)
- Pidió Sergio: “Quiero resolver el pendiente #029” (Revisar Horizon en monitoreo: post-deploy quedó
inactivecon 0 workers systemd). - Diagnóstico (SSH read-only, autorizado por default):
horizon:statusreportabainactivePERO había 20 procesos masterartisan horizoncorriendo en paralelo, 20 supervisores, 51 workers, total 91 procesos.- RSS combinado: ~4.87 GB.
- Causa raíz:
/etc/systemd/system/laravel-worker@.servicees un template cuyoExecStartesphp /var/www/es-monitoreo/artisan horizon(el master completo de Horizon, noqueue:work). Estaban habilitadas las instancias@1..@20→ 20 Horizons compitiendo por las mismas queues Redis, cada uno con su propio supervisor name random (es-monitoreo-OCsG,-ynvr,-V0UI, …). El lock global dehorizon:statusespera un master con nombre canónico, no encontraba match → reportabainactiveaunque los masters procesaban jobs (Redis dedupea con per-job locks). - Anti-pattern: Horizon ya gestiona supervisores/workers internamente vía
config/horizon.php(maxProcesses). Debe correrse una sola vez.
- Plan ejecutado (autorizado por Sergio per-sesión):
- Sergio creó
/etc/sudoers.d/electrosystems-laravel-workercon NOPASSWD scoped asystemctl stop|disable laravel-worker@*(regla limitada, no toca otras unidades). - Loop
for i in {2..20}; do sudo systemctl stop laravel-worker@$i; sudo systemctl disable laravel-worker@$i; doneejecutado desde mi sesión.
- Sergio creó
- Verificación post-cleanup:
- 1 master + 1 supervisor + 2 workers (default + aggregates) vivos.
- RSS combinado: 204 MB (de 4.87 GB → ahorro ~4.66 GB).
horizon:status→ “Horizon is running.” ✅horizon:snapshotexitoso.- Horizon auto-scaled un worker para
defaultqueue segundos después → master responde a jobs entrantes.
- Sin cambios de código.
scripts/deploy.shno necesita modificación: el postflight actual (poll 30s dehorizon:status+ count delaravel-worker@*) ya funciona correctamente con 1 sola instancia. - Docs creados/actualizados:
~/agy/electrosystems/servers/monitoreo/README.md— perfil completo del server (no estaba documentado).~/agy/electrosystems/servers/monitoreo/findings.md— incidente detallado.~/agy/electrosystems/INVENTORY.md— entry nueva en Summary + bloque “monitoreo (on poseidon)” + entrada en changelog.PENDIENTES.md— #029 cerrado.- Memoria nueva
reference-horizon-single-systemd-master.md— pattern reusable cross-cliente.
- Follow-up opcional (no bloqueante):
- Renombrar el template
laravel-worker@.service→horizon.service(no-template, single-instance) para que la convención quede coherente con su propósito real. Update del grep en el postflight descripts/deploy.sh(cambiar delaravel-worker@*ahorizon). Baja prioridad; no abro pendiente hasta que Sergio lo pida.
- Renombrar el template
2026-05-21 — #028 cerrado: los 2 Loma Linda ya estaban soft-deleted
- Pidió Sergio: “El task #028: esos dispositivos hay que borrarlos, ya no se deben monitorear en ninguna parte.”
- Contexto: son los
dispositivos.id=26(Netonix Loma Linda [Chihuahua]) yid=54(Netonix Loma Linda [Parral]), ambos con IP192.168.46.10. El doccleanup-duplicados-2026-05-15.mdlos listaba como “duplicado intencional permitido” — equipos físicos distintos en rangos privados que colisionan en IP. - Verificación en prod (SSH
monitoreo+ tinker):Dispositivo::find(26|54)→ null (excluidos por scope de SoftDeletes).Dispositivo::withTrashed()->find(26|54)→ ambos condeleted_at = 2026-05-15 17:12:10.- Conclusión: ya estaban soft-deleted desde el barrido del stage 1 del 2026-05-15 (cayeron por el match por IP a pesar de la intención de preservarlos). Cumple la decisión de Sergio sin acción adicional.
- Blast radius confirmado:
monitoreo-collectorlee dedispositivos— los soft-deleted quedan fuera del scope, no se polean.- UISP no los tiene,
oxidizedconsume UISP+NetBox, ninguno se ve afectado.
- Cambios documentales:
PENDIENTES.md: #028 movido a “Cerrado reciente” comoResuelto 2026-05-21 [#028].cleanup-duplicados-2026-05-15.md: nota apuntando a esta bitácora — la sección 4 (“no debería tocarse ninguno”) está obsoleta de hecho.- Sin commits en el repo de es-antenas-new (nada que tocar — el estado ya era el deseado).