feat(formatter): aceitar CNPJ alfanumerico (Reforma Tributaria 2026-07-01)#36
feat(formatter): aceitar CNPJ alfanumerico (Reforma Tributaria 2026-07-01)#36rhfranzoni wants to merge 2 commits into
Conversation
…7-01)
A partir de 2026-07-01 a SEFAZ aceitara CNPJ no formato alfanumerico
(LC 214/2025 + IN RFB 2.229/2024). 12 primeiros caracteres podem
conter letras maiusculas [A-Z], dois ultimos seguem como digitos
verificadores numericos.
## Mudancas no Formatador
- Regex CNPJ: `\d{2}` -> `[0-9A-Z]{2}` nos 4 primeiros grupos (12 chars);
ultimo grupo (2 digitos verificadores) inalterado.
- CPF regex inalterado (CPF segue sendo \d{11} por norma RFB).
- `FormatarCpfCnpj` reescrita para decidir por **comprimento da string limpa
(sem mascara)** antes da regex. Elimina ambiguidade quando um CNPJ
alfanumerico hipoteticamente pudesse bater na regex CPF (improvavel mas
deterministico de mitigar).
## Comportamento preservado
- CNPJs/CPFs numericos legados continuam formatando exatamente como antes.
- Comprimento invalido devolve input trimado (sem exception) — mesmo
contrato historico.
- Null / vazio devolvem string vazia — mesmo contrato historico.
## Cobertura
Adicionado `DanfeSharp.Test/FormatadorTests.cs` com 30+ casos usando
DataTestMethod/DataRow (MSTest 1.1):
- CNPJ numerico legado (regressao)
- CNPJ alfanumerico em varias posicoes de letras
- CNPJ com mascara visual (idempotencia)
- CNPJ comprimento invalido / lowercase / DV alfabetico (todos rejeitados)
- CPF numerico
- CPF com letras (rejeitado — nunca aceita alfanumerico)
- FormatarCpfCnpj dispatching por comprimento (11=CPF, 14=CNPJ, outros=cru)
- Edge cases: null, vazio, espacos
> Nota: o test project (DanfeSharp.Test) ja apresentava erros
> preexistentes de compilacao em FabricaFake.cs e DanfeTest.cs (na main
> antes deste PR). Validacao da nova logica feita via dotnet-script
> contra a DLL compilada — 13/13 checks OK. Restauracao dos testes
> existentes fica como follow-up separado.
Issue: #35 (spike). Cobre tambem o tipo basico exigido
pela issue #33 (barcode CNPJ alfanumerico).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates document formatting so Formatador can apply the standard CNPJ mask to the upcoming alphanumeric CNPJ format while preserving CPF behavior and numeric CNPJ regressions.
Changes:
- Updates the CNPJ regex to allow uppercase letters in the first 12 characters while keeping numeric check digits.
- Rewrites
FormatarCpfCnpjto dispatch by cleaned document length before applying CPF/CNPJ formatting. - Adds MSTest coverage for CPF, numeric CNPJ, alphanumeric CNPJ, invalid inputs, null/empty, and masked inputs.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
DanfeSharp/Formatador.cs |
Adds alphanumeric CNPJ support and changes CPF/CNPJ dispatch logic. |
DanfeSharp.Test/FormatadorTests.cs |
Adds formatter regression and alphanumeric CNPJ test cases. |
DanfeSharp.Test/DanfeSharp.Test.csproj |
Includes the new formatter test file in the legacy test project. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…o invalido Endereca o review do Copilot no PR #36 (comentario inline em Formatador.cs L150). ## Bug Quando o input tinha comprimento "limpo" 11 ou 14 mas conteudo invalido (ex.: CNPJ alfanumerico com letras minusculas: "12.abc.345/0001-88"), o dispatch por comprimento mandava para FormatarCnpj("12abc345000188"), a regex rejeitava (so aceita [0-9A-Z] maiusculo), e o retorno era o clean cru — **perdendo a pontuacao visivel do input original**. Comportamento histórico (antes do PR #36): regex miss devolvia o input trimado inalterado, mantendo pontuacao. ## Fix Valida `clean` contra a regex correspondente ANTES do dispatch: ```csharp if (clean.Length == 11 && Regex.IsMatch(clean, CPF)) return FormatarCpf(clean); if (clean.Length == 14 && Regex.IsMatch(clean, CNPJ)) return FormatarCnpj(clean); return trimmed; // Conteudo nao bate na regex — devolve com pontuacao ``` Comportamento agora alinhado com o contrato historico: input invalido (comprimento, formato, lowercase em CNPJ alfanumerico, DV alfabetico) devolve `trimmed` — preservando o input visivel para que o caller identifique o problema sem corrupcao silenciosa. ## Testes adicionados `DanfeSharp.Test/FormatadorTests.cs`: - `FormatarCpfCnpj_InvalidoComMascara_PreservaPontuacaoDoInput` (4 cenarios): - CNPJ alfanumerico minusculo com mascara: `"12.abc.345/0001-88"` -> identico - CNPJ com DV alfabetico + mascara - CPF com letra + mascara - CNPJ todo letras + mascara (DV alfabetico rejeita) - `FormatarCpfCnpj_InvalidoSemMascara_DevolveInputInalterado` (2 cenarios): - CNPJ lowercase sem mascara - CPF com letra sem mascara Validacao via dotnet-script contra a DLL recem-buildada: 13/13 OK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
john182
left a comment
There was a problem hiding this comment.
🔍 Review — PR #36 feat(formatter): CNPJ alfanumérico
Mudança cirúrgica e correta. Validei o Formatador.cs completo no head do PR (incl. InternalRegexReplace, FormatarCnpj/Cpf) — a lógica de produção está sólida e retrocompatível de fato. Comentários inline nos pontos específicos.
✅ Pontos fortes
- Regex alinhada à norma: 12 primeiros
[0-9A-Z]+ DV\d{2}numérico (LC 214/2025). - Retrocompatível: CNPJ/CPF numéricos legados formatam idênticos; input inválido volta inalterado — mesmo contrato do
InternalRegexReplace, que em regex-miss retorna o input. - O refactor (dispatch por comprimento → depois regex) preserva o contrato histórico "regex miss ⇒ input intocado" e ainda ganhou teste de regressão dedicado (
...PreservaPontuacaoDoInput). - Cobertura de edge cases excelente (36 casos): null, vazio, espaços, lowercase, DV alfabético, com/sem máscara, idempotência.
- Disclosure honesto do test project quebrado + análise de breaking changes.
Compile Includeadicionado corretamente (csproj old-style, não SDK).
⚠️ Importante
Os 36 testes não executam — o assembly DanfeSharp.Test não compila (DanfeCCC inexistente em DanfeTest.cs:63, confirmado). Detalhe inline na csproj. É o único ponto que eu trataria antes do merge: teste que não roda não protege contra regressão.
💡 Menores
- Nomes dos testes seguem
Metodo_Cenario_Esperado(MSTest) em vez de Given/When/Then — ok, segue a convenção do repo. - Redundância na checagem de comprimento (inline, não bloqueia).
Veredito: lógica de produção aprovada. Recomendo destravar a execução do test project para que a rede de proteção realmente exista.
| <Compile Include="DanfeXmlTests.cs" /> | ||
| <Compile Include="Extentions.cs" /> | ||
| <Compile Include="FabricaFake.cs" /> | ||
| <Compile Include="FormatadorTests.cs" /> |
There was a problem hiding this comment.
dotnet test. O assembly DanfeSharp.Test não compila: DanfeTest.cs:63 referencia DanfeCCC, classe que não existe no repo (confirmei). Enquanto o test project não buildar, os 36 casos deste arquivo não executam em CI nem localmente — só foram validados por dotnet-script, que exercita o Formatador mas não o arquivo de teste em si.
💡 Recomendo corrigir o test project neste PR (ou em PR pré-requisito mergeado antes deste). Caso contrário, são testes mortos até o follow-up — e o risco é que nunca rodem e a regex sofra regressão silenciosa.
| // CNPJ aceita letras maiúsculas nos 12 primeiros caracteres a partir de 2026-07-01 | ||
| // (Reforma Tributária, LC 214/2025 e IN RFB 2.229/2024). Os 2 últimos seguem como | ||
| // dígitos verificadores numéricos. CPF permanece estritamente numérico. | ||
| public const String CNPJ = @"^([0-9A-Z]{2})\.?([0-9A-Z]{3})\.?([0-9A-Z]{3})\/?([0-9A-Z]{4})\-?(\d{2})$"; |
There was a problem hiding this comment.
✅ Regex correta conforme a norma: 12 primeiros [0-9A-Z], DV final \d{2} numérico.
💡 Decisão de design a confirmar: lowercase é rejeitado (volta sem máscara) em vez de normalizado para maiúsculo. É defensável — a forma canônica SEFAZ é maiúscula — e os testes assertam isso explicitamente. Só vale garantir que nenhum caminho upstream entregue o CNPJ em minúsculas ao formatador; se entregar, o DANFE renderiza sem máscara silenciosamente. Se houver essa dúvida, um .ToUpperInvariant() no clean resolveria de forma conservadora.
| // FormatarCnpj retornaria a string crua sem máscara, e a pontuação visível | ||
| // do input original seria perdida. Devolver `trimmed` para input inválido | ||
| // alinha com o contrato histórico: regex miss => input inalterado. | ||
| if (clean.Length == 11 && Regex.IsMatch(clean, CPF)) |
There was a problem hiding this comment.
Nit (não bloqueia): clean.Length == 11 é redundante com a regex CPF, que é ancorada (^...$) e já exige exatamente 11 caracteres sobre a string limpa (idem == 14 para CNPJ). Mantê-lo como fast-path/intenção explícita é aceitável — só registrando que não é necessário para corretude.
Resumo
A SEFAZ vai aceitar CNPJ alfanumerico a partir de 2026-07-01 (LC 214/2025, IN RFB 2.229/2024). 12 primeiros caracteres podem conter letras maiusculas; 2 ultimos seguem como digitos verificadores numericos.
Hoje o
Formatador.FormatarCnpj/FormatarCpfCnpjusa regex\dque rejeita letras — efeito pratico: a partir de 2026-07-01 o DANFE/cupom passa a renderizar CNPJ alfanumerico sem mascara visual, prejudicando legibilidade fiscal.Mudancas
DanfeSharp/Formatador.cs\dpara[0-9A-Z]. Ultimo grupo (DV) permanece\d.FormatarCpfCnpj: reescrita para decidir por comprimento da string limpa antes da regex (elimina ambiguidade entre CPF e CNPJ alfanumerico).DanfeSharp.Test/FormatadorTests.cs(novo)30+ casos via
DataTestMethod/DataRow:12ABC345000188,98XYZ123000158).Validacao
DanfeSharp.csproj) compila sem erros.dotnet-scriptchamandoFormatador.FormatarCnpj/FormatarCpfCnpjcom 13 vetores: 13/13 OK.Ressalva
O test project
DanfeSharp.Testja apresentava erros preexistentes de compilacao emFabricaFake.cseDanfeTest.cs(DanfeCCCnao encontrado, conversao double->decimal) na branch main antes deste PR. Esses erros nao foram introduzidos aqui e nao tocam no codigo modificado. Como follow-up separado: corrigir o test project para quedotnet testrode completo.Issues vinculadas
Closes #35apos serem feitos os outros PRs derivados (esta e a primeira etapa).#33(barcode CODE-128A/C com CNPJ alfanumerico) — esta PR resolve o requisito de tipagem; barcode em si segue como track paralelo.Test plan
Breaking changes
Nenhuma:
Pessoas consumindo
FormatarCnpjprecisam estar preparadas para receber strings com letras no retorno a partir de 2026-07-01 — mas o tipo de retorno (string) e o formato visual (XX.XXX.XXX/XXXX-XX) sao os mesmos.Generated with Claude Code (https://claude.com/claude-code)