Módulo 8: Project Validate Mercados Recommendations

Proyecto final: valida `recommendations`, de punta a punta

Descripción

Este es el cierre de las ocho lecciones de este módulo, y el cierre de los ocho módulos que llevas construyendo desde la lección 1 de esta guía. El encargo es el mismo que ha acompañado la guía entera: el equipo de producto de Mercado tiene la suposición riesgosa de recommendations —"los usuarios van a comprar más si ven recomendaciones personalizadas"— con risk: 0.6, impact: 5, y el confidence de RICE topado en 0.3 desde que product-thinking-for-engineers-guide la dejó sin probar. Tu trabajo es entregar, con la lógica ejecutada y no con una opinión, el pipeline de discovery completo: el plan de una página, los hallazgos que arrojó correrlo, y una decisión final —seguir, pivotar, o matar— que puedas defender, paso a paso, frente a cualquiera que pregunte "¿y con qué evidencia?".

Conexión con el módulo. Las siete lecciones anteriores armaron, una capa a la vez, las piezas de este proyecto: la hipótesis falsable (L2), el test elegido con pickTest() (L3), el guion de entrevista verificado (L4), la señal del fake door (L5), la evidencia auditada por sesgo (L6), y la síntesis con la decisión final (L7). Este proyecto no agrega ninguna herramienta nueva: reúne los seis pasos en una sola corrida de Node, de principio a fin, sobre el caso completo — exactamente lo que un equipo de discovery real hace cada vez que cierra una ronda, no una vez por lección.

Cierra el círculo: del diagnóstico vago al veredicto con evidencia

En la presentación de este módulo, el médico que había pedido varios exámenes por separado se sentaba, por fin, con todos los resultados sobre el escritorio, para dar el diagnóstico. Este proyecto es esa consulta final. No hay ningún examen más que pedir —los seis ya están corridos—; lo que queda es ponerlos todos juntos, en el mismo archivo, y firmar el veredicto completo: esta es la evidencia, esta es la dirección en la que apunta, y esto es lo que hacemos ahora con recommendations.

La solución de referencia, verificada

Vamos a correr el pipeline completo una vez más, de punta a punta, y verificarlo paso a paso. (Los ejercicios de transferencia del final te piden aplicar el mismo método a un caso que esta guía nunca vio.)

Parte 1 — El plan de discovery, en una página

Antes de ejecutar nada, este es el documento que el equipo de Mercado llevaría a la reunión de planeación de la semana — el resumen de las decisiones que las lecciones 2 y 3 de este módulo ya verificaron:

PLAN DE DISCOVERY -- recommendations (Mercado)
════════════════════════════════════════════════════════════════════
Suposicion riesgosa:   "Los usuarios van a comprar mas si ven
                        recomendaciones personalizadas"
Fuente:                 product-thinking-for-engineers-guide, M6
                        (risk=0.6, impact=5, score=3.0)

Hipotesis falsable (isFalsifiable -> true):
  creemos que:          Los usuarios van a comprar mas si ven
                        recomendaciones personalizadas
  lo sabremos si:        >= 8% de quienes ven una recomendacion
                        la agregan al carrito
  estaremos mal si:      < 8%, incluso despues de 2 semanas de
                        exposicion

Test principal (pickTest, byValue):
  fake_door_checkout (cost=3, strength=6, value=2.00)
  -- descartados: behavioral_interview (1.50), clickable_prototype
     (1.40), build_the_engine (0.23)

Complemento:            guion de 7 preguntas (classifyQuestion,
                        7/7 good), corrido ANTES del fake door

Muestra planeada:       10 personas -- 3 power_user / 4 occasional_
                        buyer / 3 new_user (sampleBias.biased=false)

Umbral del fake door:   5% clic-through, acordado ANTES de medir
Umbrales de decide():   persevere >= 0.6 | pivot >= 0.3 | kill si
                        no llega a ninguno -- acordados ANTES de
                        calcular el confidence

Cronograma:  dias 1-3  entrevistas (8 compradores)
             dias 4-10 fake door corre 1 semana completa
             dia 11    sintesis, actualizacion de confianza,
                       decision
════════════════════════════════════════════════════════════════════

Fíjate en algo que este documento hace explícito, y que un plan improvisado casi nunca hace: cada umbral de decisión está escrito antes de la fila de cronograma que produce el dato que ese umbral va a evaluar. El 8% de la hipótesis, el 5% del fake door, y los 0.6/0.3 de decide() — los tres se fijaron en esta página, antes del día 1, no ajustados después con el resultado ya en la mano.

🟡 Una brecha que vale la pena reconciliar antes de seguir. El wrongIf de la hipótesis habla de agregar al carrito (< 8%); el fake door mide clic-through (5%) sobre el botón "Ver mis recomendaciones". No son la misma métrica, y el plan no debería tratarlas como si lo fueran: el clic del fake door es un proxy temprano de esa condición —mide si alguien se interesa lo suficiente como para descubrir la recomendación, no si de verdad la agrega al carrito—. Es la métrica correcta para una señal barata y rápida como esta, pero no cierra por sí sola la condición completa de wrongIf; medir el add-to-cart real, con el rigor que esa condición exige, es trabajo de product-metrics-and-experimentation-guide una vez que el equipo lance la solución de verdad.

