Módulo 4: North Star And Guardrails

Eligiendo la North Star con un criterio, no a ojo

Descripción

La lección 2 te dejó tres candidatas de Mercado miradas "a ojo" con la definición de North Star: weeklyGMVPerActiveBuyer se veía fuerte, totalRevenue y totalSignups se veían débiles. "Se veían" es la palabra floja de esa frase — un argumento en prosa, por más razonable que suene, no escala bien cuando tienes ocho o diez candidatas reales sobre la mesa, propuestas por gente distinta, cada una defendida con su propio argumento intuitivo. Hace falta un criterio explícito, con las mismas cuatro preguntas aplicadas a cada candidata por igual, para que la elección sea defendible y no dependa de quién argumentó con más convicción en la reunión.

Esta lección formaliza exactamente eso: cuatro criterios booleanos —refleja valor (reflectsValue), es accionable (actionable), no es de vanidad (notVanity), no es ingreso directo (notDirectRevenue)— y una función, evaluateNorthStar(), que los aplica a una lista de candidatas y elige la que pasa más pruebas.

Conexión con el módulo. Esta lección toma la definición cualitativa de la lección 2 y la convierte en la primera pieza ejecutable del módulo. El resultado de hoy —weeklyGMVPerActiveBuyer como North Star elegida, no solo intuida— es el insumo directo de la lección 4 (sus input metrics) y de las lecciones 5 y 6 (qué la protege). El proyecto de la lección 8 va a reutilizar esta misma función, sin cambios, sobre una lista más larga de candidatas.

Una analogía: el jurado con una rúbrica, no con el aplauso más fuerte

Imagina un concurso donde varios jueces evalúan candidatos, pero sin ninguna rúbrica: cada juez vota según su impresión general, y gana quien "convenció más" en el momento. Ese formato favorece al candidato más carismático, no necesariamente al mejor preparado —y dos jurados distintos, el mismo día, podrían llegar a resultados opuestos—.

Ahora imagina el mismo concurso, pero con una rúbrica de cuatro criterios específicos, cada uno evaluado por separado, con un puntaje que se suma al final. Un candidato ya no gana por "sonar mejor" — gana porque, criterio por criterio, cumple más de lo que la rúbrica exige. La rúbrica no elimina el juicio humano —alguien sigue decidiendo si un candidato cumple o no cada criterio—, pero sí elimina la arbitrariedad de "el aplauso más fuerte gana". evaluateNorthStar() es esa rúbrica aplicada a métricas: cuatro preguntas fijas, la misma vara para cada candidata, un puntaje que cualquiera puede verificar.

Ejemplo trabajado: evaluateNorthStar() sobre cuatro candidatas de Mercado

Los cuatro criterios, con la pregunta exacta que cada uno contesta:

  1. reflectsValue — ¿la métrica refleja el valor real que el usuario recibe (no solo alcance, no solo cantidad de gente)?
  2. actionable — ¿el equipo puede moverla con trabajo directo (mejor checkout, mejor descubrimiento, mejores recomendaciones)?
  3. notVanity — ¿se recalcula por periodo (semana), en vez de ser un acumulado histórico que solo sube?
  4. notDirectRevenue — ¿es distinta del dinero que la empresa se queda (su comisión, su ingreso neto)?
// evaluateNorthStar: puntua candidatas a North Star por 4 criterios booleanos y elige
// la de mayor puntaje. Es un modelo pedagogico -- una heuristica de bolsillo, no un
// algoritmo estadistico -- para estructurar la discusion, no para reemplazarla.
function evaluateNorthStar(candidates) {
  const criteria = ['reflectsValue', 'actionable', 'notVanity', 'notDirectRevenue'];
  const scored = candidates.map((c) => {
    const passed = criteria.filter((k) => c[k]);
    const failed = criteria.filter((k) => !c[k]);
    return { name: c.name, score: passed.length, passed, failed };
  });
  const ranked = [...scored].sort((a, b) => b.score - a.score);
  return { ranked, winner: ranked[0].name };
}

