C1

Seis semanas num app de assinatura rejeitado duas vezes pela Apple

Um aplicativo com base de usuários ativa e a próxima versão travada na revisão da Apple. Em seis semanas, cinco frentes em paralelo: o binário ficou 88% mais leve, a cobertura de testes saiu de 35% para 81%, e a leitura da fatura de nuvem encontrou capacidade ociosa faturada por 27 dias que ninguém tinha visto.

  • Aplicativo de tarô e astrologia por assinatura, em produção nas duas lojas
  • 6 semanas · jul–ago de 2026
LojaPeso do appSegurançaCusto de nuvemQualidade de engenharia

O cliente não é nomeado. Citar cliente exige autorização escrita — sem ela, a narrativa é anonimizada, o que preserva o valor demonstrativo do método. Todos os números abaixo são medidos, e o que é estimativa está dito com essa palavra.

Contexto

O produto funcionava; o que não funcionava era conseguir publicar. O trabalho não começou por um roadmap, e sim por uma rejeição da Apple já em curso — o primeiro commit substantivo do período foi a remediação dela. Mobile e backend viviam num repositório único, dividindo histórico, integração contínua e fila de revisão — com ciclos de publicação que não combinam, já que o aplicativo depende da revisão da loja e o servidor sobe quando quiser. Separar em dois repositórios foi o primeiro passo estrutural, feito antes de qualquer otimização, e só a partir daí cinco frentes correram em paralelo.

Desafio

A pergunta que chegou foi “por que a Apple rejeitou de novo?”, e ela parecia pedir uma correção pontual. A primeira rejeição era mesmo pontual: compra dentro do app, URL de produção e privacidade. A segunda veio pela Guideline 4.3(b) — o app foi percebido como duplicata de uma categoria saturada. Não existe correção de código para isso: a ficha inteira liderava pelo vocabulário que define a categoria. Enquanto a rejeição fosse lida como bug, cada resubmissão seria uma tentativa de adivinhar qual palavra desagradou.

Jornada

  1. Separação do monorepo

    Os dois lados do produto compartilhavam um repositório só. Na prática, um PR de servidor disputava a mesma fila de revisão que um de aplicativo, o histórico não distinguia mais o que era de cada lado, e revisar exigia separar duas bases a cada leitura. A divisão em dois repositórios foi feita nos primeiros dias, e é o que torna possível tudo o que vem depois: cada lado passou a ter seu próprio portão de qualidade, porque um pipeline único não consegue reprovar uma ponta sem travar a outra

  2. Loja

    A ficha foi reescrita liderando pelos diferenciais que a categoria saturada não tem — meditação, respiração, jornadas guiadas, especialistas ao vivo — com os 18 campos das duas lojas consolidados em três idiomas e contagem de caracteres conferida campo a campo. A lista de vocabulário proibido virou lint: a montagem dos screenshots aborta se um termo aparecer. Uma decisão de posicionamento que passou a ser impossível de violar por distração

  3. Peso

    Os assets embarcados saíram de cerca de 250 MB para 30,5 MB, em quatro degraus — reencode das narrações, áudio para CDN, imagens de conteúdo para CDN. A cada degrau, o orçamento no CI baixou junto. É a diferença entre limpar e impedir a regressão: hoje um PR que engorde o binário além do teto quebra o build

  4. Segurança

    O token de sessão estava em armazenamento local em texto puro. Passou para o enclave seguro do sistema, com migração que apaga o valor antigo. Depois veio o épico de sessão renovável em quatro entregas encadeadas, e no backend um refresh opaco com rotação a cada uso e detecção de reuso que derruba a família de tokens do aparelho

  5. Custo de nuvem

    A leitura da fatura encontrou o serviço de API preso em quatro instâncias rodando o tempo todo por 27 dias, com o app praticamente ocioso: CPU a 0,05%, pico de 0,06 requisição por segundo. A causa não era carga — o alarme de redução de escala ficava sem dados nas horas sem tráfego, e a escala nunca descia

  6. Engenharia

    A cobertura subiu de 35% para 81% no mobile e de um piso de 3% para 61% no backend, com o piso funcionando como catraca. Cada PR passou a exigir 80% de cobertura nas linhas novas. E as decisões arquiteturais deixaram de viver só em documento: existem testes que quebram o build se alguém mudar o número escrito na decisão

Ganhos

Medidos — cada um obtido rodando algo ou lendo um valor real, com a fonte ao lado.

−88%
Peso dos assets no binário

de ~250 MB para 30,5 MB, com o teto travado no CI

81%
Cobertura de testes, mobile

era 35% no início do período

61%
Piso de cobertura, backend

era 3%, e sobe a cada entrega

27 dias
Capacidade ociosa faturada

quatro instâncias o tempo todo, com CPU a 0,05%

Ressalvas

  • A economia de nuvem projetada — cerca de metade da conta — é cálculo sobre preço de lista, não fatura realizada. Enquanto o mês seguinte não for reaberto para conferência, a frase honesta é “estimamos economizar”, nunca “economizamos”
  • A redução de 88% é do binário, que é o que o usuário baixa e o que ocupa o aparelho dele. O histórico do projeto continua carregando os arquivos originais, então o repositório não encolheu

O que este case não prova

Um case que só lista vitórias obriga o leitor a descobrir os limites sozinho, e normalmente ele descobre na hora errada. Estes são os limites:

  • A resubmissão na loja foi aprovada — não há comprovação, então não se afirma
  • A sessão renovável do aplicativo está integrada, mas ainda não chegou a produção
  • A janela de exposição de um token vazado segue em 24 horas: a peça final foi revertida
  • A limitação de tentativas em login e cadastro está declarada, mas não dispara hoje
  • Os três itens de maior impacto em performance de tela foram diagnosticados e não executados

Próximos passos

  • Fechar a janela do token em produção — toda a infraestrutura de renovação já existe dos dois lados
  • Descrever a infraestrutura base em código, porque hoje a economia não tem trava e uma edição manual a reverte sem deixar rastro
  • Ligar alarme de orçamento e detecção de anomalia de custo, que é o que evita a reincidência do incidente de 27 dias
  • Reabrir a conta do mês seguinte e trocar a projeção pelo número cobrado

Um trabalho como este

Uma conversa de 30 minutos para entender o seu contexto. Ao fim dela dá para dizer qual oferta se aplica — e se nenhuma se aplicar, isso também é resultado.

C1 · 6 semanas · jul–ago de 2026Agendar conversa