O sistema aceitava placa há anos com uma expressão regular de três letras e quatro números. Aí chegou o primeiro veículo com placa Mercosul, o cadastro recusou, e a descoberta veio pelo suporte. É a história de praticamente todo sistema de frota, seguradora, estacionamento e controle de acesso no Brasil.
O problema tem uma característica que o torna mais chato do que difícil: a mudança é pequena, cabe em uma linha de código, e está espalhada por dezenas de lugares — validação, máscara, busca, relatório, integração e leitura automática de imagem.
Os dois padrões, lado a lado
| Posição | Padrão antigo | Padrão Mercosul |
|---|---|---|
| 1 | Letra | Letra |
| 2 | Letra | Letra |
| 3 | Letra | Letra |
| 4 | Número | Número |
| 5 | Número | Letra |
| 6 | Número | Número |
| 7 | Número | Número |
| Exemplo | ABC1234 | ABC1D23 |
| Separador | Hífen por convenção | Nenhum |
A única mudança estrutural está na quinta posição. Essa economia é deliberada: ela multiplica por vinte e seis o espaço de combinações mantendo o mesmo número de caracteres, o que preserva o tamanho físico da placa e a compatibilidade de campos de sete posições já existentes.
A validação que aceita os dois
A tentação é usar uma expressão frouxa, de sete caracteres alfanuméricos. Ela aceita os dois padrões e também aceita 1234567 e AAAAAAA, o que devolve para o banco a responsabilidade de recusar lixo.
const PADRAO_ANTIGO = /^[A-Z]{3}\d{4}$/;
const PADRAO_MERCOSUL = /^[A-Z]{3}\d[A-Z]\d{2}$/;
export function normalizarPlaca(entrada) {
// Remove hífen, espaço e qualquer outro separador; a placa é sempre maiúscula.
return String(entrada).toUpperCase().replace(/[^A-Z0-9]/g, "");
}
export function validarPlaca(entrada) {
const placa = normalizarPlaca(entrada);
return PADRAO_ANTIGO.test(placa) || PADRAO_MERCOSUL.test(placa);
}
export function padraoDaPlaca(entrada) {
const placa = normalizarPlaca(entrada);
if (PADRAO_MERCOSUL.test(placa)) return "mercosul";
if (PADRAO_ANTIGO.test(placa)) return "antigo";
return null;
}Separar as duas expressões, em vez de fundi-las numa só, tem uma vantagem prática: a função que identifica o padrão permite exibir o formato correto na interface e gerar relatórios sobre quantos veículos da frota já migraram.
Armazenamento e busca
A regra é a mesma dos outros documentos: guarde a forma normalizada e formate na exibição. Uma base em que a mesma placa aparece como ABC-1234, ABC 1234 e abc1234 produz duplicatas, quebra junções e faz a busca falhar de formas difíceis de reproduzir.
export function formatarPlaca(placa) {
const normalizada = normalizarPlaca(placa);
// O padrão Mercosul não usa separador; o antigo, por convenção, usa hífen.
if (PADRAO_MERCOSUL.test(normalizada)) return normalizada;
if (PADRAO_ANTIGO.test(normalizada)) {
return `${normalizada.slice(0, 3)}-${normalizada.slice(3)}`;
}
return normalizada;
}Caracteres ambíguos e leitura automática
Se o seu sistema recebe placa por reconhecimento de imagem — cancela de estacionamento, pedágio, fiscalização, aplicativo de vistoria — um problema novo aparece: alguns caracteres são visualmente quase idênticos.
| Par | Confusão | Agravante no padrão Mercosul |
|---|---|---|
| O e 0 | Muito alta | A quinta posição virou letra, então 0 lido ali é sempre erro |
| I e 1 | Alta | A primeira posição é sempre letra; 1 ali é erro certo |
| B e 8 | Média | Depende da fonte e do ângulo |
| S e 5 | Média | Agrava com placa suja ou desgastada |
| Z e 2 | Baixa | Aparece com baixa resolução |
A boa notícia é que a estrutura do padrão elimina boa parte da ambiguidade: como cada posição tem tipo definido, um dígito lido onde só cabe letra é necessariamente erro, e pode ser corrigido pela posição.
const PARA_LETRA = { "0": "O", "1": "I", "5": "S", "8": "B", "2": "Z" };
const PARA_NUMERO = { O: "0", I: "1", S: "5", B: "8", Z: "2" };
// Posições que devem ser letra em cada padrão.
const LETRAS_ANTIGO = [0, 1, 2];
const LETRAS_MERCOSUL = [0, 1, 2, 4];
function corrigirPorPosicao(placa, posicoesDeLetra) {
return placa
.split("")
.map((caractere, indice) => {
const deveSerLetra = posicoesDeLetra.includes(indice);
if (deveSerLetra) return PARA_LETRA[caractere] ?? caractere;
return PARA_NUMERO[caractere] ?? caractere;
})
.join("");
}Onde a mudança costuma passar despercebida
- Campos com inputmode numérico, que no celular abrem um teclado sem letras.
- Máscaras que forçam número na quinta posição e apagam a letra conforme o usuário digita.
- Relatórios e filtros com expressão regular própria, escrita antes do padrão novo.
- Layouts de arquivo trocados com parceiros, cuja validação está do outro lado.
- Índices e chaves construídos sobre a placa formatada com hífen.
- Testes automatizados cuja massa só contém placas do padrão antigo.
O último item é o que mantém o problema invisível por mais tempo: a suíte passa, porque nenhum caso de teste usa o formato novo. Incluir placas dos dois padrões na massa é a mudança de menor custo e maior retorno.
Gerando placas para teste
O gerador de placa do Codigio Labs produz valores nos dois padrões, respeitando a estrutura de cada um. Eles são fictícios e não correspondem a veículos registrados, o que os torna adequados para preencher cadastros de teste, exercitar máscaras e montar a massa que faltava na sua suíte.
Resumo
- O Mercosul muda apenas a quinta posição, de número para letra; o resto da estrutura é idêntico.
- Os dois padrões convivem por tempo indeterminado, porque a troca é por evento e não por prazo.
- Valide com duas expressões separadas, o que também permite identificar o padrão.
- Guarde a placa normalizada, em maiúsculas e sem separador, e formate só na exibição.
- Em leitura automática, use a estrutura de posições para corrigir caracteres ambíguos — na busca, nunca no valor gravado.