wandres.dev
JAVASCRIPT II · Presupuesto y análisis del bundle

Fijar un presupuesto de rendimiento y defenderlo

Cómo se calcula un presupuesto que no sea un número inventado, qué magnitudes conviene presupuestar, dónde se pone la barrera para que no se pueda ignorar, y cómo se gestiona la petición de saltárselo.

⏱ 19 min

Un presupuesto de rendimiento es un número acordado por encima del cual una funcionalidad no se despliega. No es un objetivo, no es una aspiración y no es una métrica de seguimiento: es un límite con consecuencias. Sin la parte de las consecuencias, es un adorno en un documento, y los adornos no impiden que alguien añada una biblioteca de 90 KB un martes por la tarde. La dificultad de esto no es técnica —montarlo es media tarde— sino organizativa, y por eso la mitad de esta lección va de cómo se defiende.

🎯 Al terminar esta lección sabrás
  • Derivar un presupuesto a partir de un objetivo de métrica y no de una cifra arbitraria.
  • Elegir qué magnitudes presupuestar y con qué granularidad.
  • Colocar la barrera en el punto donde el coste de saltársela es visible.
  • Gestionar la excepción sin que el presupuesto se convierta en papel mojado.

Derivar el número, no inventarlo

Un presupuesto que sale de «pongamos 200 KB» dura hasta la primera discusión. Uno que sale de una cadena de razonamiento aguanta, porque cada vez que alguien lo cuestiona, la respuesta es la cadena.

La derivación va del objetivo de negocio hacia atrás. Ejemplo completo, con números que puedes rehacer con los tuyos:

Paso 1: fija el objetivo de métrica. «El percentil 75 del LCP en móvil debe estar por debajo de 2,5 segundos.» Ese umbral no es tuyo, es el umbral de la métrica, y por eso no se negocia.

Paso 2: fija las condiciones de referencia. Percentil 75 de tus usuarios reales, no una suposición. De tus datos de campo sacas, por ejemplo: 1,6 Mbps de ancho de banda efectivo, 150 ms de latencia de ida y vuelta, y una clase de dispositivo con factor 5x respecto al portátil de desarrollo.

Paso 3: reparte el tiempo. De los 2.500 milisegundos disponibles, resta lo que no controlas y lo que ya está comprometido:

Concepto Presupuesto
DNS, conexión y TLS 350 ms
Tiempo hasta el primer byte del documento 400 ms
Descarga y parseo del HTML 200 ms
CSS crítico y fuente 350 ms
Queda para JavaScript e imagen del LCP 1.200 ms

Paso 4: convierte tiempo en bytes. A 1,6 Mbps efectivos, 1.200 milisegundos son unos 240 KB transferidos. Pero el JavaScript no solo se descarga: se parsea, se compila y se ejecuta, y en un dispositivo con factor 5x eso cuesta del orden de 2 a 4 milisegundos por kilobyte comprimido. Con 150 KB de JavaScript, la descarga son 750 ms y el procesamiento otros 300 a 600. No cabe todo.

Paso 5: el reparto que sí cabe. Imagen del LCP, 90 KB en AVIF, que se descarga en paralelo. JavaScript de la ruta crítica, 100 KB comprimidos. Ese es tu presupuesto, y viene con su justificación completa.

El número final depende enteramente de tus condiciones. Un sitio de contenido con usuarios en buena red y objetivo de LCP holgado puede permitirse el triple. Una aplicación para usuarios en redes malas, la mitad. Lo que no cambia es que el número tiene que salir de esta cadena, porque es la cadena la que sobrevive a la discusión.

Qué presupuestar

Presupuestar solo el peso total del bundle es el error de principiante: es la magnitud más fácil de medir y la que peor correlaciona con la experiencia. Cuatro magnitudes, con granularidad distinta:

Bytes comprimidos por ruta crítica. No el total del proyecto: lo que descarga el usuario que entra por la página de inicio, y lo que descarga el que entra por una ficha de producto. Son presupuestos distintos porque son experiencias distintas.

Tiempo total de bloqueo en el dispositivo de referencia. Es la magnitud que mejor predice la sensación de página congelada, y la que captura el coste de ejecución que los bytes no ven. Un buen valor de partida es 200 milisegundos con el estrangulamiento calibrado.

Número de peticiones antes del primer pintado con contenido. Captura las cascadas, que no aparecen en ninguna medida de peso. Diez es holgado; treinta es un problema estructural.

Las métricas de campo directamente. LCP, INP y CLS en el percentil 75, segmentadas por clase de dispositivo. Son el resultado; las tres anteriores son las causas. Presupuestar solo el resultado no te dice qué arreglar; presupuestar solo las causas te deja optimizar lo que no importa.

Un formato de fichero que funciona bien porque es legible por humanos y por herramientas:

{
  "rutas": {
    "/": { "jsComprimido": 100000, "tbtMs": 200, "peticionesFCP": 12 },
    "/producto/*": { "jsComprimido": 140000, "tbtMs": 250, "peticionesFCP": 16 },
    "/panel/*": { "jsComprimido": 320000, "tbtMs": 500, "peticionesFCP": 24 }
  },
  "campo": {
    "lcpP75Ms": 2500,
    "inpP75Ms": 200,
    "clsP75": 0.1
  },
  "dispositivoReferencia": { "cpuThrottle": 5, "redKbps": 1600, "rttMs": 150 }
}

Fíjate en que el panel de administración tiene un presupuesto tres veces mayor. Es correcto: son usuarios que entran a diario, con la caché caliente, en un contexto de trabajo. Un presupuesto único para toda la aplicación es un presupuesto que o ahoga al panel o deja pasar cualquier cosa en la página pública.

