El oficio más allá del código: percepción, gama baja y batería
Hay una categoría de calidad que ninguna prueba automatizada captura y que decide la reputación de un producto: si el sistema de diseño se usa como un lenguaje de decisiones o como una paleta de colores, si el tiempo que el usuario percibe coincide con el que mide el perfilador, si la aplicación es usable en el teléfono de ciento veinte euros donde vive la mayoría del mercado real, y si el consumo de batería y de datos respeta un recurso que pertenece al usuario y no al producto. Esta lección recorre esas cuatro dimensiones del oficio con instrumentos concretos, umbrales defendibles y las trampas que solo se aprenden viendo caer métricas que nadie sabía que existían.
Se puede escribir una aplicación impecable en lo técnico y detestable en el uso. Es más: es lo habitual. La arquitectura correcta, las pruebas verdes, el código limpio y la cobertura alta son condiciones necesarias que no dicen absolutamente nada sobre si el producto se siente bien en la mano de alguien que va en el metro, con una mano ocupada, con un teléfono de hace cuatro años y con un dos por ciento de batería. Esa segunda capa de calidad —la que el usuario percibe y ninguna herramienta de integración continua puede afirmar— tiene sus propias reglas, sus propios instrumentos y su propio cuerpo de conocimiento, y es la que separa las aplicaciones que la gente conserva de las que desinstala sin saber explicar por qué. Cuatro dimensiones la componen y las cuatro se aprenden mal por separado: el sistema de diseño entendido como gramática de decisiones y no como catálogo de componentes; el tiempo percibido, que obedece a la psicología y no al reloj; la calidad en el dispositivo modesto, que es el dispositivo mayoritario aunque no sea el del equipo; y el respeto por la batería y por los datos, que son propiedad del usuario y no un recurso gratuito del producto. Ninguna de las cuatro se puede añadir al final. Las cuatro son consecuencia de decisiones tomadas mientras se construye.
- Usar el sistema de diseño como un lenguaje de decisiones con reglas de jerarquía, densidad y movimiento, no como un conjunto de componentes.
- Distinguir el tiempo medido del tiempo percibido y aplicar las técnicas que reducen el segundo sin tocar el primero.
- Establecer un umbral de calidad para dispositivos de gama baja y verificarlo con instrumentos reproducibles.
- Auditar el consumo de batería, red y datos de una aplicación y justificar cada gasto ante el usuario.
El sistema de diseño como gramática
Material se aprende habitualmente al revés: como una lista de componentes que se colocan en la pantalla. Entendido así produce interfaces que cumplen la especificación y no comunican nada, porque los componentes son el vocabulario y lo que falta es la sintaxis. La gramática real tiene tres reglas y todas se refieren a relaciones, no a piezas. La primera es la jerarquía: en cada pantalla hay exactamente una acción principal, y el color, el tamaño y la posición existen para señalarla; cuando dos elementos compiten por ser lo más importante, ninguno lo es. La segunda es la densidad: el espaciado no es decorativo sino semántico, y lo que está junto se lee como relacionado, de modo que un espaciado uniforme aplicado por comodidad destruye la agrupación que el usuario necesita para entender la pantalla sin leerla. La tercera es el movimiento: una transición existe para explicar de dónde viene una cosa y a dónde va, y una animación que no responde esa pregunta solo añade latencia con buen gusto.
Estas tres reglas tienen una traducción directa al código, y es la que separa un tema que se puede evolucionar de una colección de valores mágicos repartidos por doscientos ficheros. Un producto que respeta la gramática no escribe nunca un color ni un tamaño literal en una pantalla: los toma del tema, y el tema es el único sitio donde la identidad de marca se decide. Esa disciplina es también lo que hace posible el modo oscuro, el color dinámico y la accesibilidad tipográfica sin tocar una sola pantalla, porque las tres cosas son sustituciones del tema y no cambios del árbol.
// Mal: la decision de marca vive esparcida por las pantallas.
Text("Confirmar", color = Color(0xFF1B5E20), fontSize = 18.sp)
// Bien: la pantalla expresa intencion; el tema decide la apariencia.
Text(
text = stringResource(R.string.confirmar),
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.labelLarge,
)
La misma gramática gobierna un aspecto que se trata como accesorio y no lo es: el texto. La microcopia de un botón, de un estado vacío o de un mensaje de error hace más por la comprensión de una pantalla que cualquier decisión visual, y sin embargo suele escribirse al final, deprisa y por quien programa. Un error que dice que la operación no pudo completarse no informa de nada; uno que dice qué pasó, si se perdió algo y qué puede hacer el usuario a continuación convierte un momento de frustración en uno de confianza. Escribir esos textos antes que el código, y no después, cambia además el diseño, porque obliga a saber qué estados existen realmente.
De ahí se deriva un criterio práctico sobre la personalización. El sistema de diseño no exige que tu producto parezca genérico, pero sí que las desviaciones sean intencionadas y coherentes. Cambiar la forma de los contenedores, la escala tipográfica o la paleta es legítimo y a menudo necesario para la identidad de marca; lo que no es defendible es desviarse en las convenciones de interacción —dónde está la navegación, qué hace el gesto de retroceso, qué significa un elemento deslizable— porque esas convenciones no las aprendió el usuario en tu aplicación sino en las cincuenta que ya tenía instaladas, y romperlas cobra un impuesto de aprendizaje que casi nunca compensa lo que aporta.
Toda pantalla tiene cuatro estados y los equipos diseñan uno. El estado con datos se dibuja el primer día; el de carga se resuelve con un indicador giratorio; el de error se resuelve con un texto genérico; y el estado vacío, que es el primero que ve todo usuario nuevo en su vida, se resuelve con una pantalla en blanco. Invertir esa prioridad —diseñar primero el vacío y el error, luego la carga y por último el caso feliz— produce productos que se sienten cuidados, y cuesta lo mismo.
El tiempo percibido
El rendimiento es un fenómeno psicológico antes que uno computacional. Existe un umbral alrededor de los cien milisegundos por debajo del cual una respuesta se percibe como instantánea; hasta el segundo, el usuario mantiene el hilo de lo que estaba haciendo; a partir de los diez segundos, se va a otra cosa. Pero estos umbrales admiten una manipulación honesta y poderosa: lo que se mide no es la duración sino la incertidumbre. Una espera de tres segundos con una barra que progresa se soporta mejor que una de dos con un círculo que gira sin destino, porque lo que agota no es esperar sino no saber cuánto falta ni si el sistema está vivo.
// Actualizacion optimista: el estado cambia al instante y se reconcilia despues.
fun alternarFavorito(id: String) = viewModelScope.launch {
val anterior = _estado.value
_estado.update { it.conFavorito(id, activo = true) } // 0 ms percibidos
repositorio.marcarFavorito(id).onFailure {
_estado.value = anterior
_mensajes.emit(Mensaje.NoSePudoGuardar) // reversion visible y honesta
}
}
Hay un segundo factor psicológico igual de potente y menos conocido: el trabajo pasivo se percibe mucho más largo que el activo. Una espera durante la cual el usuario puede hacer algo —escribir el resto del formulario, ver la lista anterior, leer un resumen— consume una fracción de la paciencia que consume la misma espera con la pantalla bloqueada. De ahí se deriva una regla de diseño que casi ningún equipo aplica: bloquear la interfaz entera durante una operación es la peor opción disponible en casi todos los casos, y solo se justifica cuando continuar produciría un estado incoherente. Todo lo demás debe ocurrir en segundo plano con la pantalla viva.
Las tres técnicas que más tiempo percibido eliminan sin tocar el tiempo real son la respuesta optimista, que confirma la acción antes de que el servidor responda y revierte de forma visible si falla; el esqueleto de contenido, que muestra la forma de lo que va a llegar y evita el salto de disposición que obliga a releer; y la precarga anticipada de lo que el usuario probablemente pedirá a continuación, que convierte una espera futura en trabajo hecho durante una pausa. A ellas se suma la única que sí toca el tiempo real y tiene el mayor retorno de todas: el perfil de arranque, que reordena el código en el artefacto y compila anticipadamente los caminos críticos, con mejoras del orden del veinte o el treinta por ciento en el primer dibujo sin cambiar una línea de lógica.
Mostrar un indicador de carga antes de que hayan pasado unos doscientos milisegundos convierte una operación que se habría sentido instantánea en una espera consciente, y produce además el parpadeo característico de aparecer y desaparecer en el mismo fotograma. La regla es retrasar la aparición del indicador y, una vez mostrado, mantenerlo un mínimo perceptible. Contraintuitivo, medible y prácticamente nunca implementado.
El dispositivo que no tienes
Existe una desconexión estructural en el desarrollo móvil: quien construye trabaja con el teléfono más caro de la gama y quien usa, mayoritariamente, no. El dispositivo mediano del mercado real tiene menos memoria, un almacenamiento notablemente más lento, una red peor y una gestión térmica que reduce la frecuencia del procesador a los pocos minutos de uso intenso. Una aplicación que va fluida en el dispositivo del equipo y a doce fotogramas por segundo en el del usuario no tiene un problema de optimización: tiene un problema de método, porque nadie miró nunca donde había que mirar.
Hay además una asimetría de método que conviene nombrar: en el dispositivo caro, el margen de tiempo por fotograma absorbe los errores, de modo que un trabajo que no debería estar en la composición pasa desapercibido durante meses. En el dispositivo modesto, ese mismo error se manifiesta el primer día. Por eso el teléfono barato no es solo el destino de una prueba de compatibilidad: es el mejor instrumento de diagnóstico que un equipo puede tener, porque amplifica exactamente los defectos que las herramientas de perfilado tardan más en revelar.
// Umbral escrito y comprobado por la maquina, no por la memoria de nadie.
@Test
fun arranqueEnFrioDentroDelUmbral() = benchmarkRule.measureRepeated(
packageName = "com.ejemplo.app",
metrics = listOf(StartupTimingMetric()),
iterations = 15,
startupMode = StartupMode.COLD,
) {
pressHome()
startActivityAndWait()
}
La corrección es organizativa antes que técnica. Hay que definir un dispositivo de referencia bajo, comprarlo físicamente, tenerlo en la mesa y medir en él los indicadores que importan: tiempo hasta el primer dibujo, tiempo hasta que la pantalla es interactiva, fotogramas perdidos durante el desplazamiento de la lista principal y consumo de memoria en el peor caso. Esos cuatro números deben tener umbral escrito y vigilancia automática, porque una regresión de rendimiento no se detecta por casualidad. Y conviene medir siempre después de un rato de uso, no en el primer minuto, porque el estrangulamiento térmico es precisamente lo que el dispositivo caro esconde y el modesto revela.
flowchart TD
A[Dispositivo de referencia bajo] --> B[Medir arranque en frio]
A --> C[Medir fotogramas perdidos en la lista]
A --> D[Medir memoria en el peor caso]
A --> E[Medir tras diez minutos de uso continuo]
B --> F{Dentro del umbral escrito}
C --> F
D --> F
E --> F
F -->|no| G[Regresion bloqueante en integracion continua]
F -->|si| H[Publicar y seguir vigilando en campo]
H --> I[Metricas reales segmentadas por gama de dispositivo]
I --> ALa batería y los datos son del usuario
El último tramo del oficio es un asunto ético con consecuencias comerciales inmediatas. La batería no es un recurso del producto sino del usuario, y cada despertar del dispositivo, cada sondeo periódico, cada localización solicitada y cada megabyte descargado por una red móvil es un gasto que alguien paga. El sistema lleva una década construyendo defensas contra las aplicaciones que abusan —aplazamiento del trabajo diferido, restricciones por inactividad, cubos de uso, limitación del arranque en segundo plano— y esas defensas caen sobre todos, también sobre quien se porta bien, precisamente porque el sistema no puede distinguir intenciones. Aceptar esa realidad cambia la actitud: en lugar de buscar la manera de saltarse la restricción, el trabajo consiste en necesitar menos.
// Un solo trabajo periodico amplio en vez de cuatro estrechos y frecuentes.
val sincronizacion = PeriodicWorkRequestBuilder<SincronizarWorker>(6, TimeUnit.HOURS)
.setConstraints(
Constraints(
requiredNetworkType = NetworkType.UNMETERED, // respeta la conexion medida
requiresBatteryNotLow = true, // no compite con el usuario
requiresDeviceIdle = false,
)
)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.MINUTES)
.build()
Las cuatro decisiones que más consumo eliminan son conocidas y raramente aplicadas con rigor. Agrupar el trabajo diferido en pocas ejecuciones amplias en lugar de muchas frecuentes, porque el coste dominante es despertar la radio y no transmitir. Reaccionar a eventos empujados desde el servidor en lugar de sondear, que es la diferencia entre cero despertares y cuarenta y ocho diarios. Pedir la precisión mínima suficiente en la localización, ya que la precisión fina cuesta un orden de magnitud más que la aproximada y casi ningún caso de uso la necesita de verdad. Y respetar la conexión medida del usuario, aplazando descargas grandes y sincronizaciones no urgentes hasta que haya red sin coste. A eso se añade una regla de transparencia que además es rentable: cuando el producto necesita un gasto real, explicarlo antes de pedirlo aumenta la concesión de permisos y reduce las desinstalaciones.
Los datos móviles merecen la misma consideración que la batería y reciben mucha menos. En buena parte del mundo, el usuario paga por megabyte y sabe perfectamente qué aplicaciones le vacían el plan, porque el sistema se lo muestra desglosado. Una imagen servida al tamaño real de la pantalla en lugar del original de dos megabytes, una respuesta comprimida, una caché con validación condicional y una descarga grande aplazada hasta que haya red sin coste son cuatro decisiones baratas cuyo efecto acumulado es la diferencia entre aparecer o no en la lista de las que el usuario decide desinstalar cuando revisa su consumo a final de mes.
Los paneles de consumo del sistema y del propio dispositivo son la fuente que el usuario consulta antes de culpar a una aplicación, así que son la fuente que hay que auditar. Deja la aplicación instalada y sin usar durante veinticuatro horas y mira qué aparece atribuido a tu nombre en el desglose de batería y de datos. Si hay actividad que no puedes justificar en una frase, tienes un problema que ninguna prueba de rendimiento te va a señalar.
Gramática, no catálogo
Jerarquía, densidad y movimiento. Personaliza la forma y el color; no toques las convenciones de interacción que el usuario aprendió en otras aplicaciones.
Tiempo percibido
Respuesta optimista, esqueletos y precarga bajan la espera sentida sin tocar el reloj. El perfil de arranque baja las dos a la vez.
Gama baja real
Compra el dispositivo modesto, ponlo en la mesa, mide tras diez minutos de uso y escribe los umbrales antes de necesitarlos.
Recurso ajeno
Agrupar, empujar en vez de sondear, pedir la precisión mínima y respetar la conexión medida. Necesitar menos gana siempre a esquivar restricciones.
Merece la pena reflexionar sobre una asimetría que estructura toda esta lección y que la industria del software resuelve sistemáticamente mal. Todo lo que se puede medir automáticamente termina siendo medido, vigilado y optimizado, mientras que todo lo que exige juicio humano termina siendo relegado a la categoría de opinión y por tanto a la de negociable. La cobertura de pruebas, el número de advertencias del analizador y el tiempo de compilación acaban en un panel; la sensación de que una transición explica de dónde viene la pantalla, la coherencia de la jerarquía visual o la honestidad de un estado de carga no acaban en ninguno. La consecuencia es predecible: los equipos convergen hacia la excelencia en lo instrumentado y hacia la mediocridad en lo demás, no por falta de criterio sino porque el sistema de incentivos solo devuelve señal en una dirección. Y sin embargo, la parte no instrumentada es exactamente la que el usuario percibe. Nadie desinstala una aplicación por su cobertura de pruebas. Hay una razón más profunda por la que estas cuatro dimensiones aparecen juntas y no dispersas por el temario: las cuatro son formas del mismo respeto. Diseñar con jerarquía es respetar la atención de quien mira, que es finita. Reducir el tiempo percibido es respetar la paciencia de quien espera, que también lo es. Funcionar en un teléfono barato es respetar a la mayoría del mundo, que no puede permitirse el que usa el equipo de desarrollo, y aquí la dimensión deja de ser técnica para volverse abiertamente política: una aplicación que solo funciona bien en dispositivos caros excluye a la gente por su renta, aunque nadie tomara esa decisión conscientemente. Y cuidar la batería es respetar un recurso que pertenece a otra persona y del que el producto se apropia sin pedir permiso cada vez. Ninguna de las cuatro se puede añadir en una fase final de pulido, porque las cuatro son consecuencias de decisiones tomadas mucho antes: la jerarquía se decide al modelar el estado de la pantalla, el tiempo percibido al elegir dónde vive la fuente de verdad, la gama baja al escoger qué se calcula por fotograma, y la batería al decidir cómo llega la información. Por eso este nivel es de síntesis y no de detalle: estas cuatro cosas no son un apartado del oficio, son el oficio visto desde el único lugar que importa, que es el lado de quien usa lo que hemos construido.
- Audita una pantalla real contra las tres reglas de la gramática y rediseña la jerarquía para que haya una única acción principal evidente.
- Diseña los cuatro estados de esa pantalla empezando por el vacío y el de error, y compara el resultado con lo que había.
- Implementa una acción optimista con reversión visible y mide la diferencia entre el tiempo real y el percibido con usuarios reales.
- Consigue un dispositivo de gama baja, mide los cuatro indicadores tras diez minutos de uso continuo y escribe los umbrales que harán fallar la integración continua.
- Registra durante un día los despertares, las peticiones de red y la localización que solicita tu aplicación, y justifica cada uno ante un usuario imaginario que te pregunta por qué.