El dispositivo real de tus usuarios frente al tuyo
Cuánto separa a tu portátil del teléfono del percentil 75 de tu tráfico, qué señales del navegador sirven para clasificar dispositivos, y cómo construir un índice de capacidad propio con un micro-banco de pruebas.
Todo el trabajo de rendimiento se hace en una máquina que no se parece en nada a la de la persona para la que se trabaja. El desarrollador tiene un portátil caro, conectado a la corriente, con fibra y sin nada más abierto; el usuario del percentil 75 tiene un teléfono de hace tres años, con la batería al cuarenta por ciento, en una red móvil variable y con quince pestañas de fondo. La distancia entre esos dos mundos es de un orden de magnitud, y no es una intuición: se mide, y hay que medirla en tu tráfico, no en el de un informe.
- Cuantificar la diferencia de rendimiento monohilo entre tu máquina y el dispositivo objetivo.
- Enumerar las señales de capacidad que expone el navegador y sus límites de disponibilidad.
- Construir un índice de capacidad con un micro-banco de pruebas que no distorsione la carga.
- Segmentar tus métricas de campo por clase de dispositivo y elegir el percentil que decide.
La magnitud del desfase
Lo que separa a las dos máquinas no es el número de núcleos ni los gigahercios de la ficha técnica, sino el rendimiento monohilo, porque el hilo principal del navegador es uno solo y es donde ocurre todo lo que le importa al usuario. En esa dimensión, los números aproximados:
| Dispositivo | Rendimiento monohilo relativo | Comentario |
|---|---|---|
| Portátil de desarrollo reciente | 1x | La referencia, y la máquina en la que nadie mide de verdad |
| Teléfono de gama alta reciente | 1,2x a 2x más lento | Sorprendentemente cerca, y por eso engaña |
| Teléfono de gama media de hace tres años | 4x a 6x más lento | El percentil 50 de mucho tráfico real |
| Teléfono de gama baja actual | 6x a 10x más lento | El percentil 75 en muchos mercados |
| Teléfono de gama baja de hace cuatro años | 10x a 20x más lento | La cola que decide tus percentiles altos |
Tres consecuencias que conviene tener presentes al leer esa tabla.
La gama alta engaña. Un teléfono caro reciente tiene un rendimiento monohilo cercano al de un portátil, y eso hace que probar “en móvil” con el teléfono del desarrollador no sirva de nada. La prueba parece un móvil, se comporta como un portátil, y confirma que todo va bien.
El desfase no se cierra con el tiempo. Cada generación mejora los dos extremos, así que la proporción se mantiene. Y la vida útil de un teléfono ronda los tres o cuatro años, con lo que el parque instalado siempre arrastra varias generaciones.
Los efectos se multiplican, no se suman. Un dispositivo lento tiene además menos memoria, con lo que el recolector de basura entra más veces; menos caché de procesador, con lo que cada acceso a memoria cuesta más; y almacenamiento más lento, con lo que leer de la caché del navegador tarda más. Por eso el desfase medido en una tarea real suele ser mayor que el que predice un banco de pruebas sintético.
Las señales del navegador
El navegador expone tres señales de capacidad. Ninguna es buena por sí sola y las tres juntas ya sirven de algo.
navigator.hardwareConcurrency. Número de hilos que el navegador está dispuesto a usar. Ampliamente disponible. Sus límites: cuenta hilos, no potencia, así que un móvil de ocho núcleos lentos da un ocho igual que un portátil de ocho núcleos rápidos; y algunos navegadores lo acotan por privacidad.
navigator.deviceMemory. Memoria del dispositivo en gigabytes, redondeada a potencias de dos y con tope en ocho. Solo en motores basados en Chromium. El tope importa: una máquina de treinta y dos gigabytes informa de ocho, así que la señal solo distingue bien en el extremo bajo, que por suerte es el que te interesa. Un valor de uno o de dos es una señal fuerte de dispositivo limitado.
navigator.connection. Con effectiveType, que estima la calidad de la conexión en las categorías clásicas de generación de red, y saveData, que indica que el usuario ha pedido explícitamente ahorrar datos. También solo en Chromium. effectiveType es una estimación a partir de mediciones recientes, no el tipo de red real: un móvil en una wifi mala informa de una categoría baja, que es justo lo que quieres saber.
Y saveData merece una mención aparte porque es la única de las tres que es una declaración de intención del usuario y no una inferencia. Respetarla es barato y correcto: no precargar vídeo, servir imágenes más comprimidas, saltarse animaciones decorativas.
function senalesDelDispositivo() {
const c = navigator.connection || {};
return {
hilos: navigator.hardwareConcurrency || null,
memoriaGB: navigator.deviceMemory || null, // solo Chromium, tope 8
red: c.effectiveType || null, // solo Chromium
ahorroDeDatos: c.saveData === true,
movil: navigator.userAgentData?.mobile ?? /Mobi|Android/i.test(navigator.userAgent),
};
}
El micro-banco de pruebas
Como las señales declaradas son pobres, la alternativa es medir. Un banco de pruebas en el arranque tiene un problema evidente —consume el recurso que estás intentando proteger—, y hay una forma de hacerlo que lo evita: ejecutarlo en tiempo libre, después de que la página esté interactiva, y guardar el resultado para las visitas siguientes.
const CLAVE = 'indice-capacidad';
// Trabajo fijo y determinista: aritmetica entera y accesos a memoria.
function trabajoDeReferencia() {
const n = 200000;
const datos = new Float64Array(n);
for (let i = 0; i < n; i++) datos[i] = (i * 2654435761) % 1000;
let suma = 0;
for (let paso = 0; paso < 12; paso++) {
for (let i = 0; i < n; i++) suma += Math.sqrt(datos[i]) * 1.0000001;
}
return suma;
}
function medirIndice() {
// Tres pasadas: la primera calienta el compilador optimizador.
let mejor = Infinity;
for (let i = 0; i < 3; i++) {
const t = performance.now();
trabajoDeReferencia();
mejor = Math.min(mejor, performance.now() - t);
}
return Math.round(mejor);
}
export function indiceDeCapacidad() {
const guardado = localStorage.getItem(CLAVE);
if (guardado) return Promise.resolve(Number(guardado));
return new Promise((resolver) => {
const ejecutar = () => {
const ms = medirIndice();
localStorage.setItem(CLAVE, String(ms));
resolver(ms);
};
// Nunca durante la carga. En tiempo libre o, si no existe, muy tarde.
if ('requestIdleCallback' in window) {
requestIdleCallback(ejecutar, { timeout: 8000 });
} else {
setTimeout(ejecutar, 5000);
}
});
}
Tres decisiones de diseño que hay que respetar si lo adaptas. Coger el mínimo de tres pasadas y no la media: la media la contamina cualquier interrupción del sistema, mientras que el mínimo se aproxima a la capacidad real del dispositivo sin ruido. Guardar el resultado en almacenamiento local para no repetirlo nunca en el mismo navegador. Y ejecutarlo en tiempo libre y con un tope de espera, para que en un dispositivo que nunca está ocioso —que es precisamente el lento— acabe ejecutándose igualmente y no se pierda el dato que más te interesa.
Con ese número calibras las cubetas midiendo dispositivos conocidos:
export function claseDeDispositivo(ms) {
if (ms < 120) return 'rapido'; // portatil o gama alta
if (ms < 400) return 'medio'; // gama media reciente
if (ms < 900) return 'lento'; // gama media antigua o baja actual
return 'muy-lento'; // gama baja antigua
}
Los umbrales de arriba son un punto de partida y hay que ajustarlos con tres o cuatro dispositivos reales que tengas a mano. Lo que importa no es acertar los cortes exactos, sino que la clasificación sea estable: que el mismo teléfono caiga siempre en la misma cubeta.
Segmentar y elegir el percentil
Con la clase de dispositivo adjunta a cada informe de campo, el panel cambia de naturaleza. El agregado de LCP de un sitio es una mezcla de dos poblaciones muy distintas, y esa mezcla oculta exactamente lo que hay que arreglar:
LCP p75 global 2,4 s -> parece aceptable
clase rapido 58 % 1,6 s
clase medio 27 % 3,1 s
clase lento 12 % 5,8 s
clase muy-lento 3 % 9,2 s
El número global entra dentro del umbral bueno y la mitad de tus usuarios no. Y hay una segunda lectura, menos evidente y más importante: si la mayoría de tu tráfico es de dispositivos rápidos, el percentil 75 global puede no contener ni un solo dispositivo lento. Ese percentil es entonces incapaz, por construcción, de detectar una regresión que afecte solo a los lentos. Vigilar el percentil 75 dentro de cada clase es lo que resuelve el problema, y es un cambio de configuración del panel, no de código.
La pregunta de qué segmento gobierna las decisiones no la contesta la ingeniería. Un sitio cuyo negocio está en el segmento con mejores dispositivos puede decidir legítimamente optimizar para ellos. Lo que no es legítimo es tomar esa decisión sin saberlo, que es lo que ocurre cuando solo se mira el agregado.
Merece la pena enumerar todas las formas en que tu experiencia cotidiana del producto difiere de la del usuario, porque no es una y son muchas, y todas empujan en la misma dirección. Tu máquina es de cinco a diez veces más rápida. Tu red tiene una latencia de un dígito y no pierde paquetes. Tienes todos los recursos en caché caliente porque acabas de recargar catorce veces. Tienes el service worker en memoria. Tienes las fuentes ya descargadas de la última sesión. Tu conexión con la API pasa a menudo por un entorno local sin latencia de red. Bloqueas la publicidad. Tienes la sesión iniciada y saltas los pasos que un usuario nuevo recorre. Conoces la interfaz de memoria, así que nunca esperas leyendo. Y, la más traicionera de todas, sabes qué va a pasar al pulsar, con lo que tu cerebro rellena el hueco de la latencia con la expectativa y literalmente percibes menos espera que alguien que no sabe qué esperar. Cada uno de esos factores por separado es pequeño y corregible; juntos producen una divergencia que ninguna cantidad de buena voluntad cierra. Este es el punto que quiero dejar clavado: el sesgo no es una cuestión de actitud, es una cuestión de instrumentos, y por eso las exhortaciones a “acordarse de los usuarios con conexiones lentas” no han cambiado nunca nada en ningún equipo. Lo que cambia las cosas es tener el número delante todos los días, segmentado, en un sitio donde se mira sin buscarlo. Un panel con el percentil 75 de LCP y de INP por clase de dispositivo, en la pantalla donde el equipo mira los despliegues, hace más por el rendimiento de un producto que cualquier sesión de formación. Y hay un segundo instrumento, más incómodo y más eficaz todavía, que es tener un teléfono barato de verdad en la mesa y usarlo para revisar cada funcionalidad antes de darla por terminada. La razón por la que funciona no es que descubra fallos que el panel no ve, aunque también; es que convierte una abstracción estadística en una experiencia. Nadie que haya esperado once segundos en un teléfono real a que cargue su propia página vuelve a discutir un presupuesto de JavaScript de la misma manera.
- Ejecuta el micro-banco en tu portátil y en el teléfono más barato que tengas a mano. Calcula el cociente.
- Despliega la recogida del índice y de las tres señales del navegador junto a tus datos de campo durante una semana.
- Construye el histograma de clases de dispositivo de tu tráfico. Anota qué porcentaje cae en las dos cubetas lentas.
- Reconfigura tu panel para mostrar el percentil 75 por clase, no global, y compara la historia que cuenta cada versión.
- Comprueba qué fracción de tu tráfico envía
saveDatay decide qué haces distinto con ellos.