25 de out. de 2025·7 min de leitura

Corrigir um esquema de banco de dados bagunçado de um app de IA, passo a passo

Aprenda a consertar um esquema de banco de dados bagunçado gerado por IA usando um fluxo de triagem seguro: mapear entidades, remover duplicatas, normalizar tabelas e adicionar restrições.

Corrigir um esquema de banco de dados bagunçado de um app de IA, passo a passo

Como é um esquema bagunçado em apps gerados por IA

Um esquema bagunçado normalmente não parece errado à primeira vista. O app roda, as páginas carregam e os dados aparecem. O problema começa quando você tenta mudar algo e percebe que não sabe qual tabela é a fonte da verdade.

Protótipos gerados por IA frequentemente criam tabelas às pressas: uma por tela, feature ou prompt. Os nomes derivam com o tempo (User, Users, user_profiles). Formatos de ID variam (inteiro aqui, UUID ali). Relacionamentos ficam implícitos no código em vez de serem aplicados no banco.

Sinais comuns de que seu esquema já está custando caro:

  • Tabelas duplicadas armazenando a mesma coisa com colunas levemente diferentes
  • Identificadores confusos (múltiplos campos id, ou chaves estrangeiras armazenadas como texto simples)
  • Nomenclatura inconsistente (createdAt vs created_at, status vs state)
  • Colunas que misturam responsabilidades (nome, e‑mail e JSON cru tudo junto)
  • "Relacionamentos suaves" (order.userId existe, mas nada garante que corresponda a um usuário real)

O que quebra raramente é apenas "o banco" isoladamente. São as features amarradas a ele: sessões de login que deixam de coincidir com usuários, registros de pagamento que não fazem join corretamente, relatórios que contam em dobro e telas de admin que dependem de um nome de coluna antigo.

Um exemplo pequeno: um protótipo pode ter tanto customers quanto users, e pedidos às vezes apontam para customers.id e às vezes para users.email. Limpar isso é possível, mas a triagem é sobre reduzir risco primeiro: mapear o que existe, confirmar o que o app realmente lê e grava, e então melhorar a estrutura em passos pequenos.

Prepare trilhas de segurança antes de tocar em nada

Antes de mexer em migrations, coloque guardrails para que um passo errado não cause um outage. Muitos apps gerados por IA funcionam "o suficiente" porque os dados são inconsistentes, não porque o esquema é sólido. Seu trabalho é tornar isso mais seguro sem quebrar quem já usa.

Comece com um backup completo e prove que consegue restaurá‑lo. Não presuma. Restaure em um banco separado e rode o app contra ele. Se você não consegue restaurar, não tem rede de segurança.

Em seguida, crie uma cópia de staging que combine com a produção o máximo possível. Use o mesmo esquema, um snapshot recente dos dados e a mesma versão do app. O staging é onde você testa migrations, verifica telas-chave e observa performance de queries antes de tocar nos usuários reais.

Anote os poucos fluxos do app que precisam continuar funcionando durante refactors. Mantenha curto e específico. Por exemplo: "sign up -> verify email -> create project", "checkout -> payment success -> receipt", "painel admin carrega em menos de 3 segundos".

Por fim, decida um plano de rollback para cada mudança. Se uma migration falhar no meio, você precisa saber se vai rodar para frente (terminar a correção) ou reverter (restaurar e desfazer o código). Uma checklist simples ajuda:

  • Backup criado, restauração testada e timestamp registrado
  • Cópia de staging atualizada e app conectado a ela
  • 5–10 fluxos críticos documentados e responsáveis atribuídos
  • Migration é reversível (ou existe um plano claro de roll‑forward)
  • Notas de monitoramento: quais métricas ou logs confirmarão o sucesso

Passo 1: Mapear entidades e relacionamentos

Antes de alterar qualquer tabela, faça um mapa claro do que o app entende pelos dados. Apps gerados por IA costumam criar tabelas extras que soam parecidas mas representam a mesma coisa, ou misturam múltiplos conceitos em uma tabela. Um mapa simples evita "consertar" a parte errada.

Comece listando as entidades centrais em linguagem comum. Pense em usuários, contas, times, pedidos, pagamentos, assinaturas, faturas, mensagens. Se você não consegue descrever uma tabela em uma frase, provavelmente ela está fazendo demais.

Em seguida, encontre a chave primária de cada tabela. Anote qual coluna é o identificador verdadeiro (frequentemente id) e onde ela está ausente ou é pouco confiável. Fique atento a tabelas que usam e‑mail, nome de usuário ou uma composição de campos como identificador — essas tendem a gerar duplicatas depois.

