Ingeniería
Por qué fallan las aplicaciones de campo: construir para trabajar sin conexión
Una aplicación demostrada con el wifi de la oficina y usada en la puerta de una obra con una barra de 4G son dos programas distintos. Solo uno de los dos fue probado.
El patrón es lo bastante constante como para predecirlo. Se encarga una aplicación de campo, se construye con competencia, se demuestra con éxito y se despliega. En dos meses el equipo de campo ha vuelto al papel, y la razón que se da es que «perdía datos continuamente». Normalmente no perdió datos: no llegó a recibirlos, luego no dijo nada, y la diferencia es invisible para quien está de pie en la puerta.
Una vez que el personal de campo deja de confiar en una aplicación, deja de usarla, y ninguna cantidad de funcionalidades los recupera. La confianza es el producto real, y se decide por lo que la aplicación hace con mala conexión.
Por qué fallan las suposiciones sobre la conectividad
El wifi de la oficina no es una versión débil de la conexión de una obra: es otra cosa. Una obra tiene cobertura intermitente, no lenta: señal completa junto a la caseta, nada detrás de la estructura, y un portal cautivo en el edificio del cliente que devuelve HTTP 200 a cada petición con una página de inicio de sesión en el cuerpo.
Ese último caso merece detenerse, porque derrota a las comprobaciones ingenuas de conectividad. El dispositivo informa de que hay red. La petición tiene éxito. La respuesta no es la que se pidió. Una aplicación que comprueba «¿hay conexión?» en lugar de «¿respondió realmente el servidor?» creerá con gusto que ha guardado algo que no ha guardado.
La otra suposición que falla es la duración. Una aplicación que espera sincronizar en minutos se comporta de forma distinta a una que debe manejar un dispositivo desconectado tres días, durante los cuales los datos del servidor han avanzado. El segundo caso es lo normal en el trabajo de campo y cambia el diseño.
Sin conexión primero significa que el almacén local es la verdad
La distinción no es «cacheamos algunas cosas». En un diseño que funciona sin conexión primero, la base de datos local es donde vive el trabajo del usuario, las escrituras se completan localmente y devuelven de inmediato, y la sincronización es un proceso en segundo plano que concilia con el servidor más tarde. La red es una mejora, no un requisito previo.
Esto invierte el tratamiento habitual de errores. En una aplicación pensada para estar en línea, la falta de red es un error del que el usuario debe ocuparse. En una pensada para funcionar sin conexión, la falta de red es un estado normal y la interfaz lo refleja: el registro está guardado, está en cola, saldrá cuando pueda, y el usuario puede ver que es cierto.
Elegir el modelo de conflictos a propósito
Dos personas editan el mismo registro y ambas están sin conexión. Lo que ocurra a continuación es su modelo de conflictos, y si nadie eligió uno, lo eligió el código por accidente: normalmente gana la última escritura, en silencio, y el trabajo de una persona desaparece.
Solo hay unas pocas opciones viables, y la correcta depende de los datos.
- «Gana la última escritura» es aceptable para campos realmente independientes y para datos en los que quedarse ligeramente desfasado es inocuo. Es la elección equivocada para cualquier cosa financiera o para cualquier cosa a la que pueda ir asociada una disputa.
- Los registros de eventos de solo adición evitan los conflictos por completo al no editar nunca sobre lo existente. Los partes de asistencia, las salidas de material, los movimientos de stock y los registros de inspección encajan ahí de forma natural: dos encargados marcando asistencia están añadiendo hechos, no compitiendo por sobrescribir uno.
- La fusión por campos funciona cuando un registro tiene secciones que pertenecen a roles distintos, de modo que dos ediciones no suelen tocar los mismos campos.
- La resolución explícita —mostrar ambas versiones y preguntar— es correcta cuando el dato importa lo bastante como para merecer la atención de alguien, e intolerable si ocurre a menudo. Si su modelo lo necesita con frecuencia, el modelo está mal.
En operaciones de campo, el registro solo por adición encaja en muchos más casos de los que los equipos esperan, y elimina por construcción la clase de error más difícil. Merece la pena reestructurar los datos para llegar ahí.
La cola es la parte que tiene que ser a prueba de todo
Todo lo que un usuario hace sin conexión entra en una cola, y ahí vive todo modo de fallo serio. La cola debe sobrevivir a que el sistema operativo cierre la aplicación, a que el teléfono se reinicie, y a que la aplicación se actualice a una versión cuya forma de datos ha cambiado.
Tres reglas lo hacen fiable. Cada operación en cola lleva un identificador generado por el cliente para que el servidor reconozca un duplicado: los reintentos son seguros, y sin idempotencia crean registros duplicados. Las operaciones se aplican en orden donde el orden importa, porque una creación que llega después de su propia actualización es un error de pérdida de datos. Y un elemento que falla de forma permanente debe aislarse y mostrarse en lugar de reintentarse eternamente, o un registro malo bloquea todos los buenos que hay detrás.
Ese último caso es el fallo más común en producción. Un registro que el servidor rechaza por una razón de validación se queda al frente de la cola reintentándose, mientras todo lo que hay detrás espera. El teléfono muestra «sincronizando». No se ha sincronizado nada en dos días.
Hacer visible el estado de sincronización
Los usuarios perdonan a una aplicación que no puede alcanzar el servidor. No le perdonan que no pueda decirles si lo ha hecho. Cada registro debería mostrar si está guardado localmente, en cola, sincronizado o fallido, y debería haber una pantalla que diga cuándo se produjo la última sincronización correcta y cuántos elementos esperan.
Esto también transforma el soporte. «No se guardó» no tiene respuesta. «Tres elementos en cola, última sincronización hace 2 días, uno falló por un error de validación» es una pregunta con respuesta, y esa respuesta se puede dar por teléfono.
Trabajo en segundo plano que de verdad se ejecuta
La optimización de batería de Android detendrá cualquier sincronización en segundo plano ingenua, y las capas de los fabricantes son más agresivas que Android puro, algunas con mucha diferencia. Una aplicación que sincroniza con fiabilidad en un Pixel puede no sincronizar en absoluto en un teléfono cuyo fabricante mata el trabajo en segundo plano de forma agresiva, y esos teléfonos son comunes justo en la franja de precio que lleva el personal de campo.
Use el planificador propio de la plataforma en lugar de un temporizador, sincronice de forma oportunista cuando la aplicación esté en primer plano, y no dependa nunca solo del trabajo en segundo plano para algo que el usuario necesita que haya ocurrido. Pruebe con los teléfonos baratos que llevan sus usuarios, no con el dispositivo del desarrollador.
Pruebe con la red que ellos tienen
- Modo avión durante una jornada laboral completa, luego reconectar y verificar que todo llega exactamente una vez.
- Una conexión limitada y con pérdidas en lugar de simplemente lenta: los modos de fallo son distintos, y es la pérdida lo que rompe las cosas.
- Un portal cautivo que devuelve 200 con una página de inicio de sesión, para confirmar que la aplicación comprueba la respuesta y no solo la conexión.
- Cierre la aplicación a mitad de la sincronización, reinicie el teléfono a mitad de la sincronización, y confirme que la cola sobrevive a ambas cosas.
- Dos dispositivos editando el mismo registro sin conexión, para confirmar que el modelo de conflictos hace lo que usted decidió y no lo que el framework hace por defecto.
- Una versión antigua de la aplicación sincronizando con un servidor actual, porque en despliegues de campo siempre hay alguien tres versiones por detrás.
El resumen
Trabajar sin conexión no es una funcionalidad que se añada cerca del final. Determina el modelo de datos, las reglas de conflictos, el diseño de la cola y la interfaz, e incorporarlo después se acerca a una reescritura. Decídalo primero, haga que el almacén local sea el que manda, elija el modelo de conflictos explícitamente, haga visible el estado de sincronización, y pruebe con una conexión mala en vez de una buena.