← Voltar ao blog

Popular MySQL com dados brasileiros falsos no staging

·12 min de leitura

Usar dados reais de clientes no banco de staging é uma prática comum — e um passivo jurídico real. Este guia mostra como popular um banco MySQL de staging com CPF, CNPJ, endereços e chaves PIX estruturalmente válidos, sem tocar em nenhum dado de produção, usando a FakeForge API.

Dados reais em staging violam a LGPD

O que diz o Art. 7º, IX da LGPD sobre bases de teste

A Lei 13.709/2018 (LGPD) exige base legal para qualquer tratamento de dado pessoal. O Art. 7º, IX autoriza tratamento para "proteção do crédito" e finalidades legítimas — não para popular bancos de teste. Ao copiar um dump de produção para staging, você está tratando CPF, e-mail e endereço sem base legal, em ambiente sem os controles de segurança do prod.

Riscos concretos: vazamento de PII em ambientes não-produtivos

Ambientes de staging têm superfície de ataque maior: credenciais menos protegidas, acesso liberado para terceiros (QA externo, parceiros, devs freelancers) e logs menos monitorados. Uma credencial de banco de staging comprometida expõe dados reais de clientes com as mesmas consequências legais de um vazamento em produção. A ANPD pode aplicar multas de até 2% do faturamento bruto do grupo, limitado a R$ 50 milhões por infração (LGPD Art. 52).

Por que anonimização parcial não basta

Mascarar apenas CPF e e-mail enquanto mantém nome completo + endereço + histórico de compras cria um conjunto re-identificável. Estudos de re-identificação mostram que três atributos quase-identificadores (CEP de 8 dígitos, data de nascimento, sexo) são suficientes para identificar mais de 87% dos registros em bases populacionais. Anonimização eficaz exige geração sintética desde a origem — não truncamento de dados reais.

O que são dados falsos estruturalmente válidos

Diferença entre dado aleatório e dado válido (dígitos verificadores)

Um CPF gerado como sequência aleatória de 11 dígitos falha em qualquer validador. Sistemas de cadastro, gateways de pagamento e ERPs rejeitam na hora. Dado falso útil para teste é aquele que passa pelas mesmas validações que o dado real — dígitos verificadores corretos, DDD existente, CEP com prefixo válido para o estado informado.

CPF com módulo 11, CNPJ com dois dígitos verificadores

O CPF usa dois dígitos verificadores calculados por módulo 11 sobre os nove primeiros dígitos. O CNPJ aplica o mesmo algoritmo duas vezes com pesos diferentes. Gerar esses números na mão é viável, mas manter a lógica atualizada (a Receita Federal já alterou regras de validação) e garantir ausência de colisões com CPFs reais exige cuidado extra — daí o valor de uma API dedicada.

Endereço correlacionado: CEP, município, UF e DDD coerentes

Um endereço com CEP 01310-100 (Av. Paulista, SP) e DDD 71 (Brasília) é tecnicamente inválido para testes de sistemas que validam coerência geográfica. A FakeForge gera endereços onde CEP, bairro, município, UF e DDD estão correlacionados — útil para testar sistemas de frete, cálculo de ICMS interestadual e cobertura de entrega.

Arquitetura da solução: API + seed script + MySQL

Visão geral do fluxo (FakeForge API → Node.js → MySQL)

FakeForge API (/api/generate)
        │
        ▼
  Node.js seed script (TypeScript)
   ├── fetchFakeData()      ← chama a API em lotes
   ├── mapToRow()          ← transforma resposta em objeto plano
   └── insertBatch()       ← INSERT parametrizado via mysql2
        │
        ▼
   MySQL staging (usuarios, empresas, transacoes)

Dependências necessárias: mysql2, node-fetch (ou fetch nativo Node 18+)

npm install mysql2
npm install --save-dev tsx dotenv

Node 18+ inclui fetch nativo. Se usar Node 16, adicione node-fetch@3. O tsx permite rodar TypeScript diretamente sem compilar para CI.

Estrutura de pastas do projeto de seed

seed/
├── .env               # DATABASE_URL e FAKEFORGE_API_KEY
├── schema.sql         # CREATE TABLE dos três domínios
├── seed.ts            # script principal
└── types.ts           # interfaces TypeScript dos payloads da API

Configurar a FakeForge API para geração em lote

Endpoint /api/generate — parâmetros type, quantity e format

