Módulo 7: Saying No And The Roadmap

WSJF: Weighted Shortest Job First

Descripción

Ya tienes las dos piezas: costOfDelay (lección 3, cuánto pierde Mercado por semana que una apuesta no está viva) y jobSize (el mismo effort de RICE, cuánto cuesta construirla). Esta lección las combina en una sola fórmula: WSJF, Weighted Shortest Job First, el modelo que usa el Scaled Agile Framework (SAFe) para ordenar un backlog por valor entregado por unidad de tiempo, no solo por valor total. Vas a escribir la función, correrla sobre el backlog completo de Mercado, y ver el orden cambiar frente al de RICE — a veces de forma dramática.

Conexión con el módulo. RICE (módulo 3) responde "¿cuánto vale esto?". WSJF responde una pregunta distinta: "¿cuánto valor entrega esto por cada semana que el equipo le dedica, tomando en cuenta lo que cuesta esperarlo?". Las dos preguntas son legítimas y las dos importan, pero no siempre están de acuerdo — y cuando no lo están, WSJF es el criterio diseñado específicamente para decidir el orden de construcción, que es la pregunta que abre este módulo.

Una analogía: la fila del supermercado

Estás en el supermercado con dos cajas abiertas. En la caja A hay una sola persona con un carrito lleno — va a tardar un buen rato—. En la caja B hay tres personas, cada una con dos o tres artículos — se ve más larga, pero cada persona se resuelve rápido—. Si eliges caja por "cuál tiene menos gente", eliges A y te equivocas: A tiene una sola persona, pero esa persona te va a hacer esperar más que las tres de B juntas. La pregunta correcta no es "¿cuántas personas hay?" ni siquiera "¿cuánto vale mi tiempo?" en abstracto — es "¿cuánto avanzo por minuto de espera en cada fila?". Eso es exactamente WSJF: no pregunta solo cuánto vale una apuesta (como RICE) ni solo cuánto cuesta esperarla (como el costo de demora solo); pregunta cuánto valor recibes por cada unidad de tiempo que el equipo le dedica.

      RICE responde:            "cuanto vale esto"        (reach x impact x confidence / effort)
      Costo de demora responde: "cuanto pierdo esperando"  ($ por semana que no esta viva)
      WSJF responde:            "cuanto valor por semana   costOfDelay
                                  de trabajo del equipo"  = ───────────
                                                              jobSize

Verificando la fórmula antes de programarla

Antes de escribir una sola línea, vale la pena confirmar la fórmula tal como la define SAFe, la fuente de WSJF: WSJF es el costo de demora relativo dividido entre la duración relativa del trabajoWSJF = Cost of Delay / Job Duration. La idea viene del trabajo de Donald Reinertsen (que la llamó CD3, "Cost of Delay Divided by Duration"), y SAFe la adoptó con ese nombre. La intuición detrás de la fórmula, y por qué divide en vez de restar o sumar: dos apuestas pueden tener el mismo costo de demora, pero si una tarda el triple en construirse, esa apuesta "bloquea" al equipo el triple de tiempo mientras genera el mismo valor por semana esperada — por eso el tamaño del trabajo va en el denominador, igual que el effort en RICE.

Ejemplo trabajado: el backlog completo de Mercado, con el tiempo adentro

Reutilizamos, sin cambios, el backlog de cinco apuestas del módulo 3 —mismo reach, impact, confidence y effort— y le agregamos el costOfDelay que cada apuesta trae desde la lección 3 (o una estimación equivalente para las que no habías visto todavía). jobSize reutiliza exactamente el mismo effort que ya alimentaba RICE — no es una estimación nueva, es el mismo número visto desde otro ángulo.

// WSJF (Weighted Shortest Job First) = costOfDelay / jobSize (SAFe).
// Reutilizamos EXACTAMENTE el backlog de 5 apuestas del modulo 3 (mismo
// reach/impact/confidence/effort), y le agregamos costOfDelay ($/semana,
// estimacion del equipo, modulo pedagogico). jobSize reusa el mismo effort
// (person-months) que ya alimentaba RICE.
function riceScore({ reach, impact, confidence, effort }) {
  return (reach * impact * confidence) / effort;
}

function wsjf({ costOfDelay, jobSize }) {
  return costOfDelay / jobSize;
}

function prioritize(items) {
  return items.map((item) => ({ ...item, score: riceScore(item) })).sort((a, b) => b.score - a.score);
}

