C1

Seis semanas num app rejeitado duas vezes pela Apple, publicado depois de trocar de categoria

Um aplicativo com base de usuários ativa e a próxima versão travada na revisão da Apple, lido pela loja como mais um numa categoria saturada. A recomendação aos sócios foi trocar o que o produto lidera, e entregar de verdade o que a categoria nova cobra. A versão reposicionada foi publicada, o aplicativo saiu em três idiomas, o binário ficou 88% mais leve e a cobertura de testes subiu de 35% para 81%.

  • Aplicativo de autoconhecimento e bem-estar, em produção nas duas lojas
  • 6 semanas · jul–ago de 2026
Loja e posicionamentoTrês idiomasPeso 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í as 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), com o aplicativo percebido como duplicata de uma categoria saturada de astrologia e tarô. Para isso não existe correção de código. A orientação aos sócios foi a que ninguém quer ouvir com a fila de revisão andando: parar de liderar pela leitura de signo e de carta, e liderar por prática de bem-estar. 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. Posicionamento

    Mudar a ficha sem mudar o produto seria repetir o erro anterior, quando a resposta à loja prometeu meditação guiada que não existia. A categoria nova cobra funcionalidade, então ela passou a existir dentro das seis semanas: sessões guiadas com passos cronometrados, exercícios de respiração, temporizador silencioso, trilhas de áudio por humor, sons para dormir com acervo de produção própria, catálogo de posturas com desafio de trinta dias, e a gravação dos minutos praticados no aplicativo de saúde do sistema. A gravação foi ligada num ponto único, por onde as quatro formas de prática já passavam, com opção explícita do usuário e falha silenciosa, porque um problema na integração não pode derrubar o registro local nem a tela

  3. Loja

    A ficha foi reescrita liderando pelos diferenciais que a categoria saturada não tem: meditação, respiração, jornadas guiadas e 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

  4. Três idiomas

    O aplicativo saiu de um idioma para três, português do Brasil, espanhol e inglês, e o que sustenta isso não é um arquivo de tradução. São 3.081 chaves com paridade exata nos três pacotes, o idioma dentro da chave de todo cache local e dentro da chave primária dos caches do servidor, e a mesma regra de normalização de código de idioma escrita duas vezes, uma de cada lado, porque interface e conteúdo servido não podem falar línguas diferentes. Quando falta um pedaço de uma leitura, a leitura inteira cai para o idioma de origem e a resposta diz qual idioma serviu, já que tela com dois idiomas misturados é pior que tela traduzida pela metade. A detecção do idioma do aparelho foi feita sem dependência nativa nova, decisão tomada depois de um módulo ausente do pacote derrubar o aplicativo no lançamento. E dois portões de integração contínua seguram o resto: chave faltando reprova, e a régua de texto fixo na tela só pode descer

  5. 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

  6. 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, hoje publicado: o aplicativo renova no 401 e repete a chamada uma vez só, mesmo com várias telas pedindo ao mesmo tempo, e o servidor guarda um refresh opaco com rotação a cada uso e detecção de reuso que derruba a família de tokens do aparelho. Os tetos de tentativa em login e cadastro, que estavam declarados e não disparavam, passaram a disparar

  7. 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

  8. 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.

publicada
Versão reposicionada na loja

aprovada em 2026, depois de duas rejeições

3
Idiomas do aplicativo

português do Brasil, espanhol e inglês, com paridade de chaves conferida a cada PR

−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 aprovação veio depois de tudo junto: ficha reescrita, funcionalidades novas e as correções pontuais da primeira rejeição. O case não isola causa, e não afirma que a publicação se deve a uma frente sozinha
  • 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 categoria antiga não saiu do produto: leitura de signo e de carta continuam lá e deixaram de ser a vitrine. O que mudou é o que o produto lidera, não o que ele tem
  • Os três idiomas cobrem a interface do aplicativo, com paridade conferida a cada PR. O acervo editorial é traduzido por um processo retomável, e não se afirma aqui que ele esteja completo nos três
  • A janela de exposição de um token vazado segue em 24 horas: a peça final foi revertida
  • Os três itens de maior impacto em performance de tela foram diagnosticados e não executados
  • Nada aqui mede efeito comercial. Não há número de instalação, receita ou retenção depois da publicação

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