GET https://fakeforge.com.br/api/generate?type=pessoa&quantity=500&format=json

Os tipos relevantes para este seed: pessoa, cep, empresa, pix, cartao. A documentação completa de parâmetros está em /docs.

Autenticação com API key (header x-api-key)

const response = await fetch(
  `https://fakeforge.com.br/api/generate?type=${type}&quantity=${quantity}&format=json`,
  {
    headers: {
      "x-api-key": process.env.FAKEFORGE_API_KEY ?? "",
    },
  }
);

Limites do plano gratuito (100 chamadas/dia) e quando usar o plano Dev

PlanoChamadas/diaBatch máximoPreço
Grátis100100 itensR$ 0
Devilimitado10.000 itensR$ 29/mês
Teamilimitado10.000 itensR$ 79/mês

Para um seed de 500 registros por tabela, são ao menos 5 chamadas por tipo. Se o seed rodar diariamente no CI, o plano gratuito esgota rapidamente. O plano Dev cobre a maioria dos pipelines de staging individuais.

Criar o schema MySQL compatível com os campos gerados

Tabela usuarios, empresas e transacoes

CREATE TABLE IF NOT EXISTS usuarios (
  id            INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  cpf           CHAR(11)        NOT NULL UNIQUE,  -- sem pontuação
  nome          VARCHAR(120)    NOT NULL,
  email         VARCHAR(200)    NOT NULL,
  telefone      VARCHAR(20),
  cep           CHAR(8)         NOT NULL,
  logradouro    VARCHAR(200),
  bairro        VARCHAR(100),
  municipio     VARCHAR(100),
  uf            CHAR(2)
);

CREATE TABLE IF NOT EXISTS empresas (
  id            INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  cnpj          CHAR(14)        NOT NULL UNIQUE,  -- sem pontuação
  razao_social  VARCHAR(200)    NOT NULL,
  cep           CHAR(8),
  municipio     VARCHAR(100),
  uf            CHAR(2),
  banco_codigo  CHAR(3),
  agencia       VARCHAR(10),
  conta         VARCHAR(20)
);

CREATE TABLE IF NOT EXISTS transacoes (
  id            INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  usuario_id    INT UNSIGNED    NOT NULL,
  chave_pix     VARCHAR(100),
  valor         DECIMAL(12, 2)  NOT NULL,
  banco_nome    VARCHAR(80),
  cartao_numero CHAR(16),
  cartao_bandeira VARCHAR(20),
  created_at    TIMESTAMP       DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (usuario_id) REFERENCES usuarios(id)
);

Tipos de coluna adequados

CHAR(11) para CPF e CHAR(14) para CNPJ armazenam sem pontuação — a pontuação é responsabilidade da camada de apresentação. DECIMAL(12,2) evita arredondamentos em valores monetários. Nunca use FLOAT para dinheiro.

Script de seed completo em TypeScript

Tipos e função de fetch

// types.ts
export interface PessoaGerada {
  nome: string;
  cpf: string;        // "12345678901" — sem máscara
  email: string;
  telefone: string;
}

export interface CepGerado {
  cep: string;        // "01310100"
  logradouro: string;
  bairro: string;
  municipio: string;
  uf: string;
}
// seed.ts — fetchFakeData
import "dotenv/config";

async function fetchFakeData<T>(type: string, quantity: number): Promise<T[]> {
  const url = new URL("https://fakeforge.com.br/api/generate");
  url.searchParams.set("type", type);
  url.searchParams.set("quantity", String(quantity));
  url.searchParams.set("format", "json");

  const res = await fetch(url.toString(), {
    headers: { "x-api-key": process.env.FAKEFORGE_API_KEY ?? "" },
  });

  if (!res.ok) {
    throw new Error(`FakeForge API error: ${res.status} ${await res.text()}`);
  }

  const json = await res.json();
  return json.data as T[];
}

Script de seed completo com mysql2

import mysql from "mysql2/promise";
import type { PessoaGerada, CepGerado } from "./types";

const BATCH_SIZE = 100;
const TOTAL = 500;