Parte 2 — La corrida end-to-end: los 7 pasos, encadenados

Ahora corremos, en un solo archivo, los seis modelos reutilizados sin cambios de los módulos 2, 4, 5 y 6, más los tres modelos del módulo 7:

// PROYECTO: el pipeline de discovery completo sobre "recommendations", de
// punta a punta. Encadena isFalsifiable + pickTest (M4), classifyQuestion
// (M2), fakeDoorSignal (M5), sampleBias + rankByStrength (M6), y
// synthesize + updateConfidence + decide (M7, funciones CANONICAS, sin
// ningun cambio).

// ===== M4 =====
function isFalsifiable(hypothesis) {
  const hasBelief = typeof hypothesis.believe === 'string' && hypothesis.believe.trim().length > 0;
  const hasFailureCondition = typeof hypothesis.wrongIf === 'string' && hypothesis.wrongIf.trim().length > 0;
  if (!hasBelief) return { ...hypothesis, falsifiable: false, reason: 'no declara una creencia clara (believe) -- no hay nada que probar' };
  if (!hasFailureCondition) return { ...hypothesis, falsifiable: false, reason: 'no declara una condicion de fracaso (wrongIf) -- no se puede refutar' };
  return { ...hypothesis, falsifiable: true, reason: 'tiene creencia y condicion de fracaso, ambas observables' };
}
function pickTest(assumption, candidateTests, options = {}) {
  const byValue = options.byValue || false;
  const falsifiable = candidateTests.filter((t) => t.canFalsify);
  if (falsifiable.length === 0) return { assumption, chosen: null, discarded: candidateTests, reason: 'ningun candidato puede refutar la suposicion' };
  const scored = falsifiable.map((t) => ({ ...t, value: t.strength / t.cost }));
  const chosen = byValue ? scored.reduce((best, t) => (t.value > best.value ? t : best)) : scored.reduce((best, t) => (t.cost < best.cost ? t : best));
  const discarded = candidateTests.filter((t) => t.type !== chosen.type);
  return { assumption, chosen, discarded, mode: byValue ? 'mejor strength/cost' : 'menor costo' };
}
// ===== M2 =====
function classifyQuestion(question) {
  const q = question.toLowerCase();
  const leadingSignals = ['¿verdad que', '¿no crees que', '¿no te parece', '¿cierto que', '¿no es cierto que'];
  const hypotheticalSignals = ['¿usarías', '¿comprarías', '¿te gustaría', '¿pagarías', '¿instalarías', '¿preferirías'];
  const goodSignals = ['¿qué hiciste', '¿qué hizo', '¿cuándo fue la última vez', '¿cuál fue la última vez', '¿cómo resolviste', '¿qué usaste', '¿recuerdas la última vez', '¿cómo hiciste'];
  if (leadingSignals.some((s) => q.includes(s))) return { question, label: 'leading', reason: 'mete la respuesta esperada dentro de la pregunta' };
  if (hypotheticalSignals.some((s) => q.includes(s))) return { question, label: 'hypothetical', reason: 'pide una prediccion sobre el futuro, no un hecho' };
  if (goodSignals.some((s) => q.includes(s))) return { question, label: 'good', reason: 'pide un hecho especifico de comportamiento pasado' };
  return { question, label: 'hypothetical', reason: 'sin evidencia de comportamiento pasado' };
}
// ===== M5 =====
function fakeDoorSignal({ impressions, clicks, threshold }) {
  const clickRate = clicks / impressions;
  return { impressions, clicks, clickRatePercent: Math.round(clickRate * 1000) / 10, thresholdPercent: Math.round(threshold * 1000) / 10, passesThreshold: clickRate >= threshold };
}
// ===== M6 =====
function sampleBias({ segments }) {
  const total = segments.reduce((sum, s) => sum + s.count, 0);
  const withShare = segments.map((s) => ({ ...s, share: +((s.count / total) * 100).toFixed(1) }));
  const dominant = withShare.reduce((max, s) => (s.share > max.share ? s : max));
  const missing = withShare.filter((s) => s.count === 0);
  return { total, segments: withShare, dominant, missing, biased: dominant.share >= 70 || missing.length > 0 };
}
const STRENGTH_BY_TYPE = { observed_behavior: 4, reported_behavior: 3, stated_opinion: 2, hypothetical: 1 };
function rankByStrength(evidences) {
  return evidences.map((e) => ({ ...e, strength: STRENGTH_BY_TYPE[e.type] })).sort((a, b) => b.strength - a.strength);
}
// ===== M7 (funciones CANONICAS, identicas a las del modulo 7 -- sin ningun cambio) =====
function synthesize(findings, options = {}) {
  const signalMinUsers = options.signalMinUsers || 2;
  const byProblem = {};
  findings.forEach((f) => {
    if (!byProblem[f.problem]) byProblem[f.problem] = [];
    byProblem[f.problem].push(f);
  });
  return Object.keys(byProblem)
    .map((problem) => {
      const items = byProblem[problem];
      const distinctUsersUnprompted = new Set(
        items.filter((f) => f.unprompted).map((f) => f.user)
      ).size;
      return {
        problem,
        mentions: items.length,
        distinctUsersUnprompted,
        classification: distinctUsersUnprompted >= signalMinUsers ? 'signal' : 'noise',
      };
    })
    .sort((a, b) => b.distinctUsersUnprompted - a.distinctUsersUnprompted);
}
function updateConfidence(prior, evidence) {
  const posteriorRaw = evidence.validates
    ? prior + (1 - prior) * evidence.strength
    : prior - prior * evidence.strength;
  const posterior = Math.round(posteriorRaw * 100) / 100;
  return {
    prior,
    posterior,
    delta: Math.round((posterior - prior) * 100) / 100,
    direction: evidence.validates ? 'sube' : 'baja',
  };
}
function decide(signalStrength, threshold) {
  if (signalStrength >= threshold.persevere) return { signalStrength, action: 'persevere', reason: 'la evidencia acumulada supera el umbral para seguir invirtiendo tal como esta' };
  if (signalStrength >= threshold.pivot) return { signalStrength, action: 'pivot', reason: 'hay señal real pero no alcanza para seguir sin cambios' };
  return { signalStrength, action: 'kill', reason: 'la evidencia acumulada no alcanza ni para pivotar' };
}

