Crie uma calculadora de pista inicial em JavaScript (e teste seu plano)


A maioria dos fundadores em estágio inicial mantém sua apresentação em uma planilha em que ninguém confia. Uma célula está codificada, outra fórmula foi arrastada uma linha longe demais e a mensagem “e se a rotatividade dobrar?” pergunta significa copiar a planilha inteira. Um módulo JavaScript pequeno e puro é mais fácil de testar, mais fácil de versionar e fácil de executar novamente com diferentes suposições.

Neste tutorial construiremos uma calculadora de pista em módulos ES simples, sem dependências. Ele roda no Node 18+ ou em um navegador. Modelaremos o caixa mensal, executaremos cenários de crescimento lento, básico e rápido, testaremos a sensibilidade do plano à rotatividade e adicionaremos a verificação da economia unitária (LTV, retorno) que informa se vale a pena comprar o crescimento. Cada número neste artigo vem da execução do código abaixo, para que você possa reproduzi-lo.

Uma observação sobre o escopo: este é um modelo de planejamento, não uma previsão. É tão bom quanto as suposições que você alimenta. Onde eu fizer uma simplificação, direi.

O que estamos modelando

Uma startup de assinatura com um preço único. Cada mês:

  1. Alguns clientes existentes mudam.
  2. Chegam novos clientes.
  3. A receita é clientes x preço.
  4. Os custos são um valor fixo mais um custo variável que aumenta com a receita (taxas de pagamento, hospedagem, suporte).
  5. O fluxo de caixa líquido é a receita menos os custos e é adicionado ao saldo de caixa.

A pista é simplesmente quanto tempo o dinheiro permanece acima de zero. A parte interessante não é a aritmética; é ver como a resposta se move quando suas suposições estão erradas.

A função de simulação

Criar runway.mjs:

export function simulate({
  startingCash,
  fixedCosts,
  variableCostRate = 0,
  price,
  monthlyChurn,
  newCustomers, 
}) {
  let cash = startingCash;
  let customers = 0;
  const months = ();

  newCustomers.forEach((added, i) => {
    customers = Math.round(customers * (1 - monthlyChurn)) + added;
    const revenue = customers * price;
    const costs = fixedCosts + revenue * variableCostRate;
    const net = revenue - costs;
    cash += net;
    months.push({ month: i + 1, added, customers, revenue, costs, net, cash });
  });

  return months;
}

Duas opções de design dignas de nota. Primeiro, a função é pura: mesmas entradas, mesma saída, sem estado oculto, o que torna trivial o teste unitário. Em segundo lugar, os novos clientes são um conjunto e não uma taxa de crescimento única. A aquisição real é irregular (um mês de lançamento, um verão lento), e um array permite codificar isso. Se você preferir uma taxa de crescimento, gere o array primeiro e passe-o.

O Math.round em clientes retidos nos mantém como pessoas inteiras. Introduz pequenos efeitos de arredondamento; para fins de planejamento, tudo bem.

Resumindo o resultado

Uma tabela de doze linhas é difícil de comparar entre cenários. Queremos alguns números:

export function summarize(months) {
  const lowestCash = Math.min(...months.map(m => m.cash));
  const firstPositive = months.find(m => m.net > 0)?.month ?? null;
  const cashOut = months.find(m => m.cash < 0)?.month ?? null;
  return { lowestCash, firstPositive, cashOut, endingCash: months.at(-1).cash };
}

export function scaleGrowth(newCustomers, factor) {
  return newCustomers.map(n => Math.round(n * factor));
}

lowestCash é o número que mais me interessa. Os fundadores tendem a perguntar “quando é que vou acabar?”, mas a melhor pergunta é “quão perto chego?” Um plano com valor mínimo de US$ 1.785 e outro com valor mínimo de US$ 14.327 sobrevivem, mas apenas um deles sobrevive a um mês ruim.

scaleGrowth vamos perguntar o que acontece se a aquisição chegar à metade, ou 130%, do plano sem reescrever o plano.

Executando o caso base

Nossas premissas: US$ 40.000 no banco, US$ 4.500 por mês em custos fixos, 15% de custos variáveis, um preço mensal de US$ 49, rotatividade mensal de 4% e um aumento de 0 clientes no primeiro mês até 26 novos clientes por mês após o décimo segundo mês.