const candidates = [
  {
    name: 'weeklyGMVPerActiveBuyer',
    reflectsValue: true,    // combina cuanta gente compra Y cuanto valor genera cada una
    actionable: true,       // el equipo la mueve con checkout, descubrimiento, recomendaciones
    notVanity: true,        // se recalcula cada semana, no es un acumulado historico
    notDirectRevenue: true, // GMV no es lo que Mercado factura (eso es su comision), es el valor transaccionado
  },
  {
    name: 'weeklyPurchasingBuyers',
    reflectsValue: false,   // cuenta compradores, pero no cuanto valor aporta cada uno
    actionable: true,
    notVanity: true,
    notDirectRevenue: true,
  },
  {
    name: 'totalRevenue',
    reflectsValue: false,    // el ingreso es el RESULTADO de generar valor, no el valor en si
    actionable: true,
    notVanity: true,
    notDirectRevenue: false, // es, literalmente, ingreso directo de la plataforma
  },
  {
    name: 'totalSignups',
    reflectsValue: false,   // registrarse no es recibir valor, es solo el primer paso
    actionable: false,      // acumulado historico: nadie decide nada distinto si sube
    notVanity: false,       // el ejemplo de libro de metrica de vanidad
    notDirectRevenue: true,
  },
];

console.log('=== evaluateNorthStar sobre 4 candidatas de Mercado ===\n');
const result = evaluateNorthStar(candidates);
result.ranked.forEach((c) => {
  console.log('  ' + c.name.padEnd(24) + 'score: ' + c.score + '/4' +
    '  falla en: ' + (c.failed.length ? c.failed.join(', ') : '(ninguno)'));
});
console.log('\nGanadora: ' + result.winner);

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

=== evaluateNorthStar sobre 4 candidatas de Mercado ===

  weeklyGMVPerActiveBuyer score: 4/4  falla en: (ninguno)
  weeklyPurchasingBuyers  score: 3/4  falla en: reflectsValue
  totalRevenue            score: 2/4  falla en: reflectsValue, notDirectRevenue
  totalSignups            score: 1/4  falla en: reflectsValue, actionable, notVanity

Ganadora: weeklyGMVPerActiveBuyer

Fíjate en weeklyPurchasingBuyers: no falla por ser de vanidad (se recalcula cada semana) ni por ser ingreso (no es dinero) — falla específicamente en reflectsValue, y vale la pena entender por qué. weeklyPurchasingBuyers cuenta cuántos compradores distintos compraron esta semana, pero trata igual a un comprador que gastó $5 y a uno que gastó $5,000 — ambos cuentan como "1". Es una métrica de alcance (cuánta gente participa), no de valor (cuánto vale lo que hace cada uno). weeklyGMVPerActiveBuyer, en cambio, captura las dos cosas a la vez: si más gente compra, sube; si cada comprador gasta más, también sube. Esa combinación de alcance y profundidad es, precisamente, lo que hace que la primera reciba reflectsValue: true y la segunda no.

El puntaje no reemplaza el juicio — lo estructura

evaluateNorthStar() te da un número, pero ese número depende por completo de cómo asignaste los cuatro booleanos de entrada — y ahí es donde sigue haciendo falta criterio humano, no solo ejecutar la función. Considera esto: ¿por qué weeklyGMVPerActiveBuyer recibió reflectsValue: true sin discusión? Porque alguien, antes de correr el código, argumentó por qué combina alcance y profundidad — el mismo tipo de argumento en prosa de la lección 2, solo que ahora convertido en un input verificable, no una opinión suelta.

Esto importa porque la herramienta puede empatar, o casi empatar, entre candidatas razonables — y cuando eso pase (lo vas a ver de frente en el proyecto de la lección 8, con una candidata que empata con weeklyGMVPerActiveBuyer en el puntaje máximo), el desempate no lo hace el código: lo hace el mismo tipo de argumento que construiste en la lección 2. evaluateNorthStar() no es un oráculo — es una rúbrica que obliga a que la discusión sea explícita, comparable, y defendible, no una forma de evitar la discusión por completo.

Errores comunes

