Uma falha de login causada pela divergência no armazenamento de números de telefone expôs uma vulnerabilidade. A conta de um usuário foi bloqueada porque o banco de dados continha o mesmo número de celular alemão de duas formas — 0171 5550134 em uma linha e +49 171 5550134 em outra — fazendo com que o sistema os tratasse como entradas distintas. O resultado: um OTP nunca chegou, e o botão "enviar código" tornou-se uma loteria.

Por que números de telefone são mais difíceis que datas

Desenvolvedores costumam confiar em uma regex para domar a entrada de números de telefone. Essa confiança se estilhaça no momento em que um número cruza uma fronteira ou um plano nacional muda. Datas seguem um calendário previsível; números de telefone sofrem mutações com operadoras, regulamentações e convenções culturais.

O custo oculto de strings puras

Armazenar um número de telefone como uma string simples parece único — até que duas representações apontem para a mesma linha. Sistemas de OTP que consultam o banco de dados em busca de "o" número acabam enviando um código para um duplicado que nunca chega ao usuário.

O problema piora quando os números estão em campos numéricos. Uma coluna BIGINT remove o + inicial e quaisquer zeros à esquerda, transformando +49 171 5550134 em 491715550134. Sem o sinal de mais, reconstruir o formato original torna-se um palpite.

O contrato E.164

O plano internacional de numeração telefônica, E.164, define uma representação única e portátil:

  • Começa com um +
  • Seguido por um código de país de 1 a 3 dígitos
  • Depois o número do assinante
  • No máximo 15 dígitos no total
  • Sem espaços, pontos ou traços

O E.164 não garante que o número esteja ativo; ele apenas garante que a string siga um padrão estrutural válido. Trate-o como um contrato de formato, não como um oráculo de acessibilidade.

Armadilhas comuns que regex não consegue corrigir

  • Armazenamento numérico – BIGINT descarta o + e os zeros à esquerda. Use uma coluna de texto (TEXT ou VARCHAR) em vez disso.
  • Regex codificadas (hard-coded) – Planos nacionais mudam. O México removeu seu prefixo de tronco em 2019; a Argentina agora exige um 9 após o código do país para linhas móveis. Um padrão estático torna-se obsoleto rapidamente.
  • Remoção cega de zeros – Telefones fixos italianos mantêm um zero à esquerda, os alemães não. Uma regra genérica de "remover zeros à esquerda" corrompe os dados italianos enquanto deixa os números alemães intactos.
  • Assumir que formato é igual a entregabilidade – o libphonenumber valida a estrutura, mas não pode dizer se o aparelho está ligado ou se o número foi portado.

Construindo um pipeline confiável

  1. Peça o país – Adicione um seletor de país nos formulários de cadastro e passe essa região para o parser.
  2. Mostre a formatação em tempo real – Use a formatação “AsYouType” para que os usuários vejam o padrão correto enquanto digitam.
  3. Valide no blur – Execute a validação após o usuário sair do campo, em vez de a cada tecla pressionada; isso reduz o atrito.
  4. Persista apenas a string E.164 – Armazene o número normalizado com o prefixo + no banco de dados.
  5. Formate na interface – Converta de volta para um layout amigável ao ser humano apenas na UI ou em modelos de e-mail.

Ao migrar dados legados, mantenha as linhas que falharem na validação. Analise cada entrada existente em uma nova coluna, sinalize as falhas e gere um relatório. Exclusões silenciosas criam tickets de suporte que, mais tarde, se transformam em correções dispendiosas.

Um checklist rápido para desenvolvedores

  • Armazene números como TEXT/VARCHAR no formato E.164.
  • Use a biblioteca libphonenumber do Google; ela lida com a realidade complexa dos planos globais.
  • Forneça uma região padrão para usuários que omitirem o código do país.
  • Chame is_valid_number para cadastros em tempo real; isso verifica se o número se ajusta às regras regionais.
  • Use is_possible_number ao limpar dados em massa; isso captura entradas obviamente malformadas sem rejeitar casos limítrofes.

O tratamento adequado de números de telefone não é um "luxo"; é um pré-requisito para qualquer sistema que dependa de um contato confiável com o usuário. Ao normalizar para E.164 e delegar o parsing para uma biblioteca testada em batalha, os desenvolvedores eliminam uma classe de bugs que corroem silenciosamente a confiança e a receita. Trate números de telefone como dados estruturados, não como texto livre, e deixe que os padrões façam o trabalho pesado.