Aplicação full stack para automatizar a busca, análise e priorização de vagas de tecnologia com apoio de Inteligência Artificial local.
O sistema utiliza o perfil profissional do usuário como fonte de contexto para comparar vagas, calcular compatibilidade, identificar requisitos atendidos e ausentes e, para oportunidades com alta compatibilidade, gerar materiais personalizados de candidatura.
Status: MVP técnico avançado. Os principais fluxos de backend já foram desenvolvidos, mas a integração frontend/backend e alguns recursos de produto ainda estão em evolução.
- Visão geral
- Principais funcionalidades
- Stack
- Arquitetura
- Fluxo principal
- Estrutura do projeto
- Pré-requisitos
- Configuração
- Executando o projeto
- API
- Inteligência Artificial
- Automação do LinkedIn
- Regras de negócio
- Estado atual
- Próximos passos
- Decisões técnicas
- Segurança
O Auto Job Match foi criado para reduzir o trabalho repetitivo envolvido na procura por vagas:
- acessar diferentes plataformas;
- executar pesquisas;
- abrir e ler vagas;
- comparar requisitos com o currículo;
- identificar oportunidades compatíveis;
- adaptar o resumo profissional;
- reorganizar experiências relevantes;
- gerar carta de apresentação;
- guardar links e resultados;
- evitar reprocessamento de vagas.
A aplicação centraliza esse processo e transforma uma vaga encontrada em um resultado estruturado contendo dados da oportunidade, compatibilidade e materiais de candidatura.
O sistema possui estrutura para armazenar:
- nome;
- e-mail;
- senha;
- telefone;
- senioridade;
- resumo profissional;
- currículo completo;
- habilidades;
- LinkedIn;
- GitHub e outros links, conforme a versão do modelo;
- demais informações profissionais.
O perfil é utilizado como fonte oficial de contexto para a análise da IA.
Para cada vaga, a IA analisa informações como:
- tecnologias;
- requisitos técnicos;
- responsabilidades;
- senioridade;
- experiência exigida;
- conhecimentos desejáveis;
- localização;
- modalidade;
- idioma;
- formação;
- contexto da oportunidade.
O resultado contém:
- porcentagem de compatibilidade;
- justificativa;
- requisitos atendidos;
- habilidades faltantes;
- indicação se vale a pena aplicar.
Quando o match ultrapassa o limite definido no projeto, são gerados:
- resumo profissional adaptado;
- experiências adaptadas;
- carta de apresentação;
- conteúdo personalizado para currículo;
- currículo em LaTeX.
A geração é feita em uma segunda etapa de IA para evitar processamento desnecessário em vagas com baixa compatibilidade.
As vagas analisadas são persistidas no PostgreSQL, permitindo recuperar:
- vaga;
- empresa;
- descrição;
- link;
- porcentagem;
- justificativa;
- requisitos atendidos;
- habilidades faltantes;
- decisão de candidatura;
- currículo gerado;
- carta gerada.
O projeto possui integrações/desenvolvimentos para:
- LinkedIn via Playwright;
- Gupy por API/endpoints estruturados;
- Remotive como prova de conceito.
A arquitetura procura utilizar dados estruturados quando disponíveis e Playwright quando a navegação automatizada é necessária.
- Java 21
- Spring Boot
- Spring Web
- Spring Data JPA
- Hibernate
- Spring Security
- Auth0 Java JWT
- BCrypt
- Spring AI
- Playwright for Java
- Maven
- Lombok
- React
- TypeScript
- Vite
- Tailwind CSS v4
- Fetch API
localStoragepara armazenamento do JWT
- PostgreSQL 15
- Docker / Docker Compose
- DBeaver
- Ollama
- Chromium
- Gupy
- Remotive
- Spring AI
- Ollama
- LaTeX
A aplicação utiliza arquitetura modular em camadas.
┌─────────────────────────────────────────────┐
│ USUÁRIO │
│ Login • Perfil • Vagas • Resultados │
└──────────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ FRONTEND REACT + TS │
│ Login • Dashboard • Perfil • Vagas │
└──────────────────────┬──────────────────────┘
│ HTTP / JSON + JWT
▼
┌─────────────────────────────────────────────┐
│ BACKEND SPRING BOOT │
│ │
│ Controllers │
│ Services │
│ Security / JWT │
│ MatchService │
│ Spring AI │
│ Playwright │
│ Integrações externas │
│ Geração de LaTeX │
└──────────────┬──────────────┬───────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ PostgreSQL │ │ Ollama │
│ Perfis │ │ Análise IA │
│ Vagas │ │ Geração │
└─────────────┘ └─────────────┘
┌─────────────────────────┐
│ Fontes externas │
│ LinkedIn • Gupy • etc. │
└─────────────────────────┘
O fluxo interno principal segue:
Controller
↓
Service
↓
Repository
↓
JPA / Hibernate
↓
PostgreSQL
Para recursos externos:
Service
├── Spring AI → Ollama
├── Playwright → LinkedIn
├── HTTP → Gupy / Remotive
└── Repository → PostgreSQL
Frontend
↓
POST /auth/login
↓
AuthenticationManager
↓
AutenticacaoService
↓
PerfilRepository.findByEmail()
↓
BCrypt
↓
TokenService
↓
JWT
↓
Frontend
VagaRequest
↓
Carregar perfil
↓
Montar prompt
↓
Ollama
↓
AnaliseMatch
↓
VagaAnalisada
↓
PostgreSQL
Análise
↓
Match > 70
↓
Segunda chamada à IA
↓
Resumo + experiências + carta
↓
Template LaTeX fixo
↓
Currículo personalizado
A estrutura documentada é aproximadamente:
auto_job_match/
├── backend/
│ ├── pom.xml
│ ├── linkedin-session.json
│ └── src/main/
│ ├── java/com/auto_job_match/
│ │ ├── Application.java
│ │ ├── controller/
│ │ │ ├── PerfilController.java
│ │ │ ├── MatchController.java
│ │ │ ├── BotController.java
│ │ │ └── AuthController.java
│ │ ├── dto/
│ │ │ ├── VagaRequest.java
│ │ │ ├── AnaliseMatch.java
│ │ │ ├── MateriaisCandidatura.java
│ │ │ ├── AuthenticationDto.java
│ │ │ └── LoginResponseDto.java
│ │ ├── model/
│ │ │ ├── Perfil.java
│ │ │ └── VagaAnalisada.java
│ │ ├── repository/
│ │ │ ├── PerfilRepository.java
│ │ │ └── VagaAnalisadaRepository.java
│ │ ├── service/
│ │ │ ├── MatchService.java
│ │ │ ├── AutomacaoNavegadorService.java
│ │ │ └── VagaBuscadorService.java
│ │ └── security/
│ │ ├── SecurityConfigurations.java
│ │ ├── SecurityFilter.java
│ │ ├── TokenService.java
│ │ └── AutenticacaoService.java
│ └── resources/
│ └── application.properties
├── frontend/
│ ├── package.json
│ ├── vite.config.ts
│ ├── index.html
│ └── src/
│ ├── main.tsx
│ ├── App.tsx
│ ├── index.css
│ ├── pages/
│ │ ├── Login.tsx
│ │ └── Dashboard.tsx
│ └── services/
│ └── api.ts
└── docker-compose.yml
Os nomes podem variar conforme a versão atual do código.
Antes de iniciar, é necessário ter:
- Java 21;
- Maven;
- Node.js e npm;
- Docker;
- PostgreSQL via Docker;
- Ollama;
- Chromium/Playwright configurado pelo backend.
Para automação do LinkedIn, também é necessária uma sessão autenticada criada pelo próprio fluxo do projeto.
A configuração utilizada durante o desenvolvimento foi:
spring.datasource.url=jdbc:postgresql://localhost:5432/job_match_db
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialectDocker Compose:
services:
postgres:
image: postgres:15
container_name: job_db
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: job_match_db
ports:
- "5432:5432"A configuração documentada utiliza:
spring.ai.ollama.base-url=http://localhost:11434
spring.ai.ollama.chat.options.model=llama3
spring.ai.ollama.chat.options.format=json
spring.ai.ollama.chat.options.temperature=0.2O modelo exato deve ser confirmado no application.properties atual antes da execução.
Na raiz:
docker compose up -dVerifique:
docker psO PostgreSQL deve estar disponível na porta 5432.
O Ollama deve estar executando localmente na porta:
11434
Confirme também se o modelo configurado pelo backend está instalado.
Entre na pasta:
cd backendExecute:
mvn spring-boot:runO backend utiliza a porta:
8080
cd frontend
npm installnpm run devO Vite utiliza, conforme a configuração documentada:
http://localhost:5173
Se
npm run devinformar que o script não existe, confirme se o terminal está dentro defrontend/e se opackage.jsonpossui"dev": "vite".
POST /auth/loginEntrada:
{
"email": "usuario@email.com",
"senha": "senha"
}Saída:
{
"token": "eyJ..."
}O token deve ser enviado nas rotas protegidas:
Authorization: Bearer <token>POST /api/perfisExemplo:
{
"nome": "Usuário",
"email": "usuario@email.com",
"senha": "senha",
"telefone": "...",
"senioridade": "Júnior",
"resumo": "...",
"curriculo": "...",
"habilidades": [
"Java",
"Spring Boot",
"React"
]
}GET /api/perfisA implementação atual precisa evitar que a senha, mesmo em BCrypt, seja exposta diretamente no JSON.
POST /api/matchEntrada:
{
"titulo": "Desenvolvedor Java",
"empresa": "Empresa X",
"descricaoCompleta": "Descrição integral da vaga...",
"linkVaga": "https://..."
}Saída esperada:
{
"id": 1,
"titulo": "Desenvolvedor Java",
"empresa": "Empresa X",
"descricaoCompleta": "...",
"porcentagemMatch": 82,
"justificativa": "...",
"valeApenaAplicar": true,
"requisitosAtendidos": [
"Java",
"Spring Boot"
],
"habilidadesFaltantes": [
"Kubernetes"
],
"curriculoGerado": "...",
"coverLetterGerada": "...",
"linkVaga": "https://..."
}GET /api/matchRetorna as vagas analisadas persistidas no banco.
POST /api/match/buscar-webÉ uma prova de conceito síncrona.
Rotas documentadas:
/api/bot/gerar-sessao
/api/bot/buscar
O método HTTP final deve ser confirmado no BotController atual.
A IA é executada localmente por meio do Ollama e integrada ao backend usando Spring AI.
O ChatClient transforma a resposta da IA diretamente em objetos Java:
chatClient
.prompt()
.user(prompt)
.call()
.entity(AnaliseMatch.class);A IA analisa a compatibilidade.
Perfil + Currículo + Vaga
↓
Ollama
↓
AnaliseMatch
Somente vagas acima do limite definido recebem geração de materiais.
Regra registrada:
if (porcentagemMatch > 70) {
// gerar materiais
}O limite > 70 deve ser mantido ou explicitamente alterado caso a regra de negócio passe a utilizar >= 70.
O Playwright controla o Chromium e realiza:
- abertura do LinkedIn;
- carregamento da sessão;
- localização das vagas;
- extração dos cards;
- abertura das vagas;
- extração dos detalhes;
- envio ao
MatchService; - processamento de múltiplas vagas;
- controle de erros e retry;
- deduplicação durante a execução.
O login é realizado manualmente na primeira execução.
Depois, o estado da sessão é salvo em:
linkedin-session.json
Nas execuções seguintes, o Playwright reutiliza esse estado.
Não versionar esse arquivo no Git.
Durante uma execução, o bot utiliza:
Set<String> vagasProcessadasO identificador utilizado é o jobId da plataforma.
A deduplicação entre execuções ainda precisa ser implementada no banco.
Estas regras são fundamentais para a continuidade do projeto:
- A IA não pode inventar experiências ou tecnologias.
- O perfil é a fonte oficial dos dados do candidato.
- O currículo completo deve ser considerado.
- A resposta da análise deve ser estruturada.
- Currículo e carta só devem ser gerados para matches acima do limite definido.
- A automação coleta a vaga; o
MatchServicedecide, gera e persiste. - Uma vaga só deve ser marcada como processada depois de sucesso.
- Uma falha em uma vaga não deve interromper o processamento das demais.
- O LaTeX deve ser estruturado pelo backend.
- Senhas devem utilizar BCrypt.
- Rotas protegidas exigem JWT.
- O link original da vaga deve ser preservado.
- A sessão do LinkedIn é informação sensível.
- APIs estruturadas devem ser preferidas a scraping quando disponíveis.
- Estrutura backend Spring Boot
- PostgreSQL
- Persistência do perfil
- Repository de perfil
- Análise de vagas com Ollama
- Spring AI
- Resposta estruturada da IA
- Pontuação de compatibilidade
- Justificativa
- Requisitos atendidos
- Habilidades faltantes
- Recomendação de candidatura
- Persistência das vagas analisadas
- Histórico básico no backend
- Geração condicional de materiais
- Geração textual de carta
- Geração de currículo em LaTeX
- Template LaTeX controlado pelo backend
- Playwright
- Sessão reutilizável do LinkedIn
- Processamento de múltiplas vagas
- Deduplicação durante a execução
- Retry da IA
- Logging
- JWT
- BCrypt
- CORS
- Frontend React + TypeScript
- Login
- Logout
- Dashboard inicial
- Tailwind CSS
- Alinhar completamente frontend e backend
- Atualizar frontend de
/api/vagas/analisarpara/api/match - Atualizar frontend de
response.text()pararesponse.json() - Tipar a resposta como
VagaAnalisada - Exibir análise completa no dashboard
- Histórico no frontend
- Tela de perfil
- Associar cada vaga ao usuário autenticado
- Remover uso de ID fixo no
MatchService - Ocultar senha dos retornos JSON
- Deduplicação persistente no banco
- Validação dos DTOs
- Tratamento de
401no frontend - Filtros e paginação
- Processamento assíncrono
- Estado do processamento do bot
- Tratamento centralizado de exceções
- Migrações com Flyway
- Agendamento automático
- Busca periódica
- Candidatura assistida/automática
- Preenchimento automático de perguntas
- Identificação de etapas que exigem ação manual
- Integração final com Gupy
- Novas fontes de vagas
- Geração de PDF compilado
- Dashboard com métricas
- Filas/jobs
- Testes automatizados
- Suporte completo a múltiplos usuários
- Docker Compose completo com backend, frontend, banco e Ollama
O próximo passo técnico recomendado é corrigir o contrato entre frontend e backend antes de adicionar novas funcionalidades visuais.
Ordem recomendada:
- Confirmar a rota real do
MatchController. - Confirmar o JSON retornado por
POST /api/match. - Atualizar
src/services/api.ts. - Substituir
/api/vagas/analisarpor/api/match. - Substituir
Promise<string>porPromise<VagaAnalisada>. - Substituir
response.text()porresponse.json(). - Garantir
Authorization: Bearer ${token}. - Atualizar
Dashboard.tsxpara trabalhar com o objeto completo. - Exibir:
- porcentagem;
- justificativa;
- requisitos atendidos;
- habilidades faltantes;
- recomendação;
- carta;
- currículo/LaTeX.
- Testar o fluxo completo pelo navegador.
Depois:
- Remover o ID fixo do perfil.
- Utilizar o usuário autenticado.
- Criar associação entre
VagaAnalisadaePerfil. - Criar histórico no frontend.
- Criar tela de perfil.
- Implementar deduplicação persistente.
- Transformar a busca automática em job assíncrono.
- Implementar agendamento depois que o processamento assíncrono estiver estável.
O backend centraliza:
- regras de negócio;
- banco;
- IA;
- automação;
- segurança;
- integrações;
- geração de documentos.
Foi escolhido como banco persistente principal.
O H2 foi utilizado apenas durante a prototipagem.
A IA local foi escolhida por:
- privacidade;
- ausência de custo por chamada;
- possibilidade de execução offline;
- controle do modelo;
- valor técnico para o projeto.
O Spring AI abstrai a comunicação com o Ollama e fornece o ChatClient, prompts e conversão estruturada das respostas.
A análise e a geração de materiais são separadas para:
- reduzir processamento;
- evitar gerar currículo para vagas fracas;
- diminuir tempo de execução;
- facilitar tratamento de erros.
A IA deve retornar dados estruturados em vez de texto livre para facilitar:
- persistência;
- regras condicionais;
- integração frontend/backend;
- processamento automático.
A IA gera somente o conteúdo variável.
O template fica no Java.
Isso reduz problemas com:
- escape;
- JSON inválido;
- comandos LaTeX;
- alterações involuntárias de layout.
Quando uma fonte fornece dados estruturados, a preferência é consumir a API em vez de renderizar e extrair o HTML.
O projeto utiliza:
- Spring Security;
- JWT;
- HMAC256;
- BCrypt;
- API stateless;
- filtro JWT;
- CORS.
Configuração documentada do JWT:
Issuer: AutoJobMatch
Subject: e-mail do usuário
Algoritmo: HMAC256
Expiração aproximada: 2 horas
O frontend deve enviar:
Authorization: Bearer <token>A origem documentada para desenvolvimento é:
http://localhost:5173
O backend utiliza:
http://localhost:8080
Nunca salvar senha em texto simples.
O cadastro deve aplicar:
perfil.setSenha(
passwordEncoder.encode(perfil.getSenha())
);Não manter ID de perfil fixo no MatchService.
O perfil deve ser obtido a partir do usuário autenticado/JWT.
Para uma aplicação multiusuário real, VagaAnalisada deve possuir associação com Perfil.
O arquivo:
linkedin-session.json
contém estado de autenticação e deve permanecer fora do versionamento.
O backend consolidado utiliza:
POST /api/match
e retorna um objeto VagaAnalisada.
O frontend precisa trabalhar com JSON tipado.
Backend ████████████████████ Avançado
Banco ████████████████████ Funcional
IA / Ollama ████████████████████ Funcional
Análise de vagas ████████████████████ Funcional
Persistência ████████████████████ Funcional
Playwright / LinkedIn ███████████████████░ Avançado
Autenticação ███████████████████░ Implementada
Frontend ███████████████░░░░░ Em evolução
Integração Front/Back ███████████░░░░░░░░ Pendente
Multiusuário ███████░░░░░░░░░░░░ Pendente
Jobs / Agendamento ████░░░░░░░░░░░░░░░ Planejado
Candidatura automática ██░░░░░░░░░░░░░░░░░ Planejado
Projeto em desenvolvimento. A licença ainda não está definida na documentação atual.