async function seedUsuarios(conn: mysql.Connection) {
  for (let offset = 0; offset < TOTAL; offset += BATCH_SIZE) {
    const qty = Math.min(BATCH_SIZE, TOTAL - offset);

    const [pessoas, enderecos] = await Promise.all([
      fetchFakeData<PessoaGerada>("pessoa", qty),
      fetchFakeData<CepGerado>("cep", qty),
    ]);

    const rows = pessoas.map((p, i) => [
      p.cpf,
      p.nome,
      p.email,
      p.telefone,
      enderecos[i]?.cep ?? null,
      enderecos[i]?.logradouro ?? null,
      enderecos[i]?.bairro ?? null,
      enderecos[i]?.municipio ?? null,
      enderecos[i]?.uf ?? null,
    ]);

    const results = await Promise.allSettled(
      rows.map((row) =>
        conn.execute(
          `INSERT IGNORE INTO usuarios
             (cpf, nome, email, telefone, cep, logradouro, bairro, municipio, uf)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
          row
        )
      )
    );

    const failed = results.filter((r) => r.status === "rejected").length;
    if (failed > 0) console.warn(`Lote ${offset}: ${failed} falhas ignoradas`);

    console.log(`Inseridos ${offset + qty} / ${TOTAL} usuários`);

    // backoff linear simples entre lotes
    await new Promise((r) => setTimeout(r, 200));
  }
}

async function main() {
  const conn = await mysql.createConnection(process.env.DATABASE_URL ?? "");
  try {
    await seedUsuarios(conn);
  } finally {
    await conn.end();
  }
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
DICA: INSERT IGNORE descarta silenciosamente duplicatas de CPF, tornando o script idempotente sem precisar de lógica extra. Se quiser atualizar registros existentes em vez de ignorar, troque por ON DUPLICATE KEY UPDATE nome = VALUES(nome), email = VALUES(email).

Exportar direto em SQL com CREATE TABLE pronto

Usar format=sql na API

Para volumes pequenos ou onboarding rápido, a FakeForge gera um arquivo .sql completo com CREATE TABLE e todos os INSERTs:

curl -s \
  -H "x-api-key: $FAKEFORGE_API_KEY" \
  "https://fakeforge.com.br/api/generate?type=pessoa&quantity=200&format=sql" \
  -o seed_pessoas.sql

mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" < seed_pessoas.sql

Quando preferir SQL direto vs script Node.js

Use format=sql para seeds pontuais, demos ou quando o ambiente não tem Node.js disponível. Use o script TypeScript quando precisar de lotes acima de 10 mil registros, correlação entre tabelas (foreign keys), ou transformações customizadas no dado antes de inserir.

Automatizar o seed no pipeline de CI/CD

GitHub Actions: job seed-staging

name: Seed Staging

on:
  push:
    branches: [main]

jobs:
  seed-staging:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Instalar dependências
        run: npm ci --prefix seed

      - name: Truncar tabelas e rodar seed
        env:
          DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
          FAKEFORGE_API_KEY: ${{ secrets.FAKEFORGE_API_KEY }}
        run: npx tsx seed/seed.ts

      - name: Rodar testes e2e
        run: npm run test:e2e

Variáveis de ambiente como secrets

STAGING_DATABASE_URL e FAKEFORGE_API_KEY vivem em Settings → Secrets and variables → Actions no GitHub. Nunca coloque valores reais no yaml ou no repositório.

Idempotência

Adicione um step antes do seed para garantir tabelas limpas:

await conn.execute("SET FOREIGN_KEY_CHECKS = 0");
await conn.execute("TRUNCATE TABLE transacoes");
await conn.execute("TRUNCATE TABLE usuarios");
await conn.execute("TRUNCATE TABLE empresas");
await conn.execute("SET FOREIGN_KEY_CHECKS = 1");

Validar a integridade dos dados inseridos

Query de sanidade pós-seed

-- Contagem por tabela
SELECT 'usuarios'   AS tabela, COUNT(*) AS total FROM usuarios
UNION ALL
SELECT 'empresas',              COUNT(*)          FROM empresas
UNION ALL
SELECT 'transacoes',            COUNT(*)          FROM transacoes;

-- Campos obrigatórios nulos
SELECT COUNT(*) AS cpf_nulo    FROM usuarios  WHERE cpf IS NULL OR cpf = '';
SELECT COUNT(*) AS cnpj_nulo   FROM empresas  WHERE cnpj IS NULL OR cnpj = '';
SELECT COUNT(*) AS valor_zero  FROM transacoes WHERE valor <= 0;

-- Faixa de valores monetários
SELECT MIN(valor), MAX(valor), AVG(valor) FROM transacoes;

Checar CPF com a API

Para validar uma amostra dos CPFs inseridos, use o endpoint /validar-cpf:

curl "https://fakeforge.com.br/api/generate?type=validar-cpf&cpf=12345678901"

O mesmo vale para CNPJ via /validar-cnpj. Em produção, faça essa validação no seed script antes do INSERT — não depois.

Boas práticas de segurança no ambiente de staging

Nunca commitar API key

# .gitignore
.env
.env.local
*.env
AVISO: Uma API key de FakeForge exposta no repositório pode ser usada por terceiros para esgotar sua cota ou gerar cobranças no seu plano. Ferramentas como git-secrets ou gitleaks detectam esse tipo de vazamento antes do push.

Restringir acesso ao banco de staging

Configure o security group (AWS) ou firewall do MySQL para aceitar conexões apenas do range de IP do CI (GitHub Actions publica a lista de IPs em https://api.github.com/meta) e de VPN corporativa. Evite 0.0.0.0/0 mesmo em staging.

Rotacionar dados falsos a cada ciclo de release

Dados sintéticos envelhecem: testes ficam acoplados a IDs fixos, seeds tornam-se fixtures implícitas. A cada release, execute TRUNCATE + seed para garantir que os testes e2e cubram combinações novas de dados.

Resumo

  • Schema primeiro: defina CHAR(11) para CPF, CHAR(14) para CNPJ e DECIMAL(12,2) para valores antes de rodar qualquer seed — tipos errados geram truncamentos silenciosos.
  • Use INSERT IGNORE ou ON DUPLICATE KEY UPDATE para tornar o script idempotente e seguro para re-execução no CI.
  • Separe segredos do código: DATABASE_URL e FAKEFORGE_API_KEY como GitHub Secrets, nunca hardcoded ou commitados.
  • Valide após inserir: execute a query de sanidade e cheque CPFs com /validar-cpf para detectar falhas de mapeamento antes dos testes e2e rodarem.
  • Para volumes pequenos, format=sql basta: baixe o arquivo e importe com mysql <; para seeds com correlação entre tabelas, use o script TypeScript com mysql2.
  • Próximos geradores úteis: cartão de crédito com Luhn válido, PIS/PASEP para sistemas de RH e Título de Eleitor cobrem os tipos restantes em sistemas financeiros e de RH.

Perguntas frequentes

Posso reutilizar o script de seed se meu database já tem dados?+

Sim, com INSERT IGNORE ou ON DUPLICATE KEY UPDATE. Exemplo: `INSERT IGNORE INTO usuarios (cpf, nome, email...) VALUES (?, ?, ?)` descarta silenciosamente CPFs duplicados. Se quiser atualizar campos existentes, use `ON DUPLICATE KEY UPDATE nome = VALUES(nome)`. Backups automáticos recomendados.

O que fazer se a API FakeForge retornar erro 429 (rate limit)?+

Implemente backoff exponencial no script. Exemplo: comece com 1s, dobre a cada tentativa (1s, 2s, 4s, 8s). A biblioteca `p-retry` (npm) automação isso. Para plano gratuito, reduza batch de 100 para 50 ou migre para plano Dev se seed roda diariamente no CI.

Como garantir que telefone e CEP estão correlacionados corretamente?+

A API FakeForge retorna DDD válido baseado no CEP — não precisa validação extra. Mas se misturar `pessoa` + `cep` de lotes separados (como no artigo), ordem fica aleatória. Use um seed com `empresa` (que retorna pessoa + cep correlacionados) ou indexe por nome para casar manualmente.

É seguro rodar seed em paralelo com testes e2e contra o mesmo DB?+

Não. Use TRUNCATE antes do seed atomicamente. Melhor: rode seed em banco limpo antes dos testes (GitHub Actions: `seed-staging` → `test-e2e`). Se precisa testar dados dinâmicos, crie um usuário fixture por teste (INSERT com dados únicos do teste, limpe com `teardown`).

Quanto custa gerar 10 mil registros por mês no plano Dev?+

Plano Dev é R$ 29/mês com limite de até 10 mil itens por request (ilimitado número de requests). 10 mil registros = 1 chamada. Custo fixo R$ 29/mês independente do volume. Economicamente viável para staging contínuo. Team é R$ 79/mês com features extras (suporte, SLA).