Depois de décadas em que CNPJ era, para efeitos práticos, um inteiro de quatorze dígitos, o documento passou a admitir letras. A mudança parece cosmética e não é: ela invalida uma suposição que está espalhada por colunas de banco, expressões regulares, máscaras de formulário, contratos de API e rotinas de importação de praticamente todo sistema brasileiro.
Este artigo mapeia o que efetivamente quebra, mostra o cálculo do dígito verificador no novo formato e propõe uma ordem de migração que não exige parar o sistema.
O que mudou na estrutura
O CNPJ continua com quatorze caracteres, distribuídos da mesma forma. O que muda é o conjunto de caracteres permitido em cada faixa.
| Posições | Parte | Caracteres permitidos |
|---|---|---|
| 1 a 8 | Raiz — identifica a empresa | Letras de A a Z e dígitos de 0 a 9 |
| 9 a 12 | Ordem — identifica o estabelecimento | Letras de A a Z e dígitos de 0 a 9 |
| 13 e 14 | Dígitos verificadores | Apenas dígitos de 0 a 9 |
A máscara visual não muda: continua sendo 00.000.000/0000-00, agora com a possibilidade de letras nas posições que antes só aceitavam números. Um exemplo estruturalmente válido no novo formato é 12.ABC.345/01DE-35.
O cálculo do dígito verificador
O módulo 11 continua sendo a base. A única adaptação é converter cada caractere em um valor numérico antes de aplicar os pesos, usando o código ASCII do caractere menos 48.
A escolha do 48 não é arbitrária: é o código ASCII do caractere zero. Assim, o dígito 0 vale 0, o dígito 9 vale 9, e a compatibilidade com os CNPJs numéricos antigos é preservada byte a byte. As letras, que começam em 65 na tabela ASCII, passam a valer de 17 (A) a 42 (Z).
| Caractere | Código ASCII | Valor no cálculo |
|---|---|---|
| 0 | 48 | 0 |
| 9 | 57 | 9 |
| A | 65 | 17 |
| B | 66 | 18 |
| Z | 90 | 42 |
A partir daí, tudo é como antes: os doze primeiros valores são multiplicados pelos pesos que ciclam de 2 a 9 da direita para a esquerda, somados, e o dígito é 0 quando o resto da divisão por 11 for menor que 2, ou 11 menos o resto caso contrário. O segundo dígito repete o processo incluindo o primeiro.
const PESOS = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
function valorDoCaractere(caractere) {
return caractere.charCodeAt(0) - 48;
}
function calcularDigito(caracteres) {
const pesos = PESOS.slice(PESOS.length - caracteres.length);
const soma = caracteres.reduce(
(acc, caractere, indice) => acc + valorDoCaractere(caractere) * pesos[indice],
0
);
const resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
}
export function validarCnpj(entrada) {
// Remove pontuação e normaliza: a Receita trabalha com maiúsculas.
const limpo = String(entrada).toUpperCase().replace(/[^A-Z0-9]/g, "");
if (limpo.length !== 14) return false;
if (/^(.)\1{13}$/.test(limpo)) return false;
// Os dois últimos caracteres continuam sendo obrigatoriamente numéricos.
if (!/^[A-Z0-9]{12}\d{2}$/.test(limpo)) return false;
const caracteres = limpo.split("");
const base = caracteres.slice(0, 12);
const primeiro = calcularDigito(base);
if (primeiro !== Number(caracteres[12])) return false;
const segundo = calcularDigito([...base, String(primeiro)]);
return segundo === Number(caracteres[13]);
}O que quebra na sua aplicação
Vale mapear os pontos de falha por camada, porque eles raramente estão todos no mesmo módulo.
Banco de dados
- Colunas BIGINT, NUMERIC ou INTEGER guardando CNPJ não comportam letras e falham na inserção.
- Índices e chaves estrangeiras construídos sobre essas colunas precisam ser recriados após a mudança de tipo.
- Constraints com CHECK numérico ou regex de dígitos rejeitam o novo formato silenciosamente até alguém tentar gravar.
- Views materializadas e colunas calculadas que fazem cast para número quebram na primeira letra.
Validação e regras
- Expressões regulares do tipo ^\\d{14}$ rejeitam qualquer CNPJ com letra.
- Funções que fazem parseInt ou Number sobre o documento produzem NaN ou truncamento.
- Bibliotecas de validação desatualizadas continuam aplicando a regra antiga; verifique a versão antes de confiar.
Interface
- Campos com inputmode numérico abrem teclado sem letras no celular.
- Máscaras que filtram tudo que não for dígito apagam as letras conforme o usuário digita.
- Ordenação de listas por CNPJ tratado como número passa a ordenar errado.
Integrações
- Contratos de API que declaram o campo como integer no OpenAPI precisam virar string com padrão explícito.
- Layouts de arquivo posicional geralmente já reservam quatorze posições de texto e sofrem menos, mas rotinas de parse que fazem cast numérico quebram.
- Parceiros que ainda não migraram podem rejeitar os documentos que você enviar — vale combinar a janela de corte antes.
Uma ordem de migração que funciona
A sequência importa. Trocar o tipo da coluna antes de o validador aceitar letras cria uma janela em que o banco aceita o que a aplicação recusa; fazer o contrário cria uma janela em que a aplicação aceita o que o banco recusa, o que é pior porque a falha aparece só na gravação.
- 1Inventarie onde o CNPJ aparece: colunas, DTOs, regex, máscaras, contratos e relatórios. Um grep por 'cnpj' costuma revelar mais lugares do que o esperado.
- 2Atualize o validador para aceitar os dois formatos, mantendo a rejeição de sequências repetidas e a exigência de dois dígitos numéricos no fim.
- 3Cubra o novo formato com testes antes de mexer no banco, incluindo caixa baixa, pontuação e caracteres inválidos.
- 4Migre o tipo da coluna para CHAR(14) ou VARCHAR(14), normalizando os valores existentes para dígitos sem pontuação.
- 5Afrouxe as máscaras e o inputmode na interface, normalizando para maiúsculas na saída do campo.
- 6Atualize os contratos de API e avise os parceiros que consomem o campo.
- 7Só então habilite a entrada de documentos alfanuméricos em produção.
Como testar sem depender de documentos reais
Para exercitar todos esses caminhos você precisa de massa nos dois formatos, e usar CNPJs de empresas reais em ambiente de homologação é uma exposição desnecessária. Documentos sintéticos resolvem: eles fecham no módulo 11 e passam por qualquer validação sintática, sem corresponder a uma inscrição existente.
O gerador de CNPJ do Codigio Labs produz os dois formatos, e o validador mostra o resultado da checagem caractere a caractere — útil para conferir sua própria implementação contra um caso conhecido. Ambos rodam no navegador, sem enviar o documento a nenhum servidor.
Resumo
- Letras podem aparecer nas doze primeiras posições; os dois dígitos verificadores continuam numéricos.
- A conversão de caractere para valor é o código ASCII menos 48, o que preserva a compatibilidade com o formato numérico.
- Normalize sempre para maiúsculas antes de calcular, ou o resultado sai errado.
- Colunas numéricas, regex de dígitos e máscaras restritivas são os três pontos que quebram primeiro.
- Migre o validador antes do banco, e o banco antes da interface.