fix: ancorar término de parcelas em nextChargeMonth, não no mês atual (closes #114) - #132
fix: ancorar término de parcelas em nextChargeMonth, não no mês atual (closes #114)#132Guiroos wants to merge 4 commits into
Conversation
…closes #114) /parcelas calculava "termina em" com aritmetica manual ancorada no mes corrente. Para parcelamentos cuja 1a parcela cai no mes seguinte (compra apos o fechamento do cartao), isso projetava as parcelas restantes a partir de hoje em vez de a partir de quando elas de fato comecam, adiantando o "termina em" exibido nos cards e nos dois KPIs do topo. Extrai addMonthsToYearMonth para lib/utils/date.ts e ancora o calculo em g.nextChargeMonth (com fallback para o mes atual quando nao ha parcela futura pendente).
Guiroos
left a comment
There was a problem hiding this comment.
Revisão do head af914a7. A âncora em nextChargeMonth está certa, o helper novo é a generalização mínima de nextMonth como a issue pediu, e a decisão de não tocar getInstallmentTimeline / remainingInstallments está bem justificada no corpo. Dois achados verificados, ambos bloqueantes; nenhum nit.
1. Os testes novos passam igual com a correção errada (__tests__/unit/date.test.ts:170-175). Eles exercitam só o helper puro; nenhum teste alcança page.tsx. Revertendo a âncora para currentYM, a suíte segue verde. A issue nomeou o caso exato e disse que extrair a derivação da page era pré-requisito para cobrir — os dois foram substituídos sem declaração.
2. lastEnd ordena rótulos formatados, não meses (app/(app)/parcelas/page.tsx:40). formatMonthShort devolve "set 26" / "jan 27", e .sort() alfabético é month-major — os dois KPIs de topo seguem mostrando o mês errado. Pré-existente, mas dentro do closes #114, que lista lastEnd na seção Onde.
Detalhes e correção concreta em cada comentário inline.
Generated by Claude Code
Endereca dois achados bloqueantes da revisao da PR #132 (issue #114): 1. Os testes anteriores exercitavam so addMonthsToYearMonth (aritmetica pura), nunca a logica de ancora que era o proprio bug -- revertendo page.tsx para ancorar em currentYM a suite continuava verde. Extrai installmentEndYearMonth para lib/utils/date.ts, unica fonte usada por page.tsx, e testa os dois casos que a issue #114 pedia (ancora em nextChargeMonth e fallback nextChargeMonth=null) sob fake timers. 2. lastEnd ordenava os rotulos ja formatados por formatMonthShort ('set 26', 'jan 27'), que e ordem alfabetica (month-major), nao cronologica -- os dois KPIs de topo continuavam errados mesmo apos a correcao do endLabel por card. Passa a ordenar por endYM (YYYY-MM, ano-major) e so formata o maximo no final.
Guiroos
left a comment
There was a problem hiding this comment.
Revisão do head de435b3 (a anterior foi no af914a7). Os dois achados bloqueantes daquela rodada estão resolvidos de verdade: installmentEndYearMonth encapsula a escolha da âncora e é a única fonte usada pela page, com os dois casos que a #114 nomeou (âncora em nextChargeMonth e fallback null) sob fake timers; e o lastEnd passou a ordenar por endYM (YYYY-MM, ano-major) e só formatar o máximo no fim — o .sort() alfabético month-major que eu tinha apontado sumiu.
Também confirmei o formato da âncora, que era o risco silencioso da mudança: nextChargeMonth vem de referenceMonth.slice(0, 7) (lib/queries/parcelas.ts:76), ou seja YYYY-MM, que é o que parseISO(\${yearMonth}-01`)espera — não umYYYY-MM-01que estouraria emRangeError`.
Rodei os gates nesta sessão, sobre este head: npm ci, npm run lint, npm run typecheck e npm test verdes (34 arquivos, 532 testes).
Um achado, bloqueante — e é uma consequência do próprio diff, não uma lacuna da issue. A ordenação "Termina mais cedo" (ParcelasToolbar.tsx:45) ordena por remainingInstallments, o que era equivalente ao endLabel exibido enquanto todos os grupos partiam da mesma âncora, e deixou de ser agora que cada um parte do seu nextChargeMonth. Verifiquei rodando: com dois grupos plausíveis, a lista aparece ordenada ao contrário do que os próprios cards dizem no rótulo "termina". Correção de uma linha, com o dado que este PR já criou (endYM), no comentário inline.
Generated by Claude Code
…ents Achado bloqueante da revisao da PR #132 (issue #114): a correcao da ancora por grupo (nextChargeMonth) quebrou o pressuposto que o sort 'soonest-end' de ParcelasToolbar.tsx dependia -- antes todos os grupos compartilhavam a mesma ancora (mes atual), entao remainingInstallments era uma proxy valida para "termina em". Com ancora por grupo, as duas ordens divergem, e a tela passou a mostrar cards ordenados por "Termina mais cedo" que contradizem o proprio rotulo "termina" de cada card. Ordena por endYM (YYYY-MM, ano-major) via localeCompare, mesmo dado que page.tsx ja produz para o lastEnd. applySort exportada e testada em __tests__/unit/parcelas-toolbar.test.ts com o caso que so a correcao certa passa: dois grupos cujo remainingInstallments e endYM apontam para ordens opostas. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uc5r53ktBJD1ZZL2oDEyPG
Guiroos
left a comment
There was a problem hiding this comment.
Revisão do head a26955c (as anteriores foram no af914a7 e no de435b3). O achado bloqueante da rodada passada está resolvido: soonest-end passou a ordenar por endYM (ParcelasToolbar.tsx:46), que é o mesmo dado que alimenta o rótulo "termina" do card (InstallmentGroupCard.tsx:130-134) — lista e card voltam a concordar.
Conferi por conta própria que o endYM chega mesmo ao comparador, em vez de aceitar pelo diff: a page o propaga em groupsWithEnd (app/(app)/parcelas/page.tsx:33-36) e <ParcelasToolbar groups={groupsWithEnd}> (:230) é o único consumidor do componente no repo.
Rodei os gates nesta sessão, sobre este head: npm ci, npm test (35 arquivos, 536 testes) e npx tsc --noEmit verdes.
O it() principal do arquivo novo cobre de verdade — mutei o comparador de volta para remainingInstallments e ele fica vermelho sozinho, sem arrastar os outros.
Um achado, nit — nenhum bloqueante. O segundo it() do mesmo describe (:41-48) passa igual com o bug e com o comparador virando no-op; uma linha resolve. As duas mutações que rodei e a correção verificada estão no comentário inline.
Uma nota que não é achado, registrada para não voltar como pergunta: endYM?: string no tipo Group (ParcelasToolbar.tsx:33) é opcional, então o comparador degrada em silêncio para '' se alguém montar a toolbar sem o campo. Não abro porque o caminho realista já é barrado pelo compilador — tirei endYM do map da page e tsc reprova em page.tsx:40, onde lastEndYM lê o campo.
Generated by Claude Code
… bug Nit da revisao da PR #132 (issue #114): a entrada [a, b] do teste de endYM ausente ja estava na ordem esperada pela asserção, e Array.sort e estavel -- entao qualquer comparador que empate (ou vire no-op) devolve ['A', 'B'] sem exercitar nada. Invertida para [b, a], igual ao primeiro it() do arquivo, que ja usava essa entrada e de fato falha se soonest-end voltar a ordenar por remainingInstallments. Verificado revertendo o comparador do PR e rodando so este arquivo: os dois it()s de soonest-end falham de forma independente agora. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uc5r53ktBJD1ZZL2oDEyPG
O que mudou
app/(app)/parcelas/page.tsxcalculava o "termina em" de cada grupo de parcelas com aritmética de mês feita à mão (calcEndLabel), sempre ancorada no mês atual. Para um parcelamento cuja 1ª parcela cai no mês seguinte (compra feita depois do dia de fechamento do cartão — o caminho comum, não um caso de borda), isso projetava asremainingInstallmentsparcelas a partir de hoje em vez de a partir do mês em que elas de fato começam, adiantando em 1 mês o "termina em" exibido nos cards deInstallmentGroupCarde nos dois KPIs de topo (totalRestante/lastEnd).calcEndLabel(aritmética manual dentro da page).addMonthsToYearMonth(yearMonth, n)emlib/utils/date.ts— generalização denextMonth, usando as mesmas peças (addMonths+format).page.tsxagora ancora emg.nextChargeMonth(já calculado pela querygetActiveInstallmentGroups, antes ignorado pela page), com fallback para o mês atual quando não há parcela futura pendente (nextChargeMonth === null):Por que dessa forma
Segui a proposta da issue à risca — inclusive a escolha do helper.
futureNMonths(n)(o helper "óbvio") ancora emstartOfMonth(new Date()), exatamente a âncora que causa o bug.monthOptions(...).at(-1)daria o valor certo mas materializa a faixa inteira e seu contrato é "opções de seletor de mês", não aritmética pontual.addMonthsToYearMonthé a generalização mínima denextMonth(mesmas peças, parâmetron).Não toquei
getInstallmentTimeline(lib/queries/parcelas.ts), que usafutureNMonths(12)— ali a âncora em hoje é o comportamento correto (linha do tempo dos próximos 12 meses de calendário), e não empaidInstallments/remainingInstallments, que já estão corretos e alimentam também a exportação completa.Como testei
npm run lint && npm run format:check && npm run typecheck && npm test && npm run build— todos verdes.__tests__/unit/date.test.tsparaaddMonthsToYearMonth, incluindo o caso do próprio achado:addMonthsToYearMonth('2026-09', 11)deve ser'2027-08', e a correção errada mais provável (manter âncora no mês atual) —addMonthsToYearMonth('2026-08', 11)='2027-07'— fica registrada como o valor que a correção não deve produzir.ds-reviewerrodado sobreapp/(app)/parcelas/page.tsx(hookPostToolUse:Editexige a cada edição do arquivo): confirmou que a mudança é puramente lógica, sem regressão de UI. O agente reportou 2 violações de DS pré-existentes no arquivo (uso deuppercasesobre tokenstext-label/text-caption, etabular-numsausente em duas contagens) — fora do escopo desta issue, não tocadas.Risco e o que NÃO foi coberto
/parcelasde fato (o projeto não tem@testing-library/react); a cobertura fica no nível da função pura de aritmética, que é onde o bug vivia.npm run test:integrationnão foi rodado (exige credenciais do Neon e só roda em push paramain); esta mudança não toca camada de banco.ds-reviewerficam fora de escopo — não fazem parte do que a issue [arquitetura] /parcelas calcula "termina em" na própria page, ancorado no mês atual — parcelamento que começa no mês seguinte exibe o fim um mês antes #114 pediu.Arquivos tocados
app/(app)/parcelas/page.tsxlib/utils/date.ts__tests__/unit/date.test.tsGenerated by Claude Code