Documentos brasileiros
O CNPJ agora aceita letras nas 12 primeiras posições. Cálculo do dígito verificador (módulo 11, ASCII − 48), tabela dos 36 caracteres, regex e validador online.
O CNPJ passou a aceitar letras nas 12 primeiras posições. Os dois dígitos verificadores continuam numéricos, o tamanho continua 14 e a máscara continua 00.000.000/0000-00. O cálculo do DV é o mesmo módulo 11 de sempre — a única diferença é que o valor de cada caractere passa a ser o código ASCII menos 48.
| Antes | Agora | |
|---|---|---|
| Tamanho | 14 posições | 14 posições — igual |
| Máscara | 00.000.000/0000-00 | 00.000.000/0000-00 — igual |
| Posições 1 a 12 | Apenas 0-9 | 0-9 e A-Z |
| Posições 13 e 14 (DV) | Apenas 0-9 | Apenas 0-9 — igual |
| Cálculo do DV | Módulo 11, pesos 5..2 e 6..2 | Módulo 11, mesmos pesos |
| Valor do caractere | O próprio dígito | ASCII − 48 |
| CNPJs já emitidos | — | Continuam válidos, sem reemissão |
A lista de "igual" é maior que a de "mudou", e isso é o ponto mais importante do planejamento: não é uma reescrita, é a remoção de uma premissa. A premissa de que cada posição do CNPJ é um dígito está espalhada pelo seu sistema em tipos de coluna, regex, máscaras de input, parseInt, comparações e contratos de API — e é isso que precisa ser encontrado.
5 4 3 2 9 8 7 6 5 4 3 2. Some.6 5 4 3 2 9 8 7 6 5 4 3 2.0 é 48, os dígitos valem exatamente eles mesmos. É por isso que a rotina nova produz o mesmo DV de sempre para CNPJ numérico — você troca a função por uma só, não mantém duas.| Caractere | ASCII | Valor |
|---|---|---|
| 0 | 48 | 0 |
| 1 | 49 | 1 |
| 2 | 50 | 2 |
| 3 | 51 | 3 |
| 4 | 52 | 4 |
| 5 | 53 | 5 |
| 6 | 54 | 6 |
| 7 | 55 | 7 |
| 8 | 56 | 8 |
| 9 | 57 | 9 |
| A | 65 | 17 |
| B | 66 | 18 |
| C | 67 | 19 |
| D | 68 | 20 |
| E | 69 | 21 |
| F | 70 | 22 |
| G | 71 | 23 |
| H | 72 | 24 |
| I | 73 | 25 |
| J | 74 | 26 |
| K | 75 | 27 |
| L | 76 | 28 |
| M | 77 | 29 |
| N | 78 | 30 |
| O | 79 | 31 |
| P | 80 | 32 |
| Q | 81 | 33 |
| R | 82 | 34 |
| S | 83 | 35 |
| T | 84 | 36 |
| U | 85 | 37 |
| V | 86 | 38 |
| W | 87 | 39 |
| X | 88 | 40 |
| Y | 89 | 41 |
| Z | 90 | 42 |
Base AB12CD34EFGH, calculando o 1º DV. Os valores saem da tabela acima:
| Caractere | A | B | 1 | 2 | C | D | 3 | 4 | E | F | G | H |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Valor | 17 | 18 | 1 | 2 | 19 | 20 | 3 | 4 | 21 | 22 | 23 | 24 |
| Peso | 5 | 4 | 3 | 2 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 |
| Produto | 85 | 72 | 3 | 4 | 171 | 160 | 21 | 24 | 105 | 88 | 69 | 48 |
Soma = 850. 850 ÷ 11 deixa resto 3. Como 3 ≥ 2, o 1º DV é 11 − 3 = 8. Repetindo com 13 posições (AB12CD34EFGH8) e os pesos do 2º DV, chega-se a 3. Resultado: AB.12C.D34/EFGH-83.
Uma função só, que atende numérico e alfanumérico. Esta é exatamente a rotina que roda no validador desta página:
const PESOS_DV1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const PESOS_DV2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const valor = (ch) => ch.charCodeAt(0) - 48; // '0'->0 ... '9'->9, 'A'->17 ... 'Z'->42
function dv(base, pesos) {
const soma = pesos.reduce((s, p, i) => s + valor(base[i]) * p, 0);
const resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
}
export function validarCNPJ(entrada) {
const s = String(entrada).toUpperCase().replace(/[^0-9A-Z]/g, '');
// 12 alfanuméricos + 2 dígitos verificadores NUMÉRICOS
if (!/^[0-9A-Z]{12}[0-9]{2}$/.test(s)) return false;
if (/^(.)\1{13}$/.test(s)) return false; // caractere repetido nunca é válido
const d1 = dv(s, PESOS_DV1);
const d2 = dv(s.slice(0, 12) + d1, PESOS_DV2);
return s.slice(12) === `${d1}${d2}`;
}
validarCNPJ('AB.12C.D34/EFGH-83'); // true
validarCNPJ('00.000.000/0001-91'); // true — numérico continua funcionando# Sem máscara, 14 posições
^[0-9A-Z]{12}[0-9]{2}$
# Com máscara opcional
^([0-9A-Z]{2})\.?([0-9A-Z]{3})\.?([0-9A-Z]{3})\/?([0-9A-Z]{4})-?([0-9]{2})$
# O que você provavelmente tem hoje, e que passa a rejeitar CNPJ válido:
^\d{14}$
^[0-9]{2}\.[0-9]{3}\.[0-9]{3}\/[0-9]{4}-[0-9]{2}$Regex valida formato, nunca dígito verificador. AB12CD34EFGH99 passa em qualquer regex acima e é um CNPJ inválido. Use a regex no input e o cálculo do DV na regra de negócio — os dois, não um.
Em ordem de risco. Os três primeiros são os que quebram em produção no dia em que o primeiro CNPJ com letra chega:
| Onde | O que procurar | Para onde ir |
|---|---|---|
| Banco de dados | Coluna de CNPJ em INT, BIGINT, NUMBER, DECIMAL | CHAR(14), sem máscara, em maiúsculas |
| Conversões | parseInt, Number(), CAST, to_number, Integer.parseInt sobre CNPJ | Remover: CNPJ é identificador, não número |
| Validação | ^\\d{14}$, \\d{2}\\.\\d{3}\\.\\d{3}, isDigit, IsNumeric | Aceitar [0-9A-Z] nas 12 primeiras |
| Máscara de input | Componente que só aceita tecla numérica | Aceitar letra e forçar maiúscula |
| Ordenação e índice | Ordenação numérica de CNPJ | Ordenação de texto (muda o resultado — combine com o negócio) |
| Comparação | cnpj1 == cnpj2 com um lado mascarado | Normalizar antes de comparar, sempre |
| Contratos de API | Campo de CNPJ tipado como integer em OpenAPI/JSON Schema | string com pattern |
| Integrações | Layout posicional, EDI, arquivo de remessa | Conferir se o campo aceita alfanumérico |
| Relatórios e exports | CSV/Excel convertendo CNPJ em número e comendo zero à esquerda | Forçar texto na exportação |
| Massa de teste | Fixtures só com CNPJ numérico | Incluir CNPJ com letra nos testes |
É a falha mais comum e a mais silenciosa: o sistema funciona perfeitamente até o primeiro cadastro com letra, e aí o erro aparece no ponto mais distante da causa. Vale conferir hoje, não quando acontecer:
-- PostgreSQL: colunas cujo nome sugere CNPJ e cujo tipo é numérico
SELECT table_schema, table_name, column_name, data_type
FROM information_schema.columns
WHERE column_name ILIKE '%cnpj%'
AND data_type IN ('integer','bigint','numeric','double precision','real','smallint');
-- MySQL / MariaDB
SELECT table_schema, table_name, column_name, column_type
FROM information_schema.columns
WHERE column_name LIKE '%cnpj%'
AND data_type IN ('int','bigint','decimal','double','float','smallint');Por causa do ASCII − 48, a rotina alfanumérica devolve o mesmo DV que a antiga para qualquer CNPJ numérico. Manter validarCnpjAntigo e validarCnpjNovo em paralelo cria duas fontes de verdade que vão divergir. Substitua por uma.
O DV é calculado sobre o código ASCII, então a (97) e A (65) dão valores diferentes e o DV não fecha. Um CNPJ digitado em minúsculas é válido para o usuário e inválido para o seu código, se você não normalizar. Faça toUpperCase() na entrada e grave em maiúsculas, para não acabar com o mesmo CNPJ em duas grafias na base.
Uma regex frouxa como ^[0-9A-Z]{14}$ aceita AB12CD34EFGHIJ, que não é CNPJ: as duas últimas posições são sempre numéricas. Separe as duas partes no padrão — [0-9A-Z]{12}[0-9]{2} — e você rejeita isso no formato, sem nem chegar ao DV.
Você vai precisar de massa de teste com letra antes de o primeiro caso real chegar. Sorteie as 12 posições e calcule o DV — nunca invente o DV, senão seus testes passam a validar o bug:
const ALFABETO = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
function gerarCNPJ({ comLetras = true } = {}) {
const fonte = comLetras ? ALFABETO : '0123456789';
let base = '';
for (let i = 0; i < 12; i++) {
base += fonte[Math.floor(Math.random() * fonte.length)];
}
const d1 = dv(base, PESOS_DV1);
const d2 = dv(base + d1, PESOS_DV2);
return base + d1 + d2;
}O validador no topo desta página mostra o cálculo passo a passo, posição por posição — útil para conferir a sua implementação contra a nossa quando o DV não fecha e você não sabe onde.
Não. Os CNPJs já emitidos continuam válidos, não são reemitidos e não mudam. O formato alfanumérico vale para novas inscrições. O que muda no seu sistema é a capacidade de ACEITAR letras — não a de continuar aceitando os números que já estão na base.
Não. As 12 primeiras posições aceitam 0-9 e A-Z; os 2 dígitos verificadores são sempre numéricos. Um "CNPJ" terminando em letra é inválido por definição de formato, antes mesmo de calcular o DV.
Porque o valor de cada caractere é o código ASCII menos 48. Para os dígitos, ASCII de "0" é 48, então "0" vale 0, "9" vale 9 — exatamente os mesmos valores do algoritmo antigo. Os pesos e a regra do módulo 11 não mudaram. Rodar a rotina nova num CNPJ numérico dá o mesmo resultado da rotina antiga, o que significa que você pode substituir a função sem manter duas.
Não. Continuam 14 posições e a máscara 00.000.000/0000-00. O que deixa de valer é a premissa de que cada posição é um dígito: AB.123.CD4/EFGH-83 é um CNPJ bem formado.
Sim, se a coluna for numérica. CNPJ guardado em INT, BIGINT, NUMBER ou DECIMAL para de funcionar no dia em que chegar o primeiro com letra. O destino correto é CHAR(14) (ou VARCHAR(14)) guardando apenas os 14 caracteres sem máscara, em maiúsculas. Guardar com máscara desperdiça 4 bytes por linha e cria duas representações do mesmo dado.
A representação oficial é em maiúsculas. Como o cálculo do DV usa o código ASCII, minúscula produz outro valor ("a" é 97, não 65) e o DV não fecharia. Normalize a entrada para maiúsculas antes de validar, e guarde em maiúsculas.
Não. A mudança anunciada é do CNPJ. O CPF continua com 11 dígitos numéricos e o mesmo cálculo de DV.