La mayoría de las cosas que corren en mi servidor tienen la ventaja del tiempo: si Home Assistant se cae un minuto a las tres de la mañana, nadie se entera. Pero hace un tiempo me pidieron armar el sistema para un bingo presencial con más de 80 personas, y ahí la ecuación cambia por completo. No hay reintentos silenciosos ni "recarga la página en cinco minutos": si el número no aparece en la pantalla, ochenta personas están mirando el mismo error al mismo tiempo.
Por qué no usé algo ya hecho
Existen generadores de bingo online, pero ninguno hacía exactamente lo que necesitaba: temas de color distintos según la ocasión, más de una modalidad de juego (línea, cartón lleno, esquinas), y un tablero que se viera bien tanto proyectado en una TV grande como en el celular de cada participante siguiendo el juego. Terminé escribiendo la aplicación propia en Node.js con Socket.IO — sin framework de frontend pesado, solo lo necesario para que el servidor cantara un número y todos los clientes conectados lo vieran aparecer al instante, sin que nadie tuviera que refrescar nada.
Socket.IO resuelve el problema de fondo con poco código: mantiene un canal abierto entre servidor y cliente, y si un celular pierde señal un segundo (algo casi garantizado con 80 personas conectadas a la misma red Wi-Fi), reconecta solo y el estado del juego se resincroniza. Ese comportamiento de reconexión automática no es un detalle menor — es literalmente la diferencia entre "se cortó y se recuperó" y "alguien reclamando que no le cantaron un número".
Donde el desarrollo se topa con la infraestructura
El código de la aplicación fue la parte fácil. Lo que me hizo pensar distinto fue dónde y cómo la iba a correr. Vive en el mismo servidor Debian que todo lo demás, como un contenedor Docker más, expuesta en su propio subdominio de ulloa.cc a través del mismo Cloudflare Tunnel que uso para Home Assistant y el resto de los servicios. Ese patrón — un solo túnel, un contenedor por servicio, cero puertos abiertos al router — funciona perfecto para un dashboard doméstico que puede tener un hiccup sin consecuencias. Para un evento en vivo, la misma arquitectura significa que la disponibilidad del bingo depende de la disponibilidad del túnel, y el túnel depende de mi conexión residencial a internet.
Eso es una dependencia que en un contexto corporativo jamás aceptaría sin un plan B, pero que aquí decidí aceptar consulcientemente después de sopesarla: para un evento de un par de horas, la probabilidad de un corte de internet residencial en Chile durante esa ventana era baja, y el costo de armar redundancia (otro proveedor, un enlace de respaldo) no se justificaba frente al beneficio. La decisión correcta en DevOps no siempre es "elimina el punto único de falla" — a veces es "identifica el punto único de falla, mide qué tan probable es que falle, y decide con esa información si vale la pena pagar el costo de eliminarlo".
Diseñar para un momento, no para el largo plazo
La diferencia más grande con el resto de lo que corre en mi servidor es que esta aplicación no necesita escalar, no necesita alta disponibilidad de meses, no necesita monitoreo a largo plazo. Necesita funcionar perfecto durante dos o tres horas, una sola vez. Ese tipo de software — que existe para un evento puntual y no para operar indefinidamente — tiene un perfil de riesgo distinto al de un servicio productivo, y diseñarlo con las mismas prácticas de "tolerancia a fallas eventual" que usaría para un backend de producción habría sido sobre-ingeniería.
En cambio, lo que sí valió la pena hacer fue probar la reconexión de Socket.IO deliberadamente antes del evento: cortar el Wi-Fi de un celular a mitad de una partida simulada y verificar que el estado se recuperaba solo. Esa prueba de cinco minutos me dio más confianza real que cualquier métrica de uptime que pudiera haber configurado. Al final, la lección que me llevo no es sobre Socket.IO ni sobre Docker — es que la exigencia de un sistema no la define la tecnología detrás, la define quién está mirando la pantalla cuando algo sale mal.