Asignar los booleanos de entrada sin argumentar por qué. Qué pasa: alguien arma la lista de candidatas y le pone true o false a cada criterio "a ojo", sin escribir en ningún lado el razonamiento detrás de cada valor —igual que la trampa que esta lección busca evitar, solo que ahora escondida un paso antes, en los datos de entrada en vez de en la conclusión final—. Por qué pasa: una vez que tienes una función que calcula el puntaje, se siente como si el trabajo difícil ya estuviera resuelto, y llenar los cuatro campos booleanos parece trivial. Cómo detectarlo: si le pides a alguien que explique por qué weeklyPurchasingBuyers tiene reflectsValue: false y no puede dar el argumento de "alcance sin profundidad" de esta lección, el input se llenó sin criterio real. Cómo corregirlo: cada valor booleano necesita, al lado, una línea de comentario con el argumento —exactamente como en el código de hoy—, no solo el valor suelto.

Detenerse en la primera candidata con puntaje alto, sin comparar contra alternativas. Qué pasa: el equipo evalúa una sola candidata, ve que saca 4/4, y la declara North Star sin haber corrido evaluateNorthStar() sobre ninguna otra opción con la que compararla. Por qué pasa: un puntaje perfecto se siente concluyente, y comparar contra más candidatas se siente como trabajo innecesario una vez que "ya ganó una". Cómo detectarlo: si la lista de candidates que se corrió tenía un solo elemento, o solo candidatas obviamente débiles puestas ahí para que la favorita gane fácil, la comparación no fue justa. Cómo corregirlo: siempre incluye, como en el ejemplo de hoy, al menos una candidata de vanidad y una de ingreso directo en la lista — no para que pierdan (aunque van a perder), sino para verificar que la función las distingue correctamente de la ganadora, y que la ganadora de verdad brilla por comparación, no por ausencia de competencia.

Tratar un puntaje de 3/4 como automáticamente descalificado. Qué pasa: alguien descarta weeklyPurchasingBuyers como "mala métrica" sin más, solo porque no sacó el puntaje perfecto, y la borra del análisis por completo. Por qué pasa: un número que no es el máximo se siente como un fracaso binario, cuando en realidad el puntaje es una escala, no un pasa/no-pasa. Cómo detectarlo: la conversación trata a cualquier candidata con menos de 4/4 como "descartada", sin preguntar para qué otro rol del árbol de métricas podría servir. Cómo corregirlo: como vas a ver en la lección 4, una candidata que pierde la carrera por la North Star (como weeklyPurchasingBuyers, que falla solo en reflectsValue) casi siempre sigue siendo una input metric excelente — sigue siendo accionable, no es de vanidad, y no es ingreso. Perder el trono no significa perder el lugar en el árbol.

Ejercicios

Ejercicio 1 — Corre la función a mano sobre una candidata nueva. Sin ejecutar Node todavía, calcula qué puntaje le daría evaluateNorthStar() a esta candidata: { name: 'weeklyRecommendationImpressions', reflectsValue: false, actionable: true, notVanity: true, notDirectRevenue: true }. Después, verifica corriendo la función con esta candidata agregada a la lista.

Ver solución

A mano: de los cuatro criterios, tres son true (actionable, notVanity, notDirectRevenue) y uno es false (reflectsValue) — el puntaje es 3/4, y failed sería ['reflectsValue']. Al correr evaluateNorthStar() con esta candidata agregada, el resultado confirma exactamente eso: aparecería empatada con weeklyPurchasingBuyers en el segundo lugar, ambas con 3/4, ambas fallando por la misma razón — weeklyRecommendationImpressions cuenta cuántas veces se mostró el carrusel, sin decir nada sobre si esa exposición se tradujo en valor real (una compra, no solo una vista).

Ejercicio 2 — Diseña una candidata que falle los cuatro criterios. Escribe un objeto de candidata (con los cuatro campos booleanos) que evaluateNorthStar() puntúe con 0/4. Descríbela en una frase: ¿qué tipo de métrica sería esa en la vida real?

Ver solución

Un ejemplo: { name: 'totalRevenueAllTime', reflectsValue: false, actionable: false, notVanity: false, notDirectRevenue: false } — el ingreso total acumulado desde el lanzamiento, sin acotar a ningún periodo. Falla los cuatro a la vez: no refleja valor entregado (es ingreso), no es accionable de forma directa y comparable (es un acumulado, no hay "esta semana" contra la cual reaccionar), es la definición misma de una métrica de vanidad (solo sube), y es, obviamente, ingreso directo. En la vida real, sería el tipo de número que aparece en una presentación de inversionistas —"hemos generado $50 millones de ingreso acumulado desde el lanzamiento"— que es verdad, y a la vez, inútil como guía de decisiones semanales de producto.

