Back to deblo
deblo

Los paquetes que desaparecían sin error: pulsar para hablar y Colgar en Déblo Duo

En Déblo Duo, pulsar para hablar y Colgar nunca llegaban al servidor, y nada lo indicaba. Una sonda en producción, seis llamadas de prueba y una lectura de la base de datos revelaron tres fallos silenciosos en cuatro días.

Juste Thales Gnimavo & Claude | November 13, 2026 10 min deblo
EN/ FR/ ES
deblodeblo-duoreal-time-audiodata-messagesaccess-tokenspermissionssilent-failurehang-uppush-to-talkprod-proofdatabase-proofmobilereact-nativeclaude-codeclaude-opus-5.5

Por Juste Thales Gnimavo (CEO, ZeroSuite) y Claude Opus 5.5 (Claude Code)

Entre el 27 y el 30 de octubre de 2026 encontramos en Déblo Duo tres bugs que tenían algo en común. Ninguno lanzaba un error. La app parecía funcionar, el servidor también, los logs estaban limpios. Cada vez, lo que nos avisó fue un número: una llamada que duró cero segundos, un recuento de mensajes que debía ser seis y era cero, un retraso de 14 segundos donde no debía haber ninguno.

Déblo Duo es nuestro intérprete en tiempo real: cada persona habla su idioma y escucha a la otra en el suyo. (El contexto está en Por qué existe Déblo Duo.) Una llamada reúne a varios participantes en una sala de nuestra capa de audio en tiempo real: el anfitrión, el invitado y un worker del lado del servidor que lleva su audio al modelo de traducción de voz en tiempo real de Google. Casi todo lo que importa viaja como audio. Algunas cosas viajan como pequeños mensajes de datos: en el modo de un solo teléfono, «estoy pulsando el botón, me toca hablar»; en todos los modos, «estoy colgando».

27 de octubre: una llamada que colgaba a los cero segundos

El primero vino de Juste, que probaba Duo 1.0.9 en su teléfono. Su reporte: «le premier appui sur le gros micro lance un appel, le deuxième marche.» La primera pulsación en el micrófono grande inicia una llamada, la segunda funciona.

Es el tipo de reporte que se descarta fácilmente como un toque mal dado. En lugar de eso, leímos la base de datos. En la tabla de sesiones de voz había sesiones en modo de un solo teléfono de exactamente 0 segundos a las 05:37 y 05:39 UTC, cada una seguida de un intento que sí se mantuvo. El bug era real, y reproducible.

La causa era un estado obsoleto. Al terminar una llamada, el estado compartido de la llamada quedaba en su fase «terminada». La pantalla de un solo teléfono es la única pantalla de llamada que arranca directamente en el paso de la llamada; las demás empiezan con una invitación o una pantalla de carga. En su primer render, el efecto de fin de llamada de la pantalla leía la fase que había quedado de la llamada anterior antes de que se ejecutara el reinicio, concluía que la llamada había terminado, y colgaba.

La corrección fue reiniciar el estado de la llamada antes de navegar a la pantalla de un solo teléfono, desde su único punto de entrada. Duo 1.0.10 salió con ella. Juste confirmó que ninguna llamada volvía a colgar a los cero segundos, y la base de datos no mostraba ninguna sesión de 0 segundos desde la instalación. También dejamos anotada la reserva: la secuencia exacta (una llamada y luego una llamada de un solo teléfono en la misma sesión de la app) no era visible en la base de datos, porque solo se había registrado una sesión. Esa parte se apoya en su observación.

29 de octubre: un permiso que nadie había concedido

Dos días después, preparando una jornada completa de pruebas, miramos cómo el backend emite los tokens de acceso de una llamada. Cada participante entra en la sala con un token que enumera lo que puede hacer: publicar audio, suscribirse, publicar datos.

Los tokens del anfitrión y del invitado no incluían el permiso de publicar datos. Nunca lo habían tenido, desde los primeros briefs del intérprete. Y sin embargo, los clientes envían dos cosas como datos: la señal de pulsar para hablar y el mensaje de colgar.

Leer el código nos decía lo que debía pasar. Queríamos saber lo que pasaba de verdad. Así que escribimos una sonda: tres participantes en una sala del servicio de audio de producción, sin worker de traducción y sin facturación. Un emisor sin permiso de datos, otro con él, y un receptor escuchando.

La salida fueron dos líneas. El emisor sin el permiso: ningún error de publicación. El receptor: un mensaje, del emisor que tenía el permiso. El otro mensaje simplemente no llegó, y nada se lo dijo a su emisor.

Eso significaba que dos funciones nunca habían funcionado en producción. En el modo de un solo teléfono, el botón de pulsar para hablar nunca llegaba al worker. El mensaje «Colgar» tampoco. El modo de un solo teléfono se había marcado como entregado en una sesión anterior. Su banco de pruebas pasaba. El banco generaba sus propios tokens, con el permiso de datos incluido, así que no podía ver lo que el backend emitía en realidad. Un banco que fabrica sus propias credenciales no prueba las reales.

Antes de abrir el permiso, comprobamos el otro sentido: ¿podría ahora un invitado enviar un mensaje de control falso? Los clientes web y móvil ignoran cualquier paquete de datos que no venga del agente. El worker comprueba el rol del emisor: colgar exige un participante conocido, la señal de pulsar para hablar solo se acepta del anfitrión. Los oyentes de una presentación no tienen ningún permiso de datos.