// =========================================================
console.log('=== recommendationsBet, tal como llega desde product-thinking-for-engineers ===\n');
const recommendationsBet = { assumption: 'Los usuarios van a comprar mas si ven recomendaciones personalizadas', risk: 0.6, impact: 5, tested: false, confidenceCeiling: 0.3 };
console.log('assumption: "' + recommendationsBet.assumption + '"');
console.log('risk=' + recommendationsBet.risk + '  impact=' + recommendationsBet.impact + '  score=' + (recommendationsBet.risk * recommendationsBet.impact));
console.log('tested=' + recommendationsBet.tested + '  confidenceCeiling=' + recommendationsBet.confidenceCeiling);

console.log('\n=== Paso 1/7: la hipotesis falsable (M4 isFalsifiable) ===\n');
const recommendationsHypothesis = {
  believe: 'Los usuarios van a comprar mas si ven recomendaciones personalizadas',
  weWillKnowIf: 'al menos el 8% de los usuarios que ven una recomendacion la agregan al carrito',
  wrongIf: 'menos del 8% de los usuarios que ven una recomendacion la agrega al carrito, incluso despues de dos semanas de exposicion',
};
console.log('falsifiable: ' + isFalsifiable(recommendationsHypothesis).falsifiable);

console.log('\n=== Paso 2/7: pickTest() elige el test mas barato que la puede refutar (M4) ===\n');
const candidateTests = [
  { type: 'behavioral_interview', cost: 2, canFalsify: true, strength: 3 },
  { type: 'fake_door_checkout', cost: 3, canFalsify: true, strength: 6 },
  { type: 'clickable_prototype', cost: 5, canFalsify: true, strength: 7 },
  { type: 'build_the_engine', cost: 40, canFalsify: true, strength: 9 },
];
const testDecision = pickTest(recommendationsHypothesis.believe, candidateTests, { byValue: true });
console.log('elegido: ' + testDecision.chosen.type + ' (cost=' + testDecision.chosen.cost + ', value=' + testDecision.chosen.value.toFixed(2) + ')');

console.log('\n=== Paso 3/7: el guion de entrevista, verificado (M2 classifyQuestion) ===\n');
const interviewScript = [
  '¿Cuándo fue la última vez que compraste algo en Mercado sin buscarlo directamente, porque lo viste en otro lugar?',
  '¿Cuándo fue la última vez que compraste algo basado en una sugerencia automática de otra tienda o app (como Amazon, Netflix o Spotify)?',
  '¿Qué hiciste la última vez que Mercado no te mostró algo que buscabas y terminaste comprando en otro lado?',
  '¿Qué hiciste la última vez que dudaste entre dos productos parecidos en Mercado?',
  '¿Qué usaste la última vez que necesitabas ayuda para decidir entre productos parecidos en Mercado?',
  '¿Recuerdas la última vez que un vendedor o una reseña te hizo comprar algo que no tenías planeado? Cuéntame qué pasó.',
  '¿Cuál fue la última vez que una recomendación de un amigo, una reseña o una tienda te hizo ahorrar tiempo buscando algo?',
];
const scriptCheck = interviewScript.map(classifyQuestion);
const goodCount = scriptCheck.filter((r) => r.label === 'good').length;
console.log(goodCount + ' de ' + scriptCheck.length + ' preguntas listas (good). Guion corrido con 8 compradores.');

