Skip to content

ADR-007: Tab-Scoped Job Bus via window.BautistaJobBus for Legacy PHP Bridge

Fecha: 2026-02-23 Estado: Aceptado Deciders: Architecture Team, Frontend Team

Contexto y Problema

Sistema Bautista está en migración progresiva desde vistas PHP legacy hacia React. Las vistas PHP que están fuera del árbol React necesitan feedback inmediato cuando los background jobs completan (por ejemplo, notificaciones toast). El JobNotificationsContext existente usa polling de 30 segundos, lo cual es demasiado lento para flujos legacy interactivos donde el usuario espera feedback inmediato después de disparar un job.

Escenario de riesgo sin solución:

  1. Usuario en vista PHP legacy dispara facturación masiva
  2. Job completa en ~10 segundos
  3. JobNotificationsContext notifica recién en el próximo ciclo de polling (hasta 30s después)
  4. Usuario no recibe feedback oportuno y puede re-disparar el job o navegar creyendo que falló

Restricción arquitectónica clave: Las vistas PHP legacy no montan React ni tienen acceso al árbol de componentes. No pueden consumir Context ni hooks de React directamente.

Timing crítico: El script listener legacy se ejecuta en DOMContentLoaded. El bus debe estar disponible en window antes de ese evento.

Opciones Consideradas

Opción A: window.BautistaJobBus — Pub/Sub tab-scoped (SELECCIONADA)

Descripción:

  • JobBus se crea en main.tsx a nivel de módulo (antes de que monte cualquier componente React)
  • Asignado a window.BautistaJobBus en scope de módulo, garantiza disponibilidad antes de DOMContentLoaded
  • Script vanilla IIFE job-status-listener.js cargado en cada página autenticada se suscribe al bus en DOMContentLoaded
  • El ID de usuario se lee desde <meta name="bautista-user-id"> para filtrar eventos del usuario actual
  • JobBusAdapter conecta el bus con el sistema de polling React (JobNotificationsContext) como puente

Garantía de timing:

main.tsx (module scope)     →  window.BautistaJobBus = createJobBus()
React mount                 →  JobBusAdapter conecta polling → bus
DOMContentLoaded            →  job-status-listener.js llama window.BautistaJobBus.on(...)

Pros:

  • Disponible globalmente sin imports ni bundler en el lado PHP
  • Timing garantizado (module scope ejecuta antes de DOMContentLoaded)
  • Cero dependencias externas (pub/sub trivial en memoria)
  • Compatible con entornos sin HTTPS
  • No requiere procesos separados ni workers del navegador
  • Fácil de testear (objeto en memoria, suscripciones directas)

Contras:

  • Alcance tab-scoped únicamente (sin notificación cross-tab por diseño)
  • Introduce window global (punto de acoplamiento)
  • Requiere que showAlertToast esté disponible globalmente en páginas legacy

Opción B: BroadcastChannel

Descripción: API nativa del navegador para comunicación cross-tab/cross-context same-origin.

Rechazada: No funciona de forma confiable en algunos contextos de iframe PHP legacy de esta aplicación. El enforcement same-origin puede fallar en configuraciones con subdominios mixtos presentes en el entorno de desarrollo. Además, no aporta beneficio real dado que el caso de uso es exclusivamente tab-scoped.


Opción C: SharedWorker

Descripción: Worker compartido entre todas las pestañas del mismo origen, con canal de mensajes dedicado.

Rechazada:

  • Requiere HTTPS (no todas las páginas legacy corren sobre HTTPS en desarrollo)
  • Agrega un proceso separado del navegador y complejidad operacional desproporcionada
  • No aporta valor adicional para el caso de uso actual (migración progresiva tab-scoped)
  • El overhead de implementación supera el beneficio en la etapa actual de migración

Opción D: Polling-only via JobNotificationsContext (30 segundos)

Descripción: No agregar bus; mantener el polling existente de 30 segundos para notificaciones legacy.

Rechazada: Latencia de 30 segundos no es aceptable para flujos interactivos legacy donde el usuario espera feedback inmediato. Degradaría la experiencia de usuario sin justificación técnica.


Decisión

Seleccionamos Opción A: window.BautistaJobBus tab-scoped.

Justificación:

  • Resuelve el problema de latencia sin introducir dependencias externas ni requisitos de infraestructura
  • El timing garantizado (module scope en main.tsx) elimina race conditions con el listener legacy
  • Compatible con la realidad del entorno de desarrollo (HTTP sin HTTPS obligatorio)
  • Apropiado para la etapa actual de migración progresiva: solución simple y reversible

Consecuencias

Positivas

  • Latencia near real-time para notificaciones legacy (tan rápido como el polling actual, pero sin esperar el intervalo completo)
  • Bus garantizado disponible en DOMContentLoaded — sin race condition con el listener script
  • Páginas PHP que no cargan React no tienen bus — el listener guarda con typeof window.BautistaJobBus === 'undefined'
  • Filtrado por user ID previene mostrar notificaciones de otro usuario cuando múltiples usuarios comparten sesión de navegador
  • Alcance cross-tab no es necesario en este caso de uso y se maneja por separado via polling de JobNotificationsContext

Negativas

  • Cross-tab notification no es provista por este bus (el polling de 30s de JobNotificationsContext cubre ese caso)
  • showAlertToast debe estar disponible globalmente en páginas legacy — el listener guarda con typeof showAlertToast === 'function'
  • Si main.tsx no está cargado en una página (sin React), el bus no existe — comportamiento esperado y documentado

Invariantes obligatorios

  • El bus se asigna a window.BautistaJobBus ANTES de cualquier mount de componente React
  • El listener legacy SIEMPRE verifica existencia del bus antes de suscribirse
  • El ID de usuario proviene ÚNICAMENTE del <meta name="bautista-user-id"> — no de localStorage ni cookies
  • JobBusAdapter es el único punto que publica eventos al bus desde React

Archivos Afectados

ArchivoTipoDescripción
public/ts/core/jobs/JobBus.tsNuevoImplementación pub/sub tab-scoped
public/ts/core/jobs/JobBusAdapter.tsNuevoPuente entre polling React y el bus
public/ts/main.tsxModificadoAsignación window.BautistaJobBus en scope de módulo
public/js/core/job-status-listener.jsNuevoIIFE vanilla que suscribe al bus en páginas legacy
public/php/components/navbar.phpModificadoIncluye <meta name="bautista-user-id"> y carga el listener

Referencias