Ejercicio 3 — Argumenta un desempate. Imagina que una quinta candidata, weeklyGMV (el valor total transaccionado, sin normalizar por comprador), también saca 4/4 en evaluateNorthStar(), empatando con weeklyGMVPerActiveBuyer. Con lo que sabes hasta ahora de esta lección y la anterior, ¿qué argumento —que el modelo de cuatro criterios no puede ver— usarías para elegir la versión normalizada por encima de la versión total?

Ver solución

El argumento tiene que ver con qué tan fácil es inflar cada versión sin generar más valor real. weeklyGMV (sin normalizar) puede subir de dos formas muy distintas: porque cada comprador activo compra más (una mejora real de producto), o simplemente porque el equipo de growth trae más compradores nuevos a la plataforma —aunque cada uno, en promedio, compre menos o ni vuelva la semana siguiente—. Las dos formas mueven el mismo número hacia arriba, pero solo una refleja una mejora genuina en el valor entregado por comprador. weeklyGMVPerActiveBuyer, al normalizar por comprador activo, es más resistente a ese tipo de inflación barata: si el equipo solo trae más gente sin mejorar la experiencia de cada una, el numerador (GMV total) sube, pero el denominador (compradores activos) sube casi en la misma proporción, y el cociente se queda prácticamente igual. Este es, en miniatura, el argumento completo de la lección 5 —qué tan fácil es "hackear" una métrica sin mejorar el producto de verdad—, aplicado aquí como criterio de desempate entre dos candidatas que el modelo de cuatro booleanos, por sí solo, no alcanza a distinguir.

Resumen y siguiente paso

En esta lección construiste y ejecutaste evaluateNorthStar(), que puntúa candidatas a North Star por cuatro criterios —reflectsValue, actionable, notVanity, notDirectRevenue— y elige la de mayor puntaje. Sobre cuatro candidatas de Mercado, weeklyGMVPerActiveBuyer ganó con 4/4, weeklyPurchasingBuyers quedó segunda (falla solo en reflejar valor, no solo alcance), y totalRevenue y totalSignups perdieron por ser, respectivamente, ingreso directo y una métrica de vanidad clásica. También viste el límite honesto de la herramienta: el puntaje estructura la comparación, pero no reemplaza el argumento humano detrás de cada valor de entrada, ni resuelve automáticamente un empate entre dos candidatas igual de fuertes.

Antes de avanzar deberías poder: nombrar los cuatro criterios de memoria y qué pregunta contesta cada uno; correr evaluateNorthStar() a mano sobre una candidata nueva; y explicar por qué una candidata con puntaje alto pero no perfecto (como weeklyPurchasingBuyers) no debería descartarse por completo.

Con weeklyGMVPerActiveBuyer elegida como North Star, la lección 4 responde la siguiente pregunta obvia: ¿qué palancas concretas la mueven semana a semana? Ahí es donde candidatas que hoy "perdieron" la carrera por la North Star —como weeklyPurchasingBuyers— encuentran un nuevo rol.

Recursos

  • Amplitude, North Star Hub — amplitude.com/north-star-hub. Incluye ejemplos de cómo distintas empresas evaluaron y compararon candidatas antes de elegir su North Star oficial — el mismo proceso que esta lección formaliza con código. En inglés.
  • Sean Ellis, "Finding the Right North Star Metric" — medium.com/growthhackers/finding-your-north-star-metric-fc1c1f71cbcb. Propone su propio conjunto de criterios para evaluar candidatas —entender el valor del cliente, permitir crecimiento sostenido, evitar incentivos perversos— con fuerte solapamiento con los cuatro de esta lección. En inglés.
  • Brian Balfour (Reforge), "Don't Let Your North Star Metric Deceive You" — reforge.com/blog/north-star-metric-growth. Argumenta, con más matices que esta lección, por qué elegir bien la North Star exige comparar candidatas con criterio explícito y no quedarse con la primera que "suena razonable". En inglés.