console.log('\n=== Paso 4/7: la senal del fake door en el checkout (M5 fakeDoorSignal) ===\n');
const weekResult = fakeDoorSignal({ impressions: 4000, clicks: 260, threshold: 0.05 });
console.log(weekResult.clicks + ' clics / ' + weekResult.impressions + ' impresiones = ' + weekResult.clickRatePercent + '% (umbral: ' + weekResult.thresholdPercent + '%) -> supera: ' + weekResult.passesThreshold);

console.log('\n=== Paso 5/7: pesar la evidencia sin sesgo (M6 sampleBias + rankByStrength) ===\n');
const correctedSample = { segments: [{ segment: 'power_user', count: 3 }, { segment: 'occasional_buyer', count: 4 }, { segment: 'new_user', count: 3 }] };
const sampleCheck = sampleBias(correctedSample);
console.log('muestra: ' + sampleCheck.total + ' personas, dominante ' + sampleCheck.dominant.segment + ' (' + sampleCheck.dominant.share + '%), sesgada: ' + sampleCheck.biased);
const correctedEvidence = [
  { claim: 'Hizo clic en "recomendado para ti" en el fake door del checkout', type: 'observed_behavior' },
  { claim: 'Ignoro la seccion de recomendados, cero clics en dos visitas', type: 'observed_behavior' },
  { claim: 'Agrego al carrito un producto recomendado en el fake door', type: 'observed_behavior' },
  { claim: 'Conto que la ultima vez que compro en otra tienda, siguio una recomendacion', type: 'reported_behavior' },
  { claim: 'Dijo que le encantaria ver recomendaciones personalizadas', type: 'hypothetical' },
];
const rankedEvidence = rankByStrength(correctedEvidence);
rankedEvidence.forEach((e) => console.log('  strength=' + e.strength + ' [' + e.type.padEnd(18) + '] ' + e.claim));

console.log('\n=== Paso 6/7: sintetizar las 8 entrevistas (M7 synthesize) ===\n');
const findings = [
  { user: 'buyer_01', problem: 'no descubro productos que me gustarian sin buscarlos por nombre exacto', unprompted: true },
  { user: 'buyer_01', problem: 'no descubro productos que me gustarian sin buscarlos por nombre exacto', unprompted: true },
  { user: 'buyer_02', problem: 'no descubro productos que me gustarian sin buscarlos por nombre exacto', unprompted: true },
  { user: 'buyer_02', problem: 'se me olvida el carrito y no vuelvo a completarlo', unprompted: true },
  { user: 'buyer_03', problem: 'no confio en vendedores nuevos sin reseñas', unprompted: true },
  { user: 'buyer_04', problem: 'no descubro productos que me gustarian sin buscarlos por nombre exacto', unprompted: false },
  { user: 'buyer_05', problem: 'se me olvida el carrito y no vuelvo a completarlo', unprompted: true },
  { user: 'buyer_06', problem: 'no descubro productos que me gustarian sin buscarlos por nombre exacto', unprompted: true },
  { user: 'buyer_07', problem: 'se me olvida el carrito y no vuelvo a completarlo', unprompted: true },
  { user: 'buyer_08', problem: 'la app se cierra sola cuando el celular tiene poca memoria', unprompted: true },
];
const synthesis = synthesize(findings);
synthesis.forEach((r) => {
  console.log('[' + r.classification.toUpperCase().padEnd(6) + '] "' + r.problem + '"  (usuarios distintos sin sugerir: ' + r.distinctUsersUnprompted + ')');
});

console.log('\n=== Paso 7/7: updateConfidence() con el fake door + decide() (M7) ===\n');
const confidenceUpdate = updateConfidence(recommendationsBet.confidenceCeiling, { validates: weekResult.passesThreshold, strength: 0.7 });
console.log('prior=' + confidenceUpdate.prior + ' (topado por RICE en product-thinking)');
console.log('posterior=' + confidenceUpdate.posterior + '  (' + confidenceUpdate.direction + ', delta=' + confidenceUpdate.delta + ')');
const THRESHOLD = { persevere: 0.6, pivot: 0.3 };
const decision = decide(confidenceUpdate.posterior, THRESHOLD);
console.log('umbrales acordados de antemano: persevere >= ' + THRESHOLD.persevere + ', pivot >= ' + THRESHOLD.pivot);
console.log('signalStrength=' + decision.signalStrength + '  ->  ' + decision.action.toUpperCase());
console.log(decision.reason);

console.log('\n=== recommendationsBet, del modulo 1 de esta guia a hoy ===\n');
console.log('ANTES:  assumption="' + recommendationsBet.assumption + '", risk=' + recommendationsBet.risk + ', impact=' + recommendationsBet.impact + ', tested=' + recommendationsBet.tested + ', confidenceCeiling=' + recommendationsBet.confidenceCeiling);
console.log('DESPUES: tested=true, confidence=' + confidenceUpdate.posterior + ', decision="' + decision.action + '"');

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

=== recommendationsBet, tal como llega desde product-thinking-for-engineers ===

assumption: "Los usuarios van a comprar mas si ven recomendaciones personalizadas"
risk=0.6  impact=5  score=3
tested=false  confidenceCeiling=0.3

