O Veredito em Uma Frase: Uma planilha glorificada para rastrear gastos recorrentes que redescobre a aritmética de calendário e a representação de ponto fixo da década de 1970, enquanto trava uma guerra inglória contra o gerenciamento de energia draconiano da Apple.
A Dor (Real ou Psicossomática?):
Você construiu um aplicativo de assinaturas para pessoas que têm tantas assinaturas que precisam de um aplicativo (provavelmente pago ou freemium) para gerenciar suas assinaturas. A ironia metalinguística é admirável. Para um ser humano comum com menos de 30 serviços de streaming ativos simultaneamente, o aplicativo nativo "Lembretes" do iOS ou o "Calendário" com eventos recorrentes (RRULE do padrão iCalendar, RFC 5545, criado em 1998) resolvem exatamente o mesmo problema, sincronizam via iCloud sem código extra e — pasme — não sofrem do limite de 64 notificações locais de terceiros porque são da própria Apple. Mas admito: o Calendário não desenha um gráfico bonito mostrando o tamanho do rombo financeiro anual.
O Que Surpreendentemente Funciona:
Para um Product Manager fazendo pair programming com o Claude Code, você evitou armadilhas onde muito desenvolvedor sênior cai de cabeça:
- Zero Backend e Zero Telemetria: Não exigir login, não subir um cluster na AWS para guardar uma lista de 15 linhas e não entupir o binário com SDKs de rastreamento do Facebook/Firebase para dados financeiros pessoais é uma postura ética e arquiteturalmente impecável.
- Desconfiança do ORM (SwiftData): Testar a persistência, perceber que o SwiftData estava silenciosamente convertendo
Decimalpara ponto flutuante binário (Double) por baixo dos panos no SQLite e estragando a precisão na volta é o tipo de atenção aos detalhes que separa quem testa de quem só aceita o código gerado pela IA. - Lógica de Data Ancorada: Perceber o drift de datas (31 de janeiro + 1 mês = 28 de fevereiro; 28 de fevereiro + 1 mês = 28 de março) antes que os usuários reclamassem no suporte mostra que você realmente pensou nos casos de borda do calendário gregoriano.
Onde o Castelo de Cartas Desmorona (O Que Não Funciona):
- A Ilusão do
BackgroundTasksno iOS: Você confia noBGAppRefreshTaskpara rodar em segundo plano e renovar a fila de 60 notificações sem o usuário abrir o app. Tenho péssimas notícias: o agendador do iOS (Duet/DAS) é um ditador implacável. Se o usuário só interagir com o seu app lendo os banners de notificação na tela de bloqueio sem nunca tocar neles para abrir o app de fato, a heurística do iOS vai classificar o Assinei como "app abandonado" e simplesmente parar de conceder tempo de execução em background. Sua última notificação ("Abra o Assinei para continuar recebendo alertas...") não é um simples fallback de segurança; para um app utilitário de consulta rara, ela será o comportamento padrão. E quando o usuário ignorar essa 60ª notificação porque estava ocupado, o app entra em coma silencioso e a Netflix renova no cartão sem aviso. - Dinheiro como String (
amountText): Guardar dinheiro comoStringno banco para fugir do bug doDoubleno SwiftData funciona para um app com 50 registros locais, mas faz qualquer DBA chorar sangue. Você perde a capacidade de fazer agregações nativas via SQL (SUM,AVG,ORDER BYnumérico direto no índice do SQLite), forçando o app a carregar objetos na memória e fazer parsing de string paraDecimala cada cálculo. - Ponto Único de Perda de Dados: "100% local, sem conta, sem backend". Se você não implementou sincronização automática via
CloudKit(que continua sendo "sem backend próprio" e gratuito dentro da conta iCloud do usuário), o dia em que o usuário perder o iPhone na praia ou trocar de aparelho sem um backup completo criptografado no Mac, toda a curadoria das 30 assinaturas dele vai para o limbo digital.
Respostas às Perguntas do Autor (Se houver):
- Como contornar o limite de 64 notificações locais do
UNUserNotificationCenter?
A sua abordagem de janela deslizante (rolling window das próximas 60) com o canário de segurança na posição 60 é, de fato, o padrão de mercado para apps offline-first no iOS. Porém, existe uma alternativa menos usada: se a cobrança é estritamente mensal no mesmo dia (ex: todo dia 15) e não exige mudança dinâmica de texto a cada mês, um únicoUNCalendarNotificationTriggercomrepeats: trueeDateComponents(day: 15, hour: 9)ocupa apenas 1 slot dos 64 para sempre, sem precisar reagendar (embora falhe na sua regra do dia 31 sem lógica extra). Empurrar para um servidor via APNs (Push Notifications) resolveria o limite, mas destruiria o seu maior argumento de vendas (privacidade total e custo zero de infraestrutura) e exigiria manter tokens APNs vivos. Fique no local, mas deixe claro na UI quando o plano de notificações está perto de expirar. - Guardar dinheiro como texto, inteiro em centavos ou Decimal?
Inteiro na menor unidade da moeda (Int64representando centavos / minor units), acompanhado do código ISO 4217 da moeda (BRL,USD,JPY). É assim que o Stripe, a Adyen e qualquer sistema de pagamento sério operam. UmInt64é nativo no SQLite, ocupa 8 bytes fixos, permiteSUM()instantâneo via banco sem perda de precisão de IEEE 754, não sofre com problemas de Locale (vírgula vs. ponto na serialização de String) e o SwiftData não consegue estragar um número inteiro. Se precisar suportar criptomoedas ou frações de centavo (ex: tarifas de nuvem), você guarda em micros ($1.00 =1_000_000).
Nota na Escala "Reinventando a Roda": 6/10
Existem literalmente centenas de rastreadores de assinatura na App Store (Bobby, Subtrack, etc.), e a própria Apple já tem uma tela nativa em Ajustes -> ID Apple -> Assinaturas para tudo que passa pela App Store. Ainda assim, como banco e cartão de crédito adoram esconder cobranças recorrentes no meio da fatura, um centralizador manual offline sem mensalidade irônica tem seu valor prático. Só troque esse String por Int64 antes que alguém olhe o schema do seu SQLite.