Privacidade Por Equipe DevBras

Capa do artigo: Pedido de cópia dos dados (LGPD): como gerar um export que a pessoa consegue abrir

Pedido de cópia dos dados (LGPD): como gerar um export que a pessoa consegue abrir

Monte um export com o que você guarda sobre a pessoa, em arquivos que ela abre sem programa especial, sem dado de terceiros e entregue por link protegido que expira.

A cliente da loja pediu uma cópia de tudo o que vocês têm sobre ela. Uma semana depois recebeu um arquivo user_48213.json com campos como fk_addr_id, datas em milissegundos e o hash da senha dela no meio. Ela não entendeu nada, mandou de novo para o suporte e agora o pedido tem duas reclamações em vez de uma. A resposta curta: o export precisa ser legível por quem pediu, conter o que vocês guardam sobre a pessoa (cadastro, conteúdo, pedidos, preferências, histórico de consentimento), deixar de fora o que é de outras pessoas ou segredo de segurança, e chegar por um caminho protegido que expira.

Na prática, isso vira um pacote com um resumo em HTML que abre no navegador, planilhas CSV para o que é lista (pedidos, endereços) e um JSON para quem quiser levar os dados a outro serviço. Tudo gerado por um job, sem um desenvolvedor rodando SQL na mão a cada pedido.

Por que o export sai no formato do banco

O primeiro export quase sempre nasce como um SELECT * serializado. Quem escreveu pensou em completude, não em leitura. O resultado carrega nomes de coluna internos, chaves estrangeiras sem significado, códigos de status (st=3) e campos técnicos que ninguém fora do time entende.

O outro problema é o contrário: o SELECT * traz demais. Hash de senha, token de sessão, segredo de autenticação em dois fatores, nota interna do antifraude, mensagem que outra pessoa mandou. Parte disso coloca a própria conta em risco se o arquivo vazar; parte é dado de terceiro, que a pessoa não tem direito de receber só porque aparece na mesma tabela.

A LGPD garante ao titular o acesso aos dados e a portabilidade (artigo 18), e o artigo 19 fala em entregar a confirmação ou o acesso em formato simplificado, de imediato, ou por declaração clara e completa num prazo curto. Confira os detalhes no texto oficial e nas orientações da ANPD. O espírito é claro: a pessoa precisa entender o que vocês sabem sobre ela.

Como montar o export passo a passo

  1. Inventarie as fontes. Liste cada tabela, bucket e serviço externo que guarda dado da pessoa: cadastro, endereços, pedidos, avaliações escritas por ela, preferências de e-mail, registro de consentimento, tickets de suporte. Se você já fez o mapa da adequação à LGPD, ele é o ponto de partida.
  2. Defina o que sai de cada fonte. Para cada coluna, decida: entra traduzida, entra como está, ou fica de fora com motivo escrito.
  3. Traduza para gente. Status vira texto ("Entregue"), data vira data e hora no fuso dela, centavos viram reais formatados, chaves internas somem.
  4. Corte o que não é dela. Mensagens recebidas de outra pessoa, nome de quem avaliou o produto junto, notas internas que revelem regra antifraude. Na dúvida sobre segredo comercial, leve ao jurídico, não decida no código.
  5. Gere em formatos comuns. HTML para ler, CSV para listas, JSON para portabilidade, tudo num ZIP.
  6. Entregue com proteção. Arquivo em armazenamento privado, link que exige login, expira em poucos dias e é registrado quando baixado.
  7. Registre o atendimento. Data do pedido, data de entrega, quem aprovou, o que foi incluído. Isso é a sua prova de que respondeu.

Exemplo: job em Node.js que gera o pacote

No caso da loja, o job recebe o id da cliente, busca as fontes com consulta parametrizada e grava um ZIP numa pasta privada. Usa mysql2/promise e o pacote archiver:

const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  timezone: 'Z'
});

const STATUS = { 1: 'Aguardando pagamento', 2: 'Pago', 3: 'Entregue', 4: 'Cancelado' };

