2026-05-23 — Sesión maratónica monitoreo
Sesión de ~9 horas (mediodía → noche). Diagnóstico, fix urgente, recovery y documentación de un incidente catastrófico de ingesta.
Resumen ejecutivo
Sergio reportó dos síntomas: 63 dispositivos sin datos del colector + portal con error 500. Las causas resultaron ser dos bugs separados con efectos amplificadores:
- Endpoint
/metricscolgaba PHP-FPM —Dispositivo::with('ultima_lectura_historica.detalles')generaba subquery agregada sobrelecturas_historicas_detalles(49.6M filas). Cada scrape tardaba 30-60s y se apilaba. - Bug de permisos del log volvió (el "fix" del 2026-05-15 no era suficiente) —
laravel-2026-05-23.lognacióelectrosystems:electrosystems 664porque un cron escribió primero a las 00:00:07; workers de Horizon (www-data) no podían escribir → cada job deProcesarSyncCollectorreventaba → 2,573 failed_jobs + ~5,800 archivos enfailed_syncs/acumulados en 14 horas.
Mi diagnóstico inicial fue ERRÓNEO. Sospeché de "red interna rota en sitios" basándome en los patrones de caídas simultáneas y UISP viendo equipos por cloud. Sergio refutó entrando a los collectors remotos y confirmando que sí veían los dispositivos por LAN. Eso me obligó a buscar la causa real en el server. Lección: cuando hay datos contraintuitivos, validar la hipótesis con el usuario antes de profundizar.
Acciones cerradas hoy
/metricsdesactivado (commit2efa022, deploy 98s). Endpoint público que disparaba la query letal cuando un scraper externo lo pegaba. ArchivoMetricsController.phpquedó intacto como referencia. Restauración futura: ver pendiente #190.- Permisos del log fix definitivo (
sudo chmod g+s /var/www/es-monitoreo/storage/logs/+chgrp www-data *.log+chmod 664 *.log). Verificado con touch: archivos nuevos heredan group www-data independiente del user que escriba primero. Memoriareference-laravel-daily-log-permsactualizada con nota para aplicar preventivamente al resto de proyectos Laravel del hub. - Recovery completo del backlog — ~5,800 archivos a través de 11 sitios. Estrategia evolucionó: primero script en bloques de 200 con polling (se atoraba porque el polling salía a "pending<10" antes de que el batch terminara realmente); cambié a "mover todo failed → syncs + 1 job por sitio" (el lock per-sitio del job serializa por sí solo, throttling natural del loop interno de 4 min con auto-redispatch). Tepehuanes (sitio 12) requirió ejecución INLINE con
(new Job)->handle()porque el worker Horizon no tomaba el dispatched job.queue:flushcerró el ciclo (2,573 → 0).
Estado final
- 0 archivos en
failed_syncs/de cualquier sitio. - 0 jobs nuevos failed desde el fix de permisos (13:44).
- 11 dispositivos siguen marcados "Sin datos recientes" — pero son problemas REALES post-recovery:
- Tepehuanes 6 (collector remoto del sitio caído desde antes de la sesión,
min_sin_collector=487cuando empezamos). - Torreón 3 + Villa Ahumada 2 (probablemente deadlocks MySQL ocasionales que siguen ocurriendo — 133 contados durante el día).
- Tepehuanes 6 (collector remoto del sitio caído desde antes de la sesión,
Pendientes abiertos
- #193 — Revisión integral del pipeline de ingesta: Sergio pidió expresamente revisar TODO el código de jobs (PHP) + collector Go para identificar mejoras de eficiencia/robustez/throughput. Sin restricción de tecnología — abierto a Go/Rust microservicio dedicado, bulk inserts, particionado, Redis Streams, etc. Salida esperada: documento de hallazgos + propuestas con tradeoffs + recomendación priorizada.
- #194 — Deadlocks MySQL en
lecturas_historicas_detalles: 133 deadlocks contados hoy, cada uno = archivo perdido. Patrón: jobs paralelos chocan enINSERT IGNOREcon batches grandes. Mitigaciones por explorar dentro de #193. - #195 — Worker Horizon zombie: dispatches sin procesar ni encolar. Backoff/lock/supervisor balance issue. Probablemente conectado a #193.
- #196 — Tepehuanes collector remoto caído: operación de red en sitio físico, capturar en
electrosystems-network-mapcuando exista. - #190 — Retomar
/metrics(cuando se necesite Prometheus) + arreglar la misma query monstruosa en el mailable de alertas (visible en error de log a las 13:08:39 con "Query execution was interrupted"). - #191 — Bug
NotificarLogs.php:686(Attempt to read property "enlaces" on null): el cron de las 07:00 hoy se abortó por esto y las alertas por correo de las caídas no se enviaron.
Cierres del día
- #192 — Fix de fondo permisos del log (setgid).
Sesión cerrada (monitoreo)
Sergio cerró pidiendo documentar #193 explícitamente. Sin más acción para esa sesión.
Sesiones tareas-hijo — aprendizaje Phoenix LiveView
Después del cierre de la sesión monitoreo, Sergio abrió dos sesiones del mismo día sobre tareas-hijo (tarde + noche maratón) para completar el mapa de aprendizaje C1-C10 que arrancó el 2026-05-22.
Sesión tarde — C5 Mix tooling
- Sergio: "Vamos a seguir en lo que nos quedamos de tareas-hijo."
- AskUserQuestion confirma seguir orden del mapa: C5 — Mix tooling.
- Sección C5 escrita en
LEARNING.md(~190 líneas, 12 subsecciones). - Cubre: Mix vs composer/artisan/npm scripts en una sola herramienta;
mix.exscomocomposer.json+ service provider; tareas core (deps/compile/test/help); tareas Phoenix/Ecto (generadores como código tuyo, sin registro oculto);iex -S mix phx.servercomo tinker + REPL dentro del server corriendo;mix releasepara Fly.io; aliases como scripts.json;mix.lock; config por entorno con la trampaprod.exsvsruntime.exs(vars de entorno DEBEN ir enruntime.exso se hornean en build vacías); tareas custom propias; cheatsheet diario. - 3 puntos no obvios resaltados a Sergio en chat: (1) REPL-en-servidor que Laravel no tiene; (2)
runtime.exspara env vars en prod; (3) generadores que NO son magia runtime.
Sesión noche — maratón C6→C10 + cierre del mapa
- Sergio: "Es sesion nuevo, vamos a seguir con C6, explicame a fondo aqui en el chat, como en la sesion pasada".
- Después en cadena, sin descansos: pasar a archivo + arrancar C7 → C8 → C9 → C10. Modo estricto "Sergio escribe, Antigravity explica" mantenido todo el camino.
- 5 capas explicadas a fondo primero en chat, después persistidas en
LEARNING.md. Total agregado al archivo durante la noche: ~1,500 líneas. Cierre del mapa con sección "🎉 Mapa del territorio completo" listando las 10 capas con resumen 1-línea.
Lo cubierto por capa
- C6 — Estructura Phoenix: árbol que
mix phx.newdeja, splitlib/app/(negocio puro, sin conocer HTTP) vslib/app_web/(HTTP/LiveView/render) con regla "app jamás llama a app_web", convención naming módulo↔archivo, contexts como API entre_weby BD, diagrama de flujo de request, archivos top-levelapp.ex+app_web.excon__using__macros, dentro de_web/,priv/(Erlang-style no-Elixir folder),test/espejalib/,application.expreview, propuesta concreta de 3 contexts para tareas-hijo (Cuentas/Tareas/Recompensas), cheatsheet "dónde va qué" Laravel→Phoenix. - C7 — Ecto: las 4 piezas (migration/schema/changeset/repo) y por qué separadas; Data Mapper vs Active Record como clave mental (
$user->save()NO existe, siempreRepo.update(changeset)); migrations conchange/0reversible ypriv/repo/migrations/coninserted_atnocreated_at; schemas confield :foo, virtual: truey asociaciones que NO cargan auto (devuelven%NotLoaded{}); changesets como pipeline (castwhitelist obligatorio +validate_*+unique_constraintetiqueta error de BD no valida); Repo como única puerta; Query DSL con macros + pinning^var; preload explícito como feature; transacciones conRepo.transactionoEcto.Multi; constraints vs validations (las dos siempre); embedded schemas para JSONB sin tabla; ejemplo realista completo deTareas.Instanciacon migration+schema+context+LV; cheatsheet Eloquent→Ecto. - C8 — LiveView lifecycle: flujo completo (HTTP initial + WS connected);
mount/3corre 2x conconnected?(socket)guard para suscripciones/timers;handle_params/3para cambios de URL sin recarga vsmount/3para inicialización;render/1+ HEEx con{expr}moderno,:for/:ifdirectivas y XSS-escape default;handle_event/3del cliente (phx-click,phx-submit,phx-changecon auto-debounce);handle_info/2de procesos (conecta C1+C10 directo);assignsy diffing (LiveView no re-envía partes que no cambiaron); forms conto_form+phx-change="validate"+Map.put(:action, :validate); streams para listas grandes (cliente guarda, server no); LiveComponent vs function component vs LiveView aparte; navegaciónpush_navigatevspush_patchvsredirect+ sigil~p"/..."validado en compile-time. - C9 — Supervisores + "let it crash": filosofía contra-PHP/JS (procesos baratos+aislados+reiniciables = reiniciar > recuperarse); procesos como unidad de aislamiento; supervisor tree;
Application.start/2como raíz; child specs (atom/tupla/mapa) con restart:permanent/:transient/:temporary; 3 estrategias (:one_for_onedefault,:one_for_all,:rest_for_one);max_restarts/max_secondscomo red de seguridad; GenServer básico concall/castyinit/1/handle_*;DynamicSupervisorpara procesos que aparecen/mueren; cómo Phoenix encaja (Endpoint es supervisor que contiene LV dynamic supervisor); panorama completo del árbol de tareas-hijo (TareasHijo.Supervisor → Repo + PubSub + Finch + Oban + Endpoint); aplicación práctica + cuándotry/rescueSÍ tiene sentido (límite con código externo, LiveView para no matar sesión del user, legacy Erlang). - C10 — PubSub: qué es (broker pub/sub intra-cluster BEAM) y qué NO es (no Redis, no MQ, no WS, no service bus); arquitectura supervisor + process group; los 3 verbos (
subscribe/broadcast/unsubscribe);handle_info/2como integración con C1+C8; estrategia de tópicos (granularidad: por_tarea = ruidoso, "todo" = no aislado, sweet spotfamilia:N+user:N+validacion:token); variantesbroadcast/broadcast_from/local_broadcast; distribuido entre nodos BEAM (DNSCluster de Fly.io auto); patrón "context emite, LiveView escucha" (broadcast desde el context con función helper privada, NO desde LV — si emites desde LV otros callers no broadcastean); caso real completo de tareas-hijo (hijo marca → context broadcasts → todos los LVs reciben → DOM update via diff de LV); uso con uploads (foto va en el struct broadcastado) ypush_event/3al cliente para confetti/sonido; comparación con stack Laravel (Redis + Pusher/Reverb + Echo + auth + handlers = 3 líneas Elixir).
Estado del proyecto tareas-hijo
- ✅ Arquitectura completa cerrada (#182, 2026-05-22 madrugada). Doc en
ARCHITECTURE.md. - ✅ Cambio Meta Cloud API → deep link
wa.me/(2026-05-23 mañana). - ✅ Mapa de aprendizaje C1-C10 completo en
LEARNING.md(2026-05-22 → 2026-05-23). - ⏳ Próxima sesión: Fase 0 (#183) —
mix phx.new tareas_hijo --database postgres --no-mailer, Fly.io app + Postgres provisionados, "hola Leonardo" deployado en subdominio (tentativotareas.sevaor.dev), Tailwind funcionando, repo GitHub privado. Estimación 0.5 sem. - Modo cambia: deja de ser "Sergio escribe, Antigravity explica" y pasa a co-construcción con el modelo mental en su lugar.
Sesión cerrada (final del día)
Sesión cerrada por Sergio tras cerrar el mapa C1-C10. Día con dos cierres significativos: incidente monitoreo (recovery + #192 setgid permanente + #193-#196 abiertos) y mapa de aprendizaje Phoenix completo.