function sequence(items) {
  return items
    .map((item) => ({ ...item, wsjfScore: wsjf({ costOfDelay: item.costOfDelay, jobSize: item.effort }) }))
    .sort((a, b) => b.wsjfScore - a.wsjfScore);
}

const backlog = [
  { feature: 'fasterCheckout',  reach: 8000, impact: 2,   confidence: 0.8, effort: 2, costOfDelay: 8000 },
  { feature: 'recommendations', reach: 5000, impact: 1,   confidence: 0.5, effort: 3, costOfDelay: 18000 },
  { feature: 'sellerTools',     reach: 1200, impact: 2,   confidence: 0.8, effort: 2, costOfDelay: 6000 },
  { feature: 'reviews',         reach: 6000, impact: 0.5, confidence: 0.8, effort: 1, costOfDelay: 9000 },
  { feature: 'improvedSearch',  reach: 9000, impact: 1,   confidence: 0.5, effort: 3, costOfDelay: 6000 },
];

console.log('=== Orden por RICE (modulo 3): value/effort, sin tiempo ===\n');
const byRice = prioritize(backlog);
byRice.forEach((b, i) => console.log('  ' + (i + 1) + '. ' + b.feature.padEnd(16) + 'riceScore=' + (Math.round(b.score * 100) / 100)));
console.log('\n  orden: ' + byRice.map((b) => b.feature).join(' > '));

console.log('\n=== Orden por WSJF: costOfDelay / jobSize ===\n');
const byWsjf = sequence(backlog);
byWsjf.forEach((b, i) =>
  console.log(
    '  ' + (i + 1) + '. ' + b.feature.padEnd(16) +
    'costOfDelay=$' + b.costOfDelay.toLocaleString('en-US') + '/sem' +
    '  jobSize=' + b.effort + 'pm' +
    '  ->  wsjf=' + b.wsjfScore
  )
);
console.log('\n  orden: ' + byWsjf.map((b) => b.feature).join(' > '));

console.log('\n=== Que cambio ===\n');
const riceOrder = byRice.map((b) => b.feature);
const wsjfOrder = byWsjf.map((b) => b.feature);
riceOrder.forEach((feature, i) => {
  const newPos = wsjfOrder.indexOf(feature);
  const moved = newPos === i ? 'igual' : newPos < i ? 'subio' : 'bajo';
  console.log('  ' + feature.padEnd(16) + 'RICE #' + (i + 1) + '  ->  WSJF #' + (newPos + 1) + '  (' + moved + ')');
});

Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:

=== Orden por RICE (modulo 3): value/effort, sin tiempo ===

  1. fasterCheckout  riceScore=6400
  2. reviews         riceScore=2400
  3. improvedSearch  riceScore=1500
  4. sellerTools     riceScore=960
  5. recommendations riceScore=833.33

  orden: fasterCheckout > reviews > improvedSearch > sellerTools > recommendations

=== Orden por WSJF: costOfDelay / jobSize ===

  1. reviews         costOfDelay=$9,000/sem  jobSize=1pm  ->  wsjf=9000
  2. recommendations costOfDelay=$18,000/sem  jobSize=3pm  ->  wsjf=6000
  3. fasterCheckout  costOfDelay=$8,000/sem  jobSize=2pm  ->  wsjf=4000
  4. sellerTools     costOfDelay=$6,000/sem  jobSize=2pm  ->  wsjf=3000
  5. improvedSearch  costOfDelay=$6,000/sem  jobSize=3pm  ->  wsjf=2000

  orden: reviews > recommendations > fasterCheckout > sellerTools > improvedSearch

=== Que cambio ===

  fasterCheckout  RICE #1  ->  WSJF #3  (bajo)
  reviews         RICE #2  ->  WSJF #1  (subio)
  improvedSearch  RICE #3  ->  WSJF #5  (bajo)
  sellerTools     RICE #4  ->  WSJF #4  (igual)
  recommendations RICE #5  ->  WSJF #2  (subio)

