Compare commits
4 Commits
a0e9fc120f
...
e9e214195a
| Author | SHA1 | Date | |
|---|---|---|---|
| e9e214195a | |||
| a6a09aa740 | |||
| 08eea51d5a | |||
| cfa99a4881 |
548
README.md
548
README.md
@ -1,29 +1,545 @@
|
||||
# Omnichannel Sothis - Deploy
|
||||
# Omnichannel Sothis
|
||||
|
||||
Este repositorio e o **deploy** do ecossistema. Ele **nao** contem o codigo do frontend nem do backend em producao. Esses dois sao repositorios separados e devem ser clonados ao lado deste deploy para subir o ambiente completo.
|
||||
Plataforma omnichannel para atendimento com foco inicial em WhatsApp, autenticação corporativa, controle de acesso por perfil, filas por especialidade, agenda de contatos, templates de mensagens e painéis operacionais para agente, supervisor e administrador.
|
||||
|
||||
## Estrutura esperada (3 repositorios)
|
||||
Este repositório é a raiz do ambiente local/deploy. Ele contém o `docker-compose.yml`, as migrations de banco em `database/` e espera os projetos `frontend/` e `backend/` clonados ou presentes na mesma pasta.
|
||||
|
||||
- `deploy/` (este repo): `docker-compose.yml`, `database/`, `.gitignore`, `README.md`
|
||||
- `frontend/`: interface do produto
|
||||
- `backend/`: API e regras de negocio
|
||||
## Estado Atual do Produto
|
||||
|
||||
## Como subir tudo localmente
|
||||
O produto hoje permite:
|
||||
|
||||
Passo a passo (na mesma pasta raiz):
|
||||
- Login via Active Directory/LDAP e Microsoft OAuth.
|
||||
- Redirecionamento da Home conforme perfil do usuário.
|
||||
- Atendimento WhatsApp em tempo real com socket.
|
||||
- Triagem automática inicial pelo Agente Virtual Sothis.
|
||||
- Fila de atendimento por especialidade.
|
||||
- Assumir, liberar e transferir atendimentos.
|
||||
- Controle da janela de 24 horas do WhatsApp.
|
||||
- Envio e recebimento de texto, imagem, vídeo, áudio e documentos.
|
||||
- Lazy loading de mídias históricas.
|
||||
- Agenda geral de contatos.
|
||||
- Novo atendimento ativo via templates aprovados.
|
||||
- Notas pessoais do atendente.
|
||||
- Administração de usuários, perfis e especialidades.
|
||||
- Workflow de criação, aprovação, reprovação e exclusão de templates.
|
||||
- Painel mensal do admin e painel operacional do supervisor.
|
||||
|
||||
1. Clonar o repo de deploy na raiz (.)
|
||||
2. Clonar o repo de frontend na pasta `frontend`
|
||||
3. Clonar o repo de backend na pasta `backend`
|
||||
4. Subir tudo:
|
||||
## Perfis e Acesso
|
||||
|
||||
### Usuário sem perfil
|
||||
|
||||
Quando um usuário autentica pela primeira vez e ainda não tem perfil/especialidade definida, ele cai em uma tela vazia informando que não está atribuído a uma área/perfil.
|
||||
|
||||
A liberação desse usuário é feita no painel do admin em `Usuários & Acessos`.
|
||||
|
||||
### Agente
|
||||
|
||||
O agente:
|
||||
|
||||
- Vê somente atendimentos das especialidades em que está vinculado.
|
||||
- Pode ver chamados da especialidade enquanto estão na fila.
|
||||
- Só pode responder depois de assumir o atendimento.
|
||||
- Pode liberar um atendimento para voltar para a fila.
|
||||
- Pode transferir atendimento somente depois de assumir.
|
||||
- Pode transferir para outra especialidade ou para outro usuário elegível.
|
||||
- Pode editar/salvar contato na agenda.
|
||||
- Pode criar notas pessoais.
|
||||
- Pode abrir novo atendimento usando template aprovado.
|
||||
|
||||
### Supervisor
|
||||
|
||||
O supervisor:
|
||||
|
||||
- Acessa a Home operacional da supervisão.
|
||||
- Vê indicadores e filas das especialidades que supervisiona.
|
||||
- Pode usar templates, base de conhecimento, auditoria, atendimento, abrir atendimento, disparo em massa e contatos.
|
||||
- Pode solicitar novos templates, mas eles entram primeiro em aprovação do admin.
|
||||
|
||||
### Admin
|
||||
|
||||
O admin:
|
||||
|
||||
- Não precisa pertencer a uma especialidade.
|
||||
- Vê todas as filas e todos os atendimentos já roteados/classificados.
|
||||
- Não vê conversas ainda em triagem automática do Agente Virtual Sothis (`bot_triage`).
|
||||
- Cria e gerencia especialidades.
|
||||
- Define perfis de usuários.
|
||||
- Pode tornar outro usuário admin.
|
||||
- Pode definir se um usuário atua como agente ou supervisor em especialidades.
|
||||
- Pode aprovar, reprovar e excluir templates.
|
||||
- Acessa a visão mensal administrativa.
|
||||
- Também pode atender, usando o menu administrativo.
|
||||
|
||||
## Regras de Atendimento WhatsApp
|
||||
|
||||
### Triagem automática pelo Agente Virtual Sothis
|
||||
|
||||
Toda primeira mensagem recebida no WhatsApp passa pela triagem automática do Agente Virtual Sothis quando ainda não existe atendimento classificado.
|
||||
|
||||
Regras atuais:
|
||||
|
||||
- Mensagens vazias são registradas, mas não disparam triagem.
|
||||
- Mídia sem legenda é registrada, mas não aciona o Agente Virtual Sothis automaticamente.
|
||||
- A triagem é serializada por conversa para evitar respostas duplicadas quando o WhatsApp dispara eventos quase simultâneos.
|
||||
- O backend guarda `last_routed_message_id` para evitar processar a mesma mensagem mais de uma vez.
|
||||
|
||||
Fluxo de decisão:
|
||||
|
||||
1. Se a mensagem já contém uma intenção clara, o Agente Virtual Sothis roteia direto para a especialidade.
|
||||
2. Se a primeira mensagem é genérica, o Agente Virtual Sothis envia a saudação completa e mantém a conversa em `bot_triage`.
|
||||
3. Se a segunda mensagem ainda não identifica intenção, o Agente Virtual Sothis pede explicitamente: suporte, financeiro ou comercial.
|
||||
4. Se a terceira mensagem ainda for inválida, a conversa cai automaticamente em Suporte.
|
||||
|
||||
Exemplos de intenção:
|
||||
|
||||
- Suporte: suporte, bug, problema, técnico.
|
||||
- Financeiro: boleto, dinheiro, cartão, atraso, fatura.
|
||||
- Comercial: produto, novo, contratar, proposta.
|
||||
|
||||
### Estados do atendimento
|
||||
|
||||
A tabela `whatsapp_chat_atribuicoes` representa o estado do atendimento:
|
||||
|
||||
- `bot_triage`: conversa ainda está com o Agente Virtual Sothis.
|
||||
- `queued`: conversa está na fila da especialidade, sem atendente.
|
||||
- `assigned`: conversa foi assumida ou atribuída diretamente a um atendente.
|
||||
|
||||
Regras:
|
||||
|
||||
- Conversas em `bot_triage` não aparecem para agentes.
|
||||
- Conversas em `queued` aparecem para usuários da especialidade.
|
||||
- Conversas em `assigned` continuam visíveis para a especialidade, mas só o responsável responde.
|
||||
- Admin vê todas as conversas roteadas, exceto `bot_triage`.
|
||||
|
||||
### Assumir, liberar e transferir
|
||||
|
||||
Para responder um chamado, o usuário precisa assumir o atendimento.
|
||||
|
||||
- Ao assumir, `user_id` passa a ser o usuário atual.
|
||||
- Ao liberar, o atendimento volta para fila da especialidade.
|
||||
- Transferência só fica disponível depois que o usuário assume.
|
||||
- Transferência para a mesma especialidade pode apontar para outro usuário.
|
||||
- Transferência para outra especialidade cai na fila da nova especialidade.
|
||||
- A observação da transferência fica visível para o próximo atendente e é limpa depois que o atendimento é respondido.
|
||||
|
||||
### Janela de 24 horas
|
||||
|
||||
O produto respeita a regra operacional do WhatsApp:
|
||||
|
||||
- Conversas têm uma janela de 24 horas representada por `conversation_started_at` e `expires_at`.
|
||||
- Ao abrir atendimento ativo para um cliente, o atendente deve usar template aprovado.
|
||||
- Depois do primeiro contato ativo, o atendimento fica bloqueado para novas mensagens livres até o cliente responder.
|
||||
- Quando o cliente responde, o bloqueio `awaiting_customer_reply` é removido e o atendente pode seguir a conversa.
|
||||
|
||||
## Mensagens e Mídias
|
||||
|
||||
### Envio de mensagens
|
||||
|
||||
Mensagens enviadas por atendente são formatadas com identificação:
|
||||
|
||||
```text
|
||||
Atendente: Nome do Usuário
|
||||
|
||||
Mensagem
|
||||
```
|
||||
|
||||
No WhatsApp, essa identificação é enviada usando negrito:
|
||||
|
||||
```text
|
||||
*Atendente: Nome do Usuário*
|
||||
|
||||
Mensagem
|
||||
```
|
||||
|
||||
Se uma mídia for enviada sem texto, o backend não envia cabeçalho solto. A mídia segue sem legenda.
|
||||
|
||||
### Recebimento de mídias
|
||||
|
||||
Tipos suportados:
|
||||
|
||||
- Imagens: PNG, JPG, JPEG, WEBP.
|
||||
- Vídeos: MP4, WEBM.
|
||||
- Áudios: MP3, OGG, WAV.
|
||||
- Documentos: exibidos como card de arquivo.
|
||||
|
||||
Para preservar performance, o histórico de mensagens não carrega Base64 de mídia diretamente. O frontend busca a mídia sob demanda pelo endpoint:
|
||||
|
||||
```http
|
||||
GET /whatsapp/media/:chatId/:messageId
|
||||
```
|
||||
|
||||
### Limites de upload
|
||||
|
||||
O backend aceita JSON até `25mb` por padrão, configurável via:
|
||||
|
||||
```env
|
||||
REQUEST_BODY_LIMIT=25mb
|
||||
```
|
||||
|
||||
O frontend bloqueia anexos acima de 15 MB para evitar falhas de payload e degradação da experiência.
|
||||
|
||||
## Chat
|
||||
|
||||
A tela `/chat` exibe somente conversas reais vindas do backend/WhatsApp. Não existe fallback local de conversas.
|
||||
|
||||
Recursos atuais:
|
||||
|
||||
- Lista de conversas ativas com scroll.
|
||||
- Chat com altura delimitada e scroll interno.
|
||||
- Separador de datas nas mensagens.
|
||||
- Horário por mensagem.
|
||||
- Deduplicação visual de mensagens enviadas localmente e confirmadas por socket.
|
||||
- Ocultação de mensagens vazias.
|
||||
- Botão para assumir atendimento.
|
||||
- Botão para sair do atendimento.
|
||||
- Transferência de atendimento.
|
||||
- Painel de contato do cliente.
|
||||
- Envio de mídia.
|
||||
|
||||
Indicadores na lista:
|
||||
|
||||
- Bolinha amarela: chamado está na fila da especialidade e ainda não foi atribuído.
|
||||
- Bolinha azul: chamado está atribuído ao usuário atual.
|
||||
- Bolinha vermelha: chamado está atribuído a outra pessoa.
|
||||
- Badge de especialidade: mostra para qual fila o chamado foi roteado.
|
||||
- Ícone de contato: indica que o contato está salvo na agenda.
|
||||
|
||||
## Home do Agente
|
||||
|
||||
A Home do agente usa dados reais do chat quando o WhatsApp está conectado.
|
||||
|
||||
Recursos atuais:
|
||||
|
||||
- Últimos atendimentos.
|
||||
- Preview do chat com scroll automático para a última mensagem.
|
||||
- Respostas sugeridas baseadas no contexto.
|
||||
- Envio da resposta sugerida direto para o chat correto.
|
||||
- Comunicados e notas.
|
||||
- Notas pessoais persistidas por usuário.
|
||||
- Relógio/data em tempo real.
|
||||
- Atalhos para scripts, relatórios pessoais, disparo em massa e base de conhecimento.
|
||||
|
||||
## Novo Atendimento
|
||||
|
||||
A tela `/new-attendance` permite iniciar contato ativo.
|
||||
|
||||
Regras:
|
||||
|
||||
- WhatsApp é o canal funcional.
|
||||
- Email e SMS aparecem bloqueados como funcionalidades em construção.
|
||||
- O atendente pode buscar contatos da agenda.
|
||||
- Também pode digitar novo número, nome, empresa e observação.
|
||||
- O telefone tem máscara e seletor de país.
|
||||
- Países disponíveis atualmente: Brasil, EUA, Argentina, Chile e México.
|
||||
- O primeiro contato ativo exige seleção de template aprovado.
|
||||
- Ao iniciar atendimento, o sistema salva/atualiza o contato na agenda e abre o chat daquela conversa.
|
||||
|
||||
## Agenda de Contatos
|
||||
|
||||
A agenda é geral, não vinculada a uma especialidade específica.
|
||||
|
||||
Tabela principal:
|
||||
|
||||
```text
|
||||
agenda_contatos
|
||||
```
|
||||
|
||||
Campos funcionais:
|
||||
|
||||
- Nome.
|
||||
- Empresa.
|
||||
- Telefone.
|
||||
- Observação.
|
||||
- Chat ID.
|
||||
- Usuário que criou/atualizou.
|
||||
|
||||
Na conversa, o telefone não é editável quando vem do WhatsApp, mas nome, empresa e observação podem ser atualizados.
|
||||
|
||||
## Templates WhatsApp
|
||||
|
||||
Templates ficam na tabela:
|
||||
|
||||
```text
|
||||
whatsapp_templates
|
||||
```
|
||||
|
||||
Campos relevantes:
|
||||
|
||||
- `name`
|
||||
- `content`
|
||||
- `area_id`
|
||||
- `status`
|
||||
- `requested_by_role`
|
||||
- `admin_approved_at`
|
||||
- `meta_submitted_at`
|
||||
- `meta_approved_at`
|
||||
|
||||
Status atuais:
|
||||
|
||||
- `approved`: template aprovado e disponível para uso.
|
||||
- `meta_review`: enviado para aprovação da Meta.
|
||||
- `admin_review`: aguardando aprovação do admin.
|
||||
- `rejected`: reprovado pelo admin.
|
||||
|
||||
Regras:
|
||||
|
||||
- Admin cria template e envia para aprovação.
|
||||
- Supervisor cria template, mas ele entra em aprovação do admin.
|
||||
- Admin pode aprovar e enviar para Meta.
|
||||
- Admin pode reprovar.
|
||||
- Admin pode excluir.
|
||||
- A aprovação da Meta é simulada: templates em análise são aprovados automaticamente depois de um intervalo configurado no código.
|
||||
- A listagem possui filtro por especialidade e scroll para não ocupar a página inteira.
|
||||
|
||||
## Painel Admin
|
||||
|
||||
A Home do admin é uma visão mensal.
|
||||
|
||||
Recursos atuais:
|
||||
|
||||
- Filtro por especialidade no topo.
|
||||
- KPIs mensais.
|
||||
- Total de atendimentos.
|
||||
- Tempo médio.
|
||||
- TME.
|
||||
- TMR.
|
||||
- Satisfação.
|
||||
- Atendentes ativos.
|
||||
- Gráfico de atendimentos por dia.
|
||||
- Donut de distribuição por canal.
|
||||
- Ranking de atendentes.
|
||||
- Painel de avisos.
|
||||
- Menu administrativo lateral.
|
||||
|
||||
Menu atual:
|
||||
|
||||
- Home.
|
||||
- Operação.
|
||||
- Usuários & Acessos.
|
||||
- Templates.
|
||||
- Base de conhecimento IA.
|
||||
- Auditoria.
|
||||
- Canais.
|
||||
- Atendimento.
|
||||
- Abrir Atendimento.
|
||||
- Disparo em Massa.
|
||||
- Contatos.
|
||||
- Configurações.
|
||||
- Sair.
|
||||
|
||||
## Usuários & Acessos
|
||||
|
||||
Tela administrativa para usuários, perfis e especialidades.
|
||||
|
||||
Regras atuais:
|
||||
|
||||
- Usuários vêm do banco/autenticação.
|
||||
- Admin Demo, Supervisor Demo e Atendente Demo foram removidos por migration.
|
||||
- Admin pode editar um usuário em modal.
|
||||
- Se o usuário for admin, não há seleção de especialidades.
|
||||
- Se não for admin, é possível vincular múltiplas especialidades.
|
||||
- Para cada especialidade, o usuário pode atuar como agente ou supervisor.
|
||||
- A criação/edição de especialidades é feita pelo admin.
|
||||
- A definição de supervisores agora ocorre no vínculo do usuário, não dentro da especialidade.
|
||||
|
||||
## Painel Supervisor / Operação
|
||||
|
||||
O painel de supervisor também é usado como visão de operação no menu do admin.
|
||||
|
||||
Recursos atuais:
|
||||
|
||||
- Filtro por especialidade.
|
||||
- KPIs do dia.
|
||||
- Atendimentos finalizados.
|
||||
- Atendimentos em aberto.
|
||||
- Conversas na fila.
|
||||
- Tempo médio do dia.
|
||||
- Atendentes online simulados.
|
||||
- Painel do time.
|
||||
- Fila de espera.
|
||||
- Atribuição via modal.
|
||||
- Gráfico do dia por hora.
|
||||
|
||||
Sempre que há dado operacional disponível, os painéis usam a origem real consolidada. Indicadores sem origem consolidada ainda usam dados de apoio para compor a visão.
|
||||
|
||||
## Autenticação
|
||||
|
||||
Endpoints principais:
|
||||
|
||||
```http
|
||||
GET /auth/config
|
||||
POST /auth/login
|
||||
GET /auth/oauth/microsoft/start
|
||||
GET /auth/oauth/microsoft/callback
|
||||
```
|
||||
|
||||
Providers suportados:
|
||||
|
||||
- LDAP/Active Directory.
|
||||
- Microsoft OAuth.
|
||||
|
||||
Variáveis relevantes:
|
||||
|
||||
```env
|
||||
AUTH_PROVIDERS=ldap,microsoft
|
||||
LDAP_ENABLED=true
|
||||
LDAP_URL=ldaps://...
|
||||
LDAP_DOMAIN=...
|
||||
MICROSOFT_ENABLED=false
|
||||
MICROSOFT_TENANT_ID=common
|
||||
MICROSOFT_CLIENT_ID=
|
||||
MICROSOFT_CLIENT_SECRET=
|
||||
```
|
||||
|
||||
## Endpoints Principais
|
||||
|
||||
### WhatsApp
|
||||
|
||||
```http
|
||||
GET /whatsapp/status
|
||||
GET /whatsapp/chats
|
||||
GET /whatsapp/messages/:chatId
|
||||
GET /whatsapp/media/:chatId/:messageId
|
||||
POST /whatsapp/send
|
||||
POST /whatsapp/start-attendance
|
||||
POST /whatsapp/assign
|
||||
POST /whatsapp/transfer
|
||||
DELETE /whatsapp/release/:chatId
|
||||
GET /whatsapp/assignment/:chatId
|
||||
```
|
||||
|
||||
### Templates
|
||||
|
||||
```http
|
||||
GET /whatsapp/templates
|
||||
POST /whatsapp/templates
|
||||
POST /whatsapp/templates/update/:id
|
||||
POST /whatsapp/templates/approve-admin/:id
|
||||
POST /whatsapp/templates/reject-admin/:id
|
||||
DELETE /whatsapp/templates/:id
|
||||
```
|
||||
|
||||
### Admin / Acessos
|
||||
|
||||
```http
|
||||
GET /admin/access/options
|
||||
GET /admin/access/overview
|
||||
GET /admin/access/areas
|
||||
POST /admin/access/areas
|
||||
PUT /admin/access/areas/:id
|
||||
GET /admin/access/users
|
||||
PUT /admin/access/users/:id
|
||||
```
|
||||
|
||||
## Banco de Dados
|
||||
|
||||
Migrations principais:
|
||||
|
||||
- `005_templates.sql`: criação inicial de templates.
|
||||
- `006_whatsapp_assignment_queue.sql`: fila e atribuição de WhatsApp.
|
||||
- `007_whatsapp_triage_state.sql`: estado de triagem automática.
|
||||
- `008_agent_notes.sql`: notas pessoais do atendente.
|
||||
- `009_customer_contacts.sql`: primeira versão de contatos.
|
||||
- `010_agenda_contatos.sql`: agenda geral consolidada.
|
||||
- `011_whatsapp_opening_templates.sql`: templates de abertura ativa.
|
||||
- `012_whatsapp_awaiting_customer_reply.sql`: bloqueio até resposta do cliente.
|
||||
- `013_remove_demo_access_users.sql`: remoção de usuários demo.
|
||||
- `014_whatsapp_template_workflow.sql`: workflow de aprovação de templates.
|
||||
|
||||
## Estrutura do Repositório
|
||||
|
||||
```text
|
||||
omnichannel/
|
||||
├── backend/ # API NestJS e regras de negócio
|
||||
├── frontend/ # Interface React/Vite
|
||||
├── database/migrations/ # Migrations SQL
|
||||
├── docker-compose.yml # Orquestração local
|
||||
├── .env.example # Exemplo de variáveis
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Como Subir Localmente
|
||||
|
||||
Pré-requisitos:
|
||||
|
||||
- Node.js compatível com os projetos.
|
||||
- Docker e Docker Compose.
|
||||
- PostgreSQL via `docker-compose`.
|
||||
|
||||
Na raiz:
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Isso sobe `frontend`, `backend` e `database` em uma unica operacao.
|
||||
Também é possível rodar frontend e backend em modo desenvolvimento:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
URLs locais comuns:
|
||||
|
||||
- Frontend: `http://localhost:5173`
|
||||
- Backend: `http://localhost:3001`
|
||||
- Status WhatsApp: `http://localhost:3001/whatsapp/status`
|
||||
|
||||
## Variáveis de Ambiente
|
||||
|
||||
Principais variáveis da raiz:
|
||||
|
||||
```env
|
||||
POSTGRES_USER=omnichannel
|
||||
POSTGRES_PASSWORD=change-me
|
||||
POSTGRES_DB=omnichannel
|
||||
|
||||
DB_HOST=postgres
|
||||
DB_PORT=5432
|
||||
DB_USER=omnichannel
|
||||
DB_PASSWORD=change-me
|
||||
DB_NAME=omnichannel
|
||||
|
||||
PORT=3001
|
||||
FRONTEND_URL=http://localhost:3000
|
||||
JWT_SECRET=change-this-long-random-secret
|
||||
JWT_EXPIRES_IN=8h
|
||||
|
||||
REQUEST_BODY_LIMIT=25mb
|
||||
```
|
||||
|
||||
No frontend, a URL da API deve vir de:
|
||||
|
||||
```env
|
||||
VITE_API_URL=http://localhost:3001
|
||||
```
|
||||
|
||||
## Limitações Conhecidas
|
||||
|
||||
- O WhatsApp Web carrega conversas por lazy loading. Nem sempre todo histórico aparece imediatamente após conectar o QR Code.
|
||||
- O status real de presença do contato foi removido por limitação prática da biblioteca/WhatsApp Web.
|
||||
- O painel de supervisor ainda possui indicadores simulados onde não há métrica real consolidada.
|
||||
- Email, SMS e ligação não estão operacionais como canais de atendimento.
|
||||
- A aprovação da Meta para templates é simulada.
|
||||
- Mídias ainda trafegam via Base64 no JSON; para produção, o ideal é evoluir para upload/armazenamento próprio e envio por referência.
|
||||
|
||||
## Regras Importantes Para Desenvolvimento
|
||||
|
||||
- Não reintroduzir fallback local de conversas no chat. O `/chat` deve exibir apenas conversas reais vindas do backend/WhatsApp.
|
||||
- Toda alteração de banco deve virar nova migration numerada.
|
||||
- Não permitir resposta sem atendimento assumido.
|
||||
- Não permitir transferência sem atendimento assumido.
|
||||
- Conversas em `bot_triage` não devem aparecer para agentes.
|
||||
- Admin pode ver todas as filas e atendimentos roteados.
|
||||
- Novo atendimento ativo deve usar template aprovado.
|
||||
- Depois de contato ativo, bloquear novas mensagens livres até resposta do cliente.
|
||||
- Mídia sem texto não deve enviar cabeçalho solto de atendente.
|
||||
|
||||
## Observacoes
|
||||
|
||||
- O `docker-compose.yml` deste repo espera `frontend/` e `backend/` presentes na mesma raiz.
|
||||
- Em producao, o fluxo pode mudar para imagens pre-buildadas, mas para desenvolvimento local esta estrutura funciona bem.
|
||||
|
||||
21
database/migrations/014_whatsapp_template_workflow.sql
Normal file
21
database/migrations/014_whatsapp_template_workflow.sql
Normal file
@ -0,0 +1,21 @@
|
||||
-- ============================================================
|
||||
-- Migration 014: Workflow de aprovação de templates WhatsApp
|
||||
-- Tabela: whatsapp_templates
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE whatsapp_templates
|
||||
ADD COLUMN IF NOT EXISTS area_id INTEGER REFERENCES areas (id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS status VARCHAR(40) NOT NULL DEFAULT 'approved',
|
||||
ADD COLUMN IF NOT EXISTS requested_by_role VARCHAR(40),
|
||||
ADD COLUMN IF NOT EXISTS admin_approved_at TIMESTAMP WITH TIME ZONE,
|
||||
ADD COLUMN IF NOT EXISTS meta_submitted_at TIMESTAMP WITH TIME ZONE,
|
||||
ADD COLUMN IF NOT EXISTS meta_approved_at TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
UPDATE whatsapp_templates
|
||||
SET
|
||||
status = COALESCE(status, 'approved'),
|
||||
meta_approved_at = COALESCE(meta_approved_at, updated_at, created_at, CURRENT_TIMESTAMP)
|
||||
WHERE status = 'approved';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_templates_area ON whatsapp_templates (area_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_templates_status ON whatsapp_templates (status);
|
||||
52
database/migrations/015_agent_presence_pause.sql
Normal file
52
database/migrations/015_agent_presence_pause.sql
Normal file
@ -0,0 +1,52 @@
|
||||
-- ============================================================
|
||||
-- Migration 015: Presenca do agente e reserva de chamados em pausa
|
||||
-- Tabelas:
|
||||
-- agent_presence
|
||||
-- whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agent_presence (
|
||||
user_id INTEGER PRIMARY KEY REFERENCES usuarios(id) ON DELETE CASCADE,
|
||||
status VARCHAR(40) NOT NULL DEFAULT 'offline',
|
||||
paused_at TIMESTAMP WITH TIME ZONE,
|
||||
last_seen_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'chk_agent_presence_status'
|
||||
) THEN
|
||||
ALTER TABLE agent_presence
|
||||
ADD CONSTRAINT chk_agent_presence_status
|
||||
CHECK (status IN ('available', 'paused', 'offline'));
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
INSERT INTO agent_presence (user_id, status, last_seen_at, updated_at)
|
||||
SELECT id, 'available', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
FROM usuarios
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS reserved_user_id INTEGER REFERENCES usuarios(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS reserved_at TIMESTAMP WITH TIME ZONE,
|
||||
ADD COLUMN IF NOT EXISTS pause_released_at TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_presence_status
|
||||
ON agent_presence (status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_presence_paused_at
|
||||
ON agent_presence (paused_at)
|
||||
WHERE status = 'paused';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_reserved_user
|
||||
ON whatsapp_chat_atribuicoes (reserved_user_id, status)
|
||||
WHERE reserved_user_id IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_reserved_queue
|
||||
ON whatsapp_chat_atribuicoes (area_id, status, reserved_user_id)
|
||||
WHERE status = 'queued';
|
||||
99
database/migrations/016_hr_decision_tree_keywords.sql
Normal file
99
database/migrations/016_hr_decision_tree_keywords.sql
Normal file
@ -0,0 +1,99 @@
|
||||
-- ============================================================
|
||||
-- Migration 016: Arvore de decisao por especialidade para RH
|
||||
-- Tabelas:
|
||||
-- area_routing_keywords
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS area_routing_keywords (
|
||||
id SERIAL PRIMARY KEY,
|
||||
area_id INTEGER NOT NULL REFERENCES areas(id) ON DELETE CASCADE,
|
||||
keyword VARCHAR(160) NOT NULL,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_area_routing_keyword UNIQUE (area_id, keyword)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_area_routing_keywords_area
|
||||
ON area_routing_keywords (area_id, active);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_area_routing_keywords_keyword
|
||||
ON area_routing_keywords (keyword);
|
||||
|
||||
INSERT INTO areas (nome, descricao) VALUES
|
||||
('Benefícios', 'Duvidas de RH sobre beneficios, convenios, vale transporte e vale refeicao'),
|
||||
('Ponto', 'Ajustes de ponto, banco de horas, atrasos e jornada'),
|
||||
('Holerite', 'Holerite, folha de pagamento, descontos e demonstrativos'),
|
||||
('Férias', 'Ferias, abono, programacao e saldo de descanso'),
|
||||
('Recrutamento', 'Vagas internas, candidatura, entrevista e processo seletivo')
|
||||
ON CONFLICT (nome) DO UPDATE SET
|
||||
descricao = EXCLUDED.descricao,
|
||||
ativo = TRUE,
|
||||
updated_at = NOW();
|
||||
|
||||
INSERT INTO area_routing_keywords (area_id, keyword)
|
||||
SELECT a.id, keyword
|
||||
FROM areas a
|
||||
JOIN (
|
||||
VALUES
|
||||
('Benefícios', 'beneficio'),
|
||||
('Benefícios', 'beneficios'),
|
||||
('Benefícios', 'vale refeicao'),
|
||||
('Benefícios', 'vale alimentacao'),
|
||||
('Benefícios', 'vale transporte'),
|
||||
('Benefícios', 'convenio'),
|
||||
('Benefícios', 'plano de saude'),
|
||||
('Benefícios', 'odonto'),
|
||||
('Ponto', 'ponto'),
|
||||
('Ponto', 'espelho de ponto'),
|
||||
('Ponto', 'banco de horas'),
|
||||
('Ponto', 'atraso'),
|
||||
('Ponto', 'jornada'),
|
||||
('Ponto', 'batida'),
|
||||
('Ponto', 'marcacao'),
|
||||
('Holerite', 'holerite'),
|
||||
('Holerite', 'folha'),
|
||||
('Holerite', 'pagamento'),
|
||||
('Holerite', 'salario'),
|
||||
('Holerite', 'desconto'),
|
||||
('Holerite', 'demonstrativo'),
|
||||
('Férias', 'ferias'),
|
||||
('Férias', 'abono'),
|
||||
('Férias', 'descanso'),
|
||||
('Férias', 'saldo de ferias'),
|
||||
('Férias', 'programar ferias'),
|
||||
('Recrutamento', 'vaga'),
|
||||
('Recrutamento', 'vagas'),
|
||||
('Recrutamento', 'processo seletivo'),
|
||||
('Recrutamento', 'entrevista'),
|
||||
('Recrutamento', 'curriculo'),
|
||||
('Recrutamento', 'candidatura')
|
||||
) AS seed(area_nome, keyword) ON seed.area_nome = a.nome
|
||||
ON CONFLICT (area_id, keyword) DO UPDATE SET
|
||||
active = TRUE,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
|
||||
INSERT INTO whatsapp_templates (name, content, area_id, status, requested_by_role, admin_approved_at, meta_submitted_at, meta_approved_at, updated_at)
|
||||
SELECT template.name, template.content, a.id, 'approved', 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
FROM areas a
|
||||
JOIN (
|
||||
VALUES
|
||||
('rh_abertura_beneficios', 'Ola, {nome}. Aqui e o Atendimento Sothis RH. Recebemos sua solicitacao sobre beneficios e vamos te apoiar por aqui.'),
|
||||
('rh_abertura_ponto', 'Ola, {nome}. Aqui e o Atendimento Sothis RH. Vamos te ajudar com sua solicitacao sobre ponto, jornada ou banco de horas.'),
|
||||
('rh_abertura_holerite', 'Ola, {nome}. Aqui e o Atendimento Sothis RH. Vamos te ajudar com holerite, folha ou demonstrativo de pagamento.'),
|
||||
('rh_abertura_ferias', 'Ola, {nome}. Aqui e o Atendimento Sothis RH. Vamos te apoiar com sua solicitacao sobre ferias.'),
|
||||
('rh_abertura_recrutamento', 'Ola, {nome}. Aqui e o Atendimento Sothis RH. Vamos te orientar sobre vagas, candidatura ou processo seletivo.')
|
||||
) AS template(name, content) ON template.name LIKE
|
||||
CASE a.nome
|
||||
WHEN 'Benefícios' THEN '%beneficios'
|
||||
WHEN 'Ponto' THEN '%ponto'
|
||||
WHEN 'Holerite' THEN '%holerite'
|
||||
WHEN 'Férias' THEN '%ferias'
|
||||
WHEN 'Recrutamento' THEN '%recrutamento'
|
||||
END
|
||||
ON CONFLICT (name) DO UPDATE SET
|
||||
content = EXCLUDED.content,
|
||||
area_id = EXCLUDED.area_id,
|
||||
status = 'approved',
|
||||
meta_approved_at = CURRENT_TIMESTAMP,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
145
database/migrations/017_configurable_triage_flow.sql
Normal file
145
database/migrations/017_configurable_triage_flow.sql
Normal file
@ -0,0 +1,145 @@
|
||||
-- ============================================================
|
||||
-- Migration 017: Fluxo configuravel de triagem do Agente Virtual Sothis
|
||||
-- Tabelas:
|
||||
-- bot_triage_flows
|
||||
-- bot_triage_audiences
|
||||
-- bot_triage_intents
|
||||
-- whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_triage_flows (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(160) NOT NULL UNIQUE,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
greeting_message TEXT NOT NULL,
|
||||
audience_question TEXT NOT NULL,
|
||||
intent_question_template TEXT NOT NULL,
|
||||
fallback_message TEXT NOT NULL,
|
||||
fallback_area_id INTEGER REFERENCES areas(id) ON DELETE SET NULL,
|
||||
max_attempts INTEGER NOT NULL DEFAULT 2,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_triage_audiences (
|
||||
id SERIAL PRIMARY KEY,
|
||||
flow_id INTEGER NOT NULL REFERENCES bot_triage_flows(id) ON DELETE CASCADE,
|
||||
label VARCHAR(160) NOT NULL,
|
||||
keywords TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 1,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_triage_intents (
|
||||
id SERIAL PRIMARY KEY,
|
||||
audience_id INTEGER NOT NULL REFERENCES bot_triage_audiences(id) ON DELETE CASCADE,
|
||||
label VARCHAR(160) NOT NULL,
|
||||
area_id INTEGER NOT NULL REFERENCES areas(id) ON DELETE CASCADE,
|
||||
keywords TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 1,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS triage_flow_id INTEGER REFERENCES bot_triage_flows(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS triage_audience_id INTEGER REFERENCES bot_triage_audiences(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS triage_step VARCHAR(40);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_triage_flows_active
|
||||
ON bot_triage_flows (active);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_triage_audiences_flow
|
||||
ON bot_triage_audiences (flow_id, active, sort_order);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_triage_intents_audience
|
||||
ON bot_triage_intents (audience_id, active, sort_order);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_triage_flow
|
||||
ON whatsapp_chat_atribuicoes (triage_flow_id, triage_audience_id, triage_step);
|
||||
|
||||
INSERT INTO areas (nome, descricao) VALUES
|
||||
('Documentos RH', 'Documentos para ex-colaboradores, informe de rendimentos e comprovantes'),
|
||||
('Rescisao', 'Rescisao, FGTS, verbas rescisorias e encerramento de contrato')
|
||||
ON CONFLICT (nome) DO UPDATE SET
|
||||
descricao = EXCLUDED.descricao,
|
||||
ativo = TRUE,
|
||||
updated_at = NOW();
|
||||
|
||||
INSERT INTO bot_triage_flows (
|
||||
name,
|
||||
active,
|
||||
greeting_message,
|
||||
audience_question,
|
||||
intent_question_template,
|
||||
fallback_message,
|
||||
fallback_area_id,
|
||||
max_attempts,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
'RH CAOA - Atendimento Sothis',
|
||||
TRUE,
|
||||
'Ola! Sou o Agente Virtual Sothis. Vou te direcionar para o atendimento correto de RH.',
|
||||
'Digite o numero da opcao que melhor descreve voce:',
|
||||
'Perfeito. Agora digite o numero do assunto que voce precisa:',
|
||||
'Nao consegui identificar com seguranca. Vou encaminhar seu atendimento para o suporte de RH.',
|
||||
a.id,
|
||||
2,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM areas a
|
||||
WHERE a.nome = 'Suporte'
|
||||
ON CONFLICT (name) DO UPDATE SET
|
||||
active = TRUE,
|
||||
greeting_message = EXCLUDED.greeting_message,
|
||||
audience_question = EXCLUDED.audience_question,
|
||||
intent_question_template = EXCLUDED.intent_question_template,
|
||||
fallback_message = EXCLUDED.fallback_message,
|
||||
fallback_area_id = EXCLUDED.fallback_area_id,
|
||||
max_attempts = EXCLUDED.max_attempts,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
|
||||
WITH flow AS (
|
||||
SELECT id FROM bot_triage_flows WHERE name = 'RH CAOA - Atendimento Sothis'
|
||||
)
|
||||
INSERT INTO bot_triage_audiences (flow_id, label, keywords, sort_order, active, updated_at)
|
||||
SELECT flow.id, seed.label, seed.keywords, seed.sort_order, TRUE, CURRENT_TIMESTAMP
|
||||
FROM flow
|
||||
JOIN (
|
||||
VALUES
|
||||
('Sou colaborador ativo', 'colaborador, funcionario, matricula, holerite, ferias, ponto, beneficios', 1),
|
||||
('Sou ex-colaborador', 'ex colaborador, ex-colaborador, rescisao, fgts, informe de rendimentos, desligamento', 2),
|
||||
('Sou candidato a uma vaga', 'candidato, vaga, curriculo, processo seletivo, entrevista, candidatura', 3)
|
||||
) AS seed(label, keywords, sort_order) ON TRUE
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
WITH audiences AS (
|
||||
SELECT bta.id, bta.label
|
||||
FROM bot_triage_audiences bta
|
||||
INNER JOIN bot_triage_flows btf ON btf.id = bta.flow_id
|
||||
WHERE btf.name = 'RH CAOA - Atendimento Sothis'
|
||||
),
|
||||
seed AS (
|
||||
SELECT * FROM (
|
||||
VALUES
|
||||
('Sou colaborador ativo', 'Beneficios', 'beneficios, vale refeicao, vale transporte, convenio, plano de saude', 'Benefícios', 1),
|
||||
('Sou colaborador ativo', 'Holerite', 'holerite, folha, salario, pagamento, desconto', 'Holerite', 2),
|
||||
('Sou colaborador ativo', 'Ferias', 'ferias, abono, descanso, saldo de ferias', 'Férias', 3),
|
||||
('Sou colaborador ativo', 'Ponto', 'ponto, espelho de ponto, banco de horas, jornada, batida', 'Ponto', 4),
|
||||
('Sou ex-colaborador', 'Documentos', 'documento, documentos, comprovante, informe de rendimentos', 'Documentos RH', 1),
|
||||
('Sou ex-colaborador', 'FGTS e rescisao', 'fgts, rescisao, verbas rescisorias, desligamento', 'Rescisao', 2),
|
||||
('Sou ex-colaborador', 'Informe de rendimentos', 'informe, rendimentos, imposto de renda, ir', 'Documentos RH', 3),
|
||||
('Sou candidato a uma vaga', 'Status da vaga', 'status, vaga, retorno, resultado', 'Recrutamento', 1),
|
||||
('Sou candidato a uma vaga', 'Reagendar entrevista', 'reagendar, entrevista, agenda, horario', 'Recrutamento', 2),
|
||||
('Sou candidato a uma vaga', 'Nova candidatura', 'nova candidatura, curriculo, candidatar, oportunidade', 'Recrutamento', 3)
|
||||
) AS rows(audience_label, label, keywords, area_nome, sort_order)
|
||||
)
|
||||
INSERT INTO bot_triage_intents (audience_id, label, area_id, keywords, sort_order, active, updated_at)
|
||||
SELECT audiences.id, seed.label, areas.id, seed.keywords, seed.sort_order, TRUE, CURRENT_TIMESTAMP
|
||||
FROM seed
|
||||
INNER JOIN audiences ON audiences.label = seed.audience_label
|
||||
INNER JOIN areas ON areas.nome = seed.area_nome
|
||||
ON CONFLICT DO NOTHING;
|
||||
56
database/migrations/018_triage_resolution_step.sql
Normal file
56
database/migrations/018_triage_resolution_step.sql
Normal file
@ -0,0 +1,56 @@
|
||||
-- ============================================================
|
||||
-- Migration 018: Etapa de resposta antes da escalacao para fila
|
||||
-- Tabelas:
|
||||
-- bot_triage_flows
|
||||
-- bot_triage_intents
|
||||
-- whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE bot_triage_flows
|
||||
ADD COLUMN IF NOT EXISTS resolution_question TEXT NOT NULL DEFAULT 'Essa informacao resolveu sua duvida? Responda 1 para encerrar ou 2 para falar com um especialista.';
|
||||
|
||||
ALTER TABLE bot_triage_intents
|
||||
ADD COLUMN IF NOT EXISTS response_message TEXT,
|
||||
ADD COLUMN IF NOT EXISTS resolution_question TEXT,
|
||||
ADD COLUMN IF NOT EXISTS escalation_message TEXT NOT NULL DEFAULT 'Certo, vou encaminhar seu atendimento para um especialista no assunto.';
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS triage_intent_id INTEGER REFERENCES bot_triage_intents(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_triage_intent
|
||||
ON whatsapp_chat_atribuicoes (triage_intent_id, triage_step);
|
||||
|
||||
UPDATE bot_triage_intents
|
||||
SET response_message = CASE label
|
||||
WHEN 'Beneficios' THEN 'Para consultar beneficios, verifique no portal de RH a area Beneficios. La voce encontra vale refeicao, vale transporte, convenio medico, odontologico e regras de elegibilidade.'
|
||||
WHEN 'Holerite' THEN 'Seu holerite fica disponivel no portal de RH na area Folha de pagamento. Normalmente ele pode ser consultado por competencia, com detalhamento de salario, descontos e beneficios.'
|
||||
WHEN 'Ferias' THEN 'Para ferias, consulte saldo e periodos disponiveis no portal de RH. A solicitacao precisa respeitar a politica interna e o fluxo de aprovacao do gestor.'
|
||||
WHEN 'Ponto' THEN 'Para ponto, confira o espelho de ponto e solicite correcao de batida quando necessario. Ajustes de jornada e banco de horas seguem aprovacao do gestor.'
|
||||
WHEN 'Documentos' THEN 'Documentos de ex-colaborador podem ser solicitados pelo canal de RH. Informe o documento desejado e mantenha seus dados de contato atualizados.'
|
||||
WHEN 'FGTS e rescisao' THEN 'Para FGTS e rescisao, o time de RH valida o status do desligamento, prazos legais e documentos pendentes antes de retornar.'
|
||||
WHEN 'Informe de rendimentos' THEN 'O informe de rendimentos e disponibilizado conforme calendario fiscal. Caso nao encontre, o RH pode apoiar com a segunda via.'
|
||||
WHEN 'Status da vaga' THEN 'Para status de vaga, acompanhe o processo seletivo pelo canal informado na candidatura. O RH pode consultar a etapa atual quando houver identificacao do candidato.'
|
||||
WHEN 'Reagendar entrevista' THEN 'Para reagendar entrevista, informe nome completo, vaga e melhor disponibilidade. O time de recrutamento avalia a agenda.'
|
||||
WHEN 'Nova candidatura' THEN 'Para nova candidatura, acesse o portal de vagas e mantenha seu curriculo atualizado. O RH pode orientar sobre oportunidades abertas.'
|
||||
ELSE COALESCE(response_message, 'Tenho uma orientacao inicial para esse assunto. Se ainda precisar de ajuda, posso encaminhar para um especialista.')
|
||||
END
|
||||
WHERE response_message IS NULL;
|
||||
|
||||
UPDATE bot_triage_intents
|
||||
SET resolution_question = 'Isso resolveu sua duvida? Responda 1 para encerrar ou 2 para falar com um especialista de RH.'
|
||||
WHERE resolution_question IS NULL;
|
||||
|
||||
UPDATE bot_triage_intents
|
||||
SET escalation_message = CASE label
|
||||
WHEN 'Beneficios' THEN 'Certo, vou te encaminhar para um especialista em beneficios.'
|
||||
WHEN 'Holerite' THEN 'Certo, vou te encaminhar para um especialista em holerite e folha.'
|
||||
WHEN 'Ferias' THEN 'Certo, vou te encaminhar para um especialista em ferias.'
|
||||
WHEN 'Ponto' THEN 'Certo, vou te encaminhar para um especialista em ponto.'
|
||||
WHEN 'Documentos' THEN 'Certo, vou te encaminhar para um especialista em documentos de RH.'
|
||||
WHEN 'FGTS e rescisao' THEN 'Certo, vou te encaminhar para um especialista em rescisao.'
|
||||
WHEN 'Informe de rendimentos' THEN 'Certo, vou te encaminhar para um especialista em documentos de RH.'
|
||||
WHEN 'Status da vaga' THEN 'Certo, vou te encaminhar para o time de recrutamento.'
|
||||
WHEN 'Reagendar entrevista' THEN 'Certo, vou te encaminhar para o time de recrutamento.'
|
||||
WHEN 'Nova candidatura' THEN 'Certo, vou te encaminhar para o time de recrutamento.'
|
||||
ELSE escalation_message
|
||||
END;
|
||||
127
database/migrations/019_bot_flow_builder.sql
Normal file
127
database/migrations/019_bot_flow_builder.sql
Normal file
@ -0,0 +1,127 @@
|
||||
-- ============================================================
|
||||
-- Migration 019: Flow Builder visual do bot
|
||||
-- Tabelas:
|
||||
-- bot_flow_versions
|
||||
-- bot_flow_nodes
|
||||
-- whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_flow_versions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(160) NOT NULL DEFAULT 'Fluxo RH Sothis',
|
||||
status VARCHAR(30) NOT NULL DEFAULT 'draft',
|
||||
version_number INTEGER NOT NULL DEFAULT 0,
|
||||
root_node_id INTEGER,
|
||||
published_at TIMESTAMP WITH TIME ZONE,
|
||||
snapshot JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_flow_nodes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
version_id INTEGER NOT NULL REFERENCES bot_flow_versions(id) ON DELETE CASCADE,
|
||||
parent_id INTEGER REFERENCES bot_flow_nodes(id) ON DELETE CASCADE,
|
||||
node_type VARCHAR(30) NOT NULL CHECK (node_type IN ('greeting', 'question', 'agent', 'close')),
|
||||
title VARCHAR(160) NOT NULL,
|
||||
message_text TEXT,
|
||||
keywords TEXT,
|
||||
fallback_message TEXT,
|
||||
fallback_attempts INTEGER NOT NULL DEFAULT 2,
|
||||
fallback_area_id INTEGER REFERENCES areas(id) ON DELETE SET NULL,
|
||||
area_id INTEGER REFERENCES areas(id) ON DELETE SET NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 1,
|
||||
position_x INTEGER NOT NULL DEFAULT 0,
|
||||
position_y INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'fk_bot_flow_versions_root'
|
||||
) THEN
|
||||
ALTER TABLE bot_flow_versions
|
||||
ADD CONSTRAINT fk_bot_flow_versions_root
|
||||
FOREIGN KEY (root_node_id) REFERENCES bot_flow_nodes(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS triage_builder_version_id INTEGER REFERENCES bot_flow_versions(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS triage_builder_node_id INTEGER REFERENCES bot_flow_nodes(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_flow_versions_status
|
||||
ON bot_flow_versions (status, published_at DESC, id DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_flow_nodes_version_parent
|
||||
ON bot_flow_nodes (version_id, parent_id, sort_order, id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_builder_node
|
||||
ON whatsapp_chat_atribuicoes (triage_builder_version_id, triage_builder_node_id);
|
||||
|
||||
WITH inserted_version AS (
|
||||
INSERT INTO bot_flow_versions (name, status, version_number, updated_at)
|
||||
SELECT 'Fluxo RH Sothis', 'draft', 0, CURRENT_TIMESTAMP
|
||||
WHERE NOT EXISTS (SELECT 1 FROM bot_flow_versions WHERE status = 'draft')
|
||||
RETURNING id
|
||||
),
|
||||
draft_version AS (
|
||||
SELECT id FROM inserted_version
|
||||
UNION ALL
|
||||
SELECT id
|
||||
FROM (
|
||||
SELECT id
|
||||
FROM bot_flow_versions
|
||||
WHERE status = 'draft'
|
||||
AND NOT EXISTS (SELECT 1 FROM inserted_version)
|
||||
ORDER BY id ASC
|
||||
LIMIT 1
|
||||
) existing_draft
|
||||
),
|
||||
inserted_root AS (
|
||||
INSERT INTO bot_flow_nodes (
|
||||
version_id,
|
||||
parent_id,
|
||||
node_type,
|
||||
title,
|
||||
message_text,
|
||||
keywords,
|
||||
fallback_message,
|
||||
fallback_attempts,
|
||||
sort_order,
|
||||
position_x,
|
||||
position_y,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
draft_version.id,
|
||||
NULL,
|
||||
'greeting',
|
||||
'Saudacao inicial',
|
||||
'Ola! Sou o Agente Virtual Sothis. Vou te direcionar para o atendimento correto de RH. Digite o numero da opcao que melhor descreve voce:\n\n1 - Sou colaborador ativo\n2 - Sou ex-colaborador\n3 - Sou candidato a uma vaga',
|
||||
NULL,
|
||||
'Nao consegui identificar seu perfil. Digite 1 para colaborador ativo, 2 para ex-colaborador ou 3 para candidato.',
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM draft_version
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM bot_flow_nodes node
|
||||
WHERE node.version_id = draft_version.id
|
||||
AND node.node_type = 'greeting'
|
||||
)
|
||||
RETURNING id, version_id
|
||||
)
|
||||
UPDATE bot_flow_versions version
|
||||
SET root_node_id = inserted_root.id,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
FROM inserted_root
|
||||
WHERE version.id = inserted_root.version_id
|
||||
AND version.root_node_id IS NULL;
|
||||
@ -0,0 +1,12 @@
|
||||
-- ============================================================
|
||||
-- Migration 020: No terminal de encerramento pelo bot
|
||||
-- Tabelas:
|
||||
-- bot_flow_nodes
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE bot_flow_nodes
|
||||
DROP CONSTRAINT IF EXISTS bot_flow_nodes_node_type_check;
|
||||
|
||||
ALTER TABLE bot_flow_nodes
|
||||
ADD CONSTRAINT bot_flow_nodes_node_type_check
|
||||
CHECK (node_type IN ('greeting', 'question', 'agent', 'close'));
|
||||
41
database/migrations/021_admin_audit_ai_contents.sql
Normal file
41
database/migrations/021_admin_audit_ai_contents.sql
Normal file
@ -0,0 +1,41 @@
|
||||
-- ============================================================
|
||||
-- Migration 021: Auditoria e conteúdos da IA
|
||||
-- Tabelas:
|
||||
-- admin_audit_logs
|
||||
-- ai_knowledge_contents
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS admin_audit_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
actor_user_id INTEGER REFERENCES usuarios(id) ON DELETE SET NULL,
|
||||
actor_name VARCHAR(180),
|
||||
action VARCHAR(120) NOT NULL,
|
||||
target_type VARCHAR(80),
|
||||
target_id VARCHAR(120),
|
||||
details TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_created_at
|
||||
ON admin_audit_logs (created_at DESC, id DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ai_knowledge_contents (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(220) NOT NULL,
|
||||
area_id INTEGER REFERENCES areas(id) ON DELETE SET NULL,
|
||||
filename VARCHAR(260),
|
||||
mimetype VARCHAR(160),
|
||||
file_size INTEGER,
|
||||
content_base64 TEXT,
|
||||
status VARCHAR(40) NOT NULL DEFAULT 'available',
|
||||
notes TEXT,
|
||||
created_by_user_id INTEGER REFERENCES usuarios(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_knowledge_contents_area
|
||||
ON ai_knowledge_contents (area_id, status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_knowledge_contents_created_at
|
||||
ON ai_knowledge_contents (created_at DESC, id DESC);
|
||||
14
database/migrations/022_whatsapp_template_category.sql
Normal file
14
database/migrations/022_whatsapp_template_category.sql
Normal file
@ -0,0 +1,14 @@
|
||||
-- ============================================================
|
||||
-- Migration 022: Categoria de templates WhatsApp
|
||||
-- Tabela: whatsapp_templates
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE whatsapp_templates
|
||||
ADD COLUMN IF NOT EXISTS category VARCHAR(40) NOT NULL DEFAULT 'UTILITY';
|
||||
|
||||
UPDATE whatsapp_templates
|
||||
SET category = 'UTILITY'
|
||||
WHERE category IS NULL OR category = '';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_templates_category
|
||||
ON whatsapp_templates (category);
|
||||
Loading…
Reference in New Issue
Block a user