Apps en tiempo real: WebSockets, SSE o WebRTC
“Que sea en tiempo real” es una de esas peticiones que suenan simples y esconden una docena de decisiones. Un mapa en vivo de camiones, un hilo de chat, un documento colaborativo, una videollamada, un dashboard que se actualiza solo. Para el usuario todo eso se siente parecido, pero por dentro usan herramientas muy distintas, y la herramienta es la decisión pequeña. Las grandes son qué pasa cuando se cae la conexión, cómo mantienes los mensajes en orden y cómo le empujas una actualización a diez mil personas repartidas entre muchos servidores. Este artículo cubre la elección entre WebSockets, Server-Sent Events y WebRTC, y después la ingeniería poco vistosa que convierte un demo que funciona en una función en la que la gente confía.
La respuesta corta
Empareja el transporte con la dirección y el tipo de datos. Si los datos solo van del servidor al cliente, usa Server-Sent Events. Si ambos lados hablan de forma continua, usa WebSockets. Si necesitas audio o video en vivo, o una conexión directa entre los dispositivos de dos usuarios, usa WebRTC. Y si los datos cambian cada pocos segundos en lugar de cada pocos milisegundos, hacer polling honesto sobre HTTP normal es una respuesta legítima que a muchos equipos les da vergüenza elegir. El resto del artículo es el razonamiento, porque el resumen solo no te saca del primer incidente en producción.
Los tres transportes, lado a lado
| Dimensión | Server-Sent Events | WebSockets | WebRTC |
|---|---|---|---|
| Dirección | Solo del servidor al cliente | En ambos sentidos, dúplex completo | Entre pares (o de un par a un servidor de medios) |
| Protocolo de base | Una respuesta HTTP normal que nunca termina | Conexión TCP mejorada con su propio enmarcado | Canales de medios y datos sobre UDP, negociados por una vía de señalización aparte |
| Carga útil | Eventos de texto | Mensajes de texto o binarios | Audio, video, binario arbitrario |
| Reconexión | Integrada en el navegador, con Last-Event-ID | La escribes tú | Renegociar la sesión completa |
| Atraviesa proxies y CDNs | Casi siempre, porque es HTTP común | En general; algunos proxies corporativos todavía interfieren | Necesita STUN y TURN para atravesar NAT |
| Infraestructura que debes operar | Tus servidores web de siempre | Servidores de conexión con estado más una capa pub/sub | Señalización, STUN, TURN y muchas veces un SFU |
| Encaje típico | Tickers, notificaciones, dashboards, tokens transmitidos desde un modelo de lenguaje | Chat, presencia, edición colaborativa, estado multijugador | Llamadas, pantalla compartida, datos de dispositivo a dispositivo con latencia mínima |
Server-Sent Events
SSE es la herramienta que la gente olvida que existe, y muchas veces es la correcta. El servidor responde a una petición HTTP común con una respuesta que simplemente nunca termina, y va escribiendo pequeños eventos de texto a medida que ocurren. El EventSource integrado del navegador se reconecta solo y envía el id del último evento que vio, así que un servidor que guarde un búfer corto puede reproducir lo que el cliente se perdió sin código a medida. Atraviesa balanceadores y CDNs porque, para ellos, es solo una respuesta lenta. La consola de flota en vivo que construimos para Vexolis transmite la telemetría de los camiones a los despachadores exactamente así: los camiones reportan su posición al backend, el backend empuja las actualizaciones hacia abajo, y el navegador nunca necesita responder por ese canal. Transmitir tokens de un modelo de lenguaje a una interfaz de chat tiene la misma forma, y por eso la mayoría de los productos de IA usa SSE para eso. Sus límites son reales pero estrechos: es unidireccional, solo texto, y los navegadores sobre HTTP/1.1 limitan la cantidad de conexiones abiertas por dominio, una restricción que HTTP/2 eliminó.
WebSockets
Un WebSocket nace como una petición HTTP que pide ser mejorada, y cuando el servidor acepta, ambos lados sostienen un canal abierto, persistente y de dos vías con muy poca sobrecarga por mensaje. Es el caballo de batalla de las funciones en tiempo real donde el cliente también habla: chat, cursores en vivo, presencia, una pizarra compartida, cualquier cosa con un indicador de “está escribiendo”. El costo es que la conexión tiene estado. Un servidor web convencional atiende una petición y la olvida; un servidor de WebSockets recuerda a cada cliente abierto, y eso cambia cómo lo despliegas, lo escalas y lo reinicias. Un despliegue progresivo que recicla servidores desconecta a todos los clientes de cada uno, así que la reconexión no es un caso borde: es un evento diario que programas tú mismo.
WebRTC
WebRTC es la maquinaria pesada, construida para audio, video y conexiones directas entre pares entre los dispositivos de los usuarios. Es lo que mueve las videollamadas, y es genuinamente complejo. Recurre a él solo cuando necesites medios en vivo o datos auténticos de dispositivo a dispositivo con la menor latencia posible, no para un “chat más rápido”. Trae además el detalle que sorprende a los equipos: entre pares igual necesita servidores. Dos dispositivos no pueden encontrarse sin un canal de señalización (normalmente un WebSocket) para intercambiar ofertas de conexión, y casi todos están detrás de routers caseros o cortafuegos de oficina, así que necesitas servidores STUN para descubrir direcciones públicas y servidores TURN para reenviar el tráfico cuando no se puede abrir una ruta directa, lo que en redes restrictivas ocurre seguido. TURN reenvía el flujo de medios completo, así que es la partida de ancho de banda que los proyectos que estrenan WebRTC olvidan presupuestar. Las llamadas grupales suman una pieza más: por encima de tres o cuatro participantes, una malla completa donde todos envían a todos derrite los teléfonos, así que enrutas a través de una unidad de reenvío selectivo (SFU) que recibe cada flujo una vez y lo redistribuye. En ese punto estás operando un servidor de medios, y un proveedor administrado suele ser la opción sensata a menos que las llamadas sean el producto.
Los datos solo bajan: SSE. En ambos sentidos: WebSockets. Audio, video en vivo o entre pares: WebRTC. La mayoría de las apps necesita el de en medio, y a más de las que lo admiten les bastaría el primero.
Cuándo el polling es honestamente suficiente
Antes de todo lo anterior, pregunta qué tan frescos tienen que ser los datos en realidad. Un dashboard que se refresca cada quince segundos, el estado de un pedido que cambia unas pocas veces por hora, una posición en una fila que avanza una vez por minuto: nada de eso necesita una conexión persistente. Un GET simple a intervalos, con un ETag para que los datos sin cambios cuesten casi nada, usa tus servidores sin estado de siempre, se cachea en el CDN, sobrevive a cualquier proxy y no tiene lógica de reconexión porque no hay nada que reconectar. Escala exactamente igual que el resto de tu sitio. La regla que usamos: si el usuario no notaría un retraso de unos segundos, haz polling. Si lo notaría, pero solo cuando algo ocurre rara vez, usa SSE. Solo cuando la interfaz es genuinamente viva, con actualizaciones cada segundo o más rápido y tráfico en ambos sentidos, un WebSocket se gana su costo operativo. El tiempo real es una propiedad de la experiencia, no del transporte, y la opción aburrida suele ser la que sale a producción y se mantiene arriba.
La parte difícil no es abrir la conexión
Hacer funcionar un solo WebSocket toma una tarde. Hacerlo confiable a escala es el trabajo real, y se divide en tres problemas: sobrevivir a las desconexiones, mantener los mensajes ordenados y completos, y difundir un mensaje entre muchos servidores.
Reconexión que no tumba tu servidor
Las conexiones se caen todo el tiempo en redes móviles: un elevador, un túnel, el cambio de Wi-Fi a datos. El cliente debe reconectarse solo, y la manera ingenua de hacerlo provoca una caída. Si tu servidor se reinicia y diez mil clientes reintentan exactamente un segundo después, llegan como una sola ola, el servidor vuelve a caer y el ciclo se repite. Eso es una estampida, y la solución es el retroceso exponencial con jitter: esperar más después de cada fallo, y aleatorizar la espera para que la manada se disperse. También necesitas latidos. Una conexión medio abierta, donde la red murió pero ningún lado se ha enterado, se ve exactamente igual que una tranquila. Enviar un ping cada veinte o treinta segundos y tratar un pong ausente como conexión muerta es la forma de enterarte antes que el usuario.
// Reconectar con retroceso exponencial y jitter, luego reanudar desde el último id visto
let attempt = 0;
function connect() {
const ws = new WebSocket(URL);
ws.onopen = () => {
attempt = 0;
ws.send(JSON.stringify({ type: "resume", lastSeenId }));
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.id <= lastSeenId) return; // duplicado tras una repetición, ignorar
lastSeenId = msg.id;
apply(msg);
};
ws.onclose = () => {
const base = Math.min(30_000, 500 * 2 ** attempt++);
setTimeout(connect, base / 2 + Math.random() * base / 2);
};
}Orden, huecos y duplicados
Reconectar resuelve la conexión pero no los datos. Lo que pasó durante el hueco se perdió, a menos que lo hayas diseñado. El patrón que funciona es darle a cada mensaje un id creciente por flujo, hacer que el cliente recuerde el último que aplicó y dejar que le pida al servidor reanudar desde ahí. El servidor guarda un búfer corto de repetición (un stream de Redis o una tabla pequeña alcanzan) y envía todo lo más nuevo. Si el hueco es más largo que el búfer, el cliente cae a una instantánea completa por HTTP común y después reanuda en vivo. De ahí siguen dos consecuencias. Los mensajes deben ser idempotentes, porque una repetición puede entregar de nuevo uno que el cliente ya vio, así que “pon la posición del camión en X” es seguro y “súmale 1 al contador” no lo es. Y un flujo unidireccional nunca debe ser la única fuente de verdad: el canal en vivo hace que la interfaz se sienta viva, pero una recarga de página tiene que poder reconstruir el estado desde una API normal. Diseña primero la instantánea y después el flujo, y la reconexión deja de dar miedo.
Escalar la difusión entre servidores
Un servidor puede sostener muchas conexiones abiertas, pero no todas, y en el momento en que tienes dos servidores, un mensaje enviado por un usuario en el servidor A debe llegar a los usuarios del servidor B. La respuesta estándar es una capa de publicación y suscripción detrás de los servidores de conexión: cada servidor se suscribe a los canales que les importan a sus clientes, publica cada mensaje entrante en Redis (o NATS, o un equivalente administrado) y retransmite lo que recibe a sus propios sockets locales. Los servidores de conexión siguen siendo simples y reemplazables, y la capa pub/sub es lo único que tiene que saber de todos. Dos reglas de diseño mantienen esto barato. Primero, suscribe por tema, no por usuario: una consola de flota se suscribe a “fleet:42”, y la actualización de un camión se publica una vez, no una por cada despachador que la mira. Segundo, agrupa. Si un sensor reporta diez veces por segundo y un humano mira un mapa, enviar cada lectura es trabajo desperdiciado; agrupa las actualizaciones en un cuadro cada 200 milisegundos y envía el último estado, y tu difusión baja un orden de magnitud sin diferencia visible. Las sesiones fijas en el balanceador, para que un cliente que reconecta caiga en el servidor que todavía tiene su estado, son una comodidad, no un requisito; un diseño que reanuda desde un id funciona en cualquier servidor.
La presencia es más difícil de lo que parece
“Quién está en línea” parece trivial: un usuario se conecta, márcalo en línea; se desconecta, márcalo fuera. En la práctica un usuario tiene tres pestañas y un teléfono, las conexiones parpadean cada pocos minutos en móvil, y un servidor que se cae nunca envía la desconexión. El modelo que funciona es un conjunto de conexiones por usuario con tiempo de vida: cada latido refresca la entrada, un usuario está en línea mientras al menos una entrada esté fresca, y las entradas de un servidor caído simplemente expiran. Retrasa unos segundos la transición a fuera de línea para que una reconexión no parpadee “desconectado” ante toda la sala. Es pequeño, pero los errores de presencia son los errores de tiempo real más visibles que un producto puede lanzar.
Construir o comprar
Todo lo anterior está bien entendido y en gran parte resuelto por bibliotecas y plataformas administradas, y para la mayoría de los equipos esa es la compra correcta. Un servicio de tiempo real alojado o una biblioteca madura se encarga de la reconexión, la repetición, la presencia y el escalamiento horizontal para que no los reconstruyas desde cero; el costo es una factura por conexión o por mensaje y una dependencia que no puedes parchar tú mismo. La decisión es la misma que describimos al agregar IA a un producto: compra la plomería cuando la función es de apoyo, constrúyela cuando el tiempo real es el producto o cuando el volumen de datos convierte el precio por mensaje en tu partida de nube más grande. Nuestra regla: empieza con SSE en tus servidores actuales si la dirección lo permite, pasa a una capa administrada de WebSockets cuando el cliente necesite hablar, y opera la tuya propia solo cuando puedas nombrar la razón.
Un marco de decisión que corres en una hora
- ¿En qué dirección fluyen los datos? Solo hacia abajo apunta a SSE; en ambos sentidos apunta a WebSockets.
- ¿Qué tan fresco es suficientemente fresco? Segundos significa polling o SSE; menos de un segundo significa conexión persistente.
- ¿Hay audio o video en vivo? Sí significa WebRTC, más un canal de señalización y un presupuesto de TURN.
- ¿Puede el cliente reconstruir el estado desde una instantánea? Si no, diseña esa API antes que el flujo.
- ¿Los mensajes son idempotentes? Si no, hazlos así; la repetición y los duplicados están garantizados.
- ¿Cuántos servidores? Más de uno significa una capa pub/sub desde el primer día, no después del primer incidente.
- ¿El tiempo real es el producto? Si no, una plataforma administrada casi seguro es más barata que la tuya.
En resumen
Empareja la herramienta con el flujo de datos, y después gasta tu energía en las partes poco vistosas: reconexión con retroceso, reanudar desde el último id visto, mensajes idempotentes y difusión por tema. Eso es lo que separa a un demo de una función en la que la gente confía, y es la diferencia que vimos importar en producción con Vexolis, donde los despachadores juzgan el producto entero por si el mapa dice la verdad. El tiempo real es una parte de las plataformas web y los sistemas a medida que construimos. Si estás diseñando uno y quieres una segunda opinión sobre el transporte o el plan de escalamiento, cuéntanos qué estás construyendo, o revisa nuestro trabajo.
Preguntas frecuentes
¿Debería usar WebSockets o Server-Sent Events?
Decide por la dirección. Si los datos solo van del servidor al cliente, como un mapa en vivo, un feed de notificaciones, un dashboard o los tokens que transmite un modelo de lenguaje, los Server-Sent Events son más simples: corren sobre HTTP común, atraviesan balanceadores y CDNs, y el navegador se reconecta y reanuda desde el último id de evento por su cuenta. Si el cliente también necesita enviar mensajes frecuentes, como chat, cursores en vivo, presencia o edición colaborativa, usa WebSockets, que te dan un canal persistente de dos vías a cambio de servidores con estado y una lógica de reconexión que escribes tú.
¿Cuándo basta con hacer polling en lugar de una conexión en tiempo real?
Siempre que el usuario no note un retraso de unos segundos. El estado de un pedido, una posición en la fila, un dashboard que se refresca cada quince segundos: un GET simple a intervalos con un ETag usa tus servidores sin estado de siempre, se cachea en el CDN, sobrevive a cualquier proxy y no tiene lógica de reconexión porque no hay nada que reconectar. Las conexiones persistentes se ganan su costo operativo solo cuando la interfaz es genuinamente viva, con actualizaciones de menos de un segundo y tráfico en ambos sentidos.
¿WebRTC de verdad necesita servidores si es entre pares?
Sí. Dos dispositivos no pueden encontrarse sin un canal de señalización, normalmente un WebSocket, para intercambiar ofertas de conexión, y casi todos están detrás de routers caseros o cortafuegos de oficina, así que necesitas servidores STUN para descubrir direcciones públicas y servidores TURN para reenviar el tráfico cuando no se puede establecer una ruta directa, algo común en redes restrictivas. TURN reenvía el flujo de medios completo, así que es un costo real de ancho de banda. Las llamadas grupales de más de tres o cuatro personas necesitan además una unidad de reenvío selectivo, es decir, operar o rentar un servidor de medios.
¿Cómo manejo la reconexión y los mensajes perdidos en una app con WebSockets?
Reconecta con retroceso exponencial y jitter aleatorio para que diez mil clientes no reintenten en el mismo instante y vuelvan a tumbar el servidor, y usa latidos para detectar conexiones medio abiertas. Para los datos, dale a cada mensaje un id creciente por flujo, haz que el cliente recuerde el último id que aplicó y le pida al servidor reanudar desde ahí, guarda un búfer corto de repetición en el servidor y cae a una instantánea completa por HTTP común si el hueco es más largo que el búfer. Los mensajes deben ser idempotentes, porque las repeticiones van a entregar algunos de nuevo.
¿Cómo escalan las funciones en tiempo real entre varios servidores?
Pon una capa de publicación y suscripción como Redis o NATS detrás de los servidores de conexión. Cada servidor se suscribe a los temas que les importan a sus clientes, publica los mensajes entrantes una vez y retransmite lo que recibe a sus propios sockets locales, así que los servidores de conexión siguen siendo simples y reemplazables. Suscribe por tema y no por usuario, para que una actualización se publique una sola vez sin importar cuánta gente la mira, y agrupa las actualizaciones de alta frecuencia en un cuadro cada pocos cientos de milisegundos, lo que reduce la difusión un orden de magnitud sin diferencia visible.
¿Construyo mi propia infraestructura de tiempo real o uso un servicio administrado?
Usa una plataforma administrada o una biblioteca madura a menos que el tiempo real sea el producto mismo o que tu volumen de mensajes convierta el precio por mensaje en tu partida de nube más grande. Se encargan de la reconexión, la repetición, la presencia y el escalamiento horizontal, y el costo es una factura por conexión o por mensaje y una dependencia que no puedes parchar tú mismo. Un camino sensato es empezar con Server-Sent Events en tus servidores actuales cuando la dirección lo permita, pasar a una capa administrada de WebSockets cuando el cliente necesite hablar, y operar la tuya solo cuando puedas nombrar la razón.
¿Estás pensando en construir esto?
Appluex diseña y lanza apps móviles y web en producción, incluidas funciones de inteligencia artificial. Hablemos.