Hub

2026-05-18

lunes · 18 de mayo de 2026

2026-05-18 (lunes)

Sesión mediodía — electro-ia: salvar Fase 2.6 sin commitear + incidente del jefe del día

Pidió Sergio: avanzar pendientes de electro-ia.

Hallazgo al diagnosticar: la Fase 2.6 (analyze_excel, comando /reset, buildHistory sin footer, mitigaciones contra alucinación, ollama 32b + num_ctx 8192) llevaba 2 días viva solo como working-tree edits en laptop-ia:/home/electroia/electro-ia. 9 archivos modificados + 1 nuevo + 12 backups .bak.* desordenando el repo. Riesgo real: si la laptop tiene un blip o alguien hace git checkout ., se pierde toda la sesión de la noche del 15.

Hice:

  1. Limpieza de backups: moví los 12 .bak.* a .trash-bak/ (no borré — Sergio puede revisarlos si encuentra algo que falte). .gitignore ahora ignora *.bak.*, .trash-bak/, artifacts/.
  2. 3 commits en main de laptop-ia (sin remote — el repo es local-only):
    • 3a52a47 chore: ignorar *.bak.* y artifacts/
    • c630178 feat(ollama): subir default a qwen2.5:32b-instruct + num_ctx 8192 + timeout 600s.env no committeado (sigue gitignorado).
    • 48353b4 feat(tools): analyze_excel + comando /reset + buildHistory sin footer tecnico — 9 archivos, 292 inserts, 13 deletes.
  3. Documenté en hub (projects/electro-ia/README.md bitácora 2026-05-18 + tareas Fase 2 actualizadas).

🚨 Incidente del jefe descubierto en el query a message_log:

