Compare commits

..

21 Commits

Author SHA1 Message Date
c5ebbd50b5 REFACTOR: Estrutura de migrations com rastreamento e separação de seeds
- Introduz schema_migrations para controle de migrations já aplicadas:
  apenas arquivos novos rodam a cada deploy, eliminando re-execuções

- Renumera migrations de 001 a 026 sem lacunas (gaps em 013 e 024 fechados)

- Adiciona 008_mensagens.sql: CREATE TABLE que estava ausente do repositório

- Corrige idempotência de 015_configurable_triage_flow:
  adiciona UNIQUE (flow_id, label) e (audience_id, label) e targets
  explícitos no ON CONFLICT que antes duplicava dados a cada deploy

- Separa dados de demonstração em database/seeds/ (só roda em dev/uat)

- Remove seeds e migrations sem propósito:
  seeds/002 e 027 tornados obsoletos com a remoção dos opening templates

- Atualiza deploy-dev.yml: loop com checagem de tracking + passo de seeds
2026-07-24 15:22:26 -03:00
dcb3a89e26 FIX: Remove templates de abertura re-inseridos pela migration 011 a cada deploy
All checks were successful
Deploy Dev / Deploy para VM Dev (push) Successful in 7s
2026-07-24 11:39:42 -03:00
3796c89045 Merge pull request 'FIX: Remove templates de demonstracao do seed e da base' (#4) from fix/templates into dev
All checks were successful
Deploy Dev / Deploy para VM Dev (push) Successful in 6s
Reviewed-on: #4
2026-07-23 15:12:04 -03:00
9bac5ca638 FIX: Remove templates de demonstracao do seed e da base 2026-07-23 15:07:04 -03:00
eacf0f93e5 Merge pull request 'FIX: Exclui pasta uploads/ do rsync no deploy (dev e prod)' (#3) from fix/media-bugs into dev
All checks were successful
Deploy Dev / Deploy para VM Dev (push) Successful in 6s
Reviewed-on: #3
2026-07-23 11:33:24 -03:00
2c86a34963 FIX: Exclui pasta uploads/ do rsync no deploy (dev e prod)
A pasta uploads/ e criada em runtime pelo app para armazenar midias. O --delete do rsync tentava apagar os arquivos sem permissao pois foram criados pelo container Docker com outro usuario.
2026-07-23 11:32:28 -03:00
ae199a9bd0 Merge pull request 'FIX: Adiciona migration 028 para coluna media_filename nas mensagens' (#2) from fix/media-bugs into dev
Some checks failed
Deploy Dev / Deploy para VM Dev (push) Failing after 3s
Reviewed-on: #2
2026-07-23 11:29:03 -03:00
4ab213f8bb FIX: Adiciona migration 028 para coluna media_filename nas mensagens
Necessário para persistir e exibir o nome original de arquivos enviados e recebidos via WhatsApp.
2026-07-23 11:27:12 -03:00
347265f4e9 FEAT: persiste uploads de mídia via volume Docker
All checks were successful
Deploy Dev / Deploy para VM Dev (push) Successful in 7s
2026-07-22 16:02:24 -03:00
bc74cb9fc6 FIX: migration 025 idempotente e ON_ERROR_STOP no workflow
All checks were successful
Deploy Dev / Deploy para VM Dev (push) Successful in 6s
2026-07-22 10:30:02 -03:00
41dec8ff45 CHORE: remove debug temporário do workflow de migrations
All checks were successful
Deploy Dev / Deploy para VM Dev (push) Successful in 7s
2026-07-22 10:29:05 -03:00
b2a0ff3a9c CHORE: debug bytes dos secrets de banco no workflow
All checks were successful
Deploy Dev / Deploy para VM Dev (push) Successful in 7s
2026-07-22 10:26:48 -03:00
4f987e6146 FIX: Testando porta do servidor de banco de dados
Some checks failed
Deploy Dev / Deploy para VM Dev (push) Failing after 4s
2026-07-22 10:15:05 -03:00
e2892d1df5 FIX: usa variáveis PG* no step de migrations
Some checks failed
Deploy Dev / Deploy para VM Dev (push) Failing after 4s
2026-07-22 10:12:40 -03:00
aaebed359e CHORE: Atualiza runs-on para omnichannel-uat
Some checks failed
Deploy Dev / Deploy para VM Dev (push) Failing after 4s
2026-07-22 09:51:41 -03:00
dff3c7fc9c CHORE: Adiciona workflows Gitea Actions (deploy-dev e deploy-prod) via rsync 2026-07-21 15:34:56 -03:00
fef2065601 FEAT: Adiciona migration 027 para colunas de mídia nas mensagens 2026-07-21 09:46:49 -03:00
51eaaf0735 FEAT: Adiciona migrations para config WhatsApp e templates HSM
025: Tabela whatsapp_config com access_token, phone_number_id,
     api_version e webhook_token (singleton via UNIQUE INDEX on TRUE)
026: waba_id na whatsapp_config; colunas HSM em whatsapp_templates:
     language, meta_template_name, meta_template_id, meta_status,
     header_type, header_text, footer_text, buttons (JSONB),
     body_variables (JSONB)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 09:58:01 -03:00
03d28495f0 CHORE: Remove backend e frontend do rastreamento do deploy 2026-05-29 15:47:14 -03:00
f8945bb92b Revert "DOC: Atualizado setup do deploy e estrutura do repositório"
This reverts commit c211df60fb.
2026-05-27 16:30:48 -03:00
c211df60fb DOC: Atualizado setup do deploy e estrutura do repositório 2026-05-27 16:26:02 -03:00
34 changed files with 297 additions and 177 deletions

View File

@ -4,33 +4,98 @@ on:
push: push:
branches: branches:
- dev - dev
workflow_dispatch:
jobs: jobs:
deploy: deploy:
name: Deploy para VM Dev
runs-on: omnichannel-uat runs-on: omnichannel-uat
env:
DEPLOY_PATH: /opt/omnichannel
steps: steps:
- name: Atualizar deploy - name: Checkout
run: | uses: actions/checkout@v4
cd /opt/omnichannel
git pull origin dev
- name: Atualizar backend - name: Preflight
shell: bash
run: | run: |
cd /opt/omnichannel/backend set -euo pipefail
git pull origin dev echo "Runner: $(hostname)"
docker --version
docker compose version
psql --version
- name: Atualizar frontend - name: Verificar secrets obrigatórios
shell: bash
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: | run: |
cd /opt/omnichannel/frontend set -euo pipefail
git pull origin dev if [ -z "${DB_PASSWORD:-}" ]; then
echo "Secret DB_PASSWORD não configurado."
exit 1
fi
- name: Copiar envs - name: Sync arquivos para o servidor
shell: bash
run: | run: |
cp /home/desenvolvimento/.envs/omnichannel/deploy.env.development /opt/omnichannel/.env.development set -euo pipefail
cp /home/desenvolvimento/.envs/omnichannel/backend.env.development /opt/omnichannel/backend/.env.development rsync -az --delete \
cp /home/desenvolvimento/.envs/omnichannel/frontend.env.development /opt/omnichannel/frontend/.env.development --exclude='.git/' \
--exclude='.gitea/' \
--exclude='frontend/' \
--exclude='backend/' \
--exclude='uploads/' \
./ "$DEPLOY_PATH"/
- name: Rebuild todos containers - name: Executar migrations
shell: bash
env:
PGHOST: ${{ secrets.DB_HOST }}
PGPORT: ${{ secrets.DB_PORT }}
PGUSER: ${{ secrets.DB_USER }}
PGDATABASE: ${{ secrets.DB_NAME }}
PGPASSWORD: ${{ secrets.DB_PASSWORD }}
run: | run: |
cd /opt/omnichannel set -euo pipefail
docker compose up -d --build PGHOST="${PGHOST//[$'\t\r\n ']}"
PGPORT="${PGPORT//[$'\t\r\n ']}"
export PGHOST PGPORT
echo "Inicializando controle de migrations..."
psql -v ON_ERROR_STOP=1 -f "$DEPLOY_PATH/database/migrations/000_schema_migrations.sql"
echo "Aplicando migrations pendentes..."
for f in $(ls "$DEPLOY_PATH/database/migrations/"[0-9][0-9][0-9]_*.sql | sort); do
filename=$(basename "$f")
already_run=$(psql -tAc "SELECT COUNT(*) FROM schema_migrations WHERE filename = '$filename'")
if [ "$already_run" = "0" ]; then
echo "Aplicando: $filename"
psql -v ON_ERROR_STOP=1 -f "$f"
psql -c "INSERT INTO schema_migrations (filename) VALUES ('$filename') ON CONFLICT DO NOTHING"
echo "OK: $filename"
else
echo "Ignorando (ja aplicada): $filename"
fi
done
- name: Executar seeds (dev)
shell: bash
env:
PGHOST: ${{ secrets.DB_HOST }}
PGPORT: ${{ secrets.DB_PORT }}
PGUSER: ${{ secrets.DB_USER }}
PGDATABASE: ${{ secrets.DB_NAME }}
PGPASSWORD: ${{ secrets.DB_PASSWORD }}
run: |
set -euo pipefail
PGHOST="${PGHOST//[$'\t\r\n ']}"
PGPORT="${PGPORT//[$'\t\r\n ']}"
export PGHOST PGPORT
echo "Aplicando seeds de desenvolvimento..."
for f in $(ls "$DEPLOY_PATH/database/seeds/"*.sql | sort); do
echo "Seed: $(basename "$f")"
psql -v ON_ERROR_STOP=1 -f "$f"
done

View File

@ -0,0 +1,66 @@
name: Deploy Prod
on:
push:
branches:
- master
workflow_dispatch:
jobs:
deploy:
name: Deploy para VM Prod
runs-on: self-hosted-prod
env:
DEPLOY_PATH: /opt/omnichannel
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preflight
shell: bash
run: |
set -euo pipefail
echo "Runner: $(hostname)"
docker --version
docker compose version
psql --version
- name: Verificar secrets obrigatórios
shell: bash
env:
DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
run: |
set -euo pipefail
if [ -z "${DB_PASSWORD:-}" ]; then
echo "Secret PROD_DB_PASSWORD não configurado."
exit 1
fi
- name: Sync arquivos para o servidor
shell: bash
run: |
set -euo pipefail
rsync -az --delete \
--exclude='.git/' \
--exclude='.gitea/' \
--exclude='frontend/' \
--exclude='backend/' \
--exclude='uploads/' \
./ "$DEPLOY_PATH"/
- name: Executar migrations
shell: bash
env:
PGPASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
run: |
set -euo pipefail
for f in $(ls "$DEPLOY_PATH/database/migrations/"*.sql | sort); do
echo "Aplicando: $(basename "$f")"
psql \
-h ${{ secrets.PROD_DB_HOST }} \
-p ${{ secrets.PROD_DB_PORT }} \
-U ${{ secrets.PROD_DB_USER }} \
-d ${{ secrets.PROD_DB_NAME }} \
-f "$f"
done

View File

@ -1,57 +0,0 @@
name: Diagnose SSH
on:
push:
branches:
- master
jobs:
diagnose:
name: Diagnostico SSH no servidor
runs-on: omnichannel-uat
steps:
- name: Status do sshd
shell: bash
run: |
echo "=== systemctl status sshd ==="
sudo systemctl status sshd --no-pager || true
- name: Logs recentes do sshd
shell: bash
run: |
echo "=== journalctl sshd (ultimas 50 linhas) ==="
sudo journalctl -u sshd -n 50 --no-pager || true
- name: Fail2ban status
shell: bash
run: |
echo "=== fail2ban-client status ==="
sudo fail2ban-client status 2>/dev/null || echo "fail2ban nao instalado ou sem permissao"
echo ""
echo "=== fail2ban jail sshd ==="
sudo fail2ban-client status sshd 2>/dev/null || echo "jail sshd nao encontrado"
- name: IPs banidos no fail2ban
shell: bash
run: |
echo "=== IPs banidos (iptables) ==="
sudo iptables -L f2b-sshd -n 2>/dev/null || sudo iptables -L -n 2>/dev/null | grep -A5 "f2b" || echo "Sem regras f2b encontradas"
- name: Conexoes SSH ativas e MaxStartups
shell: bash
run: |
echo "=== Conexoes na porta 60000 ==="
ss -tnp | grep ':60000' || echo "Nenhuma conexao ativa"
echo ""
echo "=== MaxStartups configurado ==="
sudo sshd -T 2>/dev/null | grep -i maxstartups || grep -i maxstartups /etc/ssh/sshd_config || echo "Configuracao padrao (10:30:100)"
- name: Quem esta logado
shell: bash
run: |
echo "=== Sessoes ativas ==="
who || true
echo ""
echo "=== Ultimos logins ==="
last -n 10 || true

11
.gitignore vendored
View File

@ -4,10 +4,6 @@ dist
*.log *.log
.DS_Store .DS_Store
# Repos clonados separadamente dentro do deploy
/backend/
/frontend/
# Backend Specific Ignore # Backend Specific Ignore
backend/node_modules backend/node_modules
backend/dist backend/dist
@ -19,3 +15,10 @@ backend/test-api-out.json
# Frontend Specific Ignore # Frontend Specific Ignore
frontend/node_modules frontend/node_modules
frontend/dist frontend/dist
/backend/
/frontend/
frontend/
backend/
docs/

View File

@ -4,29 +4,6 @@ Plataforma omnichannel para atendimento com foco inicial em WhatsApp. O sistema
O projeto foi construído para validar e evoluir um MVP de atendimento corporativo, com perfis de agente, supervisor e administrador. O projeto foi construído para validar e evoluir um MVP de atendimento corporativo, com perfis de agente, supervisor e administrador.
## Clonando o Projeto
Este repositório funciona como o repositório de deploy/orquestração. O backend e o frontend ficam em repositórios separados e devem ser clonados dentro das pastas esperadas pelo `docker-compose.yml`.
Em uma pasta vazia:
```bash
git clone https://chaleiradev.sothistelecom.com/Sothis/omnichannel-deploy.git .
git clone https://chaleiradev.sothistelecom.com/Sothis/omnichannel-backend.git backend
git clone https://chaleiradev.sothistelecom.com/Sothis/omnichannel-frontend.git frontend
```
Ao final, a estrutura deve ficar assim:
```txt
omnichannel/
├── backend/
├── frontend/
├── database/
├── docker-compose.yml
└── README.md
```
## Principais Recursos ## Principais Recursos
- Login corporativo via LDAP/Active Directory. - Login corporativo via LDAP/Active Directory.
@ -34,7 +11,7 @@ omnichannel/
- JWT próprio da aplicação com perfis e especialidades. - JWT próprio da aplicação com perfis e especialidades.
- Atendimento WhatsApp em tempo real via `whatsapp-web.js`. - Atendimento WhatsApp em tempo real via `whatsapp-web.js`.
- Socket.IO para atualização de chats/mensagens. - Socket.IO para atualização de chats/mensagens.
- Agente Virtual para triagem e roteamento. - Agente Virtual Sothis para triagem e roteamento.
- Fila por especialidade. - Fila por especialidade.
- Assumir, liberar, transferir e fechar atendimento. - Assumir, liberar, transferir e fechar atendimento.
- Abertura ativa com templates aprovados. - Abertura ativa com templates aprovados.
@ -54,10 +31,10 @@ omnichannel/
- TypeScript `^6.0`. - TypeScript `^6.0`.
- PostgreSQL via `pg`. - PostgreSQL via `pg`.
- Socket.IO `^4.8`. - Socket.IO `^4.8`.
- `whatsapp-web.js` `^1.34`, usando Puppeteer/Chrome para controlar o WhatsApp Web. - `whatsapp-web.js` `^1.34`.
- LDAP via `ldapts`. - LDAP via `ldapts`.
- JWT via `jsonwebtoken`. - JWT via `jsonwebtoken`.
- Logs com `NestJS`. - Logs com `winston`.
### Frontend ### Frontend
@ -97,8 +74,7 @@ omnichannel/
1. Configure `.env.development` na raiz com os dados do banco externo. 1. Configure `.env.development` na raiz com os dados do banco externo.
2. Garanta que o PostgreSQL externo esteja acessível a partir do container backend. 2. Garanta que o PostgreSQL externo esteja acessível a partir do container backend.
3. Garanta que o backend consiga iniciar Chrome/Chromium para o Puppeteer usado pelo `whatsapp-web.js`. 3. Suba backend e frontend:
4. Suba backend e frontend:
```bash ```bash
docker compose up -d --build docker compose up -d --build
@ -115,19 +91,6 @@ Health:
curl http://localhost:4001/health curl http://localhost:4001/health
``` ```
## Deploy Automatizado
O deploy deve seguir pelo Gitea Actions. O antigo fluxo manual via arquivo `.bat` não faz mais parte do processo recomendado.
Responsabilidades esperadas da pipeline:
- baixar/clonar os repositórios necessários;
- configurar variáveis de ambiente do ambiente alvo;
- buildar backend e frontend;
- subir os serviços com Docker Compose;
- executar validações pós-deploy;
- não gerenciar o banco dentro do compose.
## Como Rodar em Desenvolvimento ## Como Rodar em Desenvolvimento
Backend: Backend:
@ -167,8 +130,7 @@ Para ambiente novo, antes de subir backend para uso real:
2. aplicar as migrations em ordem; 2. aplicar as migrations em ordem;
3. validar tabelas principais; 3. validar tabelas principais;
4. criar/atribuir usuário admin; 4. criar/atribuir usuário admin;
5. validar Chrome/Chromium e volume de sessao WhatsApp para Puppeteer; 5. subir backend e frontend.
6. subir backend e frontend.
Detalhes em: Detalhes em:

View File

@ -0,0 +1,38 @@
-- Migration 000: Controle de migrations aplicadas
-- Este arquivo roda sempre (antes do loop principal) e e idempotente.
-- Cria a tabela de rastreamento e marca todas as migrations existentes
-- como ja aplicadas para que nao re-executem em ambientes ja provisionados.
CREATE TABLE IF NOT EXISTS schema_migrations (
filename VARCHAR(255) PRIMARY KEY,
applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO schema_migrations (filename) VALUES
('001_auth.sql'),
('002_area.sql'),
('003_whatsapp.sql'),
('004_whatsapp_templates.sql'),
('005_whatsapp_assignment_queue.sql'),
('006_whatsapp_triage_state.sql'),
('007_agent_notes.sql'),
('008_mensagens.sql'),
('009_customer_contacts.sql'),
('010_agenda_contatos.sql'),
('011_whatsapp_awaiting_customer_reply.sql'),
('012_whatsapp_template_workflow.sql'),
('013_agent_presence_pause.sql'),
('014_hr_decision_tree_keywords.sql'),
('015_configurable_triage_flow.sql'),
('016_triage_resolution_step.sql'),
('017_bot_flow_builder.sql'),
('018_bot_flow_close_node.sql'),
('019_admin_audit_ai_contents.sql'),
('020_whatsapp_template_category.sql'),
('021_agenda_contact_channels.sql'),
('022_whatsapp_config.sql'),
('023_whatsapp_templates_hsm.sql'),
('024_mensagens_media_columns.sql'),
('025_mensagens_media_filename.sql'),
('026_remove_fake_templates.sql')
ON CONFLICT DO NOTHING;

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS whatsapp_templates (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

View File

@ -1,14 +0,0 @@
CREATE TABLE IF NOT EXISTS whatsapp_templates (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO whatsapp_templates (name, content) VALUES
('aviso_fatura', 'Olá, {nome}. Estamos entrando em contato para lembrá-lo que a sua fatura está programada para {data}.'),
('boas_vindas', 'Olá, {nome}! Obrigado por entrar em contato conosco. Como podemos te ajudar hoje?'),
('lembrete_consulta', 'Olá, {nome}. Gostaríamos de confirmar o seu agendamento para {data}. Está confirmado?'),
('suporte_tecnico', 'Olá, {nome}. Sou o atendente e irei te auxiliar no seu suporte sob protocolo {protocolo}.')
ON CONFLICT (name) DO NOTHING;

View File

@ -0,0 +1,17 @@
-- Migration 008: Tabela de mensagens do WhatsApp
CREATE TABLE IF NOT EXISTS mensagens (
id SERIAL PRIMARY KEY,
wamid VARCHAR(255) NOT NULL,
chat_id VARCHAR(255) NOT NULL,
from_me BOOLEAN NOT NULL DEFAULT FALSE,
type VARCHAR(50) NOT NULL,
body TEXT,
sender_name VARCHAR(255),
status VARCHAR(50),
timestamp BIGINT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
UNIQUE (wamid)
);
CREATE INDEX IF NOT EXISTS idx_mensagens_chat_id ON mensagens (chat_id);
CREATE INDEX IF NOT EXISTS idx_mensagens_timestamp ON mensagens (timestamp DESC);

View File

@ -1,19 +0,0 @@
-- ============================================================
-- Migration 011: Templates de abertura ativa do WhatsApp
-- Tabela: whatsapp_templates
-- ============================================================
INSERT INTO whatsapp_templates (name, content) VALUES
('abertura_atendimento_padrao', 'Ola, {nome}. Tudo bem? Estamos entrando em contato pelo atendimento. Podemos seguir por aqui?'),
('abertura_retorno_contato', 'Ola, {nome}. Estamos retornando seu contato para dar continuidade ao seu atendimento.'),
('abertura_suporte', 'Ola, {nome}. Aqui e do suporte. Estamos entrando em contato para te ajudar com sua solicitacao.'),
('abertura_financeiro', 'Ola, {nome}. Aqui e do financeiro. Estamos entrando em contato para tratar de uma informacao importante sobre seu atendimento.'),
('abertura_comercial', 'Ola, {nome}. Aqui e do comercial. Estamos entrando em contato para conversar sobre sua solicitacao.'),
('abertura_confirmacao_dados', 'Ola, {nome}. Precisamos confirmar alguns dados para seguir com seu atendimento.'),
('abertura_contato_agendado', 'Ola, {nome}. Este contato foi combinado anteriormente e estamos disponiveis para seguir.'),
('abertura_pos_atendimento', 'Ola, {nome}. Estamos fazendo um acompanhamento sobre seu atendimento recente.'),
('abertura_aviso_importante', 'Ola, {nome}. Temos uma informacao importante para compartilhar com voce.'),
('abertura_contato_inicial', 'Ola, {nome}. Vamos iniciar seu atendimento por este canal.')
ON CONFLICT (name) DO UPDATE SET
content = EXCLUDED.content,
updated_at = CURRENT_TIMESTAMP;

View File

@ -1,5 +1,5 @@
-- ============================================================ -- ============================================================
-- Migration 017: Fluxo configuravel de triagem do Agente Virtual Sothis -- Migration 015: Fluxo configuravel de triagem do Agente Virtual Sothis
-- Tabelas: -- Tabelas:
-- bot_triage_flows -- bot_triage_flows
-- bot_triage_audiences -- bot_triage_audiences
@ -29,7 +29,8 @@ CREATE TABLE IF NOT EXISTS bot_triage_audiences (
sort_order INTEGER NOT NULL DEFAULT 1, sort_order INTEGER NOT NULL DEFAULT 1,
active BOOLEAN NOT NULL DEFAULT TRUE, active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
UNIQUE (flow_id, label)
); );
CREATE TABLE IF NOT EXISTS bot_triage_intents ( CREATE TABLE IF NOT EXISTS bot_triage_intents (
@ -41,7 +42,8 @@ CREATE TABLE IF NOT EXISTS bot_triage_intents (
sort_order INTEGER NOT NULL DEFAULT 1, sort_order INTEGER NOT NULL DEFAULT 1,
active BOOLEAN NOT NULL DEFAULT TRUE, active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
UNIQUE (audience_id, label)
); );
ALTER TABLE whatsapp_chat_atribuicoes ALTER TABLE whatsapp_chat_atribuicoes
@ -114,7 +116,7 @@ JOIN (
('Sou ex-colaborador', 'ex colaborador, ex-colaborador, rescisao, fgts, informe de rendimentos, desligamento', 2), ('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) ('Sou candidato a uma vaga', 'candidato, vaga, curriculo, processo seletivo, entrevista, candidatura', 3)
) AS seed(label, keywords, sort_order) ON TRUE ) AS seed(label, keywords, sort_order) ON TRUE
ON CONFLICT DO NOTHING; ON CONFLICT (flow_id, label) DO NOTHING;
WITH audiences AS ( WITH audiences AS (
SELECT bta.id, bta.label SELECT bta.id, bta.label
@ -142,4 +144,4 @@ SELECT audiences.id, seed.label, areas.id, seed.keywords, seed.sort_order, TRUE,
FROM seed FROM seed
INNER JOIN audiences ON audiences.label = seed.audience_label INNER JOIN audiences ON audiences.label = seed.audience_label
INNER JOIN areas ON areas.nome = seed.area_nome INNER JOIN areas ON areas.nome = seed.area_nome
ON CONFLICT DO NOTHING; ON CONFLICT (audience_id, label) DO NOTHING;

View File

@ -0,0 +1,18 @@
-- Migration 025: Configuração da integração WhatsApp (Meta API)
CREATE TABLE IF NOT EXISTS whatsapp_config (
id SERIAL PRIMARY KEY,
access_token TEXT,
phone_number_id VARCHAR(50),
api_version VARCHAR(20) NOT NULL DEFAULT 'v25.0',
webhook_token VARCHAR(255),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_by INTEGER REFERENCES usuarios(id) ON DELETE SET NULL
);
-- Garante que exista no máximo uma linha de configuração
CREATE UNIQUE INDEX IF NOT EXISTS whatsapp_config_singleton ON whatsapp_config ((TRUE));
-- Linha inicial vazia para facilitar o UPDATE posterior
INSERT INTO whatsapp_config (access_token, phone_number_id, api_version, webhook_token)
VALUES (NULL, NULL, 'v25.0', NULL)
ON CONFLICT DO NOTHING;

View File

@ -0,0 +1,29 @@
-- Migration 026: WhatsApp HSM Templates + WABA ID
-- Adiciona suporte a templates HSM da Meta com botões, header, footer e variáveis estruturadas.
-- WABA ID na config (necessário para gerenciar templates via API)
ALTER TABLE whatsapp_config
ADD COLUMN IF NOT EXISTS waba_id VARCHAR(100);
-- Colunas HSM na tabela de templates
ALTER TABLE whatsapp_templates
ADD COLUMN IF NOT EXISTS language VARCHAR(10) NOT NULL DEFAULT 'pt_BR',
ADD COLUMN IF NOT EXISTS meta_template_name VARCHAR(512),
ADD COLUMN IF NOT EXISTS meta_template_id VARCHAR(100),
ADD COLUMN IF NOT EXISTS meta_status VARCHAR(50),
ADD COLUMN IF NOT EXISTS header_type VARCHAR(20),
ADD COLUMN IF NOT EXISTS header_text TEXT,
ADD COLUMN IF NOT EXISTS footer_text TEXT,
ADD COLUMN IF NOT EXISTS buttons JSONB,
ADD COLUMN IF NOT EXISTS body_variables JSONB;
-- Templates existentes sem meta_template_name ficam como rascunho
UPDATE whatsapp_templates
SET status = 'draft'
WHERE status IN ('approved', 'meta_review', 'admin_review')
AND meta_template_name IS NULL;
-- Índice único para meta_template_name (ignora NULLs)
CREATE UNIQUE INDEX IF NOT EXISTS whatsapp_templates_meta_name_idx
ON whatsapp_templates (meta_template_name)
WHERE meta_template_name IS NOT NULL;

View File

@ -1,16 +0,0 @@
-- Migration 024: Persistência de mensagens WhatsApp (Meta API)
CREATE TABLE mensagens (
id SERIAL PRIMARY KEY,
wamid VARCHAR(255) UNIQUE NOT NULL,
chat_id VARCHAR(50) NOT NULL,
from_me BOOLEAN NOT NULL DEFAULT FALSE,
type VARCHAR(50) NOT NULL DEFAULT 'text',
body TEXT,
sender_name VARCHAR(255),
status VARCHAR(20),
timestamp BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_mensagens_chat_id ON mensagens(chat_id);
CREATE INDEX idx_mensagens_timestamp ON mensagens(timestamp);

View File

@ -0,0 +1,6 @@
-- Migration 027: Suporte a mídia nas mensagens
-- Adiciona colunas para armazenar caminho local e tipo MIME de mídias recebidas/enviadas.
ALTER TABLE mensagens
ADD COLUMN IF NOT EXISTS media_path VARCHAR(512),
ADD COLUMN IF NOT EXISTS media_mime_type VARCHAR(100);

View File

@ -0,0 +1,5 @@
-- Migration 028: Nome do arquivo de mídia nas mensagens
-- Permite exibir o nome original do arquivo (ex: relatorio.pdf) na interface.
ALTER TABLE mensagens
ADD COLUMN IF NOT EXISTS media_filename VARCHAR(512);

View File

@ -0,0 +1,6 @@
-- Migration 029: Remove templates de demonstração sem vinculo com a Meta
-- Apaga os seeds criados na migration 005 que não foram submetidos à Meta.
DELETE FROM whatsapp_templates
WHERE name IN ('aviso_fatura', 'boas_vindas', 'lembrete_consulta', 'suporte_tecnico')
AND meta_template_name IS NULL;

1
deploy-dev.bat Normal file
View File

@ -0,0 +1 @@
ssh desenvolvimento@10.0.120.75 -p 60000 "/home/desenvolvimento/scripts/deploy-omnichannel-dev.sh"

View File

@ -3,7 +3,6 @@ services:
build: build:
context: ./frontend context: ./frontend
dockerfile: Dockerfile dockerfile: Dockerfile
restart: unless-stopped
ports: ports:
- "4000:3000" - "4000:3000"
@ -11,8 +10,9 @@ services:
build: build:
context: ./backend context: ./backend
dockerfile: Dockerfile dockerfile: Dockerfile
restart: unless-stopped
ports: ports:
- "4001:3001" - "4001:3001"
env_file: env_file:
- .env.development - .env.development
volumes:
- ./uploads:/app/uploads