Regex não basta para validar CNPJ alfanumérico
Atualizar a regex para aceitar letras não valida CNPJ alfanumérico. Sem calcular os dígitos verificadores, CNPJs inválidos entram no seu banco silenciosamente.
Tem um ticket fechado em alguma sprint recente que diz algo como "atualizar validação de CNPJ para suportar letras". O dev que resolveu mudou \d por [A-Z0-9] na regex, os testes passaram, e o PR foi aprovado. O problema é que esse ticket resolveu metade do problema — a metade mais fácil.
A regex verifica formato. O dígito verificador verifica validade. São duas coisas diferentes, e confundir uma com a outra vai colocar dados sujos em produção de um jeito que você só descobre meses depois.
O que a regex verifica — e o que ela não verifica
Uma regex como esta faz uma coisa útil:
/^[A-Z0-9]{2}\.[A-Z0-9]{3}\.[A-Z0-9]{3}\/[A-Z0-9]{4}-\d{2}$/i
Ela garante que o input tem 14 caracteres na posição certa, com os separadores corretos, e que as posições 1–12 aceitam letras maiúsculas e dígitos. Isso elimina entradas claramente erradas — "não é um CNPJ" passa a ser detectável.
O que ela não faz: verificar se os dois últimos caracteres (os dígitos verificadores) são matematicamente corretos para aquele conjunto de 12 caracteres que vieram antes.
O dígito verificador de um CNPJ não é arbitrário. Dado uma raiz (B3QK72A1) e uma ordem (0001), existe exatamente um par de verificadores que torna o CNPJ válido. B3.QK7.2A1/0001-89 pode ser válido; B3.QK7.2A1/0001-12 tem o mesmo formato mas é matematicamente inválido. A regex não distingue os dois.
Quantos strings aleatórias passam no regex?
Vamos ser concretos. Para uma raiz+ordem alfanumérica de 12 posições, há 36^12 combinações possíveis (36 = 10 dígitos + 26 letras). Cada combinação tem exatamente um par de dígitos verificadores válido — os outros 99 pares são matematicamente inválidos.
Com regex apenas: todas as 36^12 × 100 combinações passam (qualquer \d{2} no final está certo).
Com módulo 11: apenas as 36^12 × 1 combinações com DV correto passam.
Em termos práticos: 1 em cada 100 CNPJs aleatórios bem formatados é matematicamente válido. Os outros 99 passam na regex e falham na validação real.
Isso não é hipotético. Qualquer formulário que aceite CNPJ e não valide o DV vai acumular entradas inválidas. Usuário digitou errado, copiou com um dígito trocado, testou com dado fictício — tudo vai entrar.
O que acontece quando isso chega em produção
Dados sujos de CNPJ têm consequências concretas.
Integrações fiscais rejeitam o CNPJ. A SEFAZ valida o dígito verificador na emissão de NF-e. Se o CNPJ do tomador ou emitente for inválido, a nota é rejeitada com erro 226 ("CNPJ do destinatário inválido"). O processo de venda trava em runtime, não em desenvolvimento.
Consultas à Receita Federal retornam erro. Sistemas que consultam o CNPJ para preencher dados cadastrais automaticamente vão falhar silenciosamente — ou pior, vão tratar o erro como "CNPJ não encontrado" e continuar.
Auditoria depois é impossível. Se você tem 50 mil CNPJs no banco, como saber quantos são válidos? Você precisaria recalcular o DV de cada um. Prevenir é mais barato que remediar.
Como o módulo 11 filtra o que a regex não filtra
O algoritmo de dígito verificador do CNPJ usa módulo 11 com pesos específicos. A intuição é simples: dado os 12 primeiros caracteres (raiz + ordem), você calcula uma soma ponderada, aplica módulo 11, e o resultado determina os dois DVs.
Uma troca de um único caractere na raiz ou ordem altera a soma — e portanto altera o DV esperado. Se o DV no input não bate com o calculado, o CNPJ é inválido.
Exemplo concreto: B3.QK7.2A1/0001-89 e B3.QK7.2A1/0001-88 diferem apenas no último dígito. A regex aceita os dois. O módulo 11 aceita apenas um (ou nenhum, dependendo dos caracteres exatos).
Isso é o que o dígito verificador foi projetado para fazer: detectar erros de digitação e entradas fabricadas.
A sequência correta de validação
Regex e módulo 11 não são alternativas — são etapas sequenciais. A regex filtra o que é claramente inválido; o módulo 11 filtra o que parece válido mas não é.
function validarCNPJ(input: string): boolean {
// 1. Normalizar
const s = input.replace(/[.\-\/]/g, '').toUpperCase();
// 2. Comprimento
if (s.length !== 14) return false;
// 3. Formato: posições 1-12 alfanuméricas, 13-14 numéricas
if (!/^[A-Z0-9]{12}\d{2}$/.test(s)) return false;
// 4. Rejeitar sequências homogêneas de dígitos (ex: 00000000000000)
if (/^\d+$/.test(s) && new Set(s).size === 1) return false;
// 5. Calcular e verificar os DVs — essa é a validação real
const val = (c: string) => c >= 'A' ? c.charCodeAt(0) - 55 : +c;
const w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const soma1 = w1.reduce((acc, w, i) => acc + val(s[i]) * w, 0);
const dv1 = soma1 % 11 < 2 ? 0 : 11 - soma1 % 11;
const w2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const soma2 = w2.reduce((acc, w, i) => acc + val(s[i]) * w, 0);
const dv2 = soma2 % 11 < 2 ? 0 : 11 - soma2 % 11;
return val(s[12]) === dv1 && val(s[13]) === dv2;
}
O passo 3 é a regex. O passo 5 é o módulo 11. Um sem o outro está incompleto.
Note a conversão de caractere para valor: letras A-Z usam charCodeAt(0) - 55, que mapeia A=10, B=11, ..., Z=35. Dígitos mantêm seu valor numérico. Esse mapeamento é o que a Receita Federal definiu para o novo formato.
Libs que já fazem as duas etapas
Se você não quer implementar o algoritmo manualmente, há bibliotecas npm que já suportam o formato alfanumérico:
validador-cnpj-alfanumerico— validação e geração, suporte completo ao novo formato- A função
validateCNPJdo pacotecnpj-cpf-validator— verifique a versão; as antigas não suportam letras
Antes de usar qualquer lib, teste com um CNPJ alfanumérico válido conhecido. Se a função retornar false para um CNPJ gerado corretamente, a lib não foi atualizada.
Nota: o conteúdo editorial acabou aqui. O que vem abaixo é uma indicação de ferramenta relacionada ao tema do post.
Ferramenta relacionada
Para montar fixtures de teste com CNPJs alfanuméricos matematicamente válidos, o Gerador de CNPJ já calcula os dígitos verificadores no novo formato — útil para ter entradas conhecidas na sua suite antes de subir a validação para produção.
- 01 diff antes de abrir PR: o hábito que reduz comentários de revisão Ler o próprio diff antes de abrir um PR muda o que você vê no código. Como e por que esse hábito reduz ciclos de revisão e melhora a qualidade dos PRs.
- 02 `P@ssw0rd1` passa na sua validação. Esse é o problema. O que o NIST mudou em 2024 sobre senhas: comprimento bate complexidade, leet speak está no rockyou.txt e rotação obrigatória enfraquece senhas.