Esboce os relacionamentos

Esboce como as coisas se conectam:

  • One‑to‑many: um usuário tem muitos pedidos
  • Many‑to‑many: usuários podem pertencer a muitos times (normalmente precisa de uma tabela de junção)
  • Links opcionais: um pedido pode ter cupom, mas nem sempre

Se você vir many‑to‑many armazenado como IDs separados por vírgula ou arrays JSON, marque isso cedo. Afeta todos os passos seguintes.

Trace leituras e gravações

Anote onde o app lê e grava cada tabela: quais telas, quais endpoints da API e quais jobs de background ou tarefas agendadas. Uma forma rápida é procurar no código pelos nomes de tabela e colunas-chave.

Exemplo: se a tela de checkout grava em orders e em user_orders, você provavelmente encontrou fontes de verdade concorrentes. Não una nada ainda. Capture isso no mapa.

Passo 2: Identificar duplicatas e fontes de verdade conflitantes

Apps gerados por IA costumam criar o mesmo conceito duas vezes. Você pode ver customer e user, ou team e org, cada um com sua própria tabela e campos ligeiramente diferentes. Tudo parece OK até você tentar reportar, aplicar permissões ou corrigir um bug e perceber que não existe uma única verdade.

Analise o mapa de esquema do Passo 1 e agrupe tabelas por significado, não por nome. Puxe algumas linhas de cada tabela candidata. Você quer achar lugares onde duas tabelas armazenam a mesma coisa do mundo real.

Padrões comuns de duplicação incluem tabelas de identidade (customer vs user, profile vs user_details), tabelas de organização (team vs org vs workspace), tabelas de cobrança (invoice vs bill vs payment_request) e dados de endereço repetidos em vários lugares.

Depois, procure múltiplos identificadores que se referem à mesma entidade. Ferramentas de IA frequentemente adicionam novos IDs no meio do desenvolvimento, então você acaba com id, user_id, uid e external_id flutuando por aí. Bugs começam quando uma parte do app faz join em uid e outra em id, e os dados se dividem silenciosamente.

Também fique de olho no mesmo campo armazenado com nomes e formatos diferentes (createdAt vs created_at, telefone como texto e número). Mesmo se os valores baterem hoje, eles vão divergir.

Escolha uma fonte de verdade por entidade e escreva-a. Por exemplo: "users.id é a chave interna única; external_id é opcional e única; a tabela customers será mesclada em users." Você ainda não está mudando nada. Está decidindo quem vence quando as tabelas discordam.

Passo 3: Normalizar tabelas gradualmente

Normalização é só uma maneira de fazer cada tabela significar uma coisa. Em apps gerados por IA, o ganho mais rápido é remover as partes que criam dados conflitantes enquanto mantém o app funcionando.

Comece escaneando colunas que misturam ideias. Exemplos comuns: um único campo address com rua, cidade e CEP juntados, ou um campo status que mistura significados como "paid+shipped+refunded". Esses campos são difíceis de validar e penosos para relatórios.

Em seguida, procure dados repetidos colocados no lugar errado. Um cheiro clássico é uma tabela orders com item1_name, item1_qty, item2_name, ou um blob JSON de itens. Funciona para demo e depois desmorona quando você precisa de devoluções, remessas parciais ou totais precisos.

Priorize normalização onde dói:

  • Inconsistência de dados (mesmo nome de usuário em três lugares, totais que não batem)
  • Necessidade de relatórios (consultas financeiras e operacionais lentas ou erradas)
  • Autenticação e permissões (papéis copiados entre tabelas)
  • Qualquer coisa que impeça adicionar constraints (você não consegue adicionar foreign keys ainda)
  • Tabelas que crescem rápido (logs, eventos, pedidos)

Vá devagar para evitar quebrar features. Crie tabelas novas ao lado das antigas, preencha os dados, e então mude as leituras em passos pequenos. Por exemplo, adicione order_items mantendo os campos antigos de items. Escreva novos items nos dois lugares por um curto período, verifique se os totais batem e então faça o app ler apenas de order_items.

Passo 4: Adicionar constraints em ordem segura

Refactor without a rewrite
Replace spaghetti schemas with a maintainable structure, step by step.

Constraints são onde o cleanup deixa de ser "arrumadinho" e vira segurança. Elas transformam suposições em regras que o banco aplica. A chave é adicioná‑las numa ordem que reflita a realidade hoje, não o modelo perfeito que você quer amanhã.