=== Paso 1/7: la hipotesis falsable (M4 isFalsifiable) ===

falsifiable: true

=== Paso 2/7: pickTest() elige el test mas barato que la puede refutar (M4) ===

elegido: fake_door_checkout (cost=3, value=2.00)

=== Paso 3/7: el guion de entrevista, verificado (M2 classifyQuestion) ===

7 de 7 preguntas listas (good). Guion corrido con 8 compradores.

=== Paso 4/7: la senal del fake door en el checkout (M5 fakeDoorSignal) ===

260 clics / 4000 impresiones = 6.5% (umbral: 5%) -> supera: true

=== Paso 5/7: pesar la evidencia sin sesgo (M6 sampleBias + rankByStrength) ===

muestra: 10 personas, dominante occasional_buyer (40%), sesgada: false
  strength=4 [observed_behavior ] Hizo clic en "recomendado para ti" en el fake door del checkout
  strength=4 [observed_behavior ] Ignoro la seccion de recomendados, cero clics en dos visitas
  strength=4 [observed_behavior ] Agrego al carrito un producto recomendado en el fake door
  strength=3 [reported_behavior ] Conto que la ultima vez que compro en otra tienda, siguio una recomendacion
  strength=1 [hypothetical      ] Dijo que le encantaria ver recomendaciones personalizadas

=== Paso 6/7: sintetizar las 8 entrevistas (M7 synthesize) ===

[SIGNAL] "no descubro productos que me gustarian sin buscarlos por nombre exacto"  (usuarios distintos sin sugerir: 3)
[SIGNAL] "se me olvida el carrito y no vuelvo a completarlo"  (usuarios distintos sin sugerir: 3)
[NOISE ] "no confio en vendedores nuevos sin reseñas"  (usuarios distintos sin sugerir: 1)
[NOISE ] "la app se cierra sola cuando el celular tiene poca memoria"  (usuarios distintos sin sugerir: 1)

=== Paso 7/7: updateConfidence() con el fake door + decide() (M7) ===

prior=0.3 (topado por RICE en product-thinking)
posterior=0.79  (sube, delta=0.49)
umbrales acordados de antemano: persevere >= 0.6, pivot >= 0.3
signalStrength=0.79  ->  PERSEVERE
la evidencia acumulada supera el umbral para seguir invirtiendo tal como esta

=== recommendationsBet, del modulo 1 de esta guia a hoy ===

ANTES:  assumption="Los usuarios van a comprar mas si ven recomendaciones personalizadas", risk=0.6, impact=5, tested=false, confidenceCeiling=0.3
DESPUES: tested=true, confidence=0.79, decision="persevere"

Esta última línea es el momento que toda la guía estuvo construyendo desde su primera lección: tested pasa de false a true por primera vez, y confidence deja de estar topado en 0.3 — no por decreto, sino porque dos fuentes de evidencia independientes, la síntesis de ocho entrevistas y el resultado del fake door, lo movieron hasta 0.79. El número, la decisión y el vocabulario coinciden exactamente con el proyecto de cierre del módulo 7 — la misma evidencia, procesada con las mismas funciones, no puede llegar a un veredicto distinto.

Los hallazgos, en limpio

FuenteHallazgoPeso en la decisión
Guion de entrevista (M2)7/7 preguntas verificadas como good antes de correrlasHabilita el resto — sin esto, la entrevista mide intención, no comportamiento
Entrevistas + synthesize (M7)3 de 8 compradores mencionan, sin que se les sugiera, el problema de descubrimiento de productos — empatado con "carrito olvidado"Confirma que la oportunidad subyacente (no la solución) es real y frecuente
Fake door + fakeDoorSignal (M5)6.5% de clic-through, supera el umbral del 5% por un margen ajustado (+1.5pp)La única pieza de evidencia formal que mueve el confidence (strength: 0.7)
sampleBias (M6)10 personas, 3/4/3 por segmento, ningún segmento domina ni faltaLa evidencia no está contaminada por una muestra sesgada
rankByStrength (M6)5 piezas: 3 de comportamiento observado (2 favorables, 1 desfavorable), 1 reportado, 1 hipotéticoAuditoría cualitativa: confirma que la evidencia no es unánime, sin duplicar el cálculo del confidence
updateConfidence (M7)confidence: 0.3 → 0.79 (un solo salto, con el resultado agregado del fake door)Sube con fuerza, muy por encima del umbral de persevere (0.6)
decide (M7)PERSEVERESeguir invirtiendo en recommendations tal como está planteada — no cambiar de solución, no abandonar

La decisión escrita: PERSEVERE, con justificación

Decisión: PERSEVERE. Con confidence: 0.79, muy por encima del umbral de persevere (0.6) acordado en la lección 6 del módulo 7, la evidencia respalda seguir invirtiendo en recommendations tal como está planteada — no un pivot a la solución alternativa de categorías curadas, no un kill. Dos fuentes de evidencia, completamente independientes, apuntan en la misma dirección: la síntesis de las ocho entrevistas confirma que el problema subyacente —los compradores no descubren productos que les gustarían sin buscarlos por nombre exacto— es real y frecuente, mencionado por 3 compradores distintos sin que nadie se lo sugiriera, y es exactamente la misma oportunidad de mayor impact (5) del árbol del módulo 3; y el fake door del checkout, la pieza de evidencia más fuerte del pipeline, superó su umbral acordado de antemano. La evidencia auditada por sesgo (lección 6) no cancela esta lectura: aunque una de las tres observaciones directas de comportamiento fue desfavorable —alguien ignoró la sección en dos visitas—, esa señal ya está incluida, estadísticamente, en el 6.5% agregado del fake door, que resume miles de impresiones reales, no solo los pocos casos que el equipo pudo anotar a mano.

Lo que sigue no es más discovery — es lanzar y medir. A diferencia de una decisión pivot, que exigiría diseñar una solución distinta antes de seguir, persevere significa que el equipo puede pasar de la validación a la construcción con una confianza razonable:

  1. Construir el motor de recomendaciones real — código de producción, backend, frontend — es trabajo del ecosistema Fullstack, que asume exactamente el resultado de este proyecto como punto de partida: recommendations está validado, con confidence: 0.79 y la evidencia que lo sostiene.

  2. Medir el lift real, con rigor estadístico, una vez que esté en producción. El fake door de esta guía midió interés de clic en una muestra pequeña, durante una semana — un proxy barato y temprano, no una medición de significancia. Antes de declarar victoria sobre el 8% de add-to-cart que exige wrongIf, el equipo necesita product-metrics-and-experimentation-guide: tamaño de muestra calculado, cohortes, y una comparación real contra el grupo de control. Esa guía es la que por fin cierra la brecha entre "el clic sugiere interés" y "la recomendación de verdad mueve el GMV".

  3. Lanzar con cuidado, no de una vez. shipping-and-iterating-products-guide cubre el rollout progresivo, los feature flags, y el plan de reversión si el motor real, ya en producción, no confirma lo que esta evidencia temprana sugiere — persevere es una apuesta razonable con la evidencia de hoy, no una garantía sobre el resultado de mañana.

Lo que no cambia con esta decisión: risk: 0.6 e impact: 5 de RICE siguen intactos — esta decisión no repriorizó el backlog, solo confirmó, con evidencia real, que la apuesta que ya era la más urgente merece seguir recibiendo la inversión que tenía planeada.

Rúbrica

Antes de dar por completo tu propio pipeline de discovery —o el de tu propio backlog, en los ejercicios de transferencia—, verifica cada punto:

  • La hipótesis tiene believe y wrongIf separados, con un umbral y un plazo concretos — no una versión donde la condición de fracaso es un espejo vago de la de éxito.
  • El test elegido usa pickTest() en modo byValue, no el más barato ni el más fuerte por sí solos — con los candidatos descartados nombrados explícitamente y su value calculado.
  • El guion de entrevista pasa classifyQuestion() al 100% antes de correrse con usuarios reales — no se corre un guion con preguntas leading o hypothetical sin reescribir.
  • El umbral del test principal se fija antes de ver cualquier dato, y el resultado se reporta con el número completo, no solo el booleano.
  • La muestra pasa sampleBias() y la evidencia completa —favorable y desfavorable— se pesa con rankByStrength(), sin descartar en silencio la evidencia incómoda.
  • synthesize() distingue usuarios distintos sin sugerir de menciones totales, y el umbral de señal (signalMinUsers) se declara explícitamente.
  • updateConfidence() recibe una pieza de evidencia formal, no un conteo inflado — el resultado agregado del test principal, no ese mismo resultado sumado otra vez a las observaciones individuales que ya lo componen.
  • Los umbrales de decide() se fijan antes de calcular el confidence final, no se ajustan después para que el resultado "quede bien".
  • La decisión final viene con un plan concreto, no solo con la palabra persevere/pivot/kill — si es persevere, el plan dice qué sigue (construir, medir, lanzar); si fuera pivot, hacia dónde cambiar de solución.

Ejercicios de transferencia

A diferencia de las lecciones anteriores, estos tres ejercicios no te piden recalcular con los números ya dados — te piden aplicar el método completo a algo que esta guía nunca vio. Es la prueba real de si aprendiste el pipeline o solo memorizaste el resultado de recommendations.

Ejercicio 1 — Aplica el pipeline completo a una suposición nueva. El equipo de Mercado tiene otra suposición riesgosa pendiente, de la oportunidad de "no confío en vendedores nuevos sin reseñas" (la segunda del árbol del módulo 3): "una insignia de vendedor verificado aumenta la probabilidad de que un comprador complete la compra". Aplica el método completo: escribe la hipótesis falsable (con believe y wrongIf), propón al menos tres tests candidatos con cost, canFalsify y strength estimados y elige uno con pickTest() en modo byValue, diseña dos preguntas de entrevista que pasarían classifyQuestion() como good, y propón un umbral razonable para el test elegido. No corras updateConfidence() ni decide() todavía —eso requeriría datos reales que esta guía no tiene para este caso—; el objetivo es completar el diseño, no simular un resultado inventado.

Ver solución

Hipótesis falsable: { believe: 'Una insignia de vendedor verificado aumenta la probabilidad de que un comprador complete la compra', weWillKnowIf: 'la tasa de conversion en fichas de producto con insignia es al menos 10% mayor que en fichas sin insignia, del mismo vendedor cuando sea posible comparar', wrongIf: 'la diferencia de conversion es menor al 10%, incluso despues de 3 semanas de exposicion a la insignia' }. Pasa isFalsifiable(): creencia clara, condición de fracaso con umbral y plazo.

Tests candidatos:

const trustTests = [
  { type: 'behavioral_interview', cost: 2, canFalsify: true, strength: 3 },
  { type: 'clickable_badge_prototype', cost: 3, canFalsify: true, strength: 5 },
  { type: 'ab_test_real_badge', cost: 8, canFalsify: true, strength: 8 },
];

value: behavioral_interview = 1.50, clickable_badge_prototype = 5/3 ≈ 1.67, ab_test_real_badge = 8/8 = 1.00. pickTest() en modo byValue elige clickable_badge_prototype — mejor balance que el A/B test real (que mide comportamiento más directo, pero a un costo casi tres veces mayor) y mejor que la entrevista (más barata, pero con la evidencia más débil de las tres).

Dos preguntas good para el guion: "¿Cuándo fue la última vez que decidiste no comprarle a un vendedor en Mercado por no tener suficientes reseñas?" (contiene '¿cuándo fue la última vez', de goodSignals) y "¿Qué hiciste la última vez que dudaste de la confiabilidad de un vendedor nuevo antes de comprar?" (contiene '¿qué hiciste').

Umbral propuesto: 10% de diferencia de conversión, comparando fichas con y sin insignia — un umbral más exigente que el 8% de recommendations, razonable porque el costo de construir el sistema de verificación real (fuera del alcance de este ejercicio) es alto, y justifica pedir una señal más contundente antes de comprometerlo.

Ejercicio 2 — Aplica el pipeline a tu propio trabajo. Elige una suposición real de tu propio contexto —un producto, una feature, incluso un proceso interno de tu equipo— que hoy se trate como un hecho asumido, sin haber sido validada con nadie. Escribe su hipótesis falsable, propón al menos dos tests candidatos y elige uno con el criterio de pickTest() (aunque estimes cost y strength a ojo), y define el umbral de decisión antes de correr nada. No hay una solución única —el objetivo es que completes el ejercicio con tu propio contexto—, pero antes de darlo por terminado, verifica cada casilla de la rúbrica de esta lección contra tu propio trabajo.

Ver guía de verificación

No hay números que verificar aquí —cada contexto es distinto—, pero sí una prueba de calidad que puedes aplicarte: ¿puedes completar esta frase sin dudar? "Creemos que [suposición]; lo sabremos si [umbral medible, con plazo]; estaremos equivocados si [la misma condición, en sentido contrario]. El test más barato que puede refutarla es [test elegido], porque su value (strength/cost) es mejor que el de [alternativa descartada]." Si te quedas trabado en cualquier parte de esa frase —no tienes un umbral medible, no tienes una alternativa descartada con la que comparar—, esa es exactamente la parte del pipeline que te falta reforzar. Vuelve a la lección correspondiente de este módulo (L2 para la hipótesis, L3 para la elección de test) antes de completar tu propio pipeline.

Ejercicio 3 — Defiende PERSEVERE por escrito ante un director escéptico. Imagina que tienes que enviar el resultado de este proyecto a un director que no vio ninguna de las ocho lecciones de este módulo, y que, al leer "persevere", pregunta directamente: "¿persevere es solo decir que todo salió perfecto, sin ningún matiz?". Escribe la respuesta completa —entre 120 y 200 palabras—, incluyendo el confidence real, las dos fuentes de evidencia independientes que lo sostienen, y qué falta hacer antes de considerar la apuesta completamente cerrada.

Ver solución

Una respuesta razonable:

"No, persevere no es decir que todo salió perfecto. El confidence de recommendations subió de 0.3 a 0.79 con dos fuentes de evidencia completamente independientes: las ocho entrevistas de comportamiento, donde tres compradores distintos confirmaron, sin que se les sugiera, el problema que sostiene esta apuesta; y el fake door del checkout, que superó su umbral acordado de antemano (6.5% contra 5%). El 0.79 queda muy por encima del umbral de persevere (0.6), no en el límite — por eso la decisión no es ambigua. Pero la evidencia auditada en la lección 6 también mostró una señal desfavorable: alguien ignoró la sección de recomendados en dos visitas. Esa señal no cancela el resultado —ya está incluida en el 6.5% agregado, que resume miles de impresiones reales, no solo un puñado de casos—, pero es la razón por la que seguimos hablando de un modelo pedagógico, no de una certeza absoluta.

Por eso lo que sigue no es 'ya está, construyamos y listo': es construir el motor real con esta validación como punto de partida, y medir el lift verdadero con el rigor estadístico que un fake door de una semana no puede dar — cohortes, significancia, comparación real contra el 8% de add-to-cart que exige nuestra hipótesis original. Persevere significa que vale la pena invertir el siguiente ciclo completo en esta apuesta, no que ya terminamos de medirla."

Fíjate en la estructura: nombra el número exacto, reconoce el matiz sin restarle fuerza a la decisión, y distingue con claridad entre "decidir seguir invirtiendo" y "ya medimos el resultado real" — la frontera que sostiene toda esta guía.

Resumen y siguiente paso

En este proyecto final corriste, de punta a punta, el pipeline de discovery completo sobre recommendations: siete pasos encadenados —hipótesis falsable, test elegido, guion verificado, señal del fake door, evidencia auditada sin sesgo, síntesis de las ocho entrevistas, y actualización de confianza— que llevaron el confidence de la apuesta desde el techo de 0.3 heredado de RICE hasta 0.79, y una decisión final, defendible paso a paso: PERSEVERE — el mismo número, el mismo vocabulario, y la misma decisión que ya alcanzó el proyecto de cierre del módulo 7 sobre esta idéntica evidencia.

Con esto cierras la guía completa. Tienes ahora el método entero: por qué descubrir antes de construir es más barato que construir para descubrir (módulo 1), cómo hablar con usuarios sin que la conversación te mienta (módulo 2), cómo mapear el problema completo antes de saltar a una solución (módulo 3), cómo convertir una corazonada en una hipótesis que se puede tumbar y elegir el test más barato que la tumbaría (módulo 4), cómo prototipar al nivel de fidelidad justo (módulo 5), cómo desconfiar de tu propia lectura de la evidencia (módulo 6), cómo sintetizar señales reales y mover una creencia con disciplina (módulo 7) — todo aplicado, de punta a punta, sobre una suposición real en este módulo 8.

Hacia dónde sigues

Este módulo entrega una decisión validada con usuarios: persevere, con confidence: 0.79. No entrega, y nunca prometió entregar, tres cosas que necesitas después:

  • Medir el lift real, con rigor estadístico, una vez que el motor esté en producción. El fake door de esta guía midió interés de clic en una muestra de 10 personas, en una semana — un proxy barato y temprano de la condición de add-to-cart que exige wrongIf (8%), no una medición directa. El motor de recomendaciones, una vez construido, necesita medirse con significancia real, tamaño de muestra calculado, y cohortes — eso es product-metrics-and-experimentation-guide, la guía hermana que retoma exactamente donde termina esta.
  • Lanzar con cuidado, no de una vez. Sacar recommendations a producción con feature flags, rollout progresivo, y un plan de reversión si el resultado real no confirma esta evidencia temprana es shipping-and-iterating-products-guide.
  • Volver a priorizar el backlog completo, si este resultado cambia el panorama del trimestre. El risk y el impact de RICE no cambiaron con este proyecto —pero un equipo real, con tested: true y confidence: 0.79 en la mano, probablemente quiera revisar cómo recommendations se compara ahora contra el resto del backlog. Ese trabajo, otra vez, es product-thinking-for-engineers-guide, la guía que dejó esta suposición en la puerta de esta.

Y hay una frontera final, la misma que sostuvo toda esta guía desde su primera lección: construir de verdad —el motor de recomendaciones— es el trabajo del ecosistema Fullstack. Esta guía te enseñó a decidir si vale la pena construir, con qué evidencia, y con qué nivel de confianza — un argumento que puedes defender, paso a paso, columna por columna. El resto del camino —medir con rigor, lanzar con cuidado, construir de verdad— es lo que sigue.

Recursos

  • Teresa Torres, Continuous Discovery Habitsproducttalk.org/continuous-discovery-habits. El libro que sostiene el argumento completo de esta guía, de principio a fin. Vale la pena leerlo entero ahora que tienes el método completo. En inglés.
  • Rob Fitzpatrick, The Mom Testmomtestbook.com. La fuente detrás del guion de entrevista que este proyecto corrió sin cambios desde el módulo 2. En inglés.
  • David J. Bland y Alexander Osterwalder, Testing Business Ideasstrategyzer.com/library/testing-business-ideas-book. El catálogo completo de experimentos, del que este proyecto eligió, con criterio explícito, uno solo. En inglés.
  • Eric Ries, The Lean Startuptheleanstartup.com/book. La fuente original del vocabulario "pivotar" — vale la pena releerlo ahora que la decisión final de este proyecto fue persevere, no pivot, para tener claro qué hubiera significado la alternativa. En inglés.
  • Marty Cagan (Silicon Valley Product Group), Inspiredsvpg.com/inspired-how-to-create-products-customers-love. El puente directo hacia product-metrics-and-experimentation-guide y shipping-and-iterating-products-guide — cómo medir y lanzar, con cuidado, lo que este módulo decidió que vale la pena construir. En inglés.