Engineering 16 abr 2025 · Sergei Filatov

Buffer time: el KPI invisible que está matando tus pedidos.

El tiempo entre "pedido listo" y "courier lo recoge" es el KPI que nadie monitorea — y el que más impacta la calidad. Te muestro cómo lo instrumentamos para Dodo Pizza con Spark Streaming + Delta Lake, y los 4 errores que cometimos antes de hacerlo bien.

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)

Cada minuto adicional de buffer cuesta puntos NPS reales. Y el cliente nunca escribe en la review "tu courier tardó 4 minutos en recoger mi pedido". Escribe "la pizza llegó fría". La causa raíz queda invisible si no la mides.

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.

Solución: pedir el evento directamente desde el KDS (Kitchen Display System), o instrumentar manualmente el botón "listo" en la cocina con un Raspberry Pi y un código de barras. Sí, hicimos eso en 2 locales para validar.

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.

3. No tener idempotencia desde el día 1

Cuando migramos de polling a webhooks, descubrimos que el KDS re-envía cada evento 2–3 veces por seguridad. Sin un event_id + dedup, contábamos cada pedido 2x. Métricas limpias siempre necesitan idempotencia desde el primer commit.

4. Escribir directo a Postgres

La cuarta versión escribía eventos crudos a Postgres y agregaba con queries SQL pesadas. Funcionó hasta los ~30 locales. Después cada query tomaba 8 segundos y el dashboard quedó inusable. Migramos a Delta Lake con Spark Streaming y los queries ahora corren en menos de 200ms.

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

Diagrama de flujo

Pipeline en producción. Latencia end-to-end p95: 87ms.

Código que usamos

Esta es una versión simplificada del job de Spark Streaming que escribe a la tabla gold.buffer_time_per_order :

python · pyspark
12345678910111213141516171819202122232425from 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()

La parte clave es el foreachBatch con MERGE : si el mismo pedido llega 3 veces (por reenvíos del webhook), termina como una sola fila correcta. Idempotencia por diseño.

DEMO 3:42
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%

Pero el resultado más importante fue cualitativo: los gerentes empezaron a tener conversaciones operacionales que antes no existían. "Tu buffer subió a 4min entre 19:00 y 20:30 los viernes" es accionable. "Tu NPS bajó este mes" no lo es.

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.

Si querés que armemos algo similar para tu operación, escribíme a sergephilatov@gmail.com o reservá 30 min acá . La auditoría inicial es gratis.

SF

Sergei Filatov

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.