import { simulate, summarize, scaleGrowth } from './runway.mjs';

const plan = {
  startingCash: 40000,
  fixedCosts: 4500,
  variableCostRate: 0.15,
  price: 49,
  monthlyChurn: 0.04,
  newCustomers: (0, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26),
};

console.table(simulate(plan));

Aqui está o resultado, reduzido às colunas que importam:

Mês Clientes Receita Líquido Dinheiro
1 0 US$ 0 -US$ 4.500 US$ 35.500
2 6 US$ 294 -US$ 4.250 US$ 31.250
3 14 US$ 686 -$ 3.917 US$ 27.333
4 23 US$ 1.127 -$ 3.542 US$ 23.791
5 34 US$ 1.666 -$ 3.084 US$ 20.707
6 47 US$ 2.303 -$ 2.542 US$ 18.165
7 61 US$ 2.989 -$ 1.959 US$ 16.205
8 77 US$ 3.773 -$ 1.293 US$ 14.912
9 94 US$ 4.606 -$585 US$ 14.327
10 112 US$ 5.488 US$ 165 US$ 14.492
11 132 US$ 6.468 US$ 998 US$ 15.490
12 153 US$ 7.497 US$ 1.872 US$ 17.362

O formato é o que você espera: o caixa cai, atinge o mínimo no nono mês, em US$ 14.327, e depois sobe quando o valor líquido mensal se torna positivo no décimo mês. Observe que o “mês de equilíbrio” e o “caixa mais baixo” são eventos diferentes. O dinheiro continua diminuindo até o mês antes o líquido fica positivo, porque o líquido ainda é negativo no nono mês.

Teste de resistência 1: cenários de crescimento

Os planos raramente sobrevivem ao contato com os clientes. Vamos rodar três versões do mesmo plano, com aquisição em 50%, 100% e 130% do plano:

const scenarios = { slow: 0.5, base: 1, fast: 1.3 };

for (const (name, factor) of Object.entries(scenarios)) {
  const months = simulate({ ...plan, newCustomers: scaleGrowth(plan.newCustomers, factor) });
  console.log(name, summarize(months));
}

Resultados:

Cenário Dinheiro mais baixo Primeiro mês lucrativo Acabando com o dinheiro
Lento (×0,5) US$ 1.785 nenhum em 12 meses US$ 1.785
Base US$ 14.327 10 US$ 17.362
Rápido (×1,3) US$ 18.244 9 US$ 26.775

Esta é a mesa que muda as decisões. No caso lento, o negócio não fica sem dinheiro no primeiro ano, mas termina em US$ 1.785 e ainda não é lucrativo. O décimo terceiro mês é um problema. Esse é o ponto em que você deseja que um gatilho seja anotado com antecedência, algo como “se os clientes do sexto mês estiverem abaixo de 25 anos, cortamos custos fixos ou iniciamos conversas para arrecadação de fundos”. Decidir isso em um dia calmo é melhor do que decidir com oito semanas de dinheiro sobrando.

Teste de estresse 2: sensibilidade à rotatividade

O crescimento chama a atenção, mas a rotatividade estabelece silenciosamente o teto. Podemos varrer:

for (const churn of (0.02, 0.04, 0.06, 0.08, 0.10)) {
  const s = summarize(simulate({ ...plan, monthlyChurn: churn }));
  console.log(`churn ${churn * 100}%`, s.lowestCash, s.firstPositive, s.endingCash);
}
Rotatividade mensal Dinheiro mais baixo Lucrativo a partir do mês Acabando com o dinheiro
2% US$ 15.035 10 US$ 19.195
4% US$ 14.327 10 US$ 17.362
6% US$ 13.743 11 US$ 15.863
8% US$ 12.868 11 US$ 14.155
10% US$ 12.285 11 US$ 13.073

Observe atentamente e aparece um resultado útil e um pouco contra-intuitivo: em um doze meses janela, a rotatividade é um efeito moderado. Passar de 2% para 10% custa cerca de US$ 2.750 de margem de caixa mais baixa e atrasa a lucratividade em um mês. O crescimento alterou o resultado muito mais do que a rotatividade.

