AG
Projetos
Ativo2026 — em produção

Radar de Vagas

Candidatura em vaga de tecnologia é um jogo de latência. Este radar fecha a janela entre a publicação e o aviso para 30 minutos.

Meu papel

Projeto e implementação, da medição do problema ao workflow em produção

01

O problema

Analisei meu próprio histórico de candidaturas na Gupy procurando o que separava as que avançaram das que não. O perfil não era o gargalo — a janela era. Quatro horas depois da publicação, uma vaga tinha 7 propostas concorrentes; dezoito horas depois, 67.

Descobrir a vaga abrindo o site significa entrar já na faixa ruim da curva. Não é um problema de escrever currículo melhor: é de chegar antes.

02

A solução

Um coletor bate na API pública da Gupy a cada 30 minutos, filtra e pontua as vagas contra quatro perfis de busca, e grava as novas no Postgres. O que ainda não foi avisado sai no WhatsApp pela Evolution API.

A orquestração é um workflow de n8n de 11 nós, e o coletor em Python usa só a biblioteca padrão — sem dependência para instalar. Todo o stack sobe com um docker compose, em portas próprias para não colidir com outros projetos na mesma máquina.

O detalhe que sustenta o resto: o estado da notificação mora numa coluna, não no INSERT. Uma falha de entrega no WhatsApp devolve a vaga ao ciclo seguinte em vez de queimá-la em silêncio.

03

Como funciona

  1. 01

    Cron com jitter

    O agendamento roda aos minutos 7 e 37 em vez de 0 e 30. São 192 requisições por dia num endpoint que qualquer navegador chama — o que chama atenção é regularidade de relógio, não volume.

  2. 02

    Coleta por perfil

    Quatro querystrings com filtro no servidor cobrem estágio e júnior, remoto e presencial. O loop pagina enquanto a resposta vier cheia e espera 2s entre perfis.

  3. 03

    Filtro e pontuação

    A comparação normaliza acento e olha só o título da vaga. Cada vaga recebe uma pontuação de aderência antes de entrar no banco.

  4. 04

    Gravação idempotente

    A carga entra por COPY numa tabela temporária e daí para a tabela final, com ON CONFLICT DO NOTHING sobre a chave composta de fonte e id externo.

  5. 05

    Notificação do que ficou pendente

    Um SELECT pega o que ainda não foi avisado, monta a mensagem e envia pela Evolution. Só depois do envio bem-sucedido a vaga é carimbada.

  6. 06

    Vigia do vigia

    Um Error Trigger avisa quando o radar quebra e um heartbeat diário informa quantas vagas passaram nas últimas 24h. Sem ele, nenhuma vaga nova hoje e o radar morreu na terça produzem o mesmo silêncio.

04

Decisões técnicas

Medir a API antes de escrever o coletor

O plano original usava 18 termos de busca por palavra-chave. Medindo contra a API ao vivo, 6 deles retornavam zero e uma vaga de Data Engineer júnior remoto era invisível para todos. Troquei por 4 querystrings com filtro no servidor: a cobertura foi de 117 para cerca de 1.716 vagas.

Filtrar pelo título, não pela descrição

Filtrar por descrição parecia mais abrangente e não era: 47% de falso positivo em dados e 30% de falso negativo em suporte. A descrição menciona o mundo inteiro; o título é o que a vaga realmente é.

COPY em vez de INSERT montado por string

Títulos vêm com aspas, vírgulas e emoji direto de uma API pública. A carga vai para uma tabela temporária via CSV e daí para a tabela final — concatenar isso em SQL seria injeção esperando acontecer.

Paginar pelo tamanho da página, não pelo total

O total informado pela API satura no tamanho do limite: um perfil com 1.370 vagas responde total de 100. A paginação segue enquanto a resposta vier cheia, e não enquanto faltar chegar ao total.

Os dois caminhos do filtro provam paridade

O filtro existe em Python, no coletor, e em JavaScript, dentro do n8n. Sobre as mesmas 253 vagas reais os dois aprovam exatamente as mesmas vagas com os mesmos scores — testado, não presumido.

Segredo fora do git desde o commit inicial

O gitignore foi o primeiro arquivo versionado, antes de qualquer credencial existir. A chave da Evolution entra no n8n como credencial, nunca como header literal — o export do workflow grava header literal em texto puro. O telefone mora numa tabela de configuração: chave se rotaciona, telefone não.

Bugs que só apareceram rodando de verdade

O terminal do Windows estourava em título com emoji, 7 em cada 253 vagas. E o WhatsApp só fecha o asterisco de negrito quando ele está colado a um caractere não-branco, então título que termina em espaço exibia os asteriscos crus. Nenhum dos dois sai de ler o código.

05

O que o sistema faz

  • Avisa no WhatsApp em até 30 minutos da publicação
  • Cobre quatro perfis de busca, remoto e presencial
  • Pontua a aderência de cada vaga antes de notificar
  • Não repete vaga já enviada, mesmo após falha de entrega
  • Avisa sozinho quando o próprio radar quebra
  • Heartbeat diário para distinguir silêncio de defeito
  • Coletor com cinco modos, incluindo ensaio sem gravar nem enviar
  • Sobe inteiro com um docker compose, em portas próprias
06

Stack

Python 3.12n8nPostgreSQLEvolution APIDocker ComposeAPI Gupy