Inicio/ Blog/ Buffer time (compuesto desde bloques CMS)
Engineering 2026-04-25 · Sergei Filatov

Buffer time (compuesto desde bloques CMS)

Mismo contenido que el post raw-HTML — pero esta versión se compone íntegramente desde bloques CMS tipados. Cualquier autor puede recrear este layout desde la UI de Odoo sin tocar HTML.

SF
Sergei Filatov Founder · ex-Dodo Brands · 15 años
14 min lectura

El KPI que nadie ve

Cuando empezamos a auditar la operación de delivery de un cliente nuevo, hacemos siempre la misma pregunta: "¿cuánto tiempo pasa entre que la cocina marca un pedido como listo y el courier lo recoge?". La respuesta, en el 90% de los casos, es "no sabemos". A veces escuchamos "menos de 2 minutos, normalmente", que es la forma elegante de decir lo mismo.

A ese intervalo lo llamamos buffer time. Y es el asesino silencioso de la calidad de la comida y de la experiencia del cliente. Mientras el pedido está en el buffer, la pizza pierde temperatura, la salsa pierde textura, las papas pierden todo lo que hace que valga la pena ser papa frita.

i
Definición operativa

Buffer time = timestamp de "courier picked up" − timestamp de "order ready". Se mide por pedido y se agrega por local, hora, día.

Por qué importa tanto

Existe una correlación brutalmente clara entre buffer time y puntaje NPS del cliente. En los datos de Dodo Pizza analizamos ~3.2M pedidos durante 18 meses, y encontramos lo siguiente:

  • Buffer < 60s → NPS promedio +72
  • Buffer 60–180s → NPS promedio +58
  • Buffer 180–300s → NPS promedio +34
  • Buffer > 300s → NPS promedio −12 (cliente molesto)
Distribución de NPS por bucket de buffer time. Caída no-lineal entre 180s y 300s.

Los 4 errores que cometimos

Antes de tener algo decente, fallamos cuatro veces. Vale la pena documentarlos para que no los repitas.

1. Confiar en el timestamp del POS

El primer impulso fue usar order.completed_at del POS como evento "listo". Mala idea. En la mitad de los locales ese campo se llena cuando el cajero cobra, no cuando la cocina terminó. La diferencia puede ser 90s. Tu KPI hereda ese sesgo.

2. Usar polling cada 30 segundos

La primera versión del pipeline corría un cron cada 30s que consultaba la API del KDS. Para 100 locales eso fueron 12,000 requests por hora — y aún así perdíamos eventos cuando dos órdenes pasaban a "listo" en la misma ventana.

!
Lección aprendida

Polling es la forma de no enterarte de cosas que importan. Si tu sistema soporta webhooks o eventos, úsalos. Si no, instrumenta tú mismo.

Galería · 4 vistas del dashboard final, desde overview hasta drill-down.

Arquitectura final

Después de iterar, llegamos a un setup que ya lleva 4 años en producción sin cambios estructurales. Tres capas:

  1. Captura: KDS → webhook HTTPS → Kafka topic kitchen.events
  2. Procesamiento: Spark Streaming consume Kafka, deduplica por event_id, escribe a Delta Lake (bronze → silver → gold)
  3. Servir: Superset lee la tabla gold, alertas a Telegram via webhook
Lo más sorprendente no fue el impacto en NPS. Fue que descubrimos 3 locales donde el buffer estaba en 6 minutos consistentemente. Resultó ser el mismo gerente, mala asignación de couriers.— Operations Manager, Dodo Pizza México
Pipeline en producción. Latencia end-to-end p95: 87ms.

Código que usamos

python · pyspark
from pyspark.sql import functions as F
from delta.tables import DeltaTable

# 1. Read events from Kafka
events = (spark.readStream
    .format("kafka")
    .option("subscribe", "kitchen.events")
    .load()
    .select(F.from_json("value", schema).alias("e"))
    .select("e.*"))

# 2. Pivot ready / pickup events per order
buffer = (events
    .groupBy("order_id", F.window("event_ts", "15 minutes"))
    .agg(
        F.min(F.when(F.col("type") == "ready", F.col("event_ts"))).alias("ready_at"),
        F.min(F.when(F.col("type") == "pickup", F.col("event_ts"))).alias("pickup_at"))
    .withColumn("buffer_seconds",
        F.unix_timestamp("pickup_at") - F.unix_timestamp("ready_at")))

# 3. Upsert to Delta gold table (idempotent)
def upsert(batch_df, batch_id):
    DeltaTable.forName(spark, "gold.buffer_time_per_order") \
        .merge(batch_df.alias("new"), "new.order_id = old.order_id") \
        .whenMatchedUpdateAll().whenNotMatchedInsertAll().execute()
Video · walkthrough del dashboard de buffer time en tiempo real.

Resultados después de 6 meses

Después de instrumentar buffer time en 100+ locales y darle a cada gerente regional acceso al dashboard, los números cambiaron:

  • Buffer mediano: de 187s a 84s (−55%)
  • % de pedidos con buffer > 5min: de 11.2% a 1.4%
  • NPS promedio: +18 puntos en los locales con peor buffer inicial
  • Quejas por "comida fría": −63%

Conclusión

Si operás delivery y no estás midiendo buffer time, lo estás perdiendo. Los KPIs que importan rara vez son los que tu dashboard te muestra por defecto — son los que tenés que construir explícitamente porque conoces el dominio.

Audit gratis · 30 min →
SF

Author bio

Founder de data-metrics.pro. 15 años construyendo plataformas de datos. Antes: Dodo Brands (delivery, 17 países). Forbes 30 Under 30 · 2023. Vive entre Lima y CDMX. Escribe sobre engineering honesto y casos LATAM reales.