Sistema de gestão de pedidos para e-commerce com múltiplos afiliados. Consome a Fake Store API, processa dados de forma assíncrona com filas, persiste localmente e expõe um dashboard com métricas, filtros e controle de status.
- Visão geral
- Tecnologias
- Pré-requisitos
- Instalação e execução
- Como rodar os testes
- Endpoints da API
- Máquina de estados
- Importação de pedidos
- Workflows N8N
- Decisões técnicas
- O que ficou fora do escopo
O sistema trata cada cart da Fake Store API como um pedido e cada user como um afiliado. A importação é feita de forma assíncrona via filas do Laravel (Redis), com Jobs por página e suporte a retry automático. O frontend em Vue.js consome a API REST e exibe um dashboard com métricas em tempo real.
| Camada | Tecnologia |
|---|---|
| Backend | PHP 8.2, Laravel 11 |
| Frontend | Vue.js 3 (Composition API), Tailwind CSS |
| Banco de dados | MySQL 8 |
| Cache e filas | Redis |
| Containers | Docker + Docker Compose |
| Automação | N8N |
| Testes | Pest (PHP) |
- Docker Desktop instalado e rodando
- Git
Não é necessário ter PHP, Node.js ou MySQL instalados localmente. Tudo roda dentro dos containers.
git clone https://github.com/seu-usuario/teste-fullstack-seu-nome.git
cd teste-fullstack-seu-nomecp .env.example .envAbra o .env e ajuste os valores marcados com CHANGE_ME_IN_PRODUCTION conforme necessário para o ambiente local. Os valores do .env.example já funcionam com o Docker Compose sem alterações.
docker compose up -dIsso inicializa os serviços:
app— Laravel (PHP 8.2-FPM)mysql— MySQL 8redis— Redis 7worker— Fila de jobs (php artisan queue:work)n8n— Plataforma de automação (porta 5678)
# Instalar dependências PHP
docker compose exec app composer install
# Gerar a application key
docker compose exec app php artisan key:generate
# Rodar as migrations
docker compose exec app php artisan migrate
# (Opcional) Popular com dados de seed
docker compose exec app php artisan db:seeddocker compose exec app npm install
docker compose exec app npm run builddocker compose exec app php artisan orders:syncA aplicação estará disponível em:
- Frontend/API: http://localhost:8000
- N8N: http://localhost:5678
# Todos os testes
docker compose exec app php artisan test
# Com cobertura de código
docker compose exec app php artisan test --coverage
# Apenas um grupo específico
docker compose exec app php artisan test --group=state-machine
docker compose exec app php artisan test --group=metrics
docker compose exec app php artisan test --group=sync| Grupo | O que testa |
|---|---|
state-machine |
Transições válidas e inválidas da máquina de estados |
metrics |
Endpoint /api/orders/metrics com e sem cache Redis |
sync |
Integração do comando orders:sync (API mockada) |
GET /api/orders
Lista paginada de pedidos (20 por página).
Query params disponíveis:
| Parâmetro | Tipo | Descrição |
|---|---|---|
affiliate_id |
integer | Filtrar por afiliado |
status |
string | pending, approved, cancelled, refunded |
date_from |
date | YYYY-MM-DD |
date_to |
date | YYYY-MM-DD |
min_value |
numeric | Valor mínimo do pedido |
max_value |
numeric | Valor máximo do pedido |
sort_by |
string | Campo de ordenação (ex: created_at, total) |
sort_dir |
string | asc ou desc |
GET /api/orders/{id}
Detalhe do pedido com itens e histórico completo de status.
GET /api/orders/metrics
Métricas agregadas. Cache de 5 minutos via Redis. Invalidado automaticamente ao atualizar qualquer status.
POST /api/orders/{id}/status
Atualiza o status de um pedido.
Body:
{
"status": "approved",
"reason": "Pagamento confirmado"
}Transições inválidas retornam 422 Unprocessable Entity com mensagem descritiva.
GET /api/affiliates/{id}/summary
Resumo do afiliado: total de pedidos, receita, ticket médio e taxa de cancelamento.
GET /api/health
Status dos serviços dependentes (MySQL, Redis e worker ativo).
Resposta exemplo:
{
"status": "ok",
"services": {
"mysql": "ok",
"redis": "ok",
"worker": "ok"
}
}Os pedidos seguem o seguinte fluxo de estados:
pending ──► approved ──► refunded
│
└────► cancelled
| De | Para | Permitido |
|---|---|---|
pending |
approved |
✅ |
pending |
cancelled |
✅ |
approved |
refunded |
✅ |
approved |
cancelled |
❌ |
cancelled |
qualquer | ❌ |
refunded |
qualquer | ❌ |
Toda transição é registrada em order_status_logs com timestamp e usuário responsável (auditoria).
O comando php artisan orders:sync busca dados da Fake Store API e os persiste localmente de forma assíncrona.
Funcionamento:
- O comando principal dispara um
SyncOrdersJobpor página de resultados da API. - Cada Job processa e faz
upsertdos pedidos da sua página, evitando duplicatas. - Jobs com falha são reprocessados automaticamente até 3 tentativas com backoff exponencial.
- O comando é idempotente: pode ser rodado múltiplas vezes sem gerar inconsistências.
- Rate limiting de 10 requisições por segundo nas chamadas HTTP à API externa.
Reprocessar jobs com falha manualmente:
docker compose exec app php artisan queue:retry allOs workflows estão em n8n/workflows/ e podem ser importados pelo painel do N8N.
- Acesse http://localhost:5678
- Faça login com as credenciais do
.env(N8N_USEReN8N_PASSWORD) - Vá em Workflows → Import from file
- Importe os arquivos
.jsonda pastan8n/workflows/
- Gatilho:
POST /webhook/order-approved - Filtra eventos com
new_status = "approved" - Formata mensagem:
✅ Pedido #ID aprovado — R$ VALOR | Afiliado #ID - Envia para webhook (configurado via
webhook.sitepor padrão — substituir por Slack/Discord/Gmail em produção) - Registra o evento em Google Sheets
- Gatilho:
POST /webhook/order-cancelled - Filtra eventos com
new_status = "cancelled" - Consulta
/api/affiliates/{affiliate_id}/summary - Se taxa de cancelamento > 30%: dispara alerta adicional marcando o afiliado como "atenção"
- Registra o cancelamento no Google Sheets
Nota: Os workflows usam
webhook.sitecomo destino de notificação para facilitar o teste sem credenciais reais. Para produção, substituir pelos nós de Slack, Discord ou Gmail conforme documentado nos comentários de cada workflow.
Service + Repository pattern: A lógica de negócio fica no OrderService, as queries no OrderRepository, e os controllers apenas recebem a request e devolvem a response. Essa separação facilita testes unitários e mantém o código coeso — o controller não sabe como o dado é buscado, e o service não sabe como o dado é serializado.
Jobs idempotentes: O upsert nas operações de sync garante que rodar o comando mais de uma vez nunca gera registros duplicados. A chave de unicidade usada é o ID externo da API (external_id).
Cache com invalidação explícita: O cache de métricas tem TTL de 5 minutos, mas é invalidado imediatamente sempre que um status muda. Optei por essa abordagem em vez de depender apenas do TTL para garantir que os dados do dashboard reflitam o estado real do sistema logo após uma atualização.
Índice composto (affiliate_id, status, created_at): A ordem dos campos no índice segue a seletividade dos filtros mais comuns (filtrar por afiliado e status primeiro, depois ordenar por data). Um índice separado por campo seria menos eficiente para queries que combinam os três.
softDeletes em orders: Pedidos nunca são apagados fisicamente. O soft delete preserva o histórico e facilita auditoria. A order_status_logs registra cada mudança com timestamp e usuário responsável.
cascadeOnDelete seletivo: Usado entre order_items → orders e order_status_logs → orders (os logs só fazem sentido com o pedido). Não usado entre orders → affiliates — um afiliado pode ser desativado sem apagar o histórico de pedidos dele.
Usado o pacote spatie/laravel-rate-limited-job-middleware para limitar as chamadas à Fake Store API a 10 requisições por segundo. Isso evita que um sync de grande volume queime o rate limit da API externa.
URL como fonte de verdade dos filtros: Os filtros da tabela de pedidos são sincronizados com a URL via query params usando Vue Router. Isso permite que o usuário copie e cole a URL com os filtros aplicados, e que o browser back/forward funcione corretamente.
Debounce de 400ms nos filtros de texto: Evita disparar uma requisição a cada tecla digitada. O valor de 400ms é um equilíbrio entre responsividade e número de chamadas à API.
O Laravel dispara o evento OrderStatusChanged após cada transição bem-sucedida. Um listener dedicado enfileira um SendWebhookToN8NJob, que faz o HTTP POST para o N8N com retry e backoff exponencial. Uma falha no N8N não afeta o fluxo principal — o pedido já foi atualizado antes de o Job ser enfileirado.
Não foi implementado sistema de login ou controle de acesso por papel (RBAC). Com mais tempo, implementaria autenticação via Laravel Sanctum (tokens de API) e policies do Eloquent para controlar quem pode atualizar status de pedidos.
A paginação atual usa offset/limit. Para 500 mil pedidos, o ideal seria migrar para cursor-based pagination com o campo id ou created_at, eliminando o custo crescente do OFFSET em páginas avançadas.
Não foram escritos testes de componentes Vue. Com mais tempo, usaria Vitest + Vue Test Utils para cobrir os componentes críticos: tabela com filtros, drawer de status e cards de métrica.
O /api/health verifica se o worker está ativo, mas não há dashboard de filas. Em produção, integraria Laravel Horizon para visualização em tempo real dos jobs pendentes, falhos e processados.
Não há pipeline de integração contínua. Com mais tempo, adicionaria um workflow GitHub Actions para rodar os testes automaticamente a cada push na branch develop.
Consulte o .env.example na raiz do projeto. Todas as variáveis estão documentadas com descrição e valor de exemplo.
As variáveis críticas que precisam ser alteradas antes de ir para produção estão marcadas com o comentário # CHANGE IN PRODUCTION.
├── app/
│ ├── Console/Commands/ # Comando orders:sync
│ ├── Events/ # OrderStatusChanged
│ ├── Http/
│ │ ├── Controllers/ # Controllers finos
│ │ └── Requests/ # Form Requests de validação
│ ├── Jobs/ # SyncOrdersJob, SendWebhookToN8NJob
│ ├── Listeners/ # SendOrderStatusWebhook
│ ├── Repositories/ # OrderRepository
│ └── Services/ # OrderService
├── database/
│ └── migrations/ # Todas as migrations
├── n8n/
│ └── workflows/ # Workflows exportados em JSON
├── queries.sql # Queries SQL avançadas (A–E)
├── docker-compose.yml
├── .env.example
└── README.md
Desenvolvido como teste técnico para vaga de Desenvolvedor Full Stack Pleno.
Repositório: https://github.com/seu-usuario/teste-fullstack-seu-nome