El 2026-05-18 08:06 MDT (hoy en la mañana) Gustavo Chavira mandó audio: "Buenos días, ¿me puedes dar el clima del día de hoy para El Paso, Texas?". Transcript en 0.8 s. Primer turn del agente: 53 s, modelo llamó web_fetch(https://www.weather.gov/documentation/services-web-api#current)URL incorrecta: eligió la página de docs en vez de un endpoint real de clima. La tool corrió OK (descargó HTML grande de la página de documentación). Segundo turn (sintetizar respuesta con el contenido): colgó 5 minutos y falló con fetch failed contra ollama localhost. Bot respondió: "Tuve un problema con el modelo local. Intenta /api o vuelve a intentar". Texto + voz enviados.

Hipótesis (necesita validación):

  • Causa principal: el body de 64 KB que devolvió web_fetch excede num_ctx=8192 cuando se mete al prompt como tool result. Ollama se atragantó. Opciones: bajar default body de web_fetch a 16 KB (más simple), subir num_ctx (cuesta VRAM, ya estamos en 6.9 GB de 8), o agregar tool dedicada de clima.
  • Causa secundaria: el modelo eligió la primera URL plausible (docs) en vez de un endpoint real. System prompt podría hint hacia APIs JSON.

Pendientes después de esta sesión (no cambia el panorama del jefe — el bot sigue caído para él hasta que alguien le diga "vuelve a intentar"):

  1. Validar /reset + Excel (Sergio, < 5 min, en su Telegram).
  2. Reproducir el incidente del jefe — mandar la misma URL al bot, ver si efectivamente cuelga.
  3. Decidir mitigación: cap body web_fetch, num_ctx mayor, o tool clima.

Estado del servicio: electro-ia.service activo desde 2026-05-15 18:23, sano. Single instance del jefe arroja el fetch failed iter:1 — todo lo demás funciona (audio, PDFs, tools de lectura).


Sesión mediodía continuación — electro-ia: Fase 2.6 validada E2E

Pidió Sergio: correr el smoke /reset + Excel ahora que el código estaba committeado y la infra estable.

Flujo (Sergio en su Telegram, yo monitoreando desde acá):

  1. /reset → marcador __RESET__ en message_log, ack "🧹 Listo, contexto reseteado. Empezamos de cero." en <1 s.
  2. Upload Anexo_4_Acta_de_validacion_de_pago_por_consumo_de_AB_abr_2026.xlsx (23 KB → file_id=6 en uploaded_file). Ack determinista correcto: "Puedo abrirlo: pregúntame qué hojas tiene…" (sin caer en "solo PDFs").
  3. "Que hojas tiene" → history built rows_fetched:3 included:3 (cutoff de __RESET__ funcionó — antes había 160+ rows de respuestas alucinadas que ya no contaminan).

Telemetría del agente:

  • Primer turn ollama: 31.1 s, tool_calls: 1analyze_excel({file_id: 6}).
  • Tool: 76 ms, sheet: "ACTA DE VALIDACION", total_rows: 28, total_cols: 28, nonempty_cols: 14.
  • Segundo turn ollama: 62.5 s, tool_calls: 0 (síntesis).

Respuesta al usuario (literal):

Según el análisis del archivo Anexo_4_Acta_de_validacion_de_pago_por_consumo_de_AB_abr_2026.xlsx, tiene exactamente 1 hoja llamada "ACTA DE VALIDACION".

¿Te interesa ver un resumen de esta hoja o alguna otra información específica?

🔧 analyze_excel(file_id=6) → ok · 76ms

Veredicto: cero alucinación. Cita literal del nombre real de la hoja. Footer técnico es el real (insertado por el sistema, no escrito por el modelo). La combinación de las 4 mitigaciones del 2026-05-15 noche + /reset resolvió el bloqueo. Fase 2.6 cerrada.

Aprendizaje meta: el footer técnico imitado por el modelo era la patología más sutil — incluso con history limpio (/reset aplicado), si los previous assistant messages en context tenían el formato 🔧 tool(...) → ok, el modelo lo replicaba como texto sin llamar la tool. El strip del footer en buildHistory fue la pieza que destrabó el patrón a la raíz, no las reglas del prompt.

Próximos en el proyecto (panorama post-Fase 2.6):

  1. 🚨 Incidente del jefe sigue pendiente — todavía no se ha tocado. Próxima sesión: reproducir, decidir entre cap body web_fetch, num_ctx mayor, o tool dedicada de clima.
  2. ANTHROPIC_API_KEY real — destrabaría comparación lado-a-lado API vs local que el jefe pidió desde 2026-05-14.
  3. Agregar 2-3 ingenieros más al onboarding (slugs + roles pendientes de decidir).

Sesión tarde — monitoring-homologation: devices CSV generator

Pidió Sergio: avanzar Fase 1A del proyecto. Siguiente paso del lado mío era el devices CSV generator (los 16 huérfanos origen=manual AND administrado=true AND uisp_id IS NULL).

Diseño aprobado: comando artisan en es-antenas-new (vs endpoint UI vs script Python). Un solo CSV (vs uno-por-sitio) — el sitio físico se asigna post-import en UISP UI.

Corrección crítica de Sergio (memoria nueva del hub): managed vs blackBox NO viene de plantilla.lectura_datos (eso solo describe qué lectura aplica). Viene del nombre exacto de la plantilla. Lista canónica managed (6 plantillas): PowerBeam M5, Rocket Prism 5AC, EdgeRouter, Ubiquiti AF60, Ubiquiti AF3X/AF4X/AF5X/AF11/AF24, Ubiquiti AF5XHD.

Hice:

  1. Branch feature/exportar-csv-uisp en ~/code/es-antenas-new.
  2. app/Console/Commands/ExportarCsvUisp.php con signature es:exportar-csv-uisp {--dry-run}. Match exact case-sensitive contra constante PLANTILLAS_MANAGED. Inferencia de deviceRole por nombre (netonix→switch, edgerouter/erx/mikrotik/accedian→router, resto→wireless). Output en storage/app/uisp-imports/devices-YYYY-MM-DD.csv.
  3. --dry-run imprime tabla por plantilla con conteo managed/blackBox antes de escribir nada.
  4. PHP lint OK. php artisan list registra el comando.
  5. NO commiteado — Sergio aprueba después de validar --dry-run en prod.
  6. Memoria del hub guardada: project_es_antenas_plantilla_managed_vs_blackbox.md.
  7. Creé ~/agy/.claude/settings.json con worktree.bgIsolation: "none" (autorizado por Sergio) para destrabar ediciones in-place del hub para bg sessions.

Pendientes después de esta sesión:

  1. Sergio: php artisan es:exportar-csv-uisp --dry-run en prod, revisar la tabla, avisar si alguna plantilla está mal categorizada.
  2. Sergio: después de validar, generar CSV final, subir a UISP UI (precondición: uisp-sites-fisicos-2026-05-15.csv ya cargado). Aprobar commit + merge a master.
  3. Yo (próxima sesión): implementar el sync UISP→es-antenas-new — script que polea UISP API, matchea por IP/name, hace UPDATE de uisp_id + origen. Cierra el ciclo de la Fase 1.

Sesión tarde — electro-ia: fix preventivo del web_fetch cap (incidente del jefe)

Pidió Sergio: atacar el incidente del jefe del 2026-05-18 08:06 — bajar cap body de web_fetch de 64 KB a 16 KB para que no sature num_ctx=8192 del modelo local.

Diagnóstico de causa raíz (revalidado):

  • Audio del jefe: "clima de hoy para El Paso, Texas".
  • Modelo eligió web_fetch(https://www.weather.gov/documentation/services-web-api#current) — URL incorrecta (página de documentación del API, no de datos).
  • Tool descargó 64 KB de HTML (era el cap default). Page completa son ~85 KB.
  • Segundo turn de ollama (sintetizar respuesta con el HTML como tool_result): 64 KB de HTML ≈ 15-20k tokens, satura num_ctx=8192 (system prompt ~1500 + tools ~500 + history + tool_call args + tool_result). Ollama colgó 5 min y falló con fetch failed. Bot mandó fallback "problema con el modelo local".

Hice (commit eb6976e en laptop-ia main):

  • src/tools/web_fetch.js: DEFAULT_MAX 64 KB → 16 KB. HARD_MAX sigue en 512 KB (el modelo puede subirlo explícitamente con max_bytes si necesita). Descripción del schema actualizada señalando el tradeoff.
  • Smoke directo contra la misma URL del incidente con el nuevo cap: 200 OK, 888 ms, truncated: true, 16384 bytes (vs 64 KB anteriores). Ahora cabe ~4k tokens — espacio sobrado en num_ctx=8192.
  • Servicio reiniciado (NOPASSWD systemctl). Health OK: default_backend: local, telegram bot @ElectroIA_bot activo.

Lo que el fix NO arregla:

  • Que el modelo elija la URL incorrecta. Para "clima" debió ir a api.weather.gov/points/{lat,lon} JSON, no a la página de docs HTML. Esto es model behavior — se podría empujar vía system prompt ("para clima/finance/datos estructurados usa APIs JSON, no docs HTML") o con tool dedicada. Por ahora deja al modelo retry y aprender por iteración.

Validación E2E pendiente:

  • Que el jefe (o Sergio simulando) repita el audio "clima hoy El Paso TX" al bot. Esperado: el modelo llama web_fetch, vuelven 16 KB, segundo turn ollama responde en tiempo razonable (~40-75 s como los otros casos), aunque la respuesta del clima quizá siga siendo defectuosa por la elección de URL — eso es el siguiente nivel de fix.

Estado de Fase 2 después de hoy:

  • Fase 2.6 cerrada validada (/reset + Excel).
  • Incidente jefe: causa primaria (cap body) arreglada; causa secundaria (URL choice) pendiente.
  • Sigue pendiente ANTHROPIC_API_KEY real (sigue ortogonal).

holbox — Pantalla admin de preview de WhatsApp

Pidió Sergio: funcionalidad para probar los tickets de WhatsApp antes de habilitarlos en final. El admin del cliente debe poder ver cómo van a llegar los mensajes para aprobar el template. Decisión Sergio: opción B — dejar construido el envío real para cuando tenga el token de Meta y el template aprobado.

Hecho:

  • MetaCloudWhatsAppSender::sendPreview(toE164, venta) nuevo método público (NO en contract). Mismo payload que sendTicket, pero teléfono override y retorna {success, status, request_payload, response_body, message_id, error}. Loggea con prefijo whatsapp.preview.*.
  • WhatsAppPreviewController con show/send. Si driver=meta + token configurado → POST real; si no → simulación (arma payload sin pegarle a Meta). Resultado vía session('preview_result').
  • Ruta whatsapp/preview GET+POST en grupo role:admin. Tests cubren 403 para gerente/asociada.
  • Vista Pages/WhatsApp/Preview.vue: banner del modo, form (dropdown ventas + input teléfono), mockup tipo WhatsApp con cuerpo del template + botón "Ver ticket" que abre la vista pública real del ticket, panel de resultado con HTTP status + message_id + JSON response + payload colapsable.
  • Link "Probar envío de WhatsApp" en /configuracion (admin only, independiente del feature flag).
  • 7/7 tests verde: rol guards, modo log no manda HTTP, modo meta postea correcto, error 4xx capturado limpio, teléfono inválido = 422.
  • Camino productivo (SendWhatsappTicket job + contract WhatsAppSender::sendTicket) intacto.

Pre-requisito Meta: para envío real antes de aprobación final, agregar al destinatario como test recipient en Meta Business Manager.

Estado: sin commit. Sergio decide cuándo deployar.


Sesión tarde (cont.) — monitoring-homologation: seed uisp_id baja dry-run de 116 → 18

Pidió Sergio: corrió php artisan es:exportar-csv-uisp --dry-run en prod, salieron 116 huérfanos en vez de los ~18 esperados.

Causa raíz: el filtro uisp_id IS NULL incluía los 98 dispositivos que ya tienen contraparte en UISP, porque el sync UISP→es-antenas-new (que setearía uisp_id) aún no existe.

Hice:

  1. Cruce Python local entre dispositivos-vivos-2026-05-15.tsv y uisp-devices-2026-05-15.json por IP exacta. Resultado: 98 matches (ip-only, sin colisiones) + 18 no-match + 0 ambiguos. Los 18 = 16 huérfanos sin candidato + 2 con sugerencia alta que no matchean por IP exacta (Netonix El Gato, MT Cliente Villa Ahumada).
  2. Generé projects/monitoring-homologation/seed-uisp-id-2026-05-18.sql: un solo UPDATE con CASE para los 98, setea uisp_id + origen='claimed'. Transaction + COMMIT explícito + comentario por fila + SELECT de verificación.
  3. Sergio ejecutó en prod, conteos cuadraron 98/18/0. --dry-run ahora reporta 18 huérfanos — expectativa real.

Pendientes después de esta sesión:

  1. Sergio: correr sin --dry-run → CSV con 18 filas, subir a UISP UI (sites cargados ya).
  2. Sergio: aprobar merge de feature/exportar-csv-uisp cuando cierre el ciclo.
  3. Yo (próxima sesión): implementar el sync UISP→es-antenas-new recurrente. Reemplaza el SQL one-shot por proceso continuo que también recoge los 18 cuando aterrizan en UISP.

holbox — DP de optometría en un solo campo (PM)

Pidió Sergio (foto Aarón): quitar los subcampos "Binocular" y "Monocular" del bloque "DP (Distancia Pupilar)" en el form de captura de optometría, dejar un solo renglón.

Hecho:

  • Migración reversible consolida dp_binocular/dp_monoculardp (decimal 5,2 nullable), backfill COALESCE(binocular, monocular) para preservar lo capturado en los 5 días que la feature lleva en prod.
  • Optometria model + OptometriaController validación → dp único.
  • 4 lugares de UI: form admin (un input), tabla histórica (una columna), banda POS al elegir cliente, ticket interno y ticket público — "DP: X bin / Y mon" → "DP: X".
  • Test OptometriaControllerTest ajustado; 9/9 verde en filtro Optometría.

Deploy: commit 43bf8cb → push → GHA run 26058182934 success. php artisan migrate --force corrió en prod.


Cierre del día

  • holbox: 2 features cerradas y deployadas (preview WhatsApp + DP consolidado).
  • PENDIENTES.md actualizado: marca upd 2026-05-18 en sección holbox, nota sobre /whatsapp/preview añadida al pendiente "Activar WhatsApp en prod", entrada nueva en "Cerrado reciente".
  • Otros proyectos: sin tocar — sesión exclusiva holbox.

Sesión tarde-noche — electro-ia: infraestructura + Antigravity API online

Contexto: Sergio detectó que su pregunta 170 ("trata esa hoja") nunca tuvo respuesta. Diagnóstico: yo mismo maté al agente con sudo systemctl restart electro-ia justo durante el segundo turn de síntesis. Lección operacional: antes de restart, verificar que no hay mensajes en flight.

Iteración del audio del clima:

  • Sergio mandó audio "clima Cd. Juárez" — local respondió con "Tuve un problema con el modelo local". Mismo patrón que el incidente del jefe del 08:06, pero NO por HTML grande: esta vez fue por el tool_result del Excel + history acumulado.
  • Causa raíz revisada: num_ctx=8192 se satura cuando cualquier tool_result grande entra al second turn. El cap de web_fetch (16 KB) solo cubre uno de varios casos.

Fix infra: OLLAMA_NUM_CTX=16384

  • Aplicado en .env con backup, servicio reiniciado. VRAM OK (no OOM en el smoke siguiente).
  • Smoke E2E: /reset + audio del clima → local respondió en ~100 s sin colgar. La calidad de la respuesta sigue mediocre porque el modelo inventa URLs (city_id 231476 que resultó ser Aukstadvaris/Lituania, no Cd. Juárez en accuweather).

Sergio pidió fix más arquitectural ("no quiero caer en construir una tool por cada falla"). Surfacé 3 palancas:

  1. Tool generalista web_search(query) con Brave/DuckDuckGo en vez de especialistas.
  2. Regla en system prompt para iteración silenciosa.
  3. Techo del modelo local — ANTHROPIC_API_KEY es el unlock real.

Sergio eligió (2): bajo costo + alto payoff inmediato.

Fix prompt (commit 4c0393d) — regla inviolable "iteración silenciosa con tools de lectura":

  • Si una tool de lectura corre pero no entrega lo pedido, intentar variantes EN EL MISMO TURN sin preguntar.
  • Repetir hasta 3-4 veces, después sí preguntar.
  • 3 ejemplos concretos en el prompt (clima, ssh, read_file) + ejemplo MAL / ejemplo BIEN.
  • Excepción válida: forbidden / auth required / ambigüedad real (ahí sí pregunta y espera).

Pivot: Sergio consiguió la ANTHROPIC_API_KEY y la inyectó él mismo en .env (sin pasar por chat — evita transcript leakage). Servicio reiniciado.

Smoke ~10 turns con Sonnet 4.6 (/api prefix):

Mensaje Tiempo Resultado
/api Hola 3 s Tag 🤖 claude-sonnet-4-6 correcto
/api Clima Cd. Juárez 5 s web_fetch(wttr.in/Ciudad+Juarez) directo, sin inventar URLs. Respuesta cita fuente
/api resumen del Excel 14 s Análisis estructurado con datos reales del Anexo
/api a cuáles servidores tienes acceso <10 s Antigravity leyó su propio policy/hosts.yaml, respondió tabla — comportamiento agéntico genuino
/api agrega host monitoreo ~10 s Identificó correctamente que ~/.ssh/config y hosts.yaml están fuera de su allowlist, sin pretender hacerlo

Spend total: $0.19 USD después de ~10 turns. A ese ritmo, $100/mes ≈ ~5000 turns. Margen sobrado.

Sergio's verdict (textual): "Ya hice pruebas y si, definitivamente se comporta mucho mejor". Pendiente del jefe (/api vs /local comparación lado-a-lado) ya destrabado.

Bug nuevo detectado durante el smoke:

  • Cuando Sergio mandó un mensaje sin /api después de varios con /api, entró al local. El modelo local imitó el tag de Antigravity que vio en history y respondió con 🖥️ local Y 🤖 claude-sonnet-4-6 ambos. Mismo patrón de imitación que el footer técnico ya strippeado. Fix conocido: extender el strip en agent.js::buildHistory para limpiar también el header de backend. ~5 LOC. Pendiente para próxima sesión.

Estado al cierre del día:

  • ✅ Fase 2.6 cerrada validada (analyze_excel + /reset funcionando).
  • ✅ Fase 2.7 abierta y cerrada en la misma sesión: Antigravity API online.
  • ✅ Causa primaria del incidente del jefe arreglada (web_fetch cap 64→16 KB, commit eb6976e).
  • ✅ Causa más general arreglada (num_ctx 8192→16384).
  • ✅ System prompt con regla de iteración silenciosa (commit 4c0393d).
  • ANTHROPIC_API_KEY aplicada y validada.

Commits de electro-ia hoy (5 totales sobre main en laptop-ia, sin remote):

  1. 3a52a47 chore: ignorar .bak. y artifacts/
  2. c630178 feat(ollama): qwen2.5:32b + num_ctx 8192 + timeout 600s
  3. 48353b4 feat(tools): analyze_excel + /reset + buildHistory sin footer
  4. eb6976e fix(web_fetch): DEFAULT_MAX 64 KB → 16 KB
  5. 4c0393d feat(prompt): regla iteración silenciosa

Sesión noche — electro-ia: cierre del bug de imitación de header de backend

Pidió Sergio: "Documenta, pero vamos a seguirle dando". Después de documentar, ataqué el último cabo suelto del smoke de Antigravity API.

Bug: cuando el local respondía después de turns con /api, veía los headers 🤖 \claude-sonnet-4-6`en assistant messages previos del history y los imitaba como texto. Resultado: respuestas con🖥️ `local`Y🤖 `claude-sonnet-4-6`` ambos arriba.

Fix (commit f1d86ea en laptop-ia):

  • src/agent.js: agregado BACKEND_HEADER_RE = /^[\s​]*(?:🤖|🖥️)[^\n]*\n+/ que matchea la línea inicial con el tag de backend.
  • stripTechFooter ahora aplica ambos regex en cadena: .replace(TECH_FOOTER_RE, '').replace(BACKEND_HEADER_RE, '').trim().
  • Comment block actualizado para documentar ambos patrones.

Smoke directo de 4 casos (regex aislado):

Input Output
🖥️ \local`\n\nLo siento, hubo problema.\n\n─────\n🔧 web_fetch → ok` Lo siento, hubo problema.
🤖 \claude-sonnet-4-6`\n\n¡Hola!` ¡Hola!
Sin headers ni footers, texto plano Sin headers ni footers, texto plano
🖥️ \local`\n\nMulti\nline body` Multi\nline body

Comportamiento correcto en los 4. Servicio reiniciado.

Total commits de electro-ia hoy: 6 (sobre main en laptop-ia, sin remote):

  1. 3a52a47 chore: ignorar .bak. y artifacts/
  2. c630178 feat(ollama): qwen2.5:32b + num_ctx 8192 + timeout 600s
  3. 48353b4 feat(tools): analyze_excel + /reset + buildHistory sin footer
  4. eb6976e fix(web_fetch): DEFAULT_MAX 64 KB → 16 KB
  5. 4c0393d feat(prompt): regla iteración silenciosa
  6. f1d86ea fix(agent): strippear también el header de backend del history

Resumen del día (electro-ia): Empezamos con working tree de 2 días sin salvar y terminamos con Fase 2.7 (Claude API online) cerrada, infra robusta (num_ctx 16384, prompt con iteración silenciosa), y todos los bugs detectados durante el smoke arreglados. Spend hoy: ~$0.36 USD.


Sesión tarde — monitoring-homologation: sync UISP→es-antenas-new (claim + schedule + logs)

Pidió Sergio: "Quiero seguir con monitoring-homologation".

Hice (3 sub-sesiones del proyecto, ver projects/monitoring-homologation.md bitácora 2026-05-18 segunda/tercera/cuarta para el detalle técnico):

  1. Sync command escritoapp/Console/Commands/SyncUisp.php en es-antenas-new. Pula /nms/api/v2.1/devices con x-auth-token, matchea por IP contra dispositivos con origen=manual AND administrado=true AND uisp_id IS NULL, eleva 1-a-1 a claimed cuando hay match único. Reporta ambiguos (skip), nuevos (no inserta — política pendiente), desaparecidos (no soft-delete). --dry-run + --fixture=path.json. Idempotente. Config services.uisp.{base_url,token,verify_ssl} + env. 7 tests Pest verde con SQLite override (PHP local no tiene pdo_mysql).
  2. Validado end-to-end contra prod — Sergio configuró UISP_BASE_URL/UISP_API_TOKEN (token nuevo generado desde UISP UI, dedicado a es-antenas-new — separación limpia del de oxidized) y corrió --dry-run: Claims=0 (los 98 ya seedeados con uisp_id), Nuevos=277, Desaparecidos=0. Conteos exactamente como predije. UISP API responde OK, parsing OK, CIDR stripping OK.
  3. Schedule + logsSchedule::command(SyncUisp::class)->hourly() en routes/console.php dentro del bloque if (!app.remoto). Logs estructurados (Log::info para claims, Log::warning para desaparecidos, Log::error para UISP API down) buscables con grep sync_uisp.

Regla nueva guardada en memoria (feedback_monitoring_homologation_direct_master.md): en monitoring-homologation se trabaja directo en master en ~/code/es-antenas-new, sin feature branches. Sigue aplicando "no commit/push sin autorización per-sesión".

Commits de es-antenas-new hoy en master (3, pushed):

  1. d5721b7 feat(dispositivos): comando es:sync-uisp para fase claim del sync (4 archivos, 367 inserts)
  2. c394b49 Merge branch 'feature/sync-uisp' (no-ff per patrón previo)
  3. fc1ea47 feat(sync-uisp): schedule hourly + logs de claims/desaparecidos/errores

Falta del lado de Sergio (próxima sesión, decisión tomada ya):

  • UISP está actualizándose (mantenimiento del SaaS) → no pudo subir el devices CSV de los 18 huérfanos hoy.
  • Próxima sesión: subir CSV + git pull en monitoreo para activar el schedule. Cuando se cierre ese ciclo, los 18 deben elevarse de manual a claimed automáticamente en el siguiente tick horario.

Próximos pasos míos pendientes (sin urgencia, esperan a Sergio):

  • Política de bajas (eliminado_de_uisp_at cuando uisp_id desaparece).
  • Refresh de fields para origen=uisp (no toca claimed).
  • Política de altas con notificación (insert automático origen=uisp + bandeja UI o correo).

Sesión noche tardía — electro-ia: backoff a qwen2.5:14b por límite de VRAM

Contexto: después de los fixes del bug del header y haber documentado el día, Sergio probó otra vez con el local sin /api. Falló igual con "problema con el modelo local".

Diagnóstico nuevo (no era history):

  • agent.js ya tenía max_messages: 6 para local (commit 9623447 de la sesión anterior).
  • Log mostró rows_fetched: 14, included: 6 — el cap funcionó.
  • Primer turn ollama 37 s, tool_calls:1 (analyze_excel) → 81 ms.
  • Segundo turn: fetch failed iter:1 exactamente a los 300 s (5 min).

La causa real (verificada por ollama /api/ps):

  • Con num_ctx=16384 que aplicamos para arreglar el cuelgue anterior, ollama desplazó la mayor parte del modelo a CPU: size_vram: 2.87 GB de un modelo de 25 GB. 89% en CPU.
  • Inferencia CPU de un 32B = 1-2 tok/s. Generar 200-500 tokens = 3-8 min. El timeout interno (no nuestro OLLAMA_TIMEOUT_MS=600s sino algún default de keep-alive / fetch / ollama) cortó a los 5 min.

Tradeoff irreducible con este hardware:

  • num_ctx=8192 + qwen2.5:32b → cabe en GPU (6.9/8 GB), rápido (40-75 s/turn), pero tool_results grandes saturan context.
  • num_ctx=16384 + qwen2.5:32b → no cabe en GPU (3 GB en GPU, 22 GB en CPU), lento (5+ min, falla).
  • No hay sweet spot para qwen2.5:32b en RTX 4060 Laptop 8 GB con tool_use real.

Sergio eligió: bajar a qwen2.5:14b-instruct con num_ctx=8192 (code default).

Cambios (solo .env, no commit de código):

  • OLLAMA_CHAT_MODEL=qwen2.5:14b-instruct
  • Removido OLLAMA_NUM_CTX=16384 (usa default 8192 de src/backends/ollama.js)
  • Backup .env.bak.20260518-*
  • Servicio reiniciado, warmup OK (8.6 tok/s vs 1-2 tok/s del 32b parcialmente offloaded).

Smoke E2E:

  • Sergio: "Puedes darme un resumen del archivo?" (sin file_id en context) → 14b pidió file_id (19 s, sin alucinar).
  • Sergio: "tiene file_id=8" → 14b llamó analyze_excel + sintetizó respuesta de 2307 chars con datos reales del Anexo (Telcel, abril 2026, enlaces CE, percentil 95, columnas correctas).
  • Total ~5 min (la primera síntesis pesó por cold cache); turnos posteriores ~98 s. Aceptable.

Veredicto: 14b es lo que el hardware aguanta para uso real con tool_use. 32b queda como curiosidad para inferencia sin tools.

Comentario sobre el código default: src/backends/ollama.js sigue con qwen2.5:32b-instruct como default. No lo cambié porque está la decisión del jefe del 2026-05-15 ("aunque tarde, que no alucine") en el commit c630178. El .env override gana; cualquier fresh clone tendría que decidir explícitamente. Documentado aquí para que si alguien más toca esto entienda el contexto.

Estado final del día (electro-ia):

  • ✅ Backend api: Sonnet 4.6 funcionando, $0.36 USD spend.
  • ✅ Backend local: qwen2.5:14b @ num_ctx=8192, funcional sin colgarse.
  • ✅ 6 commits de código + 2 cambios de .env (OLLAMA_CHAT_MODEL 32b→14b, OLLAMA_NUM_CTX quitado).
  • Próxima sesión: smoke real con el jefe ahora que ambos backends están sanos.

Sesión PM-3 a PM-6 — holbox: bolsitas + template WhatsApp completo

Contexto: sesión continua en holbox. Cuatro entregas a prod en una tarde.

1. Captura de 3 pendientes nuevos (Aarón)

  • Totales de optometría en reporte (cantidades + montos) — abierto, falta SUM(precio_optometria * cantidad).
  • Asociadas: vista "mis ventas del día" con totales por categoría — verificado que NO existe.
  • Flag para excluir categorías del disparador precio_sale — bug latente confirmado en PosController::store.

2. PM-3 — Flag cuenta_para_precio_sale (commit 04ad770, GHA 26066193131)

  • Backfill BOLSITA + ESTUCHE a false en migración. 6 categorías de lentes en true (compat).
  • Backend (PosController 3 puntos: catálogo, conteo, aplicación) + frontend espejo (POS/Index.vue).
  • UI: switch nuevo en /categorias/{id}/edit.
  • 4 tests Pest nuevos + bonus: arreglados 11 tests Pos pre-existentes que faltaba kioskSession() (suite pasó de 4/18 verde → 15/11).
  • Cambio operativo: 1 lente + 1 bolsita ya no recibe descuento 2x. Aarón debe avisar a las asociadas.

3. PM-4 — Template ticket_holbox_v2: cuerpo enriquecido (commit ce93f6a, GHA 26067189002)

  • Respuesta a Aarón: WhatsApp Cloud API soporta hasta 10 variables {{N}} en body.
  • Helper Venta::modeloPrincipal(): primer producto NO regalo + "(+N más)". Regalos no cuentan.
  • Refactor MetaCloudWhatsAppSender::buildPayload() público compartido por sendTicket(), sendPreview() y modo simulado del controller — cero drift posible entre lo que el admin previsualiza y lo que el cliente recibe.
  • Preview UI interpola {{1}}/{{2}} con la venta seleccionada. Default template en config cambia.

4. PM-5 — Saludo personalizado en template (commit c76b96e, GHA 26068094831)

  • Aarón pidió personalizar el saludo. Sergio escogió Opción A ("Hola, María.") con fallback cliente.
  • Helper Venta::nombreClienteCorto() con trim() + fallback (Meta rechaza variables vacías).
  • Renumeración a 3 variables: {{1}}=nombre, {{2}}=modelo, {{3}}=folio.
  • 3 tests nuevos del helper. Suite WhatsApp 22/22 verde.

5. PM-6 — Meta aprobó como ticket_holbox_v3 (commit d35174f, GHA 26068398056)

  • Re-submisión, cuerpo idéntico al v2.
  • Bump del slug default en 4 sitios. Bonus: alineado el fallback productivo del AppServiceProvider que estaba colgado en ticket_link_v1 legacy desde el primer commit de WhatsApp.

Pendiente para siguiente sesión

  • Activar WhatsApp en prod: bloqueado por que Aarón configure método de pago en el número de WhatsApp Business + genere el META_WHATSAPP_TOKEN (System User token). Cuando los tenga: 4 líneas en .env de DigitalOcean + reload php-fpm + smoke con test recipient. Sin redeploy.

Estado final del día (holbox)

  • ✅ 4 commits a prod (04ad770, ce93f6a, c76b96e, d35174f) sobre los 2 previos del día (375bc47, 43bf8cb) — 6 deploys el 2026-05-18.
  • ✅ Suite Pos: 15 verde (de 4) — +11 net pass por arreglar kioskSession faltante.
  • ✅ Suite WhatsApp: 22/22 verde (56 assertions).
  • ✅ Template ticket_holbox_v3 aprobado por Meta, código alineado al slug.
  • Próxima sesión: switch del .env cuando Aarón complete método de pago + token.