← Curso IA na prática para Programadores: do prompt ao pull request 22 aulas
Escrever código em ciclos curtos

Caso real: o relatório que contava o dia errado

Estudo de caso 12 min de leitura

Um bug de fuso horário do sintoma à correção, em quatro iterações — com o SQL de cada etapa e o teste que impede a volta.

O relatório diário de vendas fechava sempre um pouco diferente do que o time comercial contava. Nunca muito. O suficiente para alguém abrir um chamado dizendo "acho que o número está errado".

Ao final desta aula você consegue
  • Ver o ciclo curto aplicado a um bug real, do sintoma ao teste de regressão.
  • Reconhecer a família de bugs de fuso — a mais comum em relatório.
  • Escrever o teste que trava a correção no lugar.

Iteração 1 — descrever o sintoma sem propor causa

O pedido inicial
Não proponha correção ainda.

Sintoma: o relatório "vendas de ontem" fecha com números levemente diferentes
do que o time comercial conta manualmente. A diferença é sempre pequena
(1 a 4 pedidos) e sempre para menos em alguns dias, para mais em outros.

Ambiente: MySQL 8.0, servidor em UTC, aplicação PHP com timezone
America/Sao_Paulo. Pedidos gravam created_at com NOW() do MySQL.

Query atual:
SELECT COUNT(*) FROM pedidos WHERE DATE(created_at) = '2026-03-13';

Me dê hipóteses ordenadas por probabilidade, cada uma com o experimento
mínimo que a confirma ou elimina. Nada de correção.
Você escreveu
[prompt acima]
A IA respondeu
HIPÓTESE 1 (alta) — Descasamento de fuso entre gravação e leitura.
created_at está em UTC; o recorte DATE() também é feito em UTC; mas o
"dia" que o time comercial usa é America/Sao_Paulo (UTC-3).
Efeito: pedidos feitos entre 21:00 e 23:59 de um dia (horário local)
gravam com data do dia seguinte em UTC, e caem no relatório errado.
Experimento: contar quantos pedidos existem na faixa
'2026-03-14 00:00:00' a '2026-03-14 02:59:59' em UTC. Se esse número
bater com a diferença relatada, é isso.

HIPÓTESE 2 (média) — Pedidos cancelados ou de teste entrando na contagem.
Experimento: comparar COUNT(*) com COUNT(*) filtrando status.

HIPÓTESE 3 (baixa) — Horário de verão em dados históricos.
Experimento: verificar se a diferença muda de tamanho em outubro/fevereiro
de anos anteriores.

OBSERVAÇÃO: DATE(created_at) também impede o uso de índice em created_at.
Isso não causa o erro de contagem, mas provavelmente deixa a query lenta.
O que ler nessa resposta
  • A hipótese 1 explica os dois sinais do sintoma: diferença pequena e ora para mais, ora para menos. Uma hipótese que explica só metade do sintoma é uma hipótese fraca.
  • O experimento proposto é barato: uma query de contagem, sem alterar nada.
  • A observação final sobre o índice é um achado lateral — não é a causa, e veio corretamente separada dela.
  • Nenhuma correção foi proposta, porque o pedido proibiu. Isso evita fechar o diagnóstico cedo demais.

Iteração 2 — rodar o experimento

O experimento que fecha o diagnósticosql
-- Pedidos que a query atual joga para o dia 14 (UTC)
-- mas que aconteceram no dia 13 em horário de Brasília
SELECT COUNT(*) AS pedidos_deslocados
FROM pedidos
WHERE created_at >= '2026-03-14 00:00:00'
  AND created_at <  '2026-03-14 03:00:00';

-- resultado: 3
Três pedidos — exatamente a diferença que o time comercial apontou naquele dia. Hipótese confirmada com um número, não com um argumento.

Iteração 3 — a correção mínima

A correção que parece óbvia
SELECT COUNT(*) FROM pedidos
WHERE DATE(CONVERT_TZ(created_at, '+00:00', '-03:00'))
      = '2026-03-13';

