Volver al blog
BunIACostos

Límite de gastos: lo que tu backend Bun necesita tener

05 de octubre de 2026·7 min de lectura·Diego Horvatti

Subes un script Bun un viernes por la noche. Usa un agente que llama a una API de LLM en loop. Te vas a dormir. El sábado por la mañana, el correo de la factura llega antes del café. Esa es la pesadilla que Simon Willison decidió enfrentar de lleno: en el texto We're going to need default hard budget caps on pretty much everything, defiende que un límite de gastos rígido debería venir activado por defecto en casi todo servicio de pago. En este artículo explico la idea, por qué pesa más ahora y cómo aplicarla hoy en tu código Bun, sin esperar a nadie.

Lo que Simon Willison está defendiendo

El argumento es simple. Hoy, la mayoría de los servicios cobra por uso y no tiene tope. Configuras una alerta, recibes un correo cuando pasas cierto valor y el servicio sigue funcionando. Y cobrando.

La propuesta es invertir el comportamiento por defecto. Toda cuenta nueva nacería con un tope rígido. Llegaste al tope, el servicio se detiene. Quien quiera gastar más sube el límite de forma consciente.

Esto siempre tuvo sentido para la nube. Solo que antes el riesgo era un bucket público o una función mal configurada. Ahora hay un elemento nuevo en la sala: código que decide por su cuenta cuántas veces va a llamar a una API de pago.

Por qué una alerta de gastos no es un límite de gastos

Mucha gente cree que ya está protegida porque configuró una alerta. No lo está.

Una alerta es una notificación. Llega al correo, se va a spam o llega a las 3 de la mañana mientras duermes. El loop no duerme. Sigue.

La alerta te avisa que el dinero se fue. El límite impide que se vaya.

Haz la cuenta con un agente de código. Digamos que cada llamada envía 200 mil tokens de contexto, a un precio de US$ 3 por millón de tokens de entrada. Eso da US$ 0,60 por llamada, sin contar la salida. Si el agente entra en un ciclo de "intentar arreglar el test, fallar, intentar de nuevo" y corre 1.000 veces durante la noche, son US$ 600. Para mucha gente, eso es un buen tiempo de servidor tirado a la basura por culpa de un test flaky.

Y 1.000 iteraciones ni siquiera es mucho. Un agente rápido lo hace en pocas horas.

Yo siento el lado bueno del tope en carne propia. Mi cuenta de Vercel es gratuita y tiene un límite de 100 deploys por día. ¿Me ha molestado? Sí. Pero nunca recibí una factura sorpresa. El límite rígido es molesto justo hasta el día en que te salva.

Dónde entra Bun en esta historia

Bun hizo muy fácil hacer cosas que cuestan dinero. Es un elogio, pero tiene un precio.

  • bun run agente.ts ejecuta TypeScript directo, sin build. Un script de automatización nace en dos minutos.
  • Bun.serve levanta un endpoint HTTP en cinco líneas. Exponer una ruta que llama a un LLM se volvió trivial.
  • El runtime es rápido. Un loop que llama a fetch termina antes, o sea, también gasta más rápido.

Ninguno de estos puntos es un problema de Bun. El problema es que la fricción desapareció. Antes, montar un servicio que llamara a una API de pago exigía suficiente configuración como para que te detuvieras a pensar en el costo. Hoy no te detienes. Lo ejecutas.

Entonces, mientras los proveedores no adopten el comportamiento que propone Simon, la responsabilidad queda en quien escribe el código. La buena noticia: Bun ya trae todo lo que necesitas para esto, sin instalar nada.

Cómo poner un límite de gastos en tu código Bun

La idea es tener un contador persistente que corre antes de cada llamada de pago. Si el gasto estimado supera el tope del día, el código lanza un error y se detiene. Uso bun:sqlite, que viene integrado en el runtime:

// budget.ts
import { Database } from "bun:sqlite";

const db = new Database("budget.sqlite");
db.run(`CREATE TABLE IF NOT EXISTS gasto (
  dia TEXT PRIMARY KEY,
  centavos INTEGER NOT NULL
)`);

const LIMITE_DIA = Number(process.env.LIMITE_CENTAVOS_DIA ?? 500); // US$ 5

export function reservar(centavos: number) {
  const dia = new Date().toISOString().slice(0, 10);
  const { total } = db
    .query(`INSERT INTO gasto (dia, centavos) VALUES (?1, ?2)
            ON CONFLICT(dia) DO UPDATE SET centavos = centavos + ?2
            RETURNING centavos AS total`)
    .get(dia, centavos) as { total: number };

  if (total > LIMITE_DIA) {
    throw new Error(`Límite de gastos del día superado: ${total} centavos`);
  }
}

