Bucles de desconexión de WebSocket: cómo reparar funciones en tiempo real después del lanzamiento
Los bucles de desconexión de WebSocket pueden arruinar apps en tiempo real tras el lanzamiento. Aprende a depurar autenticación, timeouts, escalado y añadir degradaciones seguras.

Por qué fallan las funciones en tiempo real después del lanzamiento
“En tiempo real” normalmente significa que la pantalla se actualiza sin recargar. Los mensajes de chat aparecen al instante, la presencia muestra quién está conectado, los paneles se actualizan y las alertas saltan en cuanto ocurre un cambio.
Estas funciones pueden verse perfectas en una demo y luego desmoronarse tras el lanzamiento porque la producción se comporta de forma distinta. Más usuarios significan más conexiones simultáneas. Un proxy o balanceador se interpone entre el navegador y tu servidor. Los tokens expiran. Los móviles cambian de red, se duermen y vuelven a activarse. Cualquiera de esos factores puede convertir una conexión estable en un bucle de desconexiones y reconexiones.
Para los usuarios, un bucle de desconexión WebSocket se siente como “funciona un segundo y luego se rompe”. Los mensajes llegan tarde o no llegan. La interfaz parpadea con “reconectando…” repetidamente. La presencia titila. Los paneles se congelan y luego dan un salto. A veces los usuarios reciben duplicados porque el cliente vuelve a enviar tras reconectar.
Qué cambia tras el lanzamiento
Algunos cambios previsibles llevan los sockets al límite: más conexiones concurrentes de las que probaste localmente, proxies que cortan conexiones “inactivas”, autenticación de WebSocket que falla durante la actualización o la rotación de tokens, caídas de red móvil que disparan reconexiones agresivas, y múltiples instancias de servidor sin estado compartido (o sin sesiones sticky si dependes del estado en memoria).
La fiabilidad de los sockets no es “nunca desconectarse”. Las desconexiones ocurrirán. Fiabilidad significa que la app se recupera rápido y de forma segura, y que no se pierden eventos importantes. Perder un indicador de escritura está bien. Perder “mensaje enviado”, el estado de un pedido o el de un pago no lo está.
Detecta el patrón antes de cambiar cualquier cosa
Cuando lo en tiempo real falla tras el lanzamiento, la forma más rápida de perder tiempo es “arreglar” el código a ciegas. Empieza describiendo el bucle tan claramente que puedas predecir la siguiente desconexión.
Nombra el síntoma que realmente ves. “Es inestable” oculta pistas. “Reconecta cada 6 segundos y duplica notificaciones” es algo que puedes rastrear.
Antes de tocar configuraciones, responde unas preguntas:
- ¿Qué clientes se ven afectados (web, iOS, Android, un navegador en concreto)?
- ¿En qué entorno ocurre (local, staging, sólo producción)?
- ¿Afecta a todos o a cuentas específicas?
- ¿Se concentra en horas pico o justo después de despliegues?
Luego recopila evidencia mientras ocurre. Una instantánea corta vale más que horas de suposiciones. Como mínimo, recoge marcas de tiempo (cliente y servidor), un ID de usuario o sesión, un ID de conexión que generes por socket, el código y la razón de cierre (si están disponibles) y los últimos eventos antes de la caída (connect, auth, subscribe, ping/pong).
Para separar desconexiones del servidor de caídas del cliente, compara líneas temporales. Si el cliente muestra el código 1006 (cierre anormal) y el servidor no registra un cierre limpio, sospecha de la red, timeouts de proxy o la app entrando en suspensión. Si el servidor cierra justo después de “auth” o “subscribe”, sospecha de tu propia lógica (token malo, permisos faltantes, excepciones lanzadas).
Un truco práctico: reproduce con un usuario en una pestaña primero. Si no puedes, el desencadenante puede estar relacionado con la carga.
Paso a paso: depurar un bucle de desconexión
Cuando veas un bucle de desconexión, resiste la tentación de ajustar timeouts primero. Haz el bucle visible: ¿qué pasó justo antes de que se cayera el socket y quién programa la reconexión?
Empieza con logs simples alrededor del ciclo de vida del socket. Quieres una historia limpia de principio a fin: connect, auth, suscripción, flujo de mensajes y luego la razón de cierre. Incluye marcas de tiempo y un ID corto de conexión para que varias pestañas no se mezclen.
Registra lo básico en orden: connect iniciado/abierto, auth enviado y éxito/fallo, subscribe enviado y ack, close/error (código y razón) y reconexión programada (y por quién).
Luego reproduce con la configuración más pequeña: un usuario, una pestaña, sin jobs en segundo plano. Cuando falle de forma fiable, añade complejidad paso a paso (segunda pestaña, segundo usuario, mayor volumen de mensajes). Eso te dirá si el desencadenante es carga, concurrencia o una suscripción concreta.
A continuación, inspecciona códigos de cierre y errores. Los cierres por políticas suelen apuntar a problemas de autenticación u origen. Los timeouts normalmente indican latidos, proxies o que el servidor estuvo bloqueado. Los cierres anormales suelen significar que algo hizo crash o que la red desapareció sin un cierre correcto.
También comprueba si tienes dos mecanismos de reconexión a la vez: tu código más la reconexión por defecto de la librería. Eso puede crear tormentas de reconexión incluso cuando el problema subyacente es pequeño.
Finalmente, prueba desde otra red (hotspot móvil vs Wi‑Fi de oficina). Si sólo ocurre en una red, apunta a proxies, VPNs, portales cautivos o timeouts agresivos por inactividad.
Autenticación en sockets: dónde suele fallar
Muchos errores “de red” son en realidad errores de autenticación. La app carga bien, las llamadas API funcionan y luego la funcionalidad en vivo se queda reconectando.
Tres configuraciones comunes de auth
La mayoría de apps autenticará sockets de una de tres formas: reusar una sesión por cookie, enviar un bearer token durante la conexión o pedir primero un token efímero “para socket” por HTTPS. Todas pueden funcionar, pero cada una tiene modos de fallo comunes.
Un desajuste clásico: las peticiones HTTP normales están autenticadas, pero el handshake del WebSocket no lo está. Las cookies pueden enviarse a tu API pero bloquearse para el socket por reglas cross-origin. O el servidor espera un header Authorization, pero la librería cliente sólo puede pasar el token por query param o subprotocol.
Fallas comunes tras el lanzamiento:
- Las cookies no se incluyen por SameSite, Secure o configuración de dominio (funciona en localhost, falla en el dominio real).
- El socket se conecta antes de que la sesión esté lista.
- El socket conserva un token de acceso obsoleto después de un refresh y lo expulsan repetidamente.
- El servidor cierra como “no autorizado” y el cliente vuelve a conectar al instante, creando spam ruidoso.
Manejar cierres por no autorizado sin reconexiones infinitas
Trata las fallas de auth de forma distinta a fallas de red inestables. Si el servidor cierra con un código o mensaje relacionado con auth, detén el bucle de reconexión y recupera la sesión primero. Refresca el token (o pide login) y luego abre un socket nuevo con las credenciales nuevas.
Si debes reintentar, usa backoff (1s, 2s, 5s, 10s) y añade jitter para que muchos clientes no vuelvan a conectar al mismo momento.
Un escenario común: un panel funciona durante una hora, el token se renueva, pero el socket sigue enviando el token viejo y lo cierran cada pocos segundos. La solución no es “más reintentos”. La solución es reiniciar el socket cuando el token cambia.
Latidos, timeouts y comportamiento de reconexión
Muchos bucles de desconexión se reducen a esto: la conexión está inactiva y algo en el medio la mata. Ese “algo” puede ser tu servidor, un proxy o balanceador, un CDN, el Wi‑Fi del hotel o un teléfono que suspende la actividad en segundo plano.
La solución habitual es un heartbeat más una conducta de reconexión sensata. Los heartbeats pueden ser ping/pong (lo mejor si tu librería lo soporta) o un pequeño mensaje de keepalive a nivel de app. En cualquier caso, quieres suficiente tráfico para que los intermediarios no marquen el socket como inactivo.
Sé conservador con los tiempos. Muchos proxies cierran conexiones inactivas alrededor de 30 a 60 segundos. Un punto de partida común es un heartbeat cada 15 a 25 segundos y un timeout cliente tras 2–3 heartbeats perdidos. Muy agresivo gasta batería y datos en móvil. Muy lento muere en silencio.
La lógica de reconexión es la otra mitad. Reconectar al instante puede crear una tormenta, especialmente tras un deploy o una breve caída. Usa backoff exponencial con jitter y un tope, reinicia el backoff sólo después de que la conexión se mantenga estable un tiempo y haz que la reconexión sea idempotente: reautentica y resuscribe, pero no dupliques suscripciones.
Las conexiones medio‑abiertas son el caso escurridizo: el cliente cree que está conectado pero el servidor no existe. Los timeouts de heartbeat te permiten detectarlo rápido.
Proxies y balanceadores: causas ocultas de desconexión
Si lo en tiempo real funcionó localmente pero falla en producción, revisa la ruta de red antes de reescribir el código del socket. Reverse proxies, CDNs y balanceadores pueden cerrar conexiones inactivas, rotar instancias o eliminar cabeceras.
Qué cambian los proxies sobre WebSockets
Un WebSocket empieza como una petición HTTP y luego se actualiza. Todo lo que esté delante de tu app debe soportar esa upgrade y mantener la conexión abierta. Muchas configuraciones también imponen timeouts por inactividad o una edad máxima de conexión. Si tu app sólo envía datos cuando el usuario hace clic, la conexión puede parecer inactiva y cortarse.
Las sesiones sticky son otra trampa. Si guardas estado importante en memoria (suscripciones, pertenencia a salas, contexto de usuario) y el balanceador manda una reconexión a otra instancia, el usuario “conecta” pero se pierde eventos o fallan comprobaciones de estado. Un estado compartido (Redis, base de datos, broker de mensajes) reduce la necesidad de stickiness.
Terminación TLS y sorpresas con headers de auth
Cuando TLS se termina en el proxy, tu app puede ver la petición como HTTP a menos que se envíen cabeceras forwardeadas correctamente. Eso puede romper comprobaciones como “permitir sólo cookies seguras” o reglas estrictas de origen. Algunos proxies también eliminan o renombran cabeceras, lo que rompe la autenticación por token.
Para confirmar qué está cerrando la conexión, compara códigos de cierre en ambos lados, busca mensajes en logs del proxy como “upstream timeout” o “idle timeout”, aumenta temporalmente los timeouts por inactividad para ver si el problema para y verifica que la upgrade y las cabeceras forwardeadas llegan a la app.
Escalar en tiempo real sin perder eventos
Lo en tiempo real suele funcionar en staging porque hay un solo servidor. Tras el lanzamiento aparece una segunda instancia (o tu plataforma empieza a mover tráfico) y los mensajes desaparecen. Los broadcasts sólo llegan a usuarios en la misma máquina. Las salas y la presencia se vuelven inconsistentes. Las reconexiones pueden aterrizar en un servidor que no reconoce el estado del cliente.
La primera regla: no guardes estado importante del socket sólo en memoria. Eso incluye estado de suscripción, mapeos usuario→socket, presencia y el último ID de evento recibido. El estado en memoria desaparece en un deploy y difiere por servidor.
La mayoría de apps acaba usando uno de estos patrones: pub/sub compartido para que cualquier servidor pueda publicar y todos puedan entregar; un servicio de tiempo real dedicado que posee las conexiones mientras los servidores API permanecen stateless; o una cola para eventos que no deben perderse y que puedan reintentarse de forma segura.
Las reconexiones son donde se cuelan los duplicados. Usa un ID de evento (o número de secuencia) por canal y que el cliente envíe “último recibido”. En el servidor, haz los manejadores idempotentes para que procesar el mismo evento dos veces no cobre, cree o envíe duplicados.
Los despliegues necesitan también un plan. Si reinicias servidores sin aviso, fuerzas reconexiones masivas y condiciones de carrera. Añade un paso de drenado: deja de aceptar nuevas conexiones en la instancia antigua, permite que las existentes terminen y luego termina el proceso.
Degradaciones controladas que mantienen la app usable
Lo en tiempo real es genial hasta que no lo es. Cuando los usuarios encuentran bucles de desconexión, quieres dos cosas: sockets estables y una app que siga funcionando cuando los sockets no sean fiables.
WebSockets brillan para interacción bidireccional (chat, multijugador, cursores en vivo). Si el cliente recibe principalmente actualizaciones (cambios de estado, notificaciones, paneles), Server‑Sent Events (SSE) puede ser más simple y más fiable porque usa una conexión HTTP estándar y suele comportarse mejor a través de proxies.
Una solución práctica es degradación controlada: intenta WebSockets, cambia a SSE si el socket no se abre o cae con frecuencia, vuelve a polling corto si es necesario y, si la reconexión sigue fallando, pon la app en modo limitado (sólo lectura o “enviar después”) mientras reintentas en silencio.
Mantén la UI honesta. Muestra el estado de la conexión (Conectado, Reconectando, Offline) y la hora de la última actualización. Un botón “Reintentar ahora” ayuda cuando alguien acaba de cambiar de red.
En el servidor, diseña streams para que un cliente pueda reanudar tras reconectar. Envía eventos con IDs o timestamps y permite “todo desde X”. Para SSE puedes usar Last-Event-ID. Para WebSockets, usa un cursor de reanudación o un token en el connect.
Errores comunes que hacen los sockets frágiles
No toda desconexión es un bug. Las redes móviles caen. Los portátiles se duermen. Los navegadores pausan pestañas en segundo plano. Lo frágil es cuando tu app trata desconexiones normales como emergencias y reintenta tan agresivamente que provoca una caída auto‑infligida.
Un error de seguridad evitable es poner secretos en la URL. Las query strings se copian en logs, analíticas, informes de errores y capturas de pantalla. Si tu token de socket está en la URL, asume que se filtrará. También es fácil registrar tokens por accidente al volcar datos del handshake mientras depuras.
El desarrollo local puede engañarte. En localhost no hay proxy corporativo, balanceador ni política de timeout por inactividad. En producción, un proxy puede cerrar conexiones inactivas, eliminar cabeceras o bloquear requests de upgrade.
Algunos patrones que suelen volver frágiles los sockets:
- Bucles de reintento sin backoff ni jitter.
- Autenticación en query strings, o logs que capturan tokens.
- Sin límites servidor‑lado en intentos de reconexión por usuario/IP.
- Lógica de reconexión que resuscribe a ciegas y crea listeners duplicados.
- Omitir pruebas detrás de un proxy o balanceador.
Las suscripciones duplicadas son especialmente escurridizas. Tras reconectar, el cliente puede volver a unirse a la misma sala o registrar el mismo handler mientras el servidor nunca limpia el antiguo. Soluciona esto haciendo las suscripciones idempotentes por conexión y rastreando IDs de conexión para que un socket nuevo reemplace limpiamente al anterior.
Chequeos rápidos antes de desplegar la corrección
Antes de lanzar un cambio de WebSocket, revisa rápidamente cliente, servidor e infraestructura. La mayoría de bucles de desconexión no son un solo bug. Son dos o tres issues pequeños que sólo aparecen juntos.
Chequeos en el cliente
Tu cliente debe mantener la calma ante fallos. Usa backoff con jitter y un tope, muestra el estado en la UI, añade lógica de reanudación (último ID o versión) para que cortes breves no pierdan datos, desduplica eventos para que las reconexiones no apliquen cambios dobles y cierra sockets limpiamente al hacer logout o cambiar de cuenta.
Chequeos en servidor e infraestructura
Haz que las desconexiones sean comprensibles. Si el servidor cierra una conexión, debe ser por una razón clara y una línea de log debería explicarlo. Usa códigos de cierre claros, aplica auth al conectar y en mensajes sensibles, configura latidos y timeouts para que clientes sanos no sean expulsados, limita conexiones por usuario/IP y confirma los ajustes WebSocket del proxy/balanceador (incluyendo timeouts por inactividad y si se requiere stickiness).
Una regla rápida: si no puedes explicar una desconexión con una línea de log, no estás listo para desplegar.
Pruebas rápidas que detectan regresiones
Ejecuta algunos escenarios que suelen reproducir bucles: un usuario con muchas pestañas mientras hace login/logout, muchos usuarios conectándose a la vez (aunque sea una pequeña prueba de carga), desplegar mientras hay usuarios conectados y observar la reconexión, y simular redes inestables (alternar Wi‑Fi/celular, suspender un portátil) para confirmar que la app se recupera.
Cómo se ve “hecho”: las conexiones se mantienen por periodos predecibles, las reconexiones se ralentizan en vez de acelerarse, y cuando ocurre una caída puedes apuntar a una causa clara.
Un ejemplo realista y siguientes pasos
Un fundador lanza un panel de ventas en vivo. En staging todo parece perfecto. El día del lanzamiento llegan tickets de soporte: la página parpadea “Reconectando…” cada pocos segundos y algunos usuarios nunca reciben actualizaciones en vivo.
La primera pista está en los logs del servidor: muchas conexiones terminan justo después de que expira un access token. En el cliente, la app reconecta rápido pero sigue usando el mismo token expirado, así que la vuelven a expulsar. La solución es hacer que el handshake del socket use un token fresco (o un token efímero para sockets) y forzar un refresh antes de reconectar.
Luego aparece un segundo patrón: usuarios que dejan la página abierta se desconectan casi exactamente a los 60 segundos. Eso apunta a un timeout de infraestructura. El balanceador corta conexiones inactivas y la app no envía heartbeats. Un ping cada 25 segundos más un timeout sensato para la inactividad detiene el flapping.
Documenta lo que cambiaste para que se mantenga fijo: códigos de cierre esperados, reglas de tokens para sockets (de dónde vienen, cuándo se renuevan, qué pasa con un 401), ajustes de heartbeat (intervalo de ping, timeout de pong) más cualquier timeout del proxy, y reglas de reconexión (timings de backoff, intentos máximos, cuándo parar y mostrar un botón “Actualizar”).
A veces parchear es más lento que refactorizar. Si tu manejador de sockets mezcla conexión/auth, lógica de negocio, escrituras en BD y comprobaciones de permisos en un solo lugar, cambios pequeños crean nuevas roturas. Separa responsabilidades: una capa para conexión y auth, otra para eventos y otra para datos.
Si tratas con un prototipo generado por IA que funcionó en una demo pero sigue rompiéndose en producción, FixMyMess (fixmymess.ai) puede ayudar a diagnosticar el flujo de sockets, reparar la autenticación y la lógica de reconexión y endurecer la app para tráfico real después de una auditoría de código gratuita.
Preguntas Frecuentes
¿Por qué mi funcionalidad WebSocket funcionó en una demo pero falló tras el lanzamiento?
Porque en producción aparecen cargas reales y “cosas en medio”. Más conexiones concurrentes, proxies con tiempos de espera por inactividad, ciclos de renovación de tokens, cambios de red o suspensión en móviles y múltiples instancias de servidor pueden convertir una demo estable en desconexiones y reconexiones repetidas.
¿Cuál es la información mínima que debo registrar para depurar un bucle de desconexión?
Registra una línea temporal ajustada de una conexión: marcas de tiempo cliente y servidor, un identificador de usuario/sesión, un ID por conexión que generes, el código y la razón de cierre, y los últimos eventos antes de la caída (open, auth, subscribe, ping/pong). Con eso suele bastar para saber si el cliente desapareció, el proxy cortó o tu servidor cerró la conexión.
¿Qué suele indicar el código de cierre 1006 en WebSocket?
1006 es un cierre anormal: el navegador no recibió un frame de cierre limpio. Normalmente apunta a cortes de red, el equipo entrando en suspensión, timeouts de proxy/balanceador o un crash del servidor que terminó la conexión TCP sin cerrar el WebSocket correctamente.
¿Cómo debo manejar cierres por “no autorizado” sin reconectar sin fin?
No lo trates como un fallo de red normal. Detén el bucle de reconexión, refresca la sesión o el token primero y luego abre un socket nuevo con las credenciales actualizadas. Reconectar con el mismo token expirado sólo crea un bucle apretado que parece un problema de red pero es de autenticación.
¿Qué temporización de heartbeat debería usar para prevenir timeouts por inactividad?
Empieza con un latido cada 15–25 segundos y considera la conexión muerta tras 2–3 latidos perdidos; entonces reconecta con backoff. La idea es evitar que intermediarios marquen la conexión como inactiva sin gastar innecesariamente batería o datos en móviles.
¿Cómo provocan desconexiones aleatorias los proxies o balanceadores?
Todo lo que esté entre el navegador y tu app puede afectar WebSockets: proxies reversos, CDNs y balanceadores deben soportar la upgrade HTTP, conservar cabeceras necesarias y permitir conexiones de larga duración. Un fallo habitual es un timeout por inactividad alrededor de 30–60 segundos que mata sockets “silenciosos” si no envías latidos.
¿Necesito sesiones sticky para WebSockets en producción?
Si mantienes estado importante en memoria (salas, presencia, suscripciones), una reconexión que se enrute a otra instancia puede “conectar” pero no restaurar el estado, provocando mensajes perdidos o verificaciones fallidas. O haz servidores sin estado moviendo el estado a almacenamiento compartido/pub-sub, o exige stickiness si realmente necesitas mantener la misma instancia.
¿Cómo evito mensajes duplicados tras reconexiones?
Usa IDs o números de secuencia por evento y haz que ambos lados sean tolerantes a reintentos. Al reconectar, el cliente debe pedir “lo último recibido”, y el servidor debe procesar de forma idempotente para que reempaquetar el mismo evento no duplique cargos, creaciones o notificaciones.
¿Cuándo debo usar SSE o polling en lugar de WebSockets?
Si el cliente recibe principalmente actualizaciones y no necesita interacción bidireccional, SSE suele ser más sencillo y se comporta mejor con muchos proxies al usar HTTP estándar. Ten una ruta de degradación controlada para que la app siga funcionando aunque los sockets flaqueen, aunque las actualizaciones lleguen más lentas.
¿Cuándo debo traer a FixMyMess para arreglar mi funcionalidad en tiempo real?
Si tu app es un prototipo generado por IA y estás atrapado en un bucle de reconexión que no puedes explicar con los logs, suele ser más rápido tener un diagnóstico estructurado que seguir cambiando timeouts. FixMyMess (fixmymess.ai) puede auditar el código, identificar si el problema es de autenticación, infraestructura o escalado y arreglar el flujo en tiempo real para tráfico real.