INP con precisión: la ventana, el percentil y las tres partes
Qué cuenta como interacción, cómo se descarta el peor caso cuando hay muchas, en qué se divide la latencia, y qué medía mal FID.
INP no es “cuánto tarda un clic”. Es un estadístico calculado sobre todas las interacciones de una visita, con una regla de descarte de valores atípicos y una definición muy concreta de qué eventos agrupa. Los tres detalles que casi nadie conoce, el descarte de uno por cada cincuenta, el umbral de duración por defecto, y qué gestos quedan fuera, son los que explican las discrepancias entre lo que mides y lo que ves.
- Definir qué es una interacción y qué eventos la componen.
- Aplicar la regla de descarte de valores atípicos según el número de interacciones.
- Descomponer la latencia de una interacción en sus tres partes.
- Explicar qué medía FID y por qué era insuficiente.
Qué es una interacción
Una interacción es un grupo de manejadores de eventos que se disparan durante el mismo gesto lógico del usuario. No es un evento suelto: un toque en una pantalla táctil incluye pointerdown, pointerup y click; una pulsación de tecla incluye keydown, keypress y keyup.
Solo se observan tres tipos de gesto:
- Clic con un ratón.
- Toque en un dispositivo con pantalla táctil.
- Pulsación de una tecla, física o en pantalla.
Y quedan explícitamente fuera el desplazamiento, el paso del ratón por encima y el zoom. Esta exclusión es la zona ciega más importante de la métrica: una página cuyo scroll va a tirones y cuyo zoom se atasca puede tener un INP impecable. Ojo con el matiz: algunas variantes de esos gestos incluyen un clic o un toque, y esa parte sí se mide.
La latencia de una interacción es la duración más larga entre los manejadores del grupo, contada desde que el usuario inicia el gesto hasta el momento en que el navegador puede pintar el fotograma siguiente. En casos raros puede no haber fotograma que pintar, y entonces la interacción termina cuando el navegador habría podido pintarlo.
Las interacciones también cuentan si ocurren dentro de un iframe, porque el usuario no distingue si lo que está pulsando es un marco incrustado o no. La API, en cambio, no ve dentro de los iframes: esa es una fuente conocida de diferencia entre los datos públicos del navegador y la instrumentación propia.
Cómo se obtiene el valor
La ventana y el percentil
Aquí está la parte que casi todo el mundo se salta. INP no es simplemente la peor interacción.
Para la mayoría de páginas, sí lo es: se reporta la interacción de mayor latencia observada. Pero en páginas con muchas interacciones, un tropiezo puntual del sistema puede producir una interacción anormalmente lenta en una página que por lo demás responde bien, y cuantas más interacciones haya, más probable es que ocurra.
Para corregirlo, la métrica descarta la interacción más alta por cada 50 interacciones. Es decir:
| Interacciones en la visita | Se descartan | Se reporta |
|---|---|---|
| 1 a 50 | 0 | La peor |
| 51 a 100 | 1 | La segunda peor |
| 101 a 150 | 2 | La tercera peor |
| 151 a 200 | 3 | La cuarta peor |
La inmensa mayoría de visitas no llegan a 50 interacciones, así que en la práctica lo que se reporta es la peor. Pero en una aplicación de uso intensivo, un editor o un panel de control, la diferencia importa.
Sobre esa cifra por visita se aplica después el percentil 75 de todas las visitas, igual que en las otras dos Core Web Vitals. Hay, por tanto, dos niveles de estadístico: uno dentro de la visita, aproximadamente el percentil 98 de sus interacciones, y otro entre visitas, el percentil 75.
Calcular un percentil exacto exigiría mantener en memoria todas las muestras, lo cual sería costoso en una página de sesión larga. La aproximación estándar consiste en mantener solo una lista corta con las peores N interacciones, siendo 10 una elección habitual. Con esa lista puedes responder correctamente al descarte de uno por cada cincuenta hasta 500 interacciones, que cubre prácticamente cualquier sesión real, gastando memoria constante.
Las tres partes de la latencia
Toda interacción se descompone en tres tramos consecutivos, sin solapamiento ni huecos.
Retraso de entrada. Desde que el usuario actúa hasta que empieza a ejecutarse el primer manejador de eventos. Es el tiempo que el evento pasa esperando a que el hilo principal se libere. Su causa típica es una tarea larga en curso: ejecución de un script grande, hidratación, un temporizador que hace demasiado trabajo.
Duración del procesamiento. Desde que empieza a ejecutarse el primer manejador hasta que termina el último. Es el trabajo de tu código en respuesta a la interacción. Su causa típica es lógica pesada síncrona dentro del manejador.
Retraso de presentación. Desde que terminan todos los manejadores hasta que el fotograma con la respuesta está en pantalla. Incluye el trabajo del hilo principal posterior, como las devoluciones de llamada de requestAnimationFrame, de ResizeObserver y de IntersectionObserver, y el cálculo de estilos y layout; y también el trabajo fuera del hilo principal, en el compositor, la GPU y el rasterizado.
El diagnóstico se guía por cuál de las tres domina:
| Parte dominante | Causa habitual | Dirección del arreglo |
|---|---|---|
| Retraso de entrada | El hilo estaba ocupado con otra cosa | Trocear tareas largas, ceder el hilo, retrasar trabajo no urgente |
| Duración del procesamiento | El manejador hace demasiado | Separar la respuesta visual del trabajo pesado, mover cómputo fuera del hilo |
| Retraso de presentación | Renderizado caro tras el manejador | Reducir el trabajo de layout, simplificar el DOM, evitar observadores costosos |
Esa tabla convierte “el INP es malo” en una tarea concreta, igual que las cuatro subpartes hacen con el LCP. La librería web-vitals expone las tres en su build de atribución, como inputDelay, processingDuration y presentationDelay.
Qué medía FID y por qué no bastaba
First Input Delay medía exclusivamente el retraso de entrada de la primera interacción. Ni la duración del procesamiento, ni el retraso de presentación, ni ninguna interacción posterior.
Las consecuencias eran graves y explican la sustitución:
- Un manejador que tardaba dos segundos en ejecutarse daba un FID excelente, porque el retraso de entrada podía ser de 5 ms. El usuario esperaba dos segundos a ver una respuesta y la métrica decía que todo iba bien.
- Solo miraba la primera interacción, que suele ocurrir pronto, cuando el usuario aún está orientándose, y a menudo es un gesto trivial. El comportamiento del resto de la sesión no se medía.
- Como consecuencia de lo anterior, la inmensa mayoría de sitios aprobaban FID sin esfuerzo, con lo cual la métrica no discriminaba entre sitios buenos y malos, que es lo único que se le pide a una métrica.
INP corrige las tres cosas: mide toda la interacción hasta el pintado, observa todas las interacciones, y se queda con la peor descartando atípicos. El resultado es una métrica bastante más difícil de aprobar y bastante más informativa. El propio Chrome documenta que el 90% del tiempo que un usuario pasa en una página transcurre después de que haya cargado; medir solo el primer instante era medir el 10% de la experiencia.
El umbral de duración y por qué importa
Un detalle de implementación con consecuencias reales al medir por tu cuenta. La API de Event Timing no reporta por defecto los eventos que duran menos de 104 milisegundos. Ese valor por defecto se puede bajar al registrar el observador con el parámetro durationThreshold, que tiene un mínimo de 16 ms.
La librería web-vitals establece el umbral en 40 ms una vez inicializada, un compromiso entre utilidad y coste: ejecutar la devolución de llamada para interacciones que ocupan uno o dos fotogramas no aporta información que compense el gasto. Los eventos emitidos antes de que la librería se inicialice siguen sujetos al valor por defecto de 104 ms.
Hay una excepción deliberada: la entrada first-input, que es también una entrada de Event Timing, se observa siempre, con independencia de su duración. Sin ella, una página con interacciones todas rápidas no reportaría ningún valor de INP, y es preferible reportar un valor bueno que no reportar nada.
// Observador manual de interacciones con umbral bajo, para depurar.
new PerformanceObserver((lista) => {
for (const e of lista.getEntries()) {
if (!e.interactionId) continue; // descarta eventos sin interaccion
const retrasoEntrada = e.processingStart - e.startTime;
const procesamiento = e.processingEnd - e.processingStart;
const presentacion = e.startTime + e.duration - e.processingEnd;
console.log(e.name, Math.round(e.duration), 'ms', {
retrasoEntrada: Math.round(retrasoEntrada),
procesamiento: Math.round(procesamiento),
presentacion: Math.round(presentacion),
});
}
}).observe({ type: 'event', durationThreshold: 16, buffered: true });
La propiedad interactionId es la que agrupa los eventos de un mismo gesto; los eventos que no forman parte de una interacción la tienen a cero. El campo duration viene redondeado al múltiplo de 8 ms más cercano, y puede redondear hacia abajo, así que no esperes precisión de milisegundo.
La interacción que decide tu INP no suele ser la que tú pruebas. Es la que un usuario impaciente hizo a los 1.800 milisegundos, cuando la página ya se veía pero el hilo principal seguía ocupado ejecutando el bundle. En ese instante el retraso de entrada puede ser de 600 ms sin que ningún manejador tuyo haya hecho nada mal: el evento simplemente esperó. Tú nunca lo reproduces porque cuando pruebas tu propia página esperas educadamente a que termine de cargar. La forma de encontrarlo es mirar el campo loadState que expone el build de atribución de web-vitals: si tus peores interacciones tienen loadState en loading o dom-interactive, tu problema de INP no es un problema de manejadores, es un problema de carga disfrazado, y la solución está en enviar menos JavaScript o trocear la hidratación, no en optimizar el clic. Y hay un corolario que ahorra trabajo inútil: si tus peores interacciones tienen loadState en complete, deja de optimizar el arranque, porque no es ahí.