Migração para PostgreSQL multi-driver + correções de segurança

- Camada de banco unificada (src/database.js): drivers Postgres/Firebird,
  tradutor de SQL, suporte a schema e pool de conexões
- Conexões: novo_local (Postgres externo) e firebird_local (legado)
- Tela de rotas da API redesenhada (auth, params, exemplos de body)
- Correções de segurança (críticos/altos/médios/baixos): XSS no chat,
  escalonamento de privilégio, mídia autenticada, SQL restrito a gerente,
  JWT sem fallback + issuer, IDOR em conversas, CORS por allowlist,
  rate-limit no login, limites de corpo por rota
- Deploy alinhado: install.sh grava .env com PG_*, migracoes.js driver-aware

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 10:02:59 -03:00
commit ae629d1dc2
50 changed files with 17137 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
/**
* Script para definir/alterar a senha web de um usuário.
* Uso: node scripts/definir-senha-web.js <LOGIN> <SENHA> [alias]
*
* @param {string} LOGIN - Login do usuário
* @param {string} SENHA - Nova senha (máx. 30 caracteres)
* @param {string} [alias=lajedo] - Alias do banco de dados
*/
const db = require('../src/database');
async function main() {
const login = process.argv[2];
const senha = process.argv[3];
const alias = process.argv[4] || 'lajedo';
if (!login || !senha) {
console.log('Uso: node scripts/definir-senha-web.js <LOGIN> <SENHA> [alias]');
console.log('Ex: node scripts/definir-senha-web.js SUPORTE 123456 lajedo');
process.exit(1);
}
if (senha.length > 30) {
console.log('❌ A senha deve ter no máximo 30 caracteres.');
process.exit(1);
}
// Verifica se o usuário existe
const users = await db.query(alias,
'SELECT USU_CODIGO_ID, USU_NOME, USU_LOGIN, USU_ACESSO_WEB FROM USUARIOS WHERE USU_LOGIN = ?',
[login]
);
if (users.length === 0) {
console.log(`❌ Usuário "${login}" não encontrado no alias "${alias}".`);
process.exit(1);
}
const user = users[0];
// Atualiza a senha web
await db.execute(alias,
'UPDATE USUARIOS SET USU_SENHA_WEB = ? WHERE USU_CODIGO_ID = ?',
[senha, user.USU_CODIGO_ID]
);
console.log(`✅ Senha web do usuário "${login}" definida com sucesso! (alias: ${alias})`);
if (user.USU_ACESSO_WEB !== 1) {
console.log('⚠️ O usuário ainda não tem acesso WEB habilitado.');
console.log(` Execute: node scripts/habilitar-acesso-web.js ${login} ${alias}`);
}
process.exit(0);
}
main().catch(err => {
console.error('Erro:', err.message);
process.exit(1);
});
+130
View File
@@ -0,0 +1,130 @@
/**
* Gera um token JWT para um usuário em uma empresa específica.
* O token é armazenado no campo USU_TOKEN da tabela USUARIOS.
*
* Uso: node scripts/gerar-token-jwt.js <LOGIN> <ID_EMPRESA> [alias]
*
* Exemplos:
* node scripts/gerar-token-jwt.js SUPORTE 1
* node scripts/gerar-token-jwt.js SUPORTE 1 lajedo
*/
const jwt = require('jsonwebtoken');
const db = require('../src/database');
const authConfig = require('../src/config/auth');
async function main() {
const login = process.argv[2];
const empresaId = parseInt(process.argv[3], 10);
const alias = process.argv[4] || 'lajedo';
if (!login || !empresaId) {
console.log('Uso: node scripts/gerar-token-jwt.js <LOGIN> <ID_EMPRESA> [alias]');
console.log('Ex: node scripts/gerar-token-jwt.js SUPORTE 1 lajedo');
process.exit(1);
}
// 1. Busca o usuário pelo login
const users = await db.query(alias,
`SELECT USU_CODIGO_ID, USU_NOME, USU_LOGIN, USU_EMAIL, USU_STATUS,
USU_ACESSO_WEB, USU_TIPO, USU_TOKEN
FROM USUARIOS WHERE USU_LOGIN = ?`,
[login]
);
if (users.length === 0) {
console.log(`❌ Usuário "${login}" não encontrado no alias "${alias}".`);
process.exit(1);
}
const user = users[0];
// 2. Verifica se o usuário tem acesso à empresa
const empresas = await db.query(alias,
`SELECT USE_EMPRESA_ID FROM USUARIOS_EMPRESA
WHERE USE_USUARIO_ID = ? AND USE_EMPRESA_ID = ?`,
[user.USU_CODIGO_ID, empresaId]
);
if (empresas.length === 0) {
// Lista as empresas que o usuário tem acesso
const allEmpresas = await db.query(alias,
`SELECT USE_EMPRESA_ID FROM USUARIOS_EMPRESA
WHERE USE_USUARIO_ID = ?`,
[user.USU_CODIGO_ID]
);
const empresasList = allEmpresas.map(e => e.USE_EMPRESA_ID).join(', ');
console.log(`❌ Usuário "${login}" não tem acesso à empresa ${empresaId}.`);
console.log(` Empresas disponíveis para este usuário: ${empresasList || 'nenhuma'}`);
process.exit(1);
}
// 3. Verifica status e permissão web
const statusOk = user.USU_STATUS === 'A';
const webOk = user.USU_ACESSO_WEB === 1;
if (!statusOk) {
console.log(`❌ Usuário "${login}" não está ativo (status: ${user.USU_STATUS}).`);
process.exit(1);
}
if (!webOk) {
console.log(`⚠️ Usuário "${login}" não tem acesso WEB habilitado.`);
console.log(` Execute: node scripts/habilitar-acesso-web.js ${login} ${alias}`);
// Não impede de gerar o token, mas avisa
}
// 4. Gera o payload do token
const payload = {
id: user.USU_CODIGO_ID,
empresaId: empresaId,
nome: user.USU_NOME.trim(),
login: user.USU_LOGIN.trim(),
email: user.USU_EMAIL ? user.USU_EMAIL.trim() : null,
tipo: user.USU_TIPO ? user.USU_TIPO.trim() : null,
alias: alias,
};
// 5. Gera o token JWT
const token = jwt.sign(payload, authConfig.secret, {
expiresIn: authConfig.expiresIn,
issuer: authConfig.issuer,
});
// 6. Armazena no campo USU_TOKEN
await db.execute(alias,
'UPDATE USUARIOS SET USU_TOKEN = ? WHERE USU_CODIGO_ID = ?',
[token, user.USU_CODIGO_ID]
);
// 7. Exibe o resultado
console.log('');
console.log('='.repeat(70));
console.log(' ✅ TOKEN JWT GERADO COM SUCESSO');
console.log('='.repeat(70));
console.log('');
console.log(` Alias: ${alias}`);
console.log(` Usuário: ${user.USU_NOME.trim()} (${user.USU_LOGIN.trim()})`);
console.log(` ID Usuário: ${user.USU_CODIGO_ID}`);
console.log(` Empresa: ${empresaId}`);
console.log(` Status: ${statusOk ? '✅ Ativo' : '❌ Inativo'}`);
console.log(` Acesso Web: ${webOk ? '✅ Sim' : '❌ Não'}`);
console.log('');
console.log(' ┌─ TOKEN ─────────────────────────────────────────────────┐');
console.log(`${token}`);
console.log(' └─────────────────────────────────────────────────────────┘');
console.log('');
console.log(' Expira em: ' + authConfig.expiresIn);
console.log('');
console.log(' 📌 Headers para usar nas requisições:');
console.log(' Authorization: Bearer ' + token.substring(0, 50) + '...');
console.log(' X-Usu-Token: ' + token.substring(0, 50) + '...');
console.log('');
process.exit(0);
}
main().catch(err => {
console.error('Erro:', err.message);
process.exit(1);
});
+67
View File
@@ -0,0 +1,67 @@
/**
* Script para gerar/renovar o USU_TOKEN de um usuário.
* Uso: node scripts/gerar-token-usuario.js <LOGIN> [alias]
*
* Cada usuário recebe um token único (48 caracteres hex) armazenado
* no campo USU_TOKEN, que pode ser usado para autenticação na API
* via header X-Usu-Token.
*
* @param {string} LOGIN - Login do usuário
* @param {string} [alias=lajedo] - Alias do banco de dados
*/
const crypto = require('crypto');
const db = require('../src/database');
async function main() {
const login = process.argv[2];
const alias = process.argv[3] || 'lajedo';
if (!login) {
console.log('Uso: node scripts/gerar-token-usuario.js <LOGIN> [alias]');
console.log('Ex: node scripts/gerar-token-usuario.js SUPORTE lajedo');
process.exit(1);
}
// Verifica se o usuário existe
const users = await db.query(alias,
`SELECT USU_CODIGO_ID, USU_NOME, USU_LOGIN, USU_STATUS, USU_ACESSO_WEB
FROM USUARIOS WHERE USU_LOGIN = ?`,
[login]
);
if (users.length === 0) {
console.log(`❌ Usuário "${login}" não encontrado no alias "${alias}".`);
process.exit(1);
}
const user = users[0];
// Gera um token único (48 caracteres hex)
const token = crypto.randomBytes(24).toString('hex');
// Armazena no banco
await db.execute(alias,
'UPDATE USUARIOS SET USU_TOKEN = ? WHERE USU_CODIGO_ID = ?',
[token, user.USU_CODIGO_ID]
);
console.log('=== Token gerado com sucesso! ===');
console.log(`Alias: ${alias}`);
console.log(`Usuário: ${user.USU_NOME.trim()} (${user.USU_LOGIN.trim()})`);
console.log(`ID: ${user.USU_CODIGO_ID}`);
console.log(`Status: ${user.USU_STATUS === 'A' ? '✅ Ativo' : '❌ Inativo'}`);
console.log(`Acesso Web: ${user.USU_ACESSO_WEB === 1 ? '✅ Sim' : '❌ Não'}`);
console.log(`\n🔑 USU_TOKEN:`);
console.log(`${token}`);
console.log(`\n📌 Use no header das requisições:`);
console.log(`X-Usu-Token: ${token}`);
console.log(`ou`);
console.log(`Authorization: Bearer ${token}`);
process.exit(0);
}
main().catch(err => {
console.error('Erro:', err.message);
process.exit(1);
});
+63
View File
@@ -0,0 +1,63 @@
/**
* Script para habilitar acesso WEB a um usuário existente.
* Uso: node scripts/habilitar-acesso-web.js <LOGIN> [alias]
*
* @param {string} LOGIN - Login do usuário
* @param {string} [alias=lajedo] - Alias do banco de dados
*/
const db = require('../src/database');
async function main() {
const login = process.argv[2];
const alias = process.argv[3] || 'lajedo';
if (!login) {
console.log('Uso: node scripts/habilitar-acesso-web.js <LOGIN> [alias]');
console.log('Ex: node scripts/habilitar-acesso-web.js SUPORTE lajedo');
process.exit(1);
}
// Verifica se o usuário existe
const users = await db.query(alias,
'SELECT USU_CODIGO_ID, USU_NOME, USU_LOGIN, USU_ACESSO_WEB, USU_STATUS, USU_SENHA_WEB FROM USUARIOS WHERE USU_LOGIN = ?',
[login]
);
if (users.length === 0) {
console.log(`❌ Usuário "${login}" não encontrado no alias "${alias}".`);
process.exit(1);
}
const user = users[0];
console.log('=== Usuário encontrado ===');
console.log(`Alias: ${alias}`);
console.log(`ID: ${user.USU_CODIGO_ID}`);
console.log(`Nome: ${user.USU_NOME.trim()}`);
console.log(`Login: ${user.USU_LOGIN.trim()}`);
console.log(`Acesso Web: ${user.USU_ACESSO_WEB === 1 ? '✅ Sim' : '❌ Não'}`);
console.log(`Status: ${user.USU_STATUS === 'A' ? '✅ Ativo' : '❌ Inativo'}`);
console.log(`Senha Web: ${user.USU_SENHA_WEB || '(vazia)'}`);
// Habilita acesso web
await db.execute(alias,
'UPDATE USUARIOS SET USU_ACESSO_WEB = 1 WHERE USU_CODIGO_ID = ?',
[user.USU_CODIGO_ID]
);
console.log('\n✅ Acesso WEB habilitado com sucesso!');
console.log(` Agora o usuário "${login}" pode usar o app.`);
if (!user.USU_SENHA_WEB) {
console.log('\n⚠️ ATENÇÃO: O usuário não possui senha web (USU_SENHA_WEB).');
console.log(' Defina uma senha para que o login funcione.');
console.log(' Ex: node scripts/definir-senha-web.js SUPORTE 123456 ' + alias);
}
process.exit(0);
}
main().catch(err => {
console.error('Erro:', err.message);
process.exit(1);
});
+55
View File
@@ -0,0 +1,55 @@
/**
* Lista todos os usuários com seus respectivos USU_TOKEN.
* Uso: node scripts/listar-tokens.js [LOGIN] [alias]
*
* @param {string} [LOGIN] - Filtrar por login (opcional)
* @param {string} [alias=lajedo] - Alias do banco de dados
*/
const db = require('../src/database');
async function main() {
const filtro = process.argv[2];
const alias = process.argv[3] || 'lajedo';
const sql = filtro
? `SELECT USU_CODIGO_ID, USU_NOME, USU_LOGIN, USU_STATUS,
USU_ACESSO_WEB, USU_TOKEN
FROM USUARIOS WHERE USU_LOGIN = ?`
: `SELECT USU_CODIGO_ID, USU_NOME, USU_LOGIN, USU_STATUS,
USU_ACESSO_WEB, USU_TOKEN
FROM USUARIOS`;
const users = await db.query(alias, sql, filtro ? [filtro] : []);
if (users.length === 0) {
console.log(`Nenhum usuário encontrado no alias "${alias}".`);
process.exit(0);
}
console.log('='.repeat(80));
console.log(` USUÁRIOS E TOKENS (alias: ${alias})`);
console.log('='.repeat(80));
console.log('');
users.forEach((u, i) => {
const status = u.USU_STATUS?.trim() === 'A' ? '✅' : '❌';
const web = u.USU_ACESSO_WEB === 1 ? '✅' : '❌';
const token = u.USU_TOKEN?.trim();
console.log(`${i + 1}. ${u.USU_NOME.trim()} (${u.USU_LOGIN.trim()})`);
console.log(` ID: ${u.USU_CODIGO_ID} | Status: ${status} | Acesso Web: ${web}`);
console.log(` USU_TOKEN: ${token || '(vazio)'}`);
console.log('');
});
console.log('='.repeat(80));
console.log(`Total: ${users.length} usuário(s)`);
console.log('='.repeat(80));
process.exit(0);
}
main().catch(err => {
console.error('Erro:', err.message);
process.exit(1);
});
+490
View File
@@ -0,0 +1,490 @@
/**
* ARQUIVO DE MIGRAÇÕES DO BANCO DE DADOS
* ========================================
*
* Este script contém TODAS as alterações estruturais feitas no banco de dados
* desde a criação inicial do projeto.
*
* USO:
* node scripts/migracoes.js [alias]
*
* Exemplo:
* node scripts/migracoes.js lajedo
* node scripts/migracoes.js novo
*
* IMPORTANTE:
* - Cada migração tem um ID único e só executa uma vez
* - O controle é feito via tabela CHATC2_CONTROLE_MIGRACOES
* - Para adicionar NOVA migração, adicione um novo objeto no array MIGRACOES
* - Nunca remova ou altere migrações já existentes (apenas adicione novas)
*/
const db = require('../src/database');
// Este script aplica DDL no dialeto Firebird (BLOB SUB_TYPE, etc.).
// O schema do PostgreSQL é gerenciado externamente (banco externo) — use este
// script apenas para bancos Firebird. Alias padrão: firebird_local.
const alias = process.argv[2] || 'firebird_local';
// ============================================================
// CONTROLE DE MIGRAÇÕES
// ============================================================
async function garantirControle() {
try {
await db.execute(alias, `
CREATE TABLE CHATC2_CONTROLE_MIGRACOES (
MIG_ID INTEGER NOT NULL PRIMARY KEY,
MIG_DESCRICAO VARCHAR(200),
MIG_DATA_EXECUCAO TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
MIG_STATUS CHAR(1) DEFAULT 'A'
)
`);
console.log('✅ Tabela CHATC2_CONTROLE_MIGRACOES criada');
} catch(e) {
if (!e.message.includes('already exists')) {
console.log('⚠️ CHATC2_CONTROLE_MIGRACOES:', e.message.substring(0, 100));
}
}
}
async function migracaoJaExecutada(id) {
try {
var r = await db.query(alias,
'SELECT COUNT(*) AS CT FROM CHATC2_CONTROLE_MIGRACOES WHERE MIG_ID = ?', [id]);
return r[0]?.CT > 0;
} catch(e) {
return false;
}
}
async function registrarMigracao(id, descricao) {
try {
await db.execute(alias,
'INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (?, ?)',
[id, descricao]);
} catch(e) {
console.log('⚠️ Erro ao registrar migração:', e.message.substring(0, 80));
}
}
// ============================================================
// LISTA DE MIGRAÇÕES
// ============================================================
// SEMPRE adicione NOVAS migrações no FINAL do array, com ID sequencial.
// NUNCA remova ou modifique migrações já existentes.
const MIGRACOES = [
// ----------------------------------------------------------
// MIGRAÇÃO 1: Campo USU_TIPO_CHAT em USUARIOS
// ----------------------------------------------------------
{
id: 1,
descricao: 'Adicionar USU_TIPO_CHAT em USUARIOS (A=Atendente, G=Gerente)',
sql: [
`ALTER TABLE USUARIOS ADD USU_TIPO_CHAT CHAR(1) DEFAULT 'A'`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 2: Tabela CHATC2_INSTANCIAS (Evolution API)
// ----------------------------------------------------------
{
id: 2,
descricao: 'Criar tabela CHATC2_INSTANCIAS para conexões WhatsApp',
sql: [
`CREATE TABLE CHATC2_INSTANCIAS (
INS_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
INS_EMPRESA_ID INTEGER NOT NULL,
INS_NOME VARCHAR(100),
INS_URL VARCHAR(500),
INS_API_KEY VARCHAR(500),
INS_INSTANCE_NAME VARCHAR(100),
INS_STATUS CHAR(1) DEFAULT 'D',
INS_QR_CODE BLOB SUB_TYPE 0,
INS_DT_CONEXAO TIMESTAMP,
INS_DT_CADASTRO TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INS_SITUACAO CHAR(1) DEFAULT 'A'
)`,
`CREATE SEQUENCE GEN_INSTANCIAS`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 3: Tabela CHATC2_EQUIPES
// ----------------------------------------------------------
{
id: 3,
descricao: 'Criar tabela CHATC2_EQUIPES',
sql: [
`CREATE TABLE CHATC2_EQUIPES (
EQU_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
EQU_EMPRESA_ID INTEGER NOT NULL,
EQU_NOME VARCHAR(100),
EQU_ORDEM INTEGER DEFAULT 0,
EQU_SITUACAO CHAR(1) DEFAULT 'A'
)`,
`CREATE SEQUENCE GEN_EQUIPES`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 4: Tabela CHATC2_USU_EQUIPES
// ----------------------------------------------------------
{
id: 4,
descricao: 'Criar tabela CHATC2_USU_EQUIPES (relação N:N)',
sql: [
`CREATE TABLE CHATC2_USU_EQUIPES (
EQU_EQUIPE_ID INTEGER NOT NULL,
EQU_USUARIO_ID INTEGER NOT NULL,
PRIMARY KEY (EQU_EQUIPE_ID, EQU_USUARIO_ID)
)`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 5: Tabela CHATC2_ETIQUETAS
// ----------------------------------------------------------
{
id: 5,
descricao: 'Criar tabela CHATC2_ETIQUETAS',
sql: [
`CREATE TABLE CHATC2_ETIQUETAS (
ETI_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
ETI_EMPRESA_ID INTEGER NOT NULL,
ETI_NOME VARCHAR(50),
ETI_COR VARCHAR(7) DEFAULT '#667eea',
ETI_SITUACAO CHAR(1) DEFAULT 'A'
)`,
`CREATE SEQUENCE GEN_ETIQUETAS`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 6: Tabela CHATC2_CONVERSAS
// ----------------------------------------------------------
{
id: 6,
descricao: 'Criar tabela CHATC2_CONVERSAS',
sql: [
`CREATE TABLE CHATC2_CONVERSAS (
CON_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
CON_EMPRESA_ID INTEGER NOT NULL,
CON_INSTANCIA_ID INTEGER,
CON_CLIENTE_ID INTEGER,
CON_NUMERO VARCHAR(20),
CON_NOME_CONTATO VARCHAR(100),
CON_STATUS CHAR(1) DEFAULT 'E',
CON_USUARIO_ID INTEGER,
CON_EQUIPE_ID INTEGER,
CON_DT_INICIO TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CON_DT_FINAL TIMESTAMP,
CON_DT_ULTIMA_MSG TIMESTAMP,
CON_SAUDACAO_ENVIADA CHAR(1) DEFAULT 'N',
CON_CSAT_ENVIADO CHAR(1) DEFAULT 'N',
CON_PRIMEIRA_MSG TIMESTAMP,
CON_ORIGEM VARCHAR(20) DEFAULT 'whatsapp',
CON_SITUACAO CHAR(1) DEFAULT 'A'
)`,
`CREATE SEQUENCE GEN_CONVERSAS`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 7: Tabela CHATC2_CONVERSAS_MENSAGENS
// ----------------------------------------------------------
{
id: 7,
descricao: 'Criar tabela CHATC2_CONVERSAS_MENSAGENS',
sql: [
`CREATE TABLE CHATC2_CONVERSAS_MENSAGENS (
CME_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
CME_CONVERSA_ID INTEGER NOT NULL,
CME_REMETENTE CHAR(1) DEFAULT 'C',
CME_USUARIO_ID INTEGER,
CME_MENSAGEM BLOB SUB_TYPE 1,
CME_TEXTO VARCHAR(4000),
CME_TIPO VARCHAR(20) DEFAULT 'text',
CME_DT_ENVIO TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CME_PRIVADA CHAR(1) DEFAULT 'N',
CME_LIDA CHAR(1) DEFAULT 'N',
CME_MIDIA_ID INTEGER,
CME_SITUACAO CHAR(1) DEFAULT 'A'
)`,
`CREATE SEQUENCE GEN_CONVERSAS_MENSAGENS`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 8: Tabela CHATC2_MENSAGENS_ATENDIMENTOS (blob mídias)
// ----------------------------------------------------------
{
id: 8,
descricao: 'Criar tabela CHATC2_MENSAGENS_ATENDIMENTOS para mídias',
sql: [
`CREATE TABLE CHATC2_MENSAGENS_ATENDIMENTOS (
MAT_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
MAT_CONVERSA_ID INTEGER,
MAT_MENSAGEM_ID INTEGER,
MAT_NOME_ARQUIVO VARCHAR(255),
MAT_TIPO_ARQUIVO VARCHAR(50),
MAT_ARQUIVO BLOB SUB_TYPE 0,
MAT_DT_CADASTRO TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
MAT_SITUACAO CHAR(1) DEFAULT 'A'
)`,
`CREATE SEQUENCE GEN_MENSAGENS_ATENDIMENTO`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 9: Tabela CHATC2_CONVERSAS_ETIQUETAS
// ----------------------------------------------------------
{
id: 9,
descricao: 'Criar tabela CHATC2_CONVERSAS_ETIQUETAS',
sql: [
`CREATE TABLE CHATC2_CONVERSAS_ETIQUETAS (
CET_CONVERSA_ID INTEGER NOT NULL,
CET_ETIQUETA_ID INTEGER NOT NULL,
PRIMARY KEY (CET_CONVERSA_ID, CET_ETIQUETA_ID)
)`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 10: Tabela CHATC2_CSAT_AVALIACOES
// ----------------------------------------------------------
{
id: 10,
descricao: 'Criar tabela CHATC2_CSAT_AVALIACOES',
sql: [
`CREATE TABLE CHATC2_CSAT_AVALIACOES (
CSA_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
CSA_CONVERSA_ID INTEGER,
CSA_EMPRESA_ID INTEGER,
CSA_CLIENTE_ID INTEGER,
CSA_NOTA INTEGER,
CSA_COMENTARIO VARCHAR(500),
CSA_DT_AVALIACAO TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE SEQUENCE GEN_CSAT_AVALIACOES`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 11: Tabela CHATC2_CONFIGURACOES_EMPRESA (base)
// ----------------------------------------------------------
{
id: 11,
descricao: 'Criar tabela CHATC2_CONFIGURACOES_EMPRESA (base)',
sql: [
`CREATE TABLE CHATC2_CONFIGURACOES_EMPRESA (
CFE_EMPRESA_ID INTEGER NOT NULL PRIMARY KEY,
CFE_INSTANCIA_PADRAO_ID INTEGER,
CFE_FOTO_CELULAR CHAR(1) DEFAULT 'N',
CFE_SAUDACAO_ATIVA CHAR(1) DEFAULT 'S',
CFE_SAUDACAO_MENSAGEM VARCHAR(500),
CFE_CSAT_ATIVO CHAR(1) DEFAULT 'N',
CFE_CSAT_MENSAGEM VARCHAR(500) DEFAULT 'Avalie seu atendimento de 1 a 5 estrelas'
)`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 12: EQU_ORDEM em CHATC2_EQUIPES
// ----------------------------------------------------------
{
id: 12,
descricao: 'Adicionar EQU_ORDEM em CHATC2_EQUIPES (ordenação)',
sql: [
`ALTER TABLE CHATC2_EQUIPES ADD EQU_ORDEM INTEGER DEFAULT 0`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 13: Campos de triagem em CHATC2_CONFIGURACOES_EMPRESA
// ----------------------------------------------------------
{
id: 13,
descricao: 'Adicionar campos de triagem em CHATC2_CONFIGURACOES_EMPRESA',
sql: [
`ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_ENVIAR_NOME_USUARIO CHAR(1) DEFAULT 'N'`,
`ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_TRIAGEM_ATIVA CHAR(1) DEFAULT 'N'`,
`ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_TRIAGEM_MSG_WELCOME VARCHAR(500)`,
`ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_TRIAGEM_MSG_AFTER VARCHAR(500)`,
`ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_TRIAGEM_BOLETO_NUMERO VARCHAR(10) DEFAULT '0'`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 14: CON_MENU_ESTADO em CHATC2_CONVERSAS
// ----------------------------------------------------------
{
id: 14,
descricao: 'Adicionar CON_MENU_ESTADO em CHATC2_CONVERSAS (navegação triagem)',
sql: [
`ALTER TABLE CHATC2_CONVERSAS ADD CON_MENU_ESTADO VARCHAR(100)`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 15: CON_ETIQUETAS_DESC em CHATC2_CONVERSAS
// ----------------------------------------------------------
{
id: 15,
descricao: 'Adicionar CON_ETIQUETAS_DESC em CHATC2_CONVERSAS (estado legado)',
sql: [
`ALTER TABLE CHATC2_CONVERSAS ADD CON_ETIQUETAS_DESC VARCHAR(500)`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 16: Tabela CHATC2_MENUS_EMPRESA (fluxo personalizado)
// ----------------------------------------------------------
{
id: 16,
descricao: 'Criar tabela CHATC2_MENUS_EMPRESA para fluxo de atendimento personalizado',
sql: [
`CREATE TABLE CHATC2_MENUS_EMPRESA (
MNE_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
MNE_EMPRESA_ID INTEGER,
MNE_EQUIPE_ID INTEGER,
MNE_MENU_PAI_ID INTEGER,
MNE_ORDEM INTEGER DEFAULT 0,
MNE_TITULO VARCHAR(100),
MNE_TIPO CHAR(1) DEFAULT 'M',
MNE_TEXTO BLOB SUB_TYPE TEXT,
MNE_ACAO_ROTA VARCHAR(100),
MNE_ACAO_METODO VARCHAR(10),
MNE_ACAO_PROMPT VARCHAR(300),
MNE_SITUACAO CHAR(1) DEFAULT 'A'
)`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 17: CON_USUARIO_NOME, CON_EQUIPE_NOME, CON_ETIQUETAS_DESC em CHATC2_CONVERSAS
// ----------------------------------------------------------
{
id: 17,
descricao: 'Adicionar CON_USUARIO_NOME, CON_EQUIPE_NOME, CON_ETIQUETAS_DESC em CHATC2_CONVERSAS',
sql: [
`ALTER TABLE CHATC2_CONVERSAS ADD CON_USUARIO_NOME VARCHAR(100)`,
`ALTER TABLE CHATC2_CONVERSAS ADD CON_EQUIPE_NOME VARCHAR(100)`,
`ALTER TABLE CHATC2_CONVERSAS ADD CON_ETIQUETAS_DESC VARCHAR(500)`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 18: Etiquetas nos menus de fluxo
// ----------------------------------------------------------
{
id: 18,
descricao: 'Adicionar MNE_ETIQUETA_IDS em CHATC2_MENUS_EMPRESA (etiquetas por etapa do fluxo)',
sql: [
`ALTER TABLE CHATC2_MENUS_EMPRESA ADD MNE_ETIQUETA_IDS VARCHAR(200)`,
],
},
// ----------------------------------------------------------
// MIGRAÇÃO 19: Transcrição de áudio em CHATC2_MENSAGENS_ATENDIMENTOS
// ----------------------------------------------------------
{
id: 19,
descricao: 'Adicionar MAT_TRANSCRICAO em CHATC2_MENSAGENS_ATENDIMENTOS (transcricao de audio)',
sql: [
`ALTER TABLE CHATC2_MENSAGENS_ATENDIMENTOS ADD MAT_TRANSCRICAO BLOB SUB_TYPE TEXT`,
],
},
// ============================================================
// >>> ADICIONE NOVAS MIGRAÇÕES AQUI <<<
// ============================================================
//
// Exemplo:
// {
// id: 19,
// descricao: 'Descrição clara do que esta migração faz',
// sql: [
// `ALTER TABLE EXEMPLO ADD NOVO_CAMPO VARCHAR(100)`,
// `CREATE TABLE NOVA_TABELA ( ... )`,
// ],
// },
//
];
// ============================================================
// EXECUTOR DE MIGRAÇÕES
// ============================================================
async function main() {
console.log(`\n=== MIGRAÇÕES DE BANCO DE DADOS ===`);
console.log(`Alias: ${alias}\n`);
// Blindagem: a DDL aqui é Firebird. Se o alias for Postgres (banco externo,
// gerenciado fora), não há o que aplicar — sai sem erro.
let driver;
try { driver = db.driverOf(alias); }
catch (e) { console.error('❌', e.message); process.exit(1); }
if (driver !== 'firebird') {
console.log(`⚠️ O alias "${alias}" usa o driver "${driver}". Este script aplica DDL Firebird.`);
console.log(' O schema do PostgreSQL é gerenciado no banco externo — nada a fazer aqui.');
console.log(' Para migrar um banco Firebird: node scripts/migracoes.js firebird_local\n');
process.exit(0);
}
await garantirControle();
var executadas = 0;
var pendentes = 0;
for (var mig of MIGRACOES) {
var jaExecutou = await migracaoJaExecutada(mig.id);
if (jaExecutou) {
console.log(`⏭️ [${mig.id}] ${mig.descricao} — já executada`);
continue;
}
pendentes++;
console.log(`▶️ [${mig.id}] ${mig.descricao}...`);
var erros = 0;
for (var cmd of mig.sql) {
try {
await db.execute(alias, cmd);
console.log(`${cmd.substring(0, 80)}...`);
} catch(e) {
var msg = e.message;
// Firebird pode reportar "already exists" de várias formas
if (msg.includes('already exists') || msg.includes('Violation') || msg.includes('duplicate') || msg.includes('Unknow')) {
console.log(` ️ Já existe: ${cmd.substring(0, 60)}...`);
} else {
console.log(`${msg.substring(0, 120)}`);
erros++;
}
}
}
if (erros === 0) {
await registrarMigracao(mig.id, mig.descricao);
executadas++;
console.log(` ✅ Migração ${mig.id} concluída\n`);
} else {
console.log(` ⚠️ Migração ${mig.id} concluída com ${erros} erro(s)\n`);
}
}
console.log(`=== RESUMO ===`);
console.log(`Total de migrações: ${MIGRACOES.length}`);
console.log(`Executadas agora: ${executadas}`);
console.log(`Já executadas antes: ${MIGRACOES.length - pendentes}`);
console.log(`Pendentes (com erro): ${pendentes - executadas}`);
console.log(`\n✅ Finalizado!`);
process.exit(0);
}
main().catch(function(err) {
console.error('❌ Erro fatal:', err.message);
process.exit(1);
});