Conta certo — e continua sem usar índice, porque a coluna está dentro de função. Numa tabela grande, troca um bug por uma lentidão.

A correção que conta certo e usa índice
-- Faixa fechada-aberta, calculada no fuso do negócio
-- e convertida para UTC antes de ir ao banco
SELECT COUNT(*) FROM pedidos
WHERE created_at >= '2026-03-13 03:00:00'
  AND created_at <  '2026-03-14 03:00:00';

A coluna fica livre para o índice, e o limite superior aberto (<) elimina o clássico erro de contar duas vezes o registro exato da meia-noite.

A diferença: A conversão de fuso passa a acontecer na aplicação, que sabe o fuso do negócio, e o banco recebe uma faixa simples. Isso também resolve horário de verão de graça, se a aplicação usar uma biblioteca de data com base de fusos atualizada — em vez de somar três horas na mão.

A conversão feita onde ela pertencephp
/**
 * Faixa UTC [inicio, fim) correspondente a um dia civil em São Paulo.
 * Somar horas na mão quebra no horário de verão; DateTimeZone não.
 */
function faixaDoDia(string $dia, string $fuso = 'America/Sao_Paulo'): array
{
    $tz = new DateTimeZone($fuso);
    $inicio = new DateTimeImmutable($dia . ' 00:00:00', $tz);
    $fim = $inicio->modify('+1 day');

    $utc = new DateTimeZone('UTC');

    return [
        $inicio->setTimezone($utc)->format('Y-m-d H:i:s'),
        $fim->setTimezone($utc)->format('Y-m-d H:i:s'),
    ];
}

Iteração 4 — o teste que impede a volta

Teste de regressão com o caso exato do bugphp
public function test_pedido_das_22h_conta_no_dia_local_e_nao_no_seguinte(): void
{
    // 13/03 às 22:00 em São Paulo = 14/03 às 01:00 em UTC
    $this->criarPedidoEm('2026-03-14 01:00:00');   // gravado em UTC

    $total = $this->relatorio->pedidosDoDia('2026-03-13');

    $this->assertSame(1, $total, 'pedido das 22h caiu no dia seguinte');
}

public function test_meia_noite_exata_pertence_ao_dia_que_comeca(): void
{
    $this->criarPedidoEm('2026-03-13 03:00:00');   // 00:00 em São Paulo

    $this->assertSame(1, $this->relatorio->pedidosDoDia('2026-03-13'));
    $this->assertSame(0, $this->relatorio->pedidosDoDia('2026-03-12'));
}
O segundo teste cobre a fronteira — o lugar onde uma correção futura tem mais chance de reintroduzir o erro.
ArmadilhaA família inteira desse bug

Fuso é só uma das formas. A mesma classe de erro aparece em: comparar datetime com date e perder as horas; usar BETWEEN com dois limites fechados e contar a fronteira duas vezes; guardar data de nascimento com fuso (não deve ter); e assumir que o servidor, o banco e o navegador concordam sobre "hoje".

Nenhum desses aparece em desenvolvimento local, porque a máquina de quem programa costuma estar no mesmo fuso do negócio. Todos aparecem em produção.

Como perceber: Números "quase certos" em relatório quase sempre são fronteira de data, não erro de soma.

Checagem antes de seguir
  • A hipótese explicava todos os sinais do sintoma, não parte deles.
  • O experimento produziu um número que bateu com o relato.
  • A correção não colocou a coluna indexada dentro de função.
  • A faixa usa limite superior aberto.
  • Existe teste na fronteira, não só no caso do meio.
Em uma frase
  • Quatro iterações: descrever sem propor, medir, corrigir o mínimo, travar com teste na fronteira.

Comentários e dúvidas

Inscreva-se grátis para comentar, tirar dúvidas, marcar seu progresso e emitir o certificado ao fim do curso.

Inscrever-se grátis com Google

Ainda não há comentários nesta aula.