La corrección fue un solo cambio en el backend: conceder el permiso de datos a los tokens del anfitrión y del invitado. Repetimos la sonda con un token exactamente de la nueva forma, micrófono más datos, a las 21:38 UTC. El receptor recibió los dos mensajes. No hizo falta recompilar la app, porque los tokens se emiten de nuevo en cada llamada. Pero la sonda de transporte solo probaba el transporte. Quedaba abierto si la corrección funcionaba en una llamada real de Duo, con la observación pendiente escrita al lado: eventos de pulsar para hablar y de colgar en el log del worker de producción durante una llamada real.

Esa misma noche: seis llamadas, cero cuelgues

Juste probó desde el salón esa misma noche, el 29 de octubre, entre las 23:22 y las 23:38 UTC. El modo de un solo teléfono funcionó: 1 minuto 20 segundos. El log de producción del worker mostraba seis eventos de pulsar para hablar. Esa corrección quedaba probada.

La segunda prueba era que el invitado colgara primero. «Funcionaba». La pantalla del anfitrión acabó mostrando «Terminé» (Terminado). A los 14 segundos.

Catorce segundos es tiempo suficiente para que un anfitrión piense que la app se ha bloqueado, y lo bastante poco como para pasar desapercibido en una demo rápida. Contamos. En seis llamadas, el worker había recibido cero mensajes de colgar. Los mensajes de pulsar para hablar, enviados por el mismo camino y por los mismos clientes, llegaban siempre.

La diferencia estaba en lo que venía justo después. La señal de pulsar para hablar se envía y la conexión sigue abierta. El mensaje de colgar se envía y el cliente se desconecta de inmediato. El mensaje se perdía al cerrarse la conexión. El «Terminé» del anfitrión no era la llegada del mensaje de colgar; era el worker que acababa rindiéndose con un participante que se había quedado en silencio, mediante un timeout que nunca se pensó para transmitir un cuelgue.

Dejamos de depender del mensaje. El servicio de audio ya le dice al worker por qué se fue un participante, y distingue una salida iniciada por el cliente de un corte de red. El worker trata ahora una salida iniciada por el cliente como un cuelgue y termina la llamada tras un breve margen. Un corte de red conserva el margen más largo, para que a un invitado que atraviesa una zona sin cobertura no le cuelguen.

La prueba llegó a la mañana siguiente, el 30 de octubre. A las 07:49:06 UTC el worker registró la salida del invitado con un motivo iniciado por el cliente. El anfitrión salió a las 07:49:10. La llamada terminó a las 07:49:15 con el motivo «guest left». Unos cuatro segundos donde antes había catorce.

El cliente sigue enviando el mensaje de colgar. Ya no hace nada. Lo quitaremos en una futura versión de la app.

Lo que tienen en común los tres

Cada uno de estos bugs sobrevivió porque algo parecía funcionar.

La llamada de cero segundos iba seguida de una llamada que se mantenía, así que el segundo toque del usuario la «arreglaba». El botón de pulsar para hablar cambiaba de aspecto al pulsarlo; el servidor nunca lo oía. Colgar terminaba la llamada, al cabo de un rato, mediante un timeout que nunca se diseñó para eso.

Ninguno produjo una línea de log que dijera que algo iba mal. El servicio de audio descarta un paquete de un emisor sin el permiso y no le dice nada al emisor. Un mensaje enviado justo antes de desconectarse puede desaparecer con la conexión. Un estado obsoleto no es una excepción.

Lo que los encontró fue la lectura de algo que no se deja impresionar: la columna de duración en la base de datos, una sonda que imprimía lo que recibía el receptor, un recuento de eventos en el log del worker. Seis eventos de pulsar para hablar y cero cuelgues, en las mismas llamadas, es un diagnóstico. «En mi teléfono funciona» no lo es.

Ya teníamos una regla para esto, heredada de otros proyectos: un elemento no está corregido hasta que se ha producido su prueba sobre un objetivo real. Estos cuatro días nos mostraron de qué protege esa regla. Sin ella, pulsar para hablar habría seguido «entregado» hasta la demo con inversores, y el cuelgue de 14 segundos habría pasado por una rareza.

Lo que cambió, y lo que sigue abierto

Cambiaron tres cosas en nuestra forma de trabajar en Duo.

Un banco de pruebas tiene ahora que pedir sus tokens al backend en lugar de fabricarlos. Hasta que eso esté hecho, el banco no es fiable en nada que tenga que ver con permisos. Está en la lista de la próxima verificación agrupada.

Cada corrección lleva ahora la observación que la probará, escrita antes de la prueba: qué evento, en qué log, sobre qué objetivo. El 29 de octubre esa frase estaba lista antes de que Juste cogiera el teléfono, así que la sesión de pruebas fue una lectura, no un debate.

De esa misma noche salió una lección menor. El marcador de versión que el worker imprime al arrancar llevaba una semana atascado en un valor antiguo, así que no probaba nada sobre lo que estaba en ejecución. Lo actualizamos esa noche. Desde entonces ha vuelto a desviarse, y hasta que esté ligado automáticamente al commit, leemos los despliegues en el contenedor en ejecución, no en el marcador.

El permiso de datos, pulsar para hablar y colgar están probados en producción. Una sala de presentación con dos oyentes en dos idiomas seguía sin probar al final de esa jornada de pruebas: la única presentación que inició Juste duró 15 segundos, sin nadie escuchando. Esa prueba llegó más tarde, y encontró algo que no esperábamos.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles