Módulo 4: Assumption And Hypothesis Testing

Que de verdad se pueda falsar

Descripción

En 1934, el filósofo Karl Popper propuso una idea que hoy es el estándar para distinguir una afirmación científica de una que no lo es: no importa cuántas veces confirmes una teoría, nunca la puedes probar cierta con certeza absoluta — siempre podría aparecer el caso que la contradiga mañana. Pero una sola observación contraria sí la puede tumbar por completo. El ejemplo clásico de Popper es "todos los cisnes son blancos": puedes ver mil cisnes blancos y la afirmación sigue sin estar probada (el cisne mil uno podría ser negro); pero ves un solo cisne negro, y la afirmación queda refutada, sin discusión posible. Esta asimetría —confirmar es débil e infinito, refutar es fuerte y de un solo golpe— es la razón de fondo por la que este módulo insiste tanto en la condición de fracaso, y no en la señal de éxito.

La lección anterior te dio una herramienta —isFalsifiable()— para chequear si una hipótesis tiene, técnicamente, un campo wrongIf con contenido. Pero "tiene contenido" y "de verdad se puede refutar" no son exactamente lo mismo, y en esta lección vas a ver casos donde el chequeo automático dice que sí, mientras que una lectura humana atenta —con el criterio de Popper en mente— diría que no.

Conexión con el módulo. Esta lección reutiliza isFalsifiable() exactamente como quedó en la lección 2, sin ningún cambio en el código, sobre tres hipótesis nuevas del assumption stack de recommendations. El objetivo no es ampliar la herramienta —eso no hace falta todavía— sino calibrar tu propio criterio para los casos donde la herramienta, siendo simple, se deja engañar. Ese criterio afinado es exactamente lo que vas a necesitar en la lección 4, cuando tengas que juzgar si un test candidato de verdad puede refutar la suposición riesgosa, o solo lo parece.

Una analogía: el cisne negro y la queja que nunca llega

Piensa en la afirmación "a nuestros usuarios siempre les gusta lo que lanzamos". Como el cisne blanco, cada lanzamiento exitoso "confirma" la afirmación — y nunca hay un lanzamiento fallido suficiente para tumbarla de una vez, porque siempre se puede decir "ese no cuenta, tenía un problema técnico" o "esa vez el marketing falló, no el producto". La afirmación está diseñada, sin que nadie lo haya hecho a propósito, para nunca perder. Eso no la hace más segura — la hace inútil como guía para decidir nada, exactamente como "todos los cisnes son blancos" es inútil para predecir el color del próximo cisne si cualquier color se puede racionalizar como "una excepción, no una refutación".

Compáralo con "a los vendedores no les va a importar que sus productos aparezcan menos" y su condición de fracaso, tal como la vas a ver en el ejemplo de hoy: "si algo sale mal, lo vamos a notar". Suena a que sí tiene una condición de fracaso — tiene la palabra "mal" ahí mismo — pero es exactamente el mismo problema del cisne negro que nunca llega: no dice qué contaría como "algo salió mal", así que cualquier cosa que pase después se puede leer como "eso no era lo que queríamos decir con 'mal'". Es una condición de fracaso de mentira, vestida con las palabras correctas.

Ejemplo trabajado: isFalsifiable(), sin cambios, sobre tres casos nuevos

Reutilizamos exactamente el mismo código de la lección 2 — ni una línea distinta — sobre tres hipótesis nuevas, tomadas de otras suposiciones del assumption stack de recommendations: si el motor puede generar recomendaciones relevantes con los datos actuales, si a los vendedores no les va a importar la menor visibilidad, y si mostrar recomendaciones no hace más lento el checkout.

// L3: isFalsifiable() reusado sin cambios (identico a la leccion 2), sobre 3
// hipotesis nuevas del assumption stack de "recommendations".
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' };
}

const engineRelevance = {
  believe: 'El motor puede generar recomendaciones relevantes con los datos de compras actuales',
  weWillKnowIf: 'al menos el 70% de las recomendaciones de una muestra de 30 usuarios son relevantes segun un evaluador humano',
  wrongIf: 'menos del 70% de las recomendaciones de la muestra son relevantes segun el evaluador humano',
};

const vendorConcern = {
  believe: 'A los vendedores no les va a importar que sus productos aparezcan menos en las busquedas por las recomendaciones',
  weWillKnowIf: 'no vamos a recibir quejas',
  wrongIf: 'si algo sale mal, lo vamos a notar',
};

const checkoutSpeed = {
  believe: 'Mostrar recomendaciones no va a hacer mas lento el checkout',
  weWillKnowIf: 'medimos el tiempo de carga del checkout',
  wrongIf: '',
};

console.log('=== isFalsifiable() reusado, sobre 3 hipotesis nuevas del assumption stack ===\n');
[
  { label: 'engineRelevance', h: engineRelevance },
  { label: 'vendorConcern', h: vendorConcern },
  { label: 'checkoutSpeed', h: checkoutSpeed },
].forEach(({ label, h }) => {
  const r = isFalsifiable(h);
  console.log(label + ':');
  console.log('  wrongIf: "' + (h.wrongIf || '(vacio)') + '"');
  console.log('  falsifiable: ' + r.falsifiable + '  -> ' + r.reason);
  console.log('');
});

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

=== isFalsifiable() reusado, sobre 3 hipotesis nuevas del assumption stack ===

engineRelevance:
  wrongIf: "menos del 70% de las recomendaciones de la muestra son relevantes segun el evaluador humano"
  falsifiable: true  -> tiene creencia y condicion de fracaso, ambas observables

vendorConcern:
  wrongIf: "si algo sale mal, lo vamos a notar"
  falsifiable: true  -> tiene creencia y condicion de fracaso, ambas observables

checkoutSpeed:
  wrongIf: "(vacio)"
  falsifiable: false  -> no declara una condicion de fracaso (wrongIf) -- no se puede refutar

checkoutSpeed es el caso fácil: wrongIf está vacío, el código lo detecta sin ambigüedad, listo. engineRelevance es el caso ideal: un umbral concreto (70%), medible por un evaluador humano sobre una muestra definida. Pero mira vendorConcern con cuidado — isFalsifiable() la marca true, exactamente igual que engineRelevance, porque el código solo revisa si wrongIf tiene texto, no si ese texto describe algo observable. "Si algo sale mal, lo vamos a notar" tiene texto — pasa el chequeo — pero no dice qué cuenta como "mal", ni cómo lo notarían, ni cuándo. Es, en la práctica, tan poco falsable como no tener wrongIf del todo — solo que el heurístico automático no tiene forma de saberlo, porque nunca le enseñaste a leer el contenido de la frase, solo su presencia.

La prueba de Popper, aplicada a mano: dos preguntas que el código no puede hacer

Cuando isFalsifiable() diga true, todavía vale la pena hacerte, a mano, dos preguntas que ningún chequeo automático de este tipo puede responder por ti:

  1. ¿El wrongIf describe una observación específica y medible —un número, un umbral, un evento concreto— o una vaguedad que admite muchas lecturas? engineRelevance pasa: "menos del 70%" es un número exacto. vendorConcern no pasa: "algo sale mal" podría significar cualquier cosa, desde una queja formal hasta un comentario de pasillo.
  2. Si el resultado del test fuera ambiguo, ¿tendrías la tentación de decir "eso no cuenta, no era a lo que me refería"? Si la respuesta es sí, la condición de fracaso no está realmente fija — se puede mover después de ver el resultado, que es exactamente lo que Popper señala como la trampa de una afirmación no falsable: no es que nunca se pueda estar equivocado, es que nadie decidió de antemano qué contaría como estarlo.

vendorConcern, reescrita con esas dos preguntas en mente, se vería más parecida a esto: wrongIf: 'al menos el 15% de los vendedores activos reporta una queja formal sobre visibilidad reducida en las primeras 4 semanas'. Ahora sí hay un número, un plazo, y un canal de medición (queja formal, no "lo vamos a notar") — y las dos preguntas de arriba se responden con un sí y un no, respectivamente.

Errores comunes

Confiar en que isFalsifiable() === true cierra la discusión. Qué pasa: el equipo corre el chequeo automático, ve true, y da por terminada la revisión de la hipótesis — exactamente lo que pasaría con vendorConcern si nadie mirara más allá del resultado booleano. Por qué pasa: una herramienta automatizada se siente objetiva y definitiva, y cuestionar su veredicto parece innecesario si "ya pasó el chequeo". Cómo detectarlo: nadie en el equipo puede describir, en una frase concreta y con números, qué observación exacta activaría el wrongIf — solo pueden repetir el texto vago tal como está escrito. Cómo corregirlo: usa las dos preguntas de esta lección como el paso humano que sigue después de que el código dice true, no como un reemplazo del código — son complementarias, no una sustituye a la otra.

Confundir "nadie lo ha refutado todavía" con "está probado". Qué pasa: después de varios lanzamientos exitosos, alguien dice "ya está comprobado que a los usuarios les gusta lo que lanzamos" — tratando la ausencia de un fracaso reciente como si fuera una prueba positiva y permanente. Por qué pasa: la asimetría de Popper es contraintuitiva — nuestro instinto trata "no ha fallado" como equivalente a "funciona", cuando en realidad son estados muy distintos: uno es evidencia acumulada y provisional, el otro sería una certeza que ningún número de observaciones puede entregar. Cómo detectarlo: la frase "está comprobado que..." aparece en una conversación de producto sin que nadie mencione qué observación futura podría todavía contradecirlo. Cómo corregirlo: reemplaza "está comprobado" por "hasta ahora, no hemos visto la señal que nos haría cambiar de opinión" — es más largo, pero es honesto sobre lo que la evidencia realmente sostiene, y deja la puerta abierta a la próxima observación que sí podría cambiar la conclusión.

Ejercicios

Ejercicio 1 — Aplica las dos preguntas a una hipótesis nueva. Esta hipótesis pasaría isFalsifiable() como true. Aplícale las dos preguntas de la lección y decide si de verdad se puede refutar: { believe: 'El nuevo diseño del checkout mejora la experiencia', weWillKnowIf: 'los usuarios lo van a percibir como mas facil de usar', wrongIf: 'si la experiencia empeora, lo vamos a saber por el feedback' }.

Ver solución

Pasa el chequeo automático (wrongIf no está vacío), pero falla las dos preguntas humanas. Pregunta 1: "si la experiencia empeora, lo vamos a saber por el feedback" no da ningún número, umbral ni canal específico — "el feedback" podría ser un comentario aislado, una encuesta, o nada en absoluto. Pregunta 2: es fácil imaginar al equipo descartando cualquier comentario negativo aislado como "una opinión, no una señal real" — la condición de fracaso se puede mover después de ver el resultado. Una versión mejor: wrongIf: 'al menos el 20% de una muestra de 50 usuarios reporta, en una encuesta post-checkout, que el proceso les parecio mas dificil que antes'. Ahora hay un número, una muestra definida, y un canal de medición concreto.

Ejercicio 2 — Reescribe vendorConcern para que pase las dos preguntas. Usando la pista que da la lección, escribe tu propia versión completa del wrongIf de vendorConcern, con un número y un plazo, y verifica a mano que seguiría pasando isFalsifiable().

Ver solución

Una versión razonable: wrongIf: 'al menos el 15% de los vendedores activos reporta una queja formal, por el canal de soporte, sobre visibilidad reducida de sus productos, dentro de las primeras 4 semanas de lanzamiento'. Verificando contra el código: believe no está vacío, wrongIf no está vacío — isFalsifiable() seguiría devolviendo true, exactamente igual que la versión original vaga. La diferencia no está en lo que el código detecta —ambas versiones pasan igual— sino en lo que un lector humano puede hacer con el resultado: la versión nueva tiene un número que se puede medir sin ambigüedad quince días después de lanzar el test; la original no.

Ejercicio 3 — El cisne negro de Mercado. La afirmación "a los usuarios de Mercado siempre les importa más el precio bajo que cualquier otra cosa" se parece mucho a "todos los cisnes son blancos": cada compra de un producto barato la "confirma", y nunca hay un número suficiente de esas compras para probarla del todo. Escribe una condición de fracaso (wrongIf) que, si ocurriera, refutaría esta afirmación de forma clara e inequívoca — como el cisne negro de Popper.

Ver solución

Una respuesta razonable: wrongIf: 'en una muestra de 50 compras registradas, al menos el 30% eligio un producto que NO era el mas barato entre las opciones comparables mostradas'. Esto funciona como un cisne negro porque describe una observación específica, medible, y que un lector neutral no podría racionalizar como "no cuenta" — si el 30% de una muestra clara elige algo que no es el más barato, la afirmación absoluta ("siempre les importa más el precio") queda refutada, sin importar cuántas compras baratas se hayan visto antes. El punto del ejercicio: afirmaciones con palabras como "siempre" o "nunca" son fáciles de confirmar sin fin y difíciles de falsar — a menos que, como aquí, se les ponga una condición de fracaso concreta y medible.

Resumen y siguiente paso

Esta lección no cambió el código de isFalsifiable() — lo reutilizó exactamente igual que en la lección 2 — pero afinó tu criterio para leer sus resultados: viste que un wrongIf puede tener texto y aun así no describir nada observable, como en vendorConcern, y aprendiste las dos preguntas (¿es específico y medible? ¿podrías racionalizar cualquier resultado como "no cuenta"?) que un chequeo automático nunca podría hacer por ti. También conociste la asimetría de Popper que sostiene todo esto: confirmar nunca prueba del todo, pero una sola refutación clara sí basta.

Antes de avanzar deberías poder: explicar por qué "mil cisnes blancos" no prueba que todos los cisnes sean blancos, pero un cisne negro sí la refuta; aplicar las dos preguntas de esta lección a cualquier wrongIf que veas, incluso si isFalsifiable() ya dijo true; y reescribir una condición de fracaso vaga en una versión concreta y medible.

La lección 4 usa este criterio afinado para resolver el siguiente problema: dado un puñado de tests candidatos para la suposición riesgosa de recommendations, ¿cuál elegir? Ahí vas a construir pickTest(), la herramienta que filtra los candidatos que de verdad pueden refutar la hipótesis, y entre esos, elige el más barato.

Recursos

  • Karl Popper, entrada "Karl Popper" en la Stanford Encyclopedia of Philosophy — plato.stanford.edu/entries/popper. La fuente completa de la asimetría entre confirmar y refutar, y del criterio de falsabilidad como frontera entre lo científico y lo que no lo es. En inglés.
  • David J. Bland y Alexander Osterwalder, resumen de Testing Business Ideasstrategyzer.com/library/testing-business-ideas-book-summary. Sobre cómo diseñar experimentos de negocio con condiciones de éxito y fracaso definidas por adelantado, la misma disciplina que exige el criterio de esta lección. En inglés.
  • Teresa Torres, "Assumption Testing: Everything You Need to Know to Get Started" — producttalk.org/assumption-testing. Vuelve a esta fuente con una lectura distinta: presta atención a cómo describe la diferencia entre una suposición vaga y una que de verdad se puede poner a prueba. En inglés.