Hace unas semanas publiqué una nueva actualización de WorkIO. Desde fuera parecía una actualización cualquiera: pantallas nuevas, un widget, menos errores, algunas mejoras de diseño. Lo que casi nadie ve es que escribir el código fue, probablemente, la última decisión importante que tomé durante todo el proceso.
Si le preguntas a cualquier programador cuánto tiempo se dedica a escribir código durante una actualización, lo normal es que la respuesta ronde el todo o casi todo. Tiene sentido: es la parte que se mide en commits, en líneas cambiadas, en horas de editor. Es lo único que deja rastro visible. Pero una actualización no empieza cuando se abre el IDE. Empieza mucho antes. Y tampoco termina cuando se publica.
Todo empezó mucho antes del código
No empezó con una lista de tareas. Empezó con pequeños síntomas, de esos que uno va acumulando sin darles demasiada importancia hasta que, sumados, dejan de ser pequeños.
Los usuarios escriben por muchos canales. Algunos envían correos, otros dejan reseñas, otros hablan por redes sociales y otros, simplemente, dejan de utilizar una función, o incluso la aplicación. No todo el feedback tiene el mismo valor: una petición repetida por veinte usuarios diferentes probablemente merezca mucha más atención que una ocurrencia aislada, y al mismo tiempo un único comentario bien argumentado puede revelar un problema enorme que nadie había detectado. Al final, la confianza de un usuario no se construye solo dentro de la app, sino también en todo lo que rodea a esos canales de escucha.
En el caso de WorkIO, una de esas señales llevaba tiempo repitiéndose en soporte. Al registrar una jornada, opciones como las horas extra, el descanso o el comentario llevaban tiempo escondidas detrás de un desplegable adicional. Funcionaban perfectamente, pero el usuario tenía que saber que estaban ahí, leer el texto informativo o intuirlo para llegar a usarlas. Muchas de las dudas que recibíamos no eran sobre errores, sino sobre “¿y cómo añado las horas extra?”. La funcionalidad existía desde hacía tiempo; lo que fallaba era la comunicación.
Escuchar no consiste en apuntar todo. Consiste en entender qué problema hay realmente detrás de cada mensaje, porque muchas veces el usuario propone una solución, pero lo verdaderamente importante es descubrir cuál era el problema que intentaba resolver.
Truco práctico: cuando recibas una petición, no anotes la solución que propone el usuario, anota el problema. Dos usuarios pueden pedir cosas completamente distintas y estar describiendo, en realidad, el mismo problema de fondo.
Aquellos comentarios despertaron una sospecha. Pero una sospecha, por sí sola, no es suficiente para justificar una actualización.
Cuando escuchar ya no era suficiente
Faltaba comprobar si los datos contaban la misma historia. No todo el feedback llega escrito: parte del trabajo de escuchar consiste en revisar lo que la propia aplicación está contando en silencio, en la consola de Google Play, en las herramientas de diagnóstico, en los informes de estabilidad.
Dedicamos buena parte del ciclo previo a revisar los ANR (Application Not Responding, momentos en los que la aplicación deja de responder) más frecuentes que reportaba la Play Store. Ningún usuario nos había escrito quejándose explícitamente de ello, pero los datos estaban ahí, esperando a que alguien les prestara atención.
En paralelo, estuvimos analizando con LeakCanary varias fugas de memoria que la aplicación arrastraba desde hace tiempo. WorkIO tiene ya bastantes años de vida, y la forma en la que se construyó inicialmente respondía a unas prácticas y a un conocimiento de Android muy distintos de los que tengo ahora. Ese tipo de deuda técnica no la reporta ningún usuario porque, en la mayoría de los casos, no la percibe como tal; simplemente nota que la aplicación “a veces va más lenta” o “se cierra de vez en cuando” sin saber muy bien por qué. Detectar y corregir esas fugas fue trabajo silencioso, invisible para el usuario final, pero con un impacto directo en la estabilidad que sí se termina notando.
WorkIO ya partía de un ratio de errores realmente bajo, pero con esta revisión conseguimos reducirlo todavía más: el ratio de user-perceived crashes que reporta Google Play (el porcentaje de usuarios activos diarios que sufren al menos un fallo que probablemente hayan percibido, por ejemplo mientras la app está mostrando una pantalla o corriendo un servicio en primer plano) bajó de un 0.07 % a un 0.06 %. Puede parecer una diferencia pequeña, casi anecdótica, pero cuando ya partes de una base tan baja, cada décima que se recorta representa un porcentaje relativo importante de usuarios que dejan de sufrir un problema.
Truco práctico: si tienes una aplicación con cierto recorrido, revisa la sección de estabilidad (Android Vitals en Google Play Console, o el equivalente en App Store Connect) antes de decidir qué entra en la próxima actualización. Muchas veces el problema más importante no es el que más se comenta, es el que se sufre en silencio.
Con las quejas de soporte por un lado y los datos de estabilidad por otro, el panorama empezaba a estar bastante claro. Pero tener claro qué está fallando no es lo mismo que saber qué hacer con ello. Entonces llegó el momento de tomar decisiones.
Entonces hubo que decidir
Una de las habilidades más difíciles en producto es decir que no. No porque una idea sea mala, sino porque quizá no sea el momento adecuado. Cada nueva opción añade complejidad, cada botón nuevo aumenta la carga cognitiva y cada configuración adicional hace que otro usuario necesite más tiempo para aprender la aplicación.
En ocasiones, la mejor actualización es precisamente la que elimina una funcionalidad que no se usa, o la que consigue hacer lo mismo utilizando la mitad de elementos en pantalla. No todo lo que los datos y los usuarios señalaban podía entrar en la misma versión, así que hubo que priorizar: primero lo que afectaba a la estabilidad, después lo que afectaba a la comprensión de la app, y dejar para más adelante lo que era deseable pero no urgente. Ya hablamos en su momento de cómo cambia la importancia de una funcionalidad según el momento del producto, y este proceso fue un ejemplo más de esa misma idea.
Truco práctico: antes de dar luz verde a algo nuevo, pregúntate si un usuario habitual de la aplicación sabría usarlo a la primera, sin leer ninguna ayuda ni preguntar. Si la respuesta es no, el problema no suele ser que falte explicarlo mejor, sino que hay que simplificarlo antes.
Con las prioridades ya decididas, quedaba resolver cómo debía verse y sentirse todo aquello. Y ahí es donde entró el diseño.
Sólo entonces apareció el diseño
Una vez sabíamos qué queríamos resolver, apareció la pregunta de cómo esperaría el usuario encontrar cada mejora. No cómo podíamos implementarla, sino cómo le resultaría más natural utilizarla. Cuando una actualización está bien diseñada, el usuario apenas necesita pensar: simplemente entiende cómo funciona. Y eso rara vez ocurre por casualidad.
Las horas extra, el descanso y el comentario, escondidos hasta entonces, salieron al formulario principal con un diseño más moderno, para que estuvieran a la vista desde el primer momento y el usuario no tuviera que adivinar dónde buscar.
Aprovechamos además para modernizar el resto de la interfaz siguiendo la misma idea: separar el contenido en bloques claros, con tarjetas diferenciadas, en lugar de la maquetación más plana que tenía antes, y añadir iconos y recursos gráficos en sitios donde antes solo había un fondo de color liso, como en el aviso de que se está cargando un anuncio antes de mostrarlo.
Ese aviso de carga de publicidad merece una mención aparte, porque es un buen ejemplo de cómo hasta el detalle más pequeño es una decisión de UX, la misma idea de la que hablé cuando conté que en producto, los cambios inocentes no existen. Avisamos explícitamente antes de que se vaya a mostrar un anuncio para que al usuario no le pille por sorpresa y termine pulsándolo sin querer. No es una obligación técnica, es una decisión consciente de comunicación: preferimos ser transparentes con algo que sabemos que puede resultar molesto antes que optimizar clics accidentales.
También revisamos el flujo de fichaje rápido, simplificando los pasos necesarios y reforzando su usabilidad para que cualquier usuario, sin necesidad de haber usado antes la app, entendiera de un vistazo cómo fichar. Y para los usuarios suscritos añadimos un widget nuevo que permite registrar y controlar la jornada, con fichaje rápido incluido, directamente desde la pantalla principal del teléfono, sin necesidad de abrir la aplicación.
Ninguno de estos cambios nació de “nos apetecía modernizar la app”. Cada uno respondía a una fricción concreta que llevábamos tiempo observando: funciones escondidas, pasos de más, una interfaz que se había quedado visualmente atrás respecto a lo que el usuario espera hoy de cualquier aplicación.
Ahora sí llegó el código
Solo después de todo aquello abrimos el editor. Perseguir uno a uno los ANR ya priorizados, cerrar las fugas de memoria detectadas con LeakCanary, reconstruir el formulario de jornada y buena parte de la interfaz con el nuevo sistema de bloques, desarrollar desde cero el widget de fichaje rápido. Trabajo técnico real, sin duda, pero cada línea respondía a una decisión tomada mucho antes: qué arreglar primero, qué mostrar y dónde, qué merecía la pena construir desde cero.
Paradójicamente, esta suele ser la parte más cómoda para muchos desarrolladores, porque por fin el problema ya está definido.
El final aun quedaba lejos
Muchos considerarían que el trabajo termina cuando se genera el binario. En realidad, ahí empieza otra fase completamente distinta: preparar las notas de la versión, enviar la actualización, esperar las revisiones de las tiendas y, en muchos casos, realizar un despliegue progresivo; básicamente, atender a la evolución y las reacciones a los cambios.
Publicar al 100 % de los usuarios desde el primer minuto puede convertir un pequeño error en un problema enorme. Liberar una actualización poco a poco permite detectar incidencias antes de que afecten a toda la base de usuarios, apoyándonos en las mismas fases de prueba que ofrecen Google Play y App Store para ir ampliando la cobertura con seguridad.
Truco práctico: si tu plataforma lo permite, empieza siempre por un porcentaje reducido (5-10 %) y observa métricas de errores y opiniones durante al menos 24-48 horas antes de aumentar la cobertura. Ese margen es, muchas veces, la diferencia entre un incidente menor y una crisis de reputación.
Pero todavía faltaba la parte más importante
La publicación no es el final, es el comienzo de la siguiente conversación con los usuarios. Hay que observar errores inesperados, métricas de uso, reseñas, nuevos comentarios, tickets de soporte y el comportamiento real de las funcionalidades. En ocasiones, una característica técnicamente impecable termina utilizándose de una forma completamente distinta a la prevista, y eso obliga a volver a empezar el ciclo: escuchar, analizar, decidir, diseñar, implementar, publicar y escuchar otra vez.
En este caso, la respuesta llegó rápido y fue clara: las once reseñas recibidas tras la publicación han sido de 5 estrellas, todas, sin excepción. No es una muestra enorme, pero en una aplicación usada por decenas de miles de usuarios al mes, once reseñas seguidas con la puntuación máxima es una señal razonablemente fiable de que el rumbo tomado ha sido el correcto.
Lo interesante es que esa respuesta positiva llegó a pesar de haber introducido dos nuevos banners publicitarios en la misma actualización: uno en la parte superior de la pantalla de listado de jornadas y otro entre las tarjetas de la sección de estadísticas. En la misma versión en la que añadíamos algo que, sobre el papel, podría considerarse “negativo” para la experiencia del usuario, la satisfacción no solo se mantuvo, sino que se reflejó en la mejor puntuación posible.
Esto no ocurrió por casualidad. La ubicación de esos banners se pensó de forma discreta, en espacios que no interrumpen ninguna acción del usuario, y llegó envuelta en el resto de mejoras de usabilidad y estabilidad que la aplicación llevaba tiempo necesitando. Cuando el balance percibido es claramente positivo, el usuario tolera, e incluso pasa por alto, decisiones de negocio que en otro contexto podrían haber generado fricción. La publicidad no dejó de ser publicidad, pero dejó de ser lo primero en lo que el usuario se fijaba.
Ahora bien, que el usuario no se resintiera no significa que la decisión saliera gratis por completo. Los ingresos publicitarios de estas semanas han caído, prácticamente a la mitad, por dos motivos que se han solapado: la estacionalidad propia del verano, que hace bajar el eCPM (el ingreso estimado por cada mil impresiones) en todo el sector, y el propio salto en el número de solicitudes de anuncios al añadir dos banners nuevos de golpe, que hace que el algoritmo de la red publicitaria reajuste a la baja el valor por impresión hasta que ese nuevo volumen se estabiliza. Es un tema lo bastante amplio como para merecer su propio artículo más adelante si hay interés, pero conviene tenerlo presente: el “cuándo” y el “cómo” de una decisión de negocio pesan tanto como la decisión en sí.
El verdadero producto nunca deja de evolucionar
Con frecuencia se habla del desarrollo de software como si fuera una sucesión de versiones: la 1.2, después la 1.3, luego la 2.0. Pero desde dentro la sensación es muy distinta. No existen versiones, existe una conversación continua entre quienes construyen el producto y quienes lo utilizan. Cada actualización es, simplemente, la respuesta más reciente a esa conversación.
El código no fue el principio de todo esto. Tampoco fue el final. Fue, simplemente, el momento en el que todas esas decisiones anteriores, escuchar, mirar los datos, priorizar, diseñar, se convirtieron en algo que un usuario puede usar sin pensar en nada de lo anterior.
Y quizá esa sea una de las lecciones más importantes para cualquier desarrollador que algún día quiera acercarse al negocio: programar no es lo mismo que crear un producto; el código nunca ha sido lo más relevante. El código es la herramienta que utilizamos para mejorar el producto. Y cuanto antes entendamos esa diferencia, mejores decisiones tomaremos, tanto delante del teclado como fuera de él.
Las mejores actualizaciones no empiezan con una línea de código. Empiezan entendiendo mejor a las personas que utilizan el producto.
Happy Building!!