ub / flowpay#
Sistema full-stack para distribuição automática e monitoramento em tempo real de atendimentos da central de relacionamento da FlowPay.
Demo:
O Problema#
A FlowPay organiza sua central de atendimento em três times:
| Time | Assunto |
|---|---|
| Time Cartões | Solicitações contendo "cartão" |
| Time Empréstimos | Solicitações contendo "empréstimo" |
| Time Outros Assuntos | Todos os demais assuntos |
Regras de distribuição:
- Cada atendente atende no máximo 3 clientes simultaneamente
- Se todos os atendentes de um time estiverem ocupados, o atendimento entra na fila de espera
- Quando um atendente finaliza um atendimento, o próximo da fila é atribuído automaticamente
- Ao cadastrar ou reativar um atendente, os clientes em espera do seu time são atribuídos imediatamente
Arquitetura#
flowpay/
├── apps/
│ ├── api/ # REST API — Hono + Cloudflare Workers + D1
│ └── web/ # Dashboard — React + Vite + Cloudflare Pages
└── packages/
└── types/ # Interfaces TypeScript compartilhadas
Backend: Worker Hono no edge do Cloudflare. Toda a lógica de distribuição e enfileiramento roda aqui, com persistência em D1 (SQLite).
Frontend: SPA React servida pelo Cloudflare Pages. Consulta a API a cada 5 segundos para manter o dashboard atualizado em tempo real.
Tech Stack#
| Camada | Tecnologia |
|---|---|
| API | Hono v4 + Cloudflare Workers |
| Banco de dados | Cloudflare D1 (SQLite) |
| Validação | Zod + @hono/zod-validator |
| Frontend | React 18 + Vite 5 |
| Roteamento | React Router v6 |
| Estilo | Tailwind CSS v3 |
| Monorepo | pnpm workspaces |
| Testes | Vitest |
| Deploy | Wrangler v3 |
Desenvolvimento Local#
Pré-requisitos#
- Node.js 18+
- pnpm 8+
Instalação#
git clone <repo>
cd flowpay
pnpm install
Rodar a API localmente#
# Criar o banco D1 local e aplicar o schema
cd apps/api
wrangler d1 execute flowpay-db --local --file=schema.sql
wrangler d1 execute flowpay-db --local --file=seed.sql
# Iniciar o Worker em modo desenvolvimento
pnpm dev:api
# API disponível em http://localhost:8787
Rodar o frontend localmente#
# Em outro terminal, na raiz do projeto
pnpm dev:web
# Dashboard disponível em http://localhost:5173
O arquivo apps/web/.env.local aponta para http://localhost:8787 por padrão.
Testes#
pnpm test
# 13 testes passando (classificação de assuntos + lógica de distribuição)
API Reference#
Base URL (produção): https://flowpay-api.arthrfrts.workers.dev
Times#
GET /teams#
Retorna os 3 times cadastrados.
[
{ "id": 1, "name": "Time Cartões", "slug": "cartoes" },
{ "id": 2, "name": "Time Empréstimos", "slug": "emprestimos" },
{ "id": 3, "name": "Time Outros Assuntos", "slug": "outros" }
]
Atendentes#
GET /agents#
Lista todos os atendentes com a contagem de atendimentos ativos.
[
{
"id": 1,
"name": "Ana",
"team_id": 1,
"active": 1,
"created_at": "2026-05-19 10:00:00",
"active_count": 2
}
]
POST /agents#
Cria um novo atendente. Automaticamente atribui atendimentos em espera do time.
Body:
{ "name": "João", "team_id": 1 }
Resposta: 201 com o atendente criado.
Erros:
400—team_idnão existe
PUT /agents/:id#
Atualiza dados de um atendente. Ao reativar (active: 1), atribui automaticamente atendimentos em espera do time.
Body (todos os campos opcionais):
{ "name": "João Silva", "team_id": 2, "active": 0 }
Erros:
400— nenhum campo fornecido404— atendente não encontrado
DELETE /agents/:id#
Desativa um atendente (soft delete — active = 0). Atendimentos em curso não são alterados.
Erros:
404— atendente não encontrado
Atendimentos#
GET /attendances#
Lista atendimentos com filtros opcionais.
Query params:
| Param | Valores |
|---|---|
status |
waiting, in_progress, completed |
team_id |
ID do time |
agent_id |
ID do atendente |
[
{
"id": 1,
"customer_name": "Maria",
"subject": "Problemas com cartão",
"team_id": 1,
"agent_id": 2,
"status": "in_progress",
"queue_position": null,
"created_at": "2026-05-19 10:05:00",
"started_at": "2026-05-19 10:05:01",
"completed_at": null
}
]
POST /attendances#
Cria um novo atendimento e aciona a distribuição automática.
Body:
{ "customer_name": "Carlos", "subject": "Contratação de empréstimo" }
Lógica de classificação:
- Assunto contém "cartão" ou "cartao" (case-insensitive) → Time Cartões
- Assunto contém "empréstimo" ou "emprestimo" (case-insensitive) → Time Empréstimos
- Demais → Time Outros Assuntos
Resposta: 201
{
"attendance": {
/* objeto Attendance */
},
"assigned": true,
"team": { "id": 1, "name": "Time Cartões", "slug": "cartoes" }
}
O campo assigned: true indica atribuição imediata a um atendente; false indica que entrou na fila de espera.
POST /attendances/:id/complete#
Finaliza um atendimento em andamento. Automaticamente promove o próximo da fila para o atendente que ficou livre.
Erros:
404— atendimento não encontrado400— atendimento não está com statusin_progress
Dashboard#
GET /dashboard#
Retorna o estado agregado para o dashboard em tempo real.
{
"teams": [
{
"id": 1,
"name": "Time Cartões",
"slug": "cartoes",
"queue_count": 2,
"in_progress_count": 6,
"agents": [
{
"id": 1,
"name": "Ana",
"team_id": 1,
"active": 1,
"active_count": 3,
"capacity": 3
}
]
}
],
"totals": {
"waiting": 4,
"in_progress": 9,
"completed_today": 27
}
}
Deploy#
API (Cloudflare Workers)#
cd apps/api
# Autenticar (primeira vez)
wrangler login
# Criar banco D1
wrangler d1 create flowpay-db
# Aplicar schema e seed
wrangler d1 execute flowpay-db --remote --file=schema.sql
wrangler d1 execute flowpay-db --remote --file=seed.sql
# Deploy
wrangler deploy
Frontend (Cloudflare Pages)#
cd apps/web
# Build com a URL da API em produção
VITE_API_URL=https://flowpay-api.arthrfrts.workers.dev pnpm build
# Deploy
wrangler pages deploy dist --project-name flowpay-web
Estrutura do Banco de Dados#
-- Times (pré-cadastrados via seed)
teams: id, name, slug
-- Atendentes
agents: id, name, team_id, active, created_at
-- Atendimentos
attendances:
id, customer_name, subject,
team_id, agent_id,
status (waiting | in_progress | completed),
queue_position,
created_at, started_at, completed_at