Mira el tamaño del cambio. recommendations era la última de RICE (#5, con el score más bajo del backlog, arrastrado por su confidence baja) y salta al segundo lugar de WSJF — porque su costOfDelay ($18,000/semana, el más alto del backlog) es tan grande que compensa por completo su jobSize más largo. fasterCheckout, que dominaba RICE con más del doble del segundo lugar, cae al tercer puesto en WSJF — su costOfDelay es sólido pero no excepcional, y no le alcanza para mantener el primer lugar frente a apuestas que devuelven más valor por cada semana de trabajo del equipo. Solo sellerTools se queda exactamente en el mismo lugar (#4 en ambos) — un recordatorio de que WSJF no invierte todo el orden, solo lo reordena donde el tiempo hace la diferencia.

Por qué esto no invalida a RICE

Vale la pena decirlo con la misma claridad que el módulo 3 usó para defender los límites del scoring: WSJF no "corrige" a RICE ni lo vuelve inútil. Responden preguntas distintas, y las dos son necesarias en momentos distintos del proceso. RICE es la herramienta correcta para decidir qué apuestas merecen entrar al backlog priorizado, con la información que tienes al principio — reach, impact, confidence, effort—. WSJF es la herramienta correcta para decidir en qué orden construirlas, una vez que ya sabes cuánto cuesta esperar cada una. Un equipo que solo usa RICE puede terminar construyendo, primero, la apuesta que menos urge — como pasó aquí con fasterCheckout. Un equipo que solo usa WSJF, sin haber pasado antes por RICE, corre el riesgo de secuenciar apuestas que nunca debieron estar en el backlog para empezar — porque WSJF no pregunta "¿esto vale la pena en absoluto?", solo pregunta "¿en qué orden, dado que ya decidimos que sí?".

Errores comunes

Calcular WSJF con el jobSize de una sola persona cuando el equipo trabaja en paralelo. Qué pasa: el equipo estima jobSize en person-months (esfuerzo total), pero al construir el roadmap asume que ese tiempo se traduce directo en semanas de calendario, ignorando que varias personas pueden trabajar la misma apuesta a la vez. Por qué pasa: person-months y semanas de calendario se parecen lo suficiente como para confundirse sin pensarlo dos veces. Cómo detectarlo: si tu jobSize de 3 person-months para recommendations asume que tarda exactamente 3 meses de calendario, sin considerar cuántas personas la construyen a la vez, tu estimación de tiempo real está probablemente equivocada. Cómo corregirlo: usa jobSize como una medida relativa de tamaño (para comparar apuestas entre sí, que es lo que WSJF necesita), y conviértelo a calendario real solo cuando ya sepas cuántas personas del equipo se van a asignar — ese ajuste es del equipo de ingeniería, no de la fórmula.

Usar WSJF para justificar una decisión que ya se tomó por otra razón. Qué pasa: alguien ya decidió que quiere construir cierta apuesta primero (por preferencia personal, por presión de un stakeholder) y "ajusta" el costOfDelay hacia arriba hasta que el WSJF le da la razón. Por qué pasa: la misma tentación de manipular confidence en RICE que el módulo 3 ya advirtió, aplicada ahora al costOfDelay. Cómo detectarlo: si el costOfDelay de la apuesta "favorita" de alguien cambió justo después de que el WSJF no le diera el primer lugar, y no hay ninguna evidencia nueva que lo justifique, es manipulación, no revisión legítima. Cómo corregirlo: la misma disciplina de trazabilidad de toda la guía — cada costOfDelay debe poder explicarse con una razón concreta y verificable, independientemente de a quién beneficie el resultado.

Tratar el orden de WSJF como permanente, sin volver a calcularlo. Qué pasa: el equipo corre sequence() una vez, al principio del trimestre, y sigue ese orden aunque las condiciones cambien —una apuesta resulta más cara de lo esperado, o aparece evidencia de que otra es más urgente de lo que se pensaba—. Por qué pasa: recalcular se siente como reabrir una decisión ya cerrada. Cómo detectarlo: si el orden de construcción no se ha vuelto a correr desde que empezó el trimestre, a pesar de que el equipo ya sabe cosas que no sabía al principio, el WSJF que estás siguiendo puede estar desactualizado. Cómo corregirlo: como con el análisis de sensibilidad de RICE en el módulo 3, WSJF debería recalcularse cuando aparece evidencia real que cambia costOfDelay o jobSize — no para mover artificialmente el orden, sino para que el orden siga reflejando lo que el equipo sabe hoy.

Ejercicios

Ejercicio 1 — Calcula el WSJF de una sexta apuesta. El equipo de Mercado agrega una candidata nueva al backlog: sellerAnalytics (panel de métricas para vendedores), con costOfDelay: 4500 y jobSize: 1. Calcula su wsjfScore y di en qué posición del ranking de esta lección quedaría.

Ver solución

wsjf = 4500 / 1 = 4500. Comparado con el ranking de la lección (reviews 9000, recommendations 6000, fasterCheckout 4000, sellerTools 3000, improvedSearch 2000), 4500 queda entre recommendations (6000) y fasterCheckout (4000) — en la posición #3, empujando a fasterCheckout, sellerTools e improvedSearch un lugar hacia abajo. El orden completo sería: reviews (9000) → recommendations (6000) → sellerAnalytics (4500) → fasterCheckout (4000) → sellerTools (3000) → improvedSearch (2000).

Ejercicio 2 — Predice sin calcular. Sin hacer la cuenta todavía, ¿qué le pasaría al wsjfScore de improvedSearch (actualmente el más bajo del backlog, con 2000) si el equipo descubre evidencia de que su costOfDelay real es el doble de lo estimado ($12,000/semana en vez de $6,000/semana)? ¿Alcanzaría a superar a sellerTools (3000)? Ahora calcúlalo y confirma tu predicción.

Ver solución

Predicción razonable: duplicar el costOfDelay duplica el wsjfScore (la fórmula es una división directa, lineal en el numerador), así que improvedSearch pasaría de 2000 a 4000 — y eso sí superaría a sellerTools (3000). Cálculo: wsjf = 12000 / 3 = 4000. Confirmado: 4000 supera a sellerTools (3000) y a fasterCheckout (4000, empatado) pero no a recommendations (6000) ni a reviews (9000). improvedSearch subiría del último lugar al tercero (empatado con fasterCheckout), un salto grande causado por un solo input revisado — la misma lección de sensibilidad que el módulo 3 enseñó con RICE, ahora aplicada a WSJF.

Ejercicio 3 — Explica la reversión de recommendations a alguien que solo conoce RICE. Un compañero que vio el ranking del módulo 3 pregunta, confundido: "¿Cómo es posible que recommendations, que tenía el peor RICE score de las cinco, ahora sea la segunda prioridad?". Escribe, en 2-3 frases, la explicación que le darías, usando los números exactos de esta lección.

Ver solución

Una respuesta posible: "RICE le daba un score bajo a recommendations (833.33) porque su confidence es baja (0.5) — no estamos tan seguros de cuánto va a mover la métrica—. Pero eso mide certeza, no urgencia. Cuando le agregamos el costOfDelay —cuánto perdemos por cada semana que recommendations no está viva, que es el más alto del backlog con $18,000/semana— y lo dividimos por su jobSize (3 person-months, igual que antes), el resultado es un WSJF de 6000, el segundo más alto de las cinco. No es que RICE estuviera 'mal': RICE nunca preguntó cuánto cuesta esperar, y resulta que esperar recommendations es carísimo." La clave de la respuesta: nombrar que las dos fórmulas usan inputs distintos y responden preguntas distintas, no que una es más "correcta" que la otra.

Resumen y siguiente paso

En esta lección construiste wsjf({costOfDelay, jobSize}) y sequence(backlog), verificaste la fórmula de SAFe (costo de demora ÷ duración del trabajo), y la corriste sobre el backlog completo de Mercado. El resultado invirtió el orden de RICE de forma dramática: recommendations saltó del último lugar al segundo, y fasterCheckout cayó del primero al tercero — no porque RICE estuviera mal, sino porque WSJF responde una pregunta distinta: cuánto valor entrega cada apuesta por cada semana de trabajo del equipo, tomando en cuenta lo que cuesta esperarla.

Antes de avanzar deberías poder: escribir de memoria la fórmula de WSJF y explicar cada uno de sus dos términos; ejecutar sequence() sobre un backlog con costOfDelay y jobSize; y explicar, con un ejemplo concreto, por qué el orden de WSJF puede diferir tanto del orden de RICE.

Ya tienes el orden. La lección 5 da el siguiente paso: convertir esa secuencia en algo más que una lista ordenada — un roadmap real, con outcomes atados a cada apuesta, y una prueba numérica de por qué ese orden específico, y no otro, es el que menos le cuesta a Mercado en total.

Recursos

  • Scaled Agile Framework, "WSJF" — framework.scaledagile.com/wsjf. La fuente primaria de la fórmula: Cost of Delay / Job Duration, con el desglose completo de los tres factores del costo de demora. En inglés.
  • Donald G. Reinertsen, The Principles of Product Development Flowgoodreads.com/book/show/6278270. El origen matemático de WSJF (bajo el nombre CD3) y la demostración de por qué esta secuencia minimiza el costo total de demora de un portafolio — el argumento que la lección 5 retoma. En inglés.
  • Intercom, "RICE: Simple prioritization for product managers" — intercom.com/blog/rice-simple-prioritization-for-product-managers. Útil para releer con esta lección fresca: compara qué pregunta responde RICE y en qué momento del proceso, frente a WSJF. En inglés.