Cuando una herramienta de auditoría basada en inteligencia artificial tarda varios minutos en analizar un contrato inteligente, el usuario no debería tener que adivinar si el proceso sigue vivo o se ha colgado. La experiencia real con un spinner sin progreso demuestra que, pasados los noventa segundos, la mayoría de los usuarios recargan la página y pierden el trabajo ya realizado. Este problema llevó a rediseñar el flujo de comunicación entre el backend y el navegador utilizando Server-Sent Events (SSE), una tecnología que encaja perfectamente con trabajos largos y unidireccionales, pero que exige un diseño cuidadoso más allá de los tutoriales básicos.
La elección de SSE frente a WebSockets no es trivial. En un escenario donde el cliente solo necesita escuchar después de enviar una solicitud (subir un contrato y recibir resultados), SSE ofrece justo lo necesario: una conexión HTTP persistente desde el servidor hacia el cliente, reconexión automática mediante el objeto EventSource del navegador, y cero gestión de sockets. WebSockets añadiría complejidad bidireccional que rara vez se aprovecha. Por eso, para trabajos largos de IA donde la comunicación es mayoritariamente descendente, SSE es la opción más ligera y fiable.
Lo primero que aprendí fue la importancia de diseñar un vocabulario de eventos antes de escribir una línea de código. En un primer intento, el backend emitía lo que le parecía: tokens del modelo, líneas de log, pensamientos a medio formar. El frontend se convertía en un parser de un formato no documentado que cambiaba con cada modificación del backend. La solución fue definir una serie de tipos de eventos fijos, tratados como un contrato de API. Con cuatro tipos basta: progreso, hallazgo parcial, finalización y error. Cada uno con una estructura clara: el progreso debe indicar pasos semánticos, no porcentajes abstractos. Mostrar 'Analizando función withdraw(), paso 3 de 7' mantiene al usuario en la pantalla durante el minuto cuatro. Una barra que pasa de 41% a 43% no lo consigue, y los porcentajes honestos son imposibles cuando no se sabe cuánto durará cada llamada al modelo.
El evento de hallazgo parcial es el que realmente cambia la percepción del tiempo de espera. En lugar de esperar a que termine todo el análisis, el backend envía cada hallazgo en cuanto se valida. El usuario empieza a leer el primer resultado mientras el modelo sigue trabajando en el resto. El tiempo total no se reduce, pero la espera percibida se derrumba. Si tu trabajo largo produce resultados incrementales, transmitirlos en cuanto están listos supera a cualquier barra de progreso. Pero es crucial que esos hallazgos solo se envíen después de pasar una validación de esquema en el servidor; nunca se debe transmitir basura al frontend. Validar antes de emitir evita que errores del modelo lleguen a la interfaz.
La gestión de errores también debe ser explícita. Un error recuperable (por ejemplo, un fallo en una llamada al modelo que se reintenta) debe notificarse al usuario sin detener el trabajo. Un error fatal (presupuesto agotado, entrada malformada) debe detener el proceso. El evento de error debe indicar claramente si es recuperable o no, para que el frontend actúe en consecuencia: mostrar un mensaje de aviso o redirigir a una pantalla de error.
El mayor desafío técnico son las reconexiones. Los portátiles se cierran, las redes cambian, los proxies cortan conexiones inactivas. En trabajos de varios minutos, la desconexión es casi segura. EventSource reconecta automáticamente enviando la cabecera Last-Event-ID con el último evento recibido. Pero esa cabecera solo es útil si el servidor está preparado. Para ello, cada evento debe tener un ID monótono creciente, y el servidor debe mantener un log reproducible de eventos por trabajo, independiente de cualquier conexión. Esto obliga a una arquitectura donde el trabajo no viva dentro de la petición HTTP. El análisis se ejecuta en un proceso duradero (un worker, un consumidor de cola, un contenedor) que escribe eventos en un almacenamiento compartido. El endpoint SSE es solo un cursor sobre ese log: reproduce los eventos que el cliente se perdió y luego sigue en vivo. Si el cliente se desconecta en el evento 12 y se reconecta, recibe desde el 13 hasta el estado actual, sin perder nada.
Un latido (heartbeat) en forma de comentario SSE (líneas que empiezan con dos puntos) mantiene la conexión viva durante largos silencios del modelo, y la cabecera Cache-Control: no-transform impide que proxies bienintencionados almacenen en búfer el flujo.
Los entornos serverless presentan una dificultad adicional. Una función Lambda o similar que muere al alcanzar su límite de tiempo no puede alojar un trabajo de cuatro minutos. Mantener la función viva mediante streaming solo alarga el techo, no lo elimina, y además una desconexión del cliente podría matar el trabajo para todos los demás usuarios. La solución es separar el worker del stream: el trabajo se ejecuta en un entorno con vida larga (un worker process, un contenedor, un servicio de colas), escribe eventos en un almacenamiento compartido, y el endpoint SSE es una función serverless barata y sin estado que solo lee y reenvía. Así, el log es la fuente de verdad y la reconexión funciona de forma natural.
Otro aspecto crítico es la contrapresión (backpressure). Cuando un modelo local en una GPU rápida emite eventos más rápido de lo que el navegador puede renderizar, se acumulan en un búfer. Para evitarlo, se pueden coalescer eventos habladores del lado del servidor: por ejemplo, agrupar actualizaciones de progreso en intervalos cortos y enviar solo el último estado. Los hallazgos nunca se coalescen, cada uno es importante. Además, se debe respetar el propio flujo del stream comprobando controller.desiredSize antes de encolar eventos de baja prioridad, descartando ticks de progreso obsoletos si el búfer se llena.
En Q2BSTUDIO, aplicamos estos patrones en el desarrollo de aplicaciones a medida que integran inteligencia artificial, ciberseguridad y análisis de datos. Por ejemplo, una plataforma de auditoría de contratos inteligentes puede beneficiarse de este enfoque para ofrecer resultados parciales en tiempo real, mejorando la experiencia del usuario sin aumentar la carga del servidor. También utilizamos servicios cloud en AWS y Azure para alojar workers duraderos que gestionan estos trabajos largos, asegurando escalabilidad y fiabilidad. Nuestros agentes de IA se combinan con dashboards de Power BI para visualizar el progreso y los hallazgos de forma dinámica.
El patrón que une todo esto es tratar el log de eventos como el producto y la conexión SSE como una vista desechable del mismo. Cada problema difícil (reconexión, límites serverless, contrapresión, múltiples pestañas viendo el mismo trabajo) se vuelve sencillo una vez que la conexión deja de ser el lugar donde reside el estado. ¿Cuál ha sido el trabajo más largo que has tenido que mantener en el navegador, y qué se rompió primero? En nuestra experiencia, el primer fallo suele ser la falta de retroalimentación temprana, que lleva al usuario a interrumpir el proceso. Con SSE y una arquitectura basada en logs, ese problema desaparece.





