2026-05-20 — bitácora del día
es-antenas-new — fan-out de recordatorios de Hercules
Pidió Sergio: captura de Gmail con muchos correos 📊 RECORDATORIO Hercules - <X> repetidos en pocas horas (~10 en 1 hora) — Notario RB Camargo, Hercules-1, Hercules-2, Bolsa Tete, Colonia del Notario, todos del mismo sitio Hercules con monitoreos eléctricos (03 Monitoreos Electricity). "¿Se puede hacer algo para que sean menos correos los que llegan tan seguido?"
Diagnóstico: la cascada a sitio del 2026-05-18 (commit a9e1566) solo promueve cuando hay ≥2 buckets en el MISMO tick. En producción los ultimo_notificado_at de un sitio quedan desfasados por minutos (cada enlace cruzó el umbral en un tick distinto), así que cada tick procesaba 1 enlace suelto → 1 correo por enlace cada 120 min. Mismo patrón que ya se arregló para flapping el 2026-05-14 (commit 616d7fc) pero nunca aplicado a alertas normales.
Hice: apliqué el patrón "seeds + arrastre por sitio" al bloque de recordatorios en NotificarLogs.php. El primer seed vencido arrastra a TODAS las métricas en falla del mismo sitio, despacharPendientes las promueve a 1 correo de sitio, y enviarCorreo resincroniza ultimo_notificado_at de todo el grupo. Filtro adicional excluye notificados <5 min para evitar colisión con alertas iniciales del mismo tick (caso atrapado por test).
Commit + deploy:
74d2e83 fix(recordatorios): arrastre por sitio para evitar fan-out de N correos cada 2h(+219/-10, +4 tests).- 232/232 tests passing.
- Push autorizado per-sesión (classifier bloqueó la primera vez; Sergio confirmó "sí, autoriza el push y deploy").
- Deploy vía
ssh monitoreo "/var/www/es-monitoreo/scripts/deploy.sh"— primer uso real del script (commiteado 2026-05-18). 126s, sin migraciones, 20 workers systemd activos post-deploy. Verificado:git log -1en server =74d2e83.
Falta: observar el próximo ciclo de recordatorios en las próximas 2 horas para confirmar que Hercules ahora genera 1 solo correo de sitio en lugar de N.
Follow-up opcional: mismo issue teórico en procesarFlapping's expansión (un nuevo "detectado" en mismo tick que un "recordatorio" del mismo sitio podría salir mezclado). Riesgo bajo. Capturado en pendientes del proyecto.
es-antenas-new — fan-out de FLAPPING detectado (sesión 2 de hoy)
Pidió Sergio: segunda captura de Gmail post-fix de recordatorios — los correos [FLAPPING] también llegan repetidos: 3 correos [FLAPPING] Chihuahua · WiTek Hermosillo · 3 métricas a 07:00, 07:01, 07:02; 2 correos [FLAPPING] Hércules · 9 métricas a 07:00 y 07:02.
Diagnóstico: el bloque "detectados" en procesarFlapping agrupa solo lo que vio en ESE tick. Si métricas del mismo sitio cruzan el umbral N-en-15min en ticks distintos, cada tick manda su propio correo. (Confirmado por SSH + lectura de código; Sergio autorizó SSH read-only para diagnóstico permanente.)
Hice: apliqué el mismo patrón "seeds + arrastre por sitio" del fix anterior, pero con un debounce adicional. Métricas con flapping_desde reciente (<debounce_minutos) esperan; cuando un seed vence, arrastra a todas las hermanas del mismo sitio aunque sean recientes → 1 correo por sitio. Config nueva: monitoreo.flapping.debounce_minutos (env MONITOREO_FLAPPING_DEBOUNCE, default 5).
Commit + deploy:
017e1b9 fix(flapping): debounce + arrastre por sitio en detectados(+185/-11, +4 tests, 237/237 passing).- Deploy via
ssh monitoreo "/var/www/es-monitoreo/scripts/deploy.sh"— 62s. Postflight reportó "workers: 0" pero verificación manual confirma 20 workersactive running(bug del script, follow-up menor).
Reglas persistidas en memoria del hub (Sergio explícito "sí, autoriza el push y deploy, y persiste ambas reglas"):
feedback-es-antenas-new-push-deploy-authorized.md: push directo a master + deploy viascripts/deploy.shautorizados por default. Commit sigue requiriendo auth per-sesión.feedback-prod-read-only-diagnostics-authorized.md: SSH read-only y SELECT a MySQL en prod ES autorizados por default. Mutaciones (UPDATE/DELETE/sudo) siguen requiriendo auth.
Falta: observar próximo evento de flapping para confirmar que múltiples métricas crucen el umbral con minutos de diferencia salgan en 1 correo.
electro-ia — Fase 2.10 cerrada + fix retry de Telegram outbound
Retomada de Fase 2.10: Sergio retomó tras la pausa del 2026-05-19, agregó credenciales SMTP al .env (Workspace electrosystems.com) y reinició electro-ia.service.
Smoke A — bug anafórico: ✅ subió 2 archivos consecutivos + preguntó "qué tiene este archivo?". tool_call_log confirma analyze_excel invocado con file_id más reciente (file_id=12), no reciclando turnos anteriores.
Smoke B — send_email:
- Con Antigravity (
/api): ✅ tool invocada, correo entregado vía Workspace SMTP, allowlist + 1 destinatario → directo sin confirmación. - Con qwen2.5:14b-instruct: falló al inicio porque qwen narraba "voy a mandar… responde sí" en vez de invocar la tool. Root cause:
prompts/system.mdno documentabasend_emailen la sección "Tools de ejecución". Patch al system prompt agregando la entrada explícita con regla "No narres la acción en prosa: llama la tool.". Restart + 2do intento con qwen: ✅ tool invocada, correo entregado.
Falso negativo en smoke B con qwen: Sergio reportó "no me mandó el mensaje en Telegram para confirmar, pero sí me llegó el correo". run.log mostró {"err":"fetch failed","msg":"telegram sendAndLog failed"} en el timestamp del cierre. Bot OK end-to-end, falla puntual de red al api.telegram.org/sendMessage. Hallazgo lateral importante: sendAndLog no tenía retry → un blip = mensaje perdido silenciosamente.
Fix lateral (commit 343e657): retry de red selectivo en src/telegram.js. tgCall y tgCallMultipart aceptan { retries }; solo reintentan errores de red (fetch failed, TimeoutError, ECONNRESET, ENOTFOUND, EAI_AGAIN, aborted). Errores del API (err.code != null) nunca se reintentan. sendMessage ahora con retries: 3 (500ms, 1s, 2s); sendVoice con retries: 2. getUpdates queda intacto — el pollLoop ya tiene backoff exponencial 1→30s y Telegram retiene updates 24h, no se pierden inbounds.
Commits creados en laptop-ia:/home/electroia/electro-ia/ (sin remote, sin push):
f89c42bchore(policy): registrar host monitoreo y permitir writes a policy/3c78eaefeat(2.10): fix anaforico de archivos + tool send_email (10 archivos, +335)343e657fix(telegram): retry de red en sendMessage/sendVoice
Deuda menor: el repo electro-ia no tiene origin. No urgente (laptop-ia es el único host) pero algún día conviene snapshot off-host.
Cierre documental: README de electro-ia actualizado (frontmatter phase a 2.10-cerrada, bloque RETOMAR eliminado, bitácora 2026-05-20 agregada). Memoria project_electroia_phase_2_10_paused.md se actualiza al cierre.
backups-infra — enumeración Sangoma 7 cerrada (6/6)
Pidió Sergio: retomar backups-infra paso a paso. Empezar con oasa-plutarco, ver si el flujo cierra, replicar.
Hice (infra del flujo):
scripts/probe-pbx-db.sh <ip> [label]— wrapper read-only que extrae creds remotas de/etc/freepbx.conf(PHP include o grep fallback), corre SHOW DATABASES + sizes + bind-check. Sinset -einterno, con sección Discovery, cascada de 4 fuentes (freepbx.conf via php → via grep →/root/.my.cnf→/etc/asterisk/cdr_mysql.conf). IP validada contra 10.11/192.168.20.- Permission rule
Bash(/home/sergio/agy/scripts/probe-pbx-db.sh:*)en.claude/settings.local.json(gitignored). Trade-off documentado en transcripción: el script encapsula la garantía read-only en código auditable; un glob sobre el comandosshautorizaría payloads opacos.
Hice (enumeración Sangoma 7, ejecutada por Sergio con !): 6 hosts cerrados limpio. Patrón canónico FreePBX confirmado uniformemente:
- DBs
asterisk+asteriskcdrdb+test(vacía). - Creds en
/etc/freepbx.conf(AMPDBUSER/AMPDBPASS). - Bind
0.0.0.0:3306en todos.
| Host | Sangoma | MariaDB | asterisk | asteriskcdrdb |
|---|---|---|---|---|
| miscelec-chih (ya estaba) | 7.x | 5.5 | 29 MB | 4.8 GB ⭐ |
| miscelec-jrz | 7.8 | 5.5.65 | 27.8 MB | 4.8 GB ⭐ (mismo que chih — investigar replica) |
| minadolores2 | 7.8 | 5.5.65 | 38 MB | 1.4 GB |
| oasa-plutarco | 7.8 | 5.5.65 | 25.7 MB | 858.9 MB |
| novamex-jrz | 7.8 | 5.5.65 | 32.8 MB | 289 MB |
| miscelec-queretaro | 7.5 | 5.5.60 | 24 MB | 0.3 MB |
| miscelec-leon | 7.5 | 5.5.60 | 25 MB | 0.3 MB |
Subtotal medido en Sangoma 7: ~15 GB (95% asteriskcdrdb). Si CDR queda OUT del backup → ~265 MB. La decisión sobre asteriskcdrdb sigue siendo la palanca principal de volumen.
Hallazgos colaterales:
- Bind
0.0.0.0:3306en 6/6 Sangoma + amadeus + deportescampeon → 8 hosts ya. No es excepción, es el default histórico de Sangoma/FreePBX viejos. Capturado como pendiente para abrir proyectonetwork-hardening-pbxfuera de scope. - Coincidencia miscelec-chih ↔ miscelec-jrz en
asteriskcdrdb4799.7 MB: demasiado idéntico para no anotarlo. Posible replica master-slave, restore del mismo dump, o inventario previo confundió hosts. Anotado en tabla del proyecto. - Sangoma 7.5 (miscelec-queretaro/leon) con PHP 5.6 + MariaDB 5.5.60: deuda más profunda que 7.8. Sin urgencia para backup.
- DB
testvacía en todos los Sangoma 7 — residuo del install de MariaDB 5.5.
Sergio cerró pidiendo: que al retomar le pegue el bloque literal de 17 invocaciones del script para los "Linux genéricos" del bloque 58695. Bloque guardado en projects/backups-infra.md sección "🔁 Retomar siguiente sesión — pegar este bloque" + memoria project_backups_infra_progreso.md instruye a copiarlo como primera acción de la próxima sesión.
Sesión miércoles noche-2 — nuevo proyecto hub-web-viewer capturado
Sergio pidió ligar los pendientes del hub a un Jira-like para visualizar en web (sync con los MD del hub). Aplicada regla [[feedback-challenge-assumptions]] antes de ejecutar: cuestioné si necesitaba bidireccional (Jira/Linear) o solo visualizar. Comparé Jira/Linear/Trello SaaS, GitHub Projects, Obsidian Publish ($8/mo), custom Laravel/Next con BD, y static site read-only. Recomendé static site: $0, sin lock-in, sync trivialmente unidireccional (push hub → rebuild → publish), versionado nativo.
Sergio confirmó: solo visualizar, prioridad medium.
Capturado proyecto hub-web-viewer.md con:
- Contexto + descartes razonados de cada alternativa.
- 5 decisiones marco abiertas para Fase 0: (1) stack — recomiendo Astro (Content Collections + frontmatter tipado + Vue opcional); (2) hosting — recomiendo GitHub Pages o Cloudflare Pages; (3) auth obligatorio — recomiendo Cloudflare Access + Google SSO
sevaor@gmail.compor sensibilidad de los datos (nombres de clientes, IPs, hostnames); (4) trigger rebuild — recomiendo GitHub Action en push (latencia 1-2 min, gratis); (5) alcance v1 — recomiendo PENDIENTES + proyectos + clientes (sin log/inbox hasta Fase 2). - Fases 0/1/2/3 estructuradas (decisiones → MVP → refinamientos → opcionales).
- Estructura propuesta
~/code/hub-web-viewer/separado del hub (no contamina; lee el hub via symlink o submodule). - Notas de seguridad: confirmar que MD no tienen secretos antes de publicar; auth obligatorio si está accesible desde internet.
Entradas agregadas a _INDEX.md (cliente=personal +1, tipo=personal +1, prioridad=medium +1) y a PENDIENTES.md sección Personal con las 3 fases.
Sesión amadeus (jueves madrugada — UX + limpieza + piloto inventarios + backlog Jira)
Sesión maratón sobre amadeus, desencadenada por "cuál es el estatus de amadeus". Continuó con la lista de pendientes acumulados después del deploy del 2026-05-19. Total: 4 deploys hot a prod + 5 cierres importantes de pendientes + 1 captura de doc nueva + 2 memorias durables.
1. UX del indicador de notificaciones (deploy hot 1). Diagnóstico encontró un bug serio: el badge "!" del sidebar solo se encendía para requiere_lectura=true, pero esas YA redirigen por middleware — el badge era redundante y las notif normales no daban ninguna señal. Fix completo: shared prop notificaciones.{count_total, tiene_requiere_lectura} en HandleInertiaRequests; badge en sidebar con contador numérico (rojo+pulse para urgentes, azul para normales); nueva campana en topbar móvil (antes no había nada con sidebar cerrado). Commits 3f957a0 → 203ed70. Backup pre-notifux-1921 1.2M, pull + artisan optimize. Confirmado por Sergio en prod.
2. Rediseño móvil de /notificaciones (deploy hot 2). Pages/Notificaciones/Historial.vue: header flex-col para móvil (botón "Volver" no comprime título), card de notif flex-col sm:flex-row con botón "Marcar como leída" full-width abajo en móvil, header de card con flex-wrap (chip baja si título es largo), padding responsive p-4 sm:p-6, fechas text-xs sm:text-sm. Commits 05e2524 → 85c9356. Backup pre-mobile-1936 1.2M. Validado en local con 2 notif de prueba (id=3 título corto + id=4 título largo con URL larga) y luego en prod.
3. Limpieza varia post-deploys (deploy hot 3 + 4). Cuatro pendientes acumulados resueltos:
- Borrado
ModalNotificaciones.vue(0 imports, código muerto) + endpointNotificacionesController::tieneNoLeidas+ su ruta/notificaciones/tiene-no-leidas(sin caller desde front). Commits638b708→c4f6e32. - Trackeado
deploy.shque vivía untracked en la VM desde 2025-10-28. Escrito idéntico en repo (MD5ebfff1901fbe...), commit6c2c65d→ merge008118c. En prod:rm deploy.sh && git pull(evita "untracked working tree files would be overwritten"). Permisos+xpreservados. - Hallazgo importante en limpieza Karla: el doc original asumía rows idénticos, pero NO lo eran. id=14 (
updated_at=2026-05-19,recibir_notif_viajes=1) vs id=15 (updated_at=2024-08-19,recibir_notif_viajes=0). ComoUsuario::permisos()eshasOnesin orderBy → MySQL devuelve por PK ascendente → id=14 era el row vivo (¡el contrario al plan original de "preservar id mayor"!). AplicadoDELETE WHERE id=15con autorización explícita. Memoria nueva [[reference-laravel-hasone-order]]: regla general para todos los proyectos Laravel. git clean -f public/build/en prod: 28 archivos huérfanos removidos. Working tree limpio salvo.env.backup-20260519-2234(deuda menor).
4. Auditoría del switch de inventarios + piloto Selene. Sergio pidió validar el flujo COMPLETO de activación antes de asignar permisos (recordaba que algo lo dejaba inactivo). Hallazgo: el módulo tiene 2 capas independientes:
- Capa 1 = permisos por usuario (
inventarios+inventarios_preparar/_entregar/_revisar). Gate enSidebar.vuey policiesMovimientoInventarioPolicy/ViajeCompraPolicy. Hoy capa 1 ON parcial = piloto. - Capa 2 = flag
INVENTARIO_VIAJE_LINK(config/env). Cuando true, al guardar un viaje se crea automáticamente unMovimientoInventarioborrador. Consumidores:ViajesController::guardarViaje:205,create/edit:358,GuardarViaje::rules:25,FormaViaje.vue:96(usarSelectorInventario). Hoy capa 2 OFF (variable no definida en.env).
UPDATE permisos SET inventarios_preparar=1, inventarios_entregar=1, inventarios_revisar=1 WHERE usuario_id=14 autorizado citando el query (classifier estricto). Selene Ortega ahora puede probar el flujo manual end-to-end. Memoria nueva [[reference-amadeus-inventarios-two-layers]]: regla durable que la capa 2 NO se toca sin autorización explícita per-sesión.
5. Análisis completo del backlog Jira (51 tickets). Sergio aportó 3 screenshots de ClickUp/Jira y luego exportó CSV completo. Parseado con Python (51 issues To Do/Medium), categorizado uno por uno contra el estado del código actual. Nuevo doc projects/amadeus-backlog.md con tabla, leyendas, buckets, dependencias.
Decisiones de Sergio en sesión (orden a-b-c-d-e que yo recomendé):
- (a) DONE confirmados (5): AM-46, AM-47, AM-84, AM-134, AM-141. Sergio confirma "AM-134 = viaje" — cierre limpio.
- (b) Descartados (9, no 8 como yo había contado): AM-9, AM-10, AM-71, AM-85, AM-91, AM-98 (epics de 1 palabra) + AM-97 (stale por decisión 2026-05-19 de remover
Producto::boot::created) + AM-58, AM-107 (ambigüedad bloqueante). - (c) Decisiones de producto (5):
- AM-132 descartado (Sergio: "habia hecho dos lugares donde ver reportes, ya se solucionó").
- AM-55 → Opción A: layer de roles persistidos encima de permisos granulares (schema
roles,rol_permiso, refactor policies, Nova UI). L, sprint 17. - AM-72 → Opción C: push + email fallback (NO SMS pagado). Reusa VAPID + Gmail Workspace. Scheduled job 16:00 → push; si no leído en X horas → email. S+, sprint 17.
- AM-146 descartado (over-engineering vs flag global existente
recibir_notif_viajes). - AM-143 → Opción D: reproducir caso real antes de decidir fix. Botón SÍ existe (
Checklist.vue:104-120); el mensaje aparece cuandoanyBad=truecon defaultok=false. En pausa hasta screenshot real.
- (d) Consolidación → Opción A con foto opcional: AM-65 + AM-73 + AM-90 + AM-99 + AM-136 → EPIC-NOTAS (modelo
Notapolimórfico connotable_type/notable_id/autor_id/contenido/notificar_al_abrir+ tablanota_fotospara multi-foto, UI inline reusable, listener al crear viaje). 5 tickets de M cada uno reducidos a 1 epic M-L. Sprint 18. - (e) Asignación a sprints (aprobada): Sprint 15 = quick wins (9× S, ~2 días). Sprint 16 = reportes/visualizaciones (4× M). Sprint 17 = roles + notif scheduled (L+S+, debe ir antes que NOTAS para no migrar policies dos veces). Sprint 18 = EPIC-NOTAS. Sprints 19-21 = UX/mobile + datos inventarios + features viaje. Backlog diferido: AM-49 (traducciones L), AM-57 (BD equipos pide aclaración), AM-139 (Fase 2 N vehículos), AM-140/144 (dep. capa 2), AM-143 (repro).
Acción humana pendiente (Sergio en Jira): cerrar los 17 tickets resueltos hoy + crear sprints 15-21 con la distribución del doc.
Commits hub del día:
f92e04dUX badge deployadoee1ca7frediseño móvil deployado0f3a2a5limpieza varia (Karla, deploy.sh, build huérfanos, código muerto)644a888piloto Selene + arquitectura del switch documentada + memoria de las 2 capas57fff07import + análisis inicial del backlog Jiraca3ac4fsesión de decisiones (5 cerrados, 12 descartados, 5→1 epic)ffd42d9priorización aprobada (29 abiertos → 7 sprints + diferidos)
Memorias nuevas:
- [[reference-laravel-hasone-order]] —
hasOnesin orderBy = PK asc, no desc. - [[reference-amadeus-inventarios-two-layers]] — 2 capas independientes; capa 2 SOLO la toca Sergio.
Backups SQL en prod: pre-notifux-1921 + pre-mobile-1936 + pre-cleanup-1945 + pre-deploysh-2008, todos 1.2M, conservados.
HEAD prod final: 008118c (post-pull de deploy.sh trackeado). Working tree de prod limpio salvo .env.backup-20260519-2234.
holbox — captura de pendiente: recordatorios de fidelidad por WhatsApp
Pidió Sergio: anotar pendiente para mandar mensajes de fidelización por WhatsApp en los mismos tiempos que los correos (días 3/7/15/25/30 desde la generación del cupón). Ayuda con el diseño del texto. Sin implementar — solo capturar.
Hice:
- Diseñé los 5 textos uno por día con tono adaptado al canal (más conversacional que el email, conservando frases ancla de los textos literales que aprobó Aarón).
- Plantillas tentativas en Meta:
fidelidad_dia_03/07/15/25/30, categoría MARKETING, variables{{1}}=primer nombre,{{2}}=código,{{3}}=%,{{4}}=vigencia. - Documenté decisiones técnicas pendientes (5 plantillas vs 1 con cuerpo variable, paralelo email+WhatsApp, categoría Meta, helper
primerNombre), implementación a alto nivel (columnarecordatorios_whatsapp_enviadosJSON separada del email, JobSendWhatsappFidelidadanálogo aSendWhatsappTicket, extenderdescuentos:enviar-recordatorioso crear comando paralelo) y riesgos de negocio (costo Meta por mensaje MARKETING, saturación del canal — considerar enviar solo días 7 y 25 por WhatsApp y dejar email para los 5). - Detalle completo en
projects/holbox.mdbitácora 2026-05-20 (PM-4). - Tarea capturada en
PENDIENTES.mdbajo holbox +upda 2026-05-20.
Bloqueador: mismo prerequisito que el ticket por WhatsApp — activar el feature flag global (WHATSAPP_FEATURE_ENABLED=true + token + phone_number_id + método de pago en Meta).