Isso não é motivo para ignorar a rotatividade. É um motivo para ter cuidado com o horizonte temporal. Compostos de rotatividade: a base de clientes que você está construindo no décimo segundo mês é a base que você pagará para substituir no segundo ano. Estenda o newCustomers matriz para 36 meses e execute novamente a varredura. A lacuna aumenta consideravelmente. Um horizonte curto favorece a má retenção.

O crescimento realmente se paga? Economia unitária

Um modelo de pista diz se você sobreviveu. Não diz se você deve estar comprando crescimento. Para isso, adicione um pequeno auxiliar de economia unitária:

export function unitEconomics({ price, grossMargin, monthlyChurn, cac }) {
  const monthlyProfit = price * grossMargin;
  const ltv = monthlyProfit / monthlyChurn;
  return { ltv, ratio: ltv / cac, paybackMonths: cac / monthlyProfit };
}

console.log(unitEconomics({ price: 49, grossMargin: 0.85, monthlyChurn: 0.04, cac: 150 }));

Com um custo de aquisição de $ 150, isso dá:

  • VLT: cerca de US$ 1.041
  • Relação LTV:CAC: 6,9
  • Retorno do CAC: cerca de 3,6 meses

Duas advertências. A fórmula LTV (lucro bruto mensal ÷ rotatividade) pressupõe rotatividade constante, o que exagera o valor da vida se a rotatividade no início da vida for maior do que a rotatividade no final da vida, como geralmente é. E um CAC de US$ 150 é uma suposição; uma proporção de 6,9 ​​seria excepcionalmente saudável, o que deve fazer você suspeitar da contribuição antes de comemorar. Uma regra prática comum é que uma proporção em torno de 3 é sólida, mas trate qualquer referência como um ponto de partida para uma conversa, não um veredicto.

Barato para calcular não significa barato para medir. O CAC real inclui seu tempo, ferramentas e quaisquer custos de conteúdo, e a maioria dos fundadores os subestima.

Testando o módulo

Porque simulate é puro, um teste consiste em algumas linhas usando o executor integrado do Node:

import test from 'node:test';
import assert from 'node:assert/strict';
import { simulate } from './runway.mjs';

test('cash falls by fixed costs when there are no customers', () => {
  const (m1) = simulate({
    startingCash: 10000, fixedCosts: 1000, price: 50,
    monthlyChurn: 0.05, newCustomers: (0),
  });
  assert.equal(m1.cash, 9000);
});

test('zero churn keeps every customer', () => {
  const months = simulate({
    startingCash: 0, fixedCosts: 0, price: 10,
    monthlyChurn: 0, newCustomers: (5, 5),
  });
  assert.equal(months(1).customers, 10);
});

Corra com node --test. Testes como esses detectam o clássico fracasso da planilha: uma fórmula que silenciosamente para de corresponder ao que você quis dizer.

Limitações que vale a pena conhecer

  • Um preço, um plano. Os produtos reais têm níveis, planos anuais e descontos. Estender price a uma receita média combinada por usuário ou modelar segmentos separadamente.
  • Sem atraso no pagamento. Os pré-pagamentos anuais e os prazos da fatura alteram significativamente o prazo de pagamento.
  • A rotatividade é uma taxa fixa. Novos clientes geralmente mudam mais rápido do que os já estabelecidos.
  • Sem contratação. Os custos fixos são constantes. Se o seu plano incluir uma primeira contratação, modele-o como uma mudança gradual no fixedCosts (uma pequena mudança para aceitar um array).
  • Não pode validar suas entradas. O modelo aceitará alegremente uma curva de crescimento fantasiosa. A disciplina está em escolher suposições honestas.

Para onde ir a partir daqui

Você pode embrulhar simulate em um formulário com alguns controles deslizantes e uma biblioteca de gráficos, ou execute-o em um script noturno que publica seu número mais baixo em dinheiro no Slack. Para o lado financeiro da mesma questão, como como a pista se conecta ao momento da arrecadação de fundos e o que colocar em um modelo financeiro inicializado, escrevi um passo a passo mais longo em um guia para modelagem financeira de startups em Saasnmum site que publico.

A conclusão é mais o hábito do que o código: anote suas suposições como entradas, analise os casos ruins antes que a realidade o faça e decida seus gatilhos de resposta enquanto o dinheiro ainda é confortável.



Source link