Fíjate en un detalle: si el límite se supera, el valor ya se sumó. Es a propósito. El sistema falla cerrado. Después de llegar al tope, toda llamada siguiente también falla hasta que cambie el día o hasta que toques el límite.

Ahora el uso, con dos frenos extra que cuestan una línea cada uno:

import { reservar } from "./budget";

const MAX_PASSOS = 25;

for (let passo = 0; passo < MAX_PASSOS; passo++) {
  const tokens = estimarTokens(contexto);
  reservar(Math.ceil((tokens / 1_000_000) * 300)); // US$ 3/M en centavos

  const res = await fetch(API_URL, {
    method: "POST",
    body: JSON.stringify(payload),
    signal: AbortSignal.timeout(30_000),
  });

  if (await terminou(res)) break;
}

Son tres capas:

  1. Tope de dinero por día, que sobrevive a un reinicio porque está en SQLite.
  2. Tope de pasos en el loop. Un agente que necesita más de 25 pasos probablemente está dando vueltas en círculo.
  3. Timeout en cada petición, para que ninguna llamada quede colgada para siempre.

La estimación de tokens no tiene que ser perfecta. Una regla gruesa de "caracteres divididos entre 4" ya sirve para un tope. El objetivo no es la contabilidad, es tener un freno de mano.

Si tu servicio corre en más de una instancia, el SQLite local deja de servir como fuente única. Ahí el mismo INSERT ... ON CONFLICT pasa a PostgreSQL, que probablemente ya tienes. La lógica es la misma.

Qué configurar fuera del código

Tu código es solo una de las capas. Un bug en él, o una API key filtrada, pasa por encima de todo. Así que vale la pena cerrar las puertas por fuera también:

  • Límite en el proveedor de LLM. La mayoría de las consolas permite definir un tope mensual por proyecto o workspace. Crea una key separada para cada app, con su propio límite. Una key de experimentos nunca debería tener el mismo tope que la de producción.
  • Presupuesto en la nube con acción, no solo con correo. Si la plataforma ofrece pausar el proyecto al llegar al monto, actívalo. Un sitio caído por unas horas sale más barato que la factura.
  • Rate limit en la ruta pública. Si expusiste un Bun.serve que llama a un LLM, un bot haciendo 50 peticiones por segundo convierte tu tope diario en un tope de cinco minutos. Menos mal que hay tope, pero mejor ni llegar a él.

Ninguno de estos puntos da trabajo. Lo difícil es acordarse de hacerlo antes del incidente, y no después.

¿Los proveedores van a adoptar esto por defecto?

Aquí va mi opinión sincera: no pronto, y no por voluntad propia.

Cobrar sin tope es un modelo de negocio cómodo. El gasto accidental también es ingreso. Algunos proveedores devuelven el dinero cuando el cliente se queja en Twitter, pero devolver después es muy distinto a impedir antes. Quien no tiene audiencia para quejarse en público normalmente paga en silencio.

Lo que puede cambiar el juego es el volumen. Cuando millones de devs estén corriendo agentes en loop, las facturas sorpresa dejan de ser casos aislados y se vuelven soporte, contracargos y clientes perdidos. Ahí el comportamiento por defecto cambia, por pura cuenta de costos.

Hasta entonces, trata todo servicio de pago como si no tuviera tope, porque probablemente no lo tiene. Pon el límite de gastos en tu código Bun hoy: son unas 20 líneas, y valen más que cualquier alerta bonita en el dashboard.

Es el tipo de detalle que pongo en todo proyecto que trabaja con IA. Si quieres ver cómo queda esto en una app real, échale un vistazo a los proyectos que ya construí.

Resumen para LinkedIn

Una alerta de gastos no protege a nadie. Solo te avisa que el dinero ya se fue.

Imagina un agente de IA corriendo en loop un viernes por la noche. Son 1.000 llamadas de US$ 0,60 mientras duermes, y el sábado la factura llega antes del café.

Simon Willison defiende que todo servicio de pago venga con un tope rígido activado por defecto. Estoy de acuerdo, pero no espero a que los proveedores cambien.

En mi backend Bun lo resuelvo con unas 20 líneas: un contador diario en bun:sqlite, un límite de pasos en el loop y timeout en cada petición.

Un límite rígido molesta en el día a día y te salva justo el día en que algo sale mal.

Escribí el paso a paso con el código completo en el blog. Si usas IA en producción, valen la pena los 5 minutos.

#Bun #TypeScript #IA #LLM #Backend