Comece com regras de baixo risco que já são verdade para a maioria das linhas. Se uma coluna já é quase sempre preenchida, torná‑la NOT NULL frequentemente pega bugs reais sem surpreender o app. Pequenos CHECK também ajudam, desde que reflitam o comportamento atual (por exemplo, "status deve ser um destes valores").

Antes de adicionar foreign keys, limpe as linhas ruins existentes. Foreign keys falham se você tiver registros órfãos, como orders.user_id apontando para um usuário inexistente. Conte quantas linhas falhariam, corrija‑as primeiro e então imponha a relação.

Índices únicos vêm depois. Eles impedem duplicatas no futuro, mas podem quebrar fluxos de cadastro ou importações se seus dados ainda contiverem duplicatas. Faça deduplicação primeiro (Passo 2) e só então adicione unicidade onde reflita uma regra real do negócio (por exemplo, um e‑mail por usuário).

Se você precisa de uma coluna obrigatória nova, faça em fases:

  • Adicione a coluna como nullable com um default para novas linhas
  • Preencha os dados existentes de forma controlada
  • Atualize o app para gravar a nova coluna
  • Só então mude para NOT NULL

Exemplo: um app gerado por IA pode ter users.email às vezes em branco, mas a UI trata como obrigatório. Preencha ou corrija os vazios, depois adicione NOT NULL e só então um índice único em email.

Passo 5: Atualizar queries do app sem quebrar features

A maioria dos outages acontece aqui, não na migration em si. O app ainda pede colunas antigas ou grava dados no lugar errado.

Comece pelas leituras. Atualize SELECTs e respostas da API para conseguirem lidar com a estrutura nova e a antiga por um curto período. Isso pode ser tão simples quanto ler das novas tabelas primeiro e fazer fallback para as colunas antigas se faltarem dados.

Uma ordem segura:

  • Adicione novas queries para o novo esquema, mantendo as antigas funcionando
  • Troque os caminhos de leitura primeiro (idealmente atrás de uma flag de feature)
  • Migre as escritas depois para que novos registros caiam nas novas tabelas
  • Mantenha compatibilidade temporária (uma view, coluna de compatibilidade ou dual‑write)
  • Remova queries antigas só após monitorar tráfego real e corrigir casos de borda

Para escritas, seja rigoroso na validação. Se seu novo esquema divide uma coluna em duas tabelas, garanta que todo create/update preencha ambos os lugares corretamente. Se fizer dual‑write, seja breve e registre toda divergência para correção.

Exemplo: um protótipo armazena status de pedido em orders.status e em payments.status. Atualize leituras para preferir a nova fonte de verdade, depois mude o código de escrita para atualizar só o novo campo, enquanto um trigger temporário mantém o campo antigo em sincronia.

Um fluxo de migração prático e repetível

Clean up schema the safe way
Turn your AI-built prototype into a database structure you can trust in production.

A abordagem mais segura é mover pouco e poder desfazer. Pense em ciclos “uma mudança, uma prova”, não em reescritas de fim de semana.

O loop repetível

Comece com uma migração minúscula que adiciona algo novo sem remover o que já existe.

  1. Adicione a tabela/coluna/índice novo que você quer (deixe a estrutura antiga no lugar).
  2. Preencha os dados existentes de forma controlada (em batches se a tabela for grande).
  3. Verifique contagens e relacionamentos (linhas copiadas, foreign keys batem, sem NULLs inesperados).
  4. Troque as leituras primeiro (atualize o app para ler da estrutura nova) e monitore.
  5. Troque as escritas (dual‑write brevemente se necessário) e então remova o caminho antigo.

Após cada loop, rode os principais fluxos de usuário end‑to‑end no staging: cadastro/login, criar o objeto central (pedido/projeto/post) e qualquer passo de pagamento ou e‑mail. Esses fluxos pegam os problemas do tipo “migrado certo, mas o app quebrou”.

Não esqueça a performance

A limpeza pode deixar coisas mais lentas silenciosamente. Após cada mudança, verifique queries lentas novas, índices faltando e consultas que pararam de usar índices porque o tipo de coluna mudou.

Mantenha uma nota curta para cada migração com (a) como revertê‑la, e (b) o que você mediu para confirmar que está correta. Equipes emperram quando mudam muito de uma vez e não conseguem identificar qual passo causou a quebra.

Erros comuns que causam outages durante o cleanup

Outages frequentemente ocorrem quando o banco fica mais rígido antes dos dados estarem prontos. Se você adicionar foreign keys, NOT NULL ou índices únicos cedo demais, a migration pode falhar em linhas existentes, ou o app começar a lançar erros após o deploy.