Dónde se pone la barrera

Un presupuesto se cumple donde duele, y el sitio donde duele es la revisión de la propuesta de cambio. En cualquier otro punto se ignora.

En el proceso de compilación local. Un aviso, no un fallo. Sirve para que quien está escribiendo el código vea el coste mientras lo escribe, que es el momento en que cambiarlo es barato.

En la revisión del cambio, como comentario automático. El diferencial de bytes por ruta, comparado con la rama principal, publicado como comentario. Esto es lo que de verdad funciona: la información aparece delante de las personas que están decidiendo, en el momento en que deciden.

Presupuesto: rutas afectadas por este cambio

  /                 98,2 KB  →  103,7 KB   (+5,5 KB, +5,6%)   ⚠ supera 100 KB
  /producto/*      131,4 KB  →  131,4 KB   (sin cambios)
  TBT dispositivo referencia   184 ms  →  231 ms  (+47 ms)    ⚠ supera 200 ms

  Principales altas:
    date-fns/locale/*    +4,1 KB   (importado desde src/util/fecha.ts)
    zod                  +1,4 KB

En la integración continua, como fallo de compilación. Aquí sí se para. Y aquí es donde empieza la parte difícil.

El presupuesto se muere por la excepción, no por el número; diseña la excepción antes que el límite

Todo presupuesto que he visto morir murió igual. Una funcionalidad importante lo supera. La fecha de lanzamiento está comprometida. Alguien con autoridad dice «esta vez lo saltamos». Se salta. Y a partir de ahí el presupuesto ya no es un límite, es una sugerencia, porque todo el mundo ha visto que se puede saltar pidiéndolo.

La causa no es la falta de disciplina. Es que el presupuesto se diseñó sin mecanismo de excepción, y un sistema sin válvula de escape revienta por donde puede. Hay que construir la válvula desde el principio, y construirla de forma que usarla tenga un coste visible pero soportable.

El mecanismo que funciona tiene tres partes.

Uno: la excepción se concede, no se toma. Saltarse el límite requiere añadir una entrada explícita a un fichero versionado, con nombre, fecha y motivo. No una bandera en la línea de comandos, no un comentario para desactivar la regla: una entrada en un registro que todo el mundo puede leer.

{
  "excepciones": [
    {
      "ruta": "/",
      "presupuesto": "jsComprimido",
      "limiteTemporal": 118000,
      "motivo": "Editor enriquecido del formulario de alta. Pendiente de mover a carga por interaccion.",
      "solicita": "equipo-altas",
      "caduca": "2026-11-15",
      "issue": "PERF-412"
    }
  ]
}

Dos: la excepción caduca. El campo caduca no es decorativo: pasada esa fecha, la compilación falla otra vez con el límite original y el mensaje cita el motivo y el número de incidencia. Esto convierte la deuda de rendimiento en algo que vuelve solo a la mesa, en lugar de disolverse.

Tres: las excepciones son visibles agregadas. Un panel, o simplemente una línea en el informe semanal, que diga cuántas excepciones activas hay y cuántos kilobytes suman. Tres excepciones de 8 KB cada una no son un escándalo; doce excepciones que suman 140 KB son la prueba de que el presupuesto ya no describe la realidad y hay que renegociarlo de verdad, con la cadena de razonamiento delante.

Y una advertencia sobre el error contrario, que también he visto: un presupuesto demasiado estricto se salta más que uno realista. Si el límite obliga a pedir excepción cada dos semanas, deja de tener autoridad moral y el equipo aprende a ignorarlo. Calíbralo con margen —entre un 10 y un 20 por ciento por encima de donde estás hoy si vienes de no tener ninguno— y baja el límite de forma programada a medida que reduces. Un presupuesto que se cumple el 90 por ciento de las veces es infinitamente más útil que uno que se cumple el 40.

La conversación con quien pide la biblioteca

La defensa del presupuesto ocurre casi siempre en la misma situación: alguien quiere añadir algo y el número no da. Tres cosas que funcionan mejor que discutir.

Traduce los bytes a tiempo, y el tiempo a la métrica. «Esta biblioteca son 42 KB comprimidos, que en el dispositivo del percentil 75 son unos 210 milisegundos de descarga más 130 de procesamiento. Nuestro margen de LCP es de 180 milisegundos.» El número solo es discutible; la consecuencia no.

Ofrece la alternativa antes de decir que no. Casi siempre hay tres: cargarlo por interacción en lugar de en el arranque, usar una parte en vez de la biblioteca entera, o sustituirlo por una implementación propia de veinte líneas. La conversación cambia por completo cuando la propuesta es «esto entra si se carga al abrir el modal» en lugar de «esto no entra».

Enseña el coste acumulado, no el marginal. El argumento de quien pide siempre es correcto en aislamiento: 42 KB no matan a nadie. Lo que mata es la duodécima vez que alguien tiene razón con ese argumento. Un gráfico del peso de la ruta crítica en los últimos doce meses, con una anotación por cada incorporación, es la herramienta retórica más eficaz de este tema, y se hace una vez.

⚔️ Reto práctico

Deriva tu propio presupuesto siguiendo los cinco pasos, con los datos de campo reales de tu producto. Después mide dónde estás hoy y calcula la distancia. Si estás por encima, fija el límite un 15 por ciento por encima de la situación actual, no en el objetivo, y programa tres reducciones trimestrales hasta llegar. Escribe el fichero de excepciones vacío el mismo día que fijas el límite: el mecanismo tiene que existir antes de que haga falta.