function csv(linhas, colunas) {
  const esc = (v) => '"' + String(v ?? '').replace(/"/g, '""') + '"';
  return [colunas.map(esc).join(';')]
    .concat(linhas.map((l) => colunas.map((c) => esc(l[c])).join(';')))
    .join('\r\n');
}

function html(texto) {
  return String(texto ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
}

async function gerarExport(clienteId, pastaSaida) {
  const [[cliente]] = await pool.execute(
    'SELECT nome, email, telefone, criado_em FROM clientes WHERE id = ?', [clienteId]);
  if (!cliente) throw new Error('Cliente não encontrada: ' + clienteId);

  const [pedidos] = await pool.execute(
    'SELECT numero, status, total_centavos, criado_em FROM pedidos WHERE cliente_id = ? ORDER BY criado_em',
    [clienteId]);
  const [consentimentos] = await pool.execute(
    'SELECT finalidade, aceitou, texto_versao, registrado_em FROM consentimentos WHERE cliente_id = ?',
    [clienteId]);

  const fmtData = (d) => new Date(d).toLocaleString('pt-BR', { timeZone: 'America/Sao_Paulo' });
  const pedidosLegiveis = pedidos.map((p) => ({
    numero: p.numero,
    situacao: STATUS[p.status] || 'Outro',
    total: (p.total_centavos / 100).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }),
    data: fmtData(p.criado_em)
  }));

  const resumo = '<!doctype html><meta charset="utf-8"><title>Seus dados</title>'
    + '<h1>Dados de ' + html(cliente.nome) + '</h1>'
    + '<p>E-mail: ' + html(cliente.email) + '<br>Telefone: ' + html(cliente.telefone)
    + '<br>Cliente desde: ' + html(fmtData(cliente.criado_em)) + '</p>'
    + '<p>Pedidos: ' + pedidos.length + ' (detalhes em pedidos.csv)</p>';

  const destino = path.join(pastaSaida, 'export-' + clienteId + '-' + Date.now() + '.zip');
  const saida = fs.createWriteStream(destino);
  const arquivo = archiver('zip', { zlib: { level: 9 } });
  const terminou = new Promise((resolve, reject) => {
    saida.on('close', resolve);
    saida.on('error', reject);
    arquivo.on('error', reject);
  });

  arquivo.pipe(saida);
  arquivo.append(resumo, { name: 'LEIA-PRIMEIRO.html' });
  arquivo.append('\ufeff' + csv(pedidosLegiveis, ['numero', 'situacao', 'total', 'data']), { name: 'pedidos.csv' });
  arquivo.append(JSON.stringify({ cliente, pedidos: pedidosLegiveis, consentimentos }, null, 2), { name: 'dados.json' });
  await arquivo.finalize();
  await terminou;
  return destino;
}

module.exports = { gerarExport };

Dois detalhes que evitam reclamação. O \ufeff no começo do CSV é o BOM, que faz o Excel abrir acentos corretamente. O separador ; casa com planilhas configuradas em português. E repare no que a consulta não pede: nada de senha_hash, token_2fa ou score_fraude. Pedir só as colunas certas é mais seguro do que pedir tudo e filtrar depois.

Como entregar sem criar um vazamento novo

O ZIP tem endereço, telefone e histórico de compras. Mandar como anexo de e-mail ou num link público eterno transforma o atendimento num risco. O caminho que funciona: guardar o arquivo em armazenamento privado, avisar por e-mail que a cópia está pronta e exigir login para baixar. O link do e-mail leva à área logada, não ao arquivo.

Defina validade (sete dias é um bom padrão) e apague o arquivo depois disso. A lógica é a mesma do link de redefinição que expira. E inclua o próprio arquivo no seu controle de retenção com data, senão a pasta de exports vira uma segunda base de clientes que ninguém lembra de limpar.

Se a pessoa não tem mais acesso à conta, confirme a identidade antes de entregar por outro canal. Enviar o export para quem só disse "sou eu" é entregar os dados a quem pedir primeiro.

Erros comuns

  • Exportar o banco cru. A pessoa recebe códigos e chaves, não entende e reabre o pedido. Você atendeu no papel e não no fato.
  • Incluir hash de senha ou segredo de 2FA. Se o arquivo vazar, facilita ataque contra a própria conta dela.
  • Levar dado de outra pessoa junto. Mensagens recebidas, nomes de quem dividiu o pedido. Você resolve um pedido e cria um incidente com terceiro.
  • Esquecer as fontes fora do banco principal. Ticket de suporte, ferramenta de e-mail, planilha do comercial. O export parece completo e não é.
  • Link público sem prazo. Qualquer pessoa que receber o e-mail encaminhado baixa tudo, para sempre.
  • Não registrar o atendimento. Quando a ANPD ou a própria pessoa perguntar, você não tem como provar data nem conteúdo.

Checklist

  • Existe uma lista escrita das fontes que guardam dado de cliente, incluindo serviços externos.
  • Cada coluna exportada tem decisão registrada: entra traduzida, entra como está ou fica de fora.
  • O pacote tem um arquivo que abre no navegador sem programa especial.
  • Hash de senha, tokens e segredos de autenticação não aparecem em nenhum arquivo.
  • Dados de outras pessoas foram removidos ou reduzidos ao mínimo.
  • O download exige login, expira e o arquivo é apagado depois.
  • Cada pedido fica registrado com data de entrada, data de entrega e conteúdo.
  • Alguém do suporte consegue disparar o export sem pedir ajuda ao time técnico.

Pegue a conta de teste da sua loja e gere o export agora: você entenderia tudo o que está ali se fosse a cliente, e há alguma coluna no arquivo que não deveria ter saído do banco?

Avalie este artigo

Referência: https://www.gov.br/anpd/pt-br