Outra armadilha é mudar o que um ID significa. Protótipos feitos por IA misturam IDs como strings, inteiros e às vezes e‑mails como identificador. Trocar user_id de string para inteiro (ou mudar o formato de um UUID) quebra joins, payloads da API, caches e qualquer código que trate o ID como string. Se precisar fazer isso, planeje uma transição com backfills cuidadosos e um período de compatibilidade temporário.

Deletar tabelas “antigas” cedo demais também é arriscado. Mesmo que a UI principal esteja migrada, algo geralmente ainda lê a tabela antiga: uma página admin, um webhook handler ou um job noturno.

Zonas fáceis de esquecer incluem jobs de background, ferramentas admin, pipelines de analytics, importações/exportações e apps móveis ou clientes antigos chamando endpoints antigos.

Erros de segurança podem causar outages também. Protótipos às vezes guardam tokens de auth, chaves de API ou links de reset de senha em texto puro. Depois, alguém adiciona criptografia ou regras de expiração e logins falham. Trate segredos como material perigoso: rote, expire e migre com corte claro.

Exemplo: você adiciona um índice único em users.email, mas a tabela tem [email protected] e [email protected]. Logins em produção falham porque a etapa de normalizar e deduplicar foi pulada.

Checklist rápido antes e depois de cada mudança

O maior risco não é o SQL. É mudar regras de dados enquanto o app ainda roda fluxos reais. Use esta checklist sempre que editar o esquema, mesmo que pareça pequeno.

Antes de mudar qualquer coisa

  • Backup confirmado, e teste de restauração bem‑sucedido (mesmo que seja em uma cópia local).
  • Seu mapa de entidades atualizado (tabelas, campos-chave e como os registros se relacionam) e outra pessoa revisou.
  • Duplicatas e fontes concorrentes de verdade removidas ou claramente depreciadas (sem novas gravações).
  • Verificação de limpeza de dados para a próxima constraint (nulls, linhas órfãs, valores inválidos).
  • Plano de rollback escrito para esta mudança específica (o que você desfaz primeiro e como verificar que é seguro).

Pausa de um minuto e escreva o raio de explosão: quais telas ou endpoints quebram se essa migration falhar.

Depois da mudança

  • Rode os fluxos-chave de usuário end‑to‑end: cadastro, login e a transação central do app.
  • Confirme que o app está lendo da nova fonte da verdade (e não caindo silenciosamente nas colunas antigas).
  • Verifique se as constraints estão realmente ativas (tente inserir uma linha inválida em ambiente de teste).
  • Cheque logs por novos erros de query e consultas lentas.
  • Atualize o mapa de entidades novamente para que a próxima mudança comece da realidade.

Cenário de exemplo: limpar users e orders em um protótipo

Make it production ready
Make the app ready for real traffic with fixes verified by humans.

Um problema comum em protótipos: você abre o banco e encontra users, customers e accounts que armazenam e‑mail, nome e campos "quase senha". Pedidos podem apontar para customers.id em um lugar, users.id em outro, e às vezes só armazenar uma string de e‑mail.

Comece escolhendo uma fonte de verdade para identidade. Se o app tem login, users normalmente é o melhor pivô. Mantenha as outras tabelas por enquanto, mas trate‑as como perfis a serem mesclados.

Para mapear IDs com segurança, adicione uma coluna temporária como customers.user_id e preencha‑a batendo por um valor estável (geralmente e‑mail normalizado). Para registros que não casam bem, crie uma lista de revisão e corrija‑os manualmente antes de adicionar constraints.

Depois, olhe os pedidos. Protótipos frequentemente repetem campos de item na tabela orders (como item_1_name, item_2_price) ou armazenam um blob JSON que muda de formato. Crie order_items com order_id, product_name, unit_price, quantity e preencha‑a a partir dos dados existentes. Mantenha as colunas antigas temporariamente para o app continuar funcionando enquanto você atualiza queries.

Quando os dados estiverem limpos e mapeados, adicione constraints com calma:

  • Coloque NOT NULL apenas em campos que você já preencheu completamente
  • Adicione unicidade (users.email) depois de remover duplicatas
  • Adicione foreign keys (orders.user_id -> users.id, order_items.order_id -> orders.id)

Por fim, teste o que os usuários sentem imediatamente: login e reset de senha, cadastro (incluindo "e‑mail já usado"), criar pedidos e itens, totais no checkout e a página de histórico de pedidos.

Próximos passos: quando chamar especialistas

Se a limpeza ainda está em sandbox, você provavelmente consegue avançar devagar e aprender. Quando há usuários reais e dados reais, o risco muda rápido. Uma migração aparentemente inofensiva pode quebrar sign‑in, corromper pedidos ou expor dados privados.

Considere ajuda especializada se envolver autenticação/permissões, pagamentos/faturas, dados sensíveis, o app estiver em produção sem tolerância a downtime, ou se o banco e a lógica do app estiverem muito emaranhados.

Se for terceirizar, você terá resultados mais rápidos se puder compartilhar um dump do esquema e backup recente, uma lista curta dos fluxos críticos (cadastro, checkout, reembolsos, ações admin), os maiores erros que você vê (queries lentas, migrations quebradas, totais inconsistentes) e incidentes conhecidos (duplicatas, registros faltando, problemas de segurança).

Se você está lidando com um protótipo gerado por IA e precisa de uma revisão end‑to‑end (esquema, lógica e segurança juntos), FixMyMess em fixmymess.ai foca em remediação de apps gerados por IA e pode começar com uma auditoria de código gratuita para identificar os problemas mais críticos antes de qualquer migration rodar.

Perguntas Frequentes

How do I know if my schema is “messy” even if the app still works?

Um esquema bagunçado é aquele em que você não consegue responder com confiança “qual tabela é a fonte da verdade?”. O app pode continuar funcionando, mas mudanças ficam arriscadas porque dados são duplicados, IDs não batem e relacionamentos existem apenas no código em vez de serem aplicados pelo banco.

What should I do before I run any migrations on a prototype database?

Comece com um backup completo e prove que consegue restaurá‑lo em um banco separado. Depois, crie um ambiente de staging que reflita a produção e execute seus fluxos críticos de usuário lá antes de qualquer migração, para detectar quebras cedo.

Which “critical flows” should I test during schema cleanup?

Escolha um pequeno conjunto de fluxos completos e reais que representem como os usuários obtêm valor — por exemplo, cadastro/login, criar o registro principal do app e qualquer passo de pagamento ou envio de e‑mail. Se esses fluxos passarem no staging após cada mudança, é muito menos provável que você quebre o app em produção.

How do I map entities and relationships in an AI-generated app quickly?

Faça um mapa simples de entidades: liste os conceitos centrais em linguagem comum, identifique a chave primária de cada tabela e esboce como os registros se relacionam. Depois, trace leituras e gravações procurando nomes de tabela/coluna no código para saber o que o app realmente toca.

How can I spot duplicate tables or competing sources of truth?

Procure tabelas que representem a mesma coisa do mundo real com colunas ou nomes levemente diferentes, como User vs Users vs user_profiles. Confirme amostrando linhas e vendo se a mesma pessoa/pedido/time aparece em várias tabelas com identificadores diferentes.

What’s the safest way to fix inconsistent IDs (UUID vs int, email as ID, etc.)?

Escolha uma chave interna e mantenha‑a (normalmente users.id). Para a transição, adicione uma coluna de mapeamento temporária, preencha‑a automaticamente quando possível, atualize o código para usar a nova chave e mantenha uma janela de compatibilidade curta para que dados antigos e novos coexistan.

Should I normalize everything at once or do it gradually?

Normalize onde dói primeiro: lugares que causam inconsistência de dados, relatórios errados, problemas de autenticação/permissões ou tabelas que crescem rápido. Faça gradualmente: adicione tabelas novas ao lado das antigas, preencha os dados, mude leituras, depois escritas, e só remova estruturas antigas quando o tráfego real confirmar segurança.

When should I add foreign keys, NOT NULL, and unique constraints?

Siga a ordem que reflita a realidade hoje: comece com regras que já são verdade na prática (por exemplo NOT NULL em colunas normalmente preenchidas), limpe linhas inválidas antes de chaves estrangeiras e remova duplicatas antes de índices únicos. Isso evita falhas na migração e erros em produção.

Why do schema cleanups usually break at the query/update step, not the migration step?

Altere primeiro as leituras para que o app consiga lidar com ambos os formatos por um período curto; depois migre as escritas. Se fizer dual‑write, mantenha por pouco tempo e registre divergências para correção antes de eliminar o caminho antigo.

When should I bring in FixMyMess instead of trying to clean the schema myself?

Traga ajuda quando houver usuários reais e dinheiro em jogo, especialmente em autenticação, permissões, pagamentos ou dados sensíveis. Se você herdou um código gerado por IA muito emaranhado, FixMyMess pode começar com uma auditoria gratuita do código e remediar esquema, lógica e segurança com verificação humana.