Gerador de Placa Mercosul para Testes: Algoritmo e Validação
Gerador de Placa Mercosul para Testes: Algoritmo e Validação
Placas Mercosul seguem um padrão determinístico introduzido no Brasil em 2018. Isso significa que gerar placas válidas para testes não é difícil — mas gerar placas que passem por todos os validadores de um sistema real exige conhecer as restrições exatas do Denatran.
O formato Mercosul tem estrutura determinística — e isso facilita a geração
Padrão ABC1D23: três letras, um dígito, uma letra, dois dígitos
O padrão Mercosul para carros de passeio segue [A-Z]{3}[0-9][A-Z][0-9]{2}, sete caracteres sem hífen. A posição 5 (índice 4) é o diferenciador entre o formato antigo e o novo: se for letra, é Mercosul. Se for dígito, é placa antiga.
Exemplos válidos: BRA2E19, ABC1D23, XYZ9K00.
Motos seguem variante ABC1D2 (seis caracteres)
Motos têm apenas seis caracteres: [A-Z]{3}[0-9][A-Z][0-9]. A lógica de geração é idêntica, com o bloco numérico final tendo apenas um dígito em vez de dois.
Por que o Denatran excluiu certas letras (I, O, Q) do bloco alfabético
O Denatran excluiu I, O e Q dos blocos alfabéticos para evitar confusão visual com os dígitos 1, 0 e 0/Q em fontes condensadas. Essa exclusão se aplica tanto às três primeiras letras quanto à letra na posição 5. O alfabeto efetivo tem 23 letras.
Restrições reais que o algoritmo precisa respeitar
Sequências proibidas: palavras obscenas e siglas de órgãos públicos (Resolução Contran nº 752/2018)
A Resolução Contran nº 752/2018 define que o Denatran mantém uma lista interna de combinações proibidas. Não há publicação oficial completa da lista, mas o princípio é claro: o bloco inicial de três letras não pode formar palavras de baixo calão ou siglas sensíveis. Para um gerador de testes, basta uma blocklist mínima:
const BLOCKED_PREFIXES = new Set([
'SSP', 'PRF', 'DPF', 'PGR', 'STF', 'STJ', 'CNJ',
]);O segundo bloco tem exatamente um caractere alfabético na posição 5
Essa é a restrição que define o formato Mercosul. O regex completo do segundo bloco é [0-9][A-HJ-NPR-Z][0-9]{1,2}. A classe [A-HJ-NPR-Z] já exclui I, O e Q.
Diferença entre geração aleatória e geração progressiva (como o Detran sequencia)
O Detran emite placas sequencialmente por UF. Para testes, geração aleatória é suficiente e mais simples. Geração progressiva só faz sentido se o sistema testado valida sequências ou prefixos regionais específicos.
Implementação do gerador em TypeScript
Função generateMercosulPlate() pura e sem dependências externas
const ALPHA = 'ABCDEFGHJKLMNPRSTUVWXYZ'; // 23 letras, sem I, O, Q
const DIGITS = '0123456789';
function randomChar(str: string): string {
return str[Math.floor(Math.random() * str.length)];
}
const BLOCKED_PREFIXES = new Set([
'SSP', 'PRF', 'DPF', 'PGR', 'STF', 'STJ', 'CNJ',
]);
export function generateMercosulPlate(
options: { format?: 'car' | 'moto' } = {}
): string {
const { format = 'car' } = options;
let prefix: string;
do {
prefix = randomChar(ALPHA) + randomChar(ALPHA) + randomChar(ALPHA);
} while (BLOCKED_PREFIXES.has(prefix));
const digit1 = randomChar(DIGITS);
const letter = randomChar(ALPHA);
const suffix = format === 'moto'
? randomChar(DIGITS)
: randomChar(DIGITS) + randomChar(DIGITS);
return `${prefix}${digit1}${letter}${suffix}`;
}
export function formatPlate(plate: string, hyphen = false): string {
if (!hyphen) return plate;
return `${plate.slice(0, 3)}-${plate.slice(3)}`;
}Gerando o bloco alfabético inicial com exclusão de I, O, Q
A constante ALPHA com 23 letras resolve isso diretamente. O loop do...while descarta prefixos bloqueados antes de retornar — na prática, o loop raramente itera mais de uma vez, dado o espaço de 23³ = 12.167 combinações possíveis.
Gerando a parte numérica com posição 5 variável A–Z (exceto I, O, Q)
O mesmo ALPHA cobre a posição 5. Não há pesos ou distribuições específicas documentadas publicamente para essa posição, então distribuição uniforme é a abordagem correta para testes.
Validação via regex e função de checagem
Regex definitivo para placas Mercosul e antigas no mesmo validador
| Formato | Regex | Exemplo |
|---|---|---|
| Mercosul carro | ^[A-Z]{3}\d[A-HJ-NPR-Z]\d{2}$ | BRA2E19 |
| Mercosul moto | ^[A-Z]{3}\d[A-HJ-NPR-Z]\d$ | BRA2E1 |
| Placa antiga | ^[A-Z]{3}\d{4}$ | ABC1234 |
Função validatePlate(plate: string) com retorno tipado
type PlateFormat = 'mercosul' | 'antiga' | 'moto' | 'invalid';
const PATTERNS: Record<Exclude<PlateFormat, 'invalid'>, RegExp> = {
mercosul: /^[A-Z]{3}\d[A-HJ-NPR-Z]\d{2}$/,
moto: /^[A-Z]{3}\d[A-HJ-NPR-Z]\d$/,
antiga: /^[A-Z]{3}\d{4}$/,
};
export function validatePlate(
plate: string
): { valid: boolean; format: PlateFormat } {
const normalized = plate.toUpperCase().replace(/[\s-]/g, '');
for (const [format, regex] of Object.entries(PATTERNS)) {
if (regex.test(normalized)) {
return { valid: true, format: format as PlateFormat };
}
}
return { valid: false, format: 'invalid' };
}Casos de borda: placas com espaço, lowercase, hífen inconsistente
A normalização toUpperCase().replace(/[\s-]/g, '') cobre os três casos. Placas recebidas de formulários raramente chegam no formato canônico, e a função acima absorve todas as variações comuns.
Dados de veículo correlacionados para testes mais realistas
Associar placa a UF de emplacamento (prefixo de três letras tem distribuição regional real)
O Denatran distribui faixas de prefixo por UF. A tabela abaixo é uma aproximação suficiente para testes:
| UF | Faixa de prefixo |
|---|---|
| SP | AAA–AZZ |
| RJ | BAA–BZZ |
| MG | CAA–CZZ |
| RS | DAA–DZZ |
| PR | EAA–EZZ |
Use um mapa de prefixo inicial por UF em vez de tentar replicar a tabela exata do Detran — para testes, correlação aproximada é suficiente.
Combinar com generatePerson() para simular proprietário
Use o gerador de pessoa do FakeForge para gerar nome, CPF e endereço do titular em um único objeto. O gerador de CEP correlacionado com a UF torna o fixture ainda mais realista:
import { generateMercosulPlate, generateRenavam } from './plate';
import { generatePerson } from './person'; // FakeForge
function createVehicleOwner() {
return {
plate: generateMercosulPlate(),
renavam: generateRenavam(),
owner: generatePerson(),
};
}Gerar RENAVAM fake com dígito verificador correto (mod 11)
O RENAVAM tem 11 dígitos. Os 10 primeiros são a base numérica; o último é o dígito verificador calculado por mod 11 com pesos específicos:
export function generateRenavam(): string {
const base = Array.from({ length: 10 }, () =>
Math.floor(Math.random() * 10)
);
const weights = [3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const sum = base.reduce((acc, d, i) => acc + d * weights[i], 0);
const remainder = sum % 11;
const digit = remainder < 2 ? 0 : 11 - remainder;
return [...base, digit].join('');
}Integração com frameworks de teste
Factory com Vitest: criando fixtures de frota em massa
import { describe, it, expect } from 'vitest';
import { generateMercosulPlate, validatePlate } from './plate';
import { generateRenavam } from './renavam';
function createVehicleFixture(count: number) {
return Array.from({ length: count }, () => ({
plate: generateMercosulPlate(),
renavam: generateRenavam(),
}));
}
describe('Vehicle fixtures', () => {
it('generates 100 valid Mercosul plates', () => {
const fleet = createVehicleFixture(100);
for (const { plate } of fleet) {
expect(validatePlate(plate).format).toBe('mercosul');
}
});
});Usando a API REST do FakeForge como seed remoto em pipelines de CI
A API REST do FakeForge suporta geração em lote via query params:
curl "https://fakeforge.com.br/api/generate?type=placa&quantity=50&format=csv"O plano Dev (R$29/mês) remove o limite de 100 chamadas/dia, adequado para pipelines que rodam a cada push.
Snapshot testing: por que fixar a semente do RNG evita falsos positivos
DICA: SubstituaMath.randompor um PRNG seedável (ex:seedrandomvia npm) nos testes de snapshot. Em produção, useMath.randomnormalmente. Isso garante que o mesmo seed sempre produz os mesmos fixtures, eliminando falsos positivos por aleatoriedade.
Cobertura de cenários negativos é obrigatória
Placas antigas (ABC1234) que sistemas legados ainda aceitam
Sistemas de frota com mais de dez anos provavelmente têm registros no formato antigo. Gere placas antigas explicitamente para testar código legado:
function generateOldPlate(): string {
const prefix = Array.from({ length: 3 }, () => randomChar(ALPHA)).join('');
const nums = Array.from({ length: 4 }, () => randomChar(DIGITS)).join('');
return `${prefix}${nums}`;
}Placas de coleção, diplomáticas e de teste (série ZZZ) — quando incluir
Placas diplomáticas (DDI-0001), de coleção e da série ZZZ são casos especiais que a maioria dos sistemas rejeita. Inclua-as na suite de testes negativos, não nos fixtures de dados válidos.
Fuzzing: gerando strings inválidas intencionais para testar boundary conditions
const FUZZ_CASES = [
'',
'ABC',
'I00A111', // I no bloco inicial — inválido
'ABC O123', // espaço e O proibido
'ABC1D2', // moto sem validação explícita de formato
'!!!1A23',
];
for (const plate of FUZZ_CASES) {
const result = validatePlate(plate);
console.assert(result.format === 'invalid', `Esperado inválido: ${plate}`);
}LGPD e dados de veículo: o que é pessoal e o que não é
Placa sozinha não identifica pessoa — mas placa + CPF + endereço forma dataset sensível (LGPD Art. 5º, I)
Uma placa isolada não é dado pessoal segundo a LGPD Art. 5º, I (Lei 13.709/2018 — texto integral). Ela não identifica nem torna identificável uma pessoa natural diretamente. A combinação placa + CPF + endereço + nome, no entanto, forma um conjunto identificador que se enquadra na definição de dado pessoal.
AVISO: Bancos de dados de teste que combinam dados reais de veículos com dados reais de pessoas — mesmo obtidos de fontes públicas — constituem tratamento de dados pessoais sem base legal clara. Use dados sintéticos em todas as camadas do pipeline de testes.
Banco de dados de testes com dados reais de veículos: risco jurídico e alternativa segura
Usar dumps de sistemas reais em ambiente de teste expõe a empresa ao Art. 46 da LGPD (obrigação de adotar medidas de segurança adequadas). A alternativa é gerar fixtures sintéticos com correlações realistas. Combine gerador de pessoa e gerador de CNPJ para montar frotas de pessoa física e jurídica sem nenhum dado real.
Diferença entre dado anonimizado e dado pseudonimizado no contexto de frota (LGPD Art. 12)
O LGPD Art. 12 define dado anonimizado como aquele que não pode ser revertido por "meio técnico razoável e disponível". Dado pseudonimizado — ex: CPF substituído por UUID, mas mapeável internamente — ainda é dado pessoal. Dados gerados sinteticamente são anonimizados por construção, sem custo adicional de processo.
Performance ao gerar grandes volumes
Geração de 10.000 placas únicas sem colisão: estratégia de Set vs. filtro probabilístico
export function generateUniquePlates(count: number): string[] {
const seen = new Set<string>();
while (seen.size < count) {
seen.add(generateMercosulPlate());
}
return [...seen];
}O espaço de placas Mercosul válidas é aproximadamente 23³ × 10 × 23 × 10² ≈ 280 milhões. Com 10.000 placas, a probabilidade de colisão é desprezível. Um Set simples é suficiente; filtro de Bloom só vale para volumes acima de 1 milhão.
Paralelismo com Promise.all e limites práticos de memória
Para volumes acima de 100.000, a geração síncrona em loop é mais eficiente do que Promise.all — o overhead de microtask supera qualquer ganho de paralelismo em operações CPU-bound. Quebre em chunks e libere o event loop entre eles:
async function generatePlatesInChunks(
total: number,
chunkSize = 10_000
): Promise<string[]> {
const results: string[] = [];
for (let i = 0; i < total; i += chunkSize) {
const chunk = generateUniquePlates(Math.min(chunkSize, total - i));
results.push(...chunk);
await new Promise(r => setImmediate(r));
}
return results;
}Export em CSV e SQL com CREATE TABLE veiculo pronto para carga
CREATE TABLE veiculo (
id SERIAL PRIMARY KEY,
placa CHAR(7) NOT NULL,
renavam CHAR(11) NOT NULL,
uf CHAR(2) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
INSERT INTO veiculo (placa, renavam, uf) VALUES
('BRA2E19', '12345678901', 'SP'),
('XYZ9K00', '98765432109', 'RJ');O export SQL da API REST do FakeForge gera o CREATE TABLE automaticamente com base no tipo solicitado.
Resumo
- Implemente
generateMercosulPlate()com o alfabeto de 23 letras (semI,O,Q) e uma blocklist mínima de prefixos proibidos — o loopdo...whiledescarta conflitos sem custo perceptível. - Use
validatePlate()com suporte a Mercosul, moto e formato antigo; normalize case e hífens antes de aplicar qualquer regex. - Gere RENAVAM com dígito verificador mod 11 para fixtures de sistema de frota que precisam passar por validadores de documento.
- Combine placa com gerador de pessoa e CEP por UF para dados correlacionados que passam por validadores de negócio, não apenas de formato.
- Nunca use dados reais de veículos em ambiente de teste — a combinação com CPF e endereço forma dataset sujeito à LGPD Art. 5º, I, independentemente da fonte dos dados.
- Próximo passo: adicionar CRV fake com número de registro, data de expedição e órgão emissor para testes de sistemas de documentação de frota completa.
Perguntas frequentes
Como gerar placa com prefixo de UF específica sem hardcodear toda a tabela do Detran?+
Mapeie UF à primeira letra do prefixo (SP→A, RJ→B, MG→C). Aleatorize as outras duas do ALPHA válido. Exemplo: `const p1 = ufMap[uf]; const p23 = randomChar(ALPHA) + randomChar(ALPHA); prefix = p1 + p23;` Não é sequencial como Detran real, mas passa validadores de correlação regional.
RENAVAM usa o mesmo algoritmo mod 11 do CPF?+
Não. RENAVAM usa 10 pesos (3,2,9,8,7,6,5,4,3,2); CPF usa dois módulos com 9 e 8 pesos. Cálculo final diverge: RENAVAM `remainder < 2 ? 0 : 11 - remainder`, CPF sempre `11 - remainder`. Reutilizar código de CPF gera dígitos inválidos.
Gerar 100 mil placas únicas com Set causa garbage collection lag perceptível?+
Não. 100k placas em Set ~0.04% do espaço válido (280M); colisões são raras. GC lag é imperceptível. Acima de 500k, chunking com `setImmediate()` reduz picos de memória. Teste em seu ambiente se for gerar milhões.
Meu validador legado rejeita placas Mercosul — devo gerar antigas em produção?+
Não force Mercosul em sistema que não suporta. Em testes, mantenha suites separadas: `testMercosulSupport()` usa `generateMercosulPlate()`, `testLegacyCompatibility()` usa `generateOldPlate()`. A migração do validador é responsabilidade da arquitetura, não do gerador.
Qual blocklist mínima protege contra rejeição em APIs de seguro ou Detran?+
SSP, PRF, DPF, PGR, STF, STJ, CNJ cobrem siglas de órgãos públicos (Resolução Contran 752/2018). Adicione palavrões se testar input real. APIs de terceiros podem ter listas maiores — solicite ao provedor ou use whitelist de prefixos conhecidos da UF ao invés de blocklist.