Mapa do Banco de Dados PostgreSQL

Host: 72.62.137.163 | Banco: postgres | User: zpro Schema: public | ORM: Sequelize (TypeScript)

Tabelas principais

Tickets (core do atendimento)

Tickets {
  id              SERIAL PRIMARY KEY
  status          ENUM('open','pending','closed')
  channel         VARCHAR  -- 'waba','baileys','gupshup','dialog360','instagram',...
  contactId       INTContacts.id
  userId          INTUsers.id (agente responsavel)
  queueId         INTQueues.id
  whatsappId      INTWhatsapps.id (canal de conexao)
  tenantId        INTTenants.id
  chatFlowId      INTChatFlows.id (chatbot ativo)
  isGroup         BOOLEAN
  isBot           BOOLEAN
  isPaused        BOOLEAN
  pauseReason     TEXT
  unreadMessages  INT
  lastMessageAt   TIMESTAMP
  firstResponseAt TIMESTAMP
  closedAt        TIMESTAMP
  isFarewellMessage BOOLEAN
  createdAt       TIMESTAMP
  updatedAt       TIMESTAMP
}

Messages

Messages {
  id              UUID PRIMARY KEY
  body            TEXT
  fromMe          BOOLEAN
  read            BOOLEAN
  mediaType       VARCHAR -- 'chat','image','video','audio','document','sticker'
  mediaUrl        TEXT
  mediaName       VARCHAR
  ack             INT -- 0=aguardando,1=enviado,2=entregue,3=lido,4=reproduzido
  ticketId        INTTickets.id
  contactId       INTContacts.id
  quotedMsgId     VARCHAR
  scheduleDate    TIMESTAMP
  isDeleted       BOOLEAN
  isEdited        BOOLEAN
  editedBody      TEXT
  isForwarded     BOOLEAN
  tenantId        INT
  createdAt       TIMESTAMP
}

Contacts

Contacts {
  id              SERIAL PRIMARY KEY
  name            VARCHAR
  number          VARCHAR  -- numero normalizado (55DDD...)
  email           VARCHAR
  profilePicUrl   TEXT
  cpf             VARCHAR
  birthdayDate    VARCHAR
  firstName       VARCHAR
  lastName        VARCHAR
  businessName    VARCHAR
  isGroup         BOOLEAN
  tenantId        INT
  walletId        INTWallets.id
  kanban          INTKanbans.id
  lid             VARCHAR  -- WhatsApp LID (novo formato)
  messengerId     VARCHAR
  instagramPK     VARCHAR
  -- campos de hub/outros canais...
}

ContactTags (relacao N:N)

ContactTags {
  contactId  INTContacts.id
  tagId      INTTags.id
  tenantId   INT
}

API Frontend: PUT /contact-tags/:contactId com body { tags: number[] }

Whatsapps (canais/sessoes)

Whatsapps {
  id              SERIAL PRIMARY KEY
  name            VARCHAR
  status          ENUM('CONNECTED','DISCONNECTED','TIMEOUT','OPENING','qrcode')
  type            VARCHAR -- 'baileys','waba','gupshup','dialog360','instagram',...
  number          VARCHAR
  tokenAPI        VARCHAR  -- phoneNumberId (WABA) ou apiKey (outros)
  bmToken         VARCHAR  -- token de acesso Meta (WABA)
  wabaId          VARCHAR
  wabaVersion     VARCHAR  -- 'v19.0'
  wavoipToken     TEXT     -- tokens WaVoIP separados por virgula
  baileysLib      VARCHAR  -- 'fork','v7','infiniteapi'
  queueId         INTQueues.id
  tenantId        INT
  isDefault       BOOLEAN
}

BaileysSessions (estado Signal Protocol)

BaileysSessions {
  id          SERIAL PRIMARY KEY
  sessionId   VARCHAR  -- identificador da sessao Baileys
  session     TEXT     -- JSON com chaves criptograficas Signal Protocol
  type        VARCHAR  -- tipo de dado da sessao
  tenantId    INT
}

⚠️ Esta tabela e limpa pelo resetBaileysConnection. Se corrompida → PreKeyError → mensagens nao chegam.

Queues (filas)

Queues {
  id              SERIAL PRIMARY KEY
  name            VARCHAR
  color           VARCHAR
  greetingMessage TEXT
  outOfHoursMessage TEXT
  farewellMessage TEXT
  isActive        BOOLEAN
  businessHours   JSONB   -- horarios de funcionamento
  autoDistribute  BOOLEAN
  autoDistributeLimit INT
  tenantId        INT
}

Users (agentes)

Users {
  id              SERIAL PRIMARY KEY
  name            VARCHAR
  email           VARCHAR UNIQUE
  password        VARCHAR (hash)
  profile         ENUM('user','admin','super','superadmin','custom')
  isOnline        BOOLEAN
  tenantId        INT
  whatsappId      INTWhatsapps.id
}

Tenants (empresas/clientes)

Tenants {
  id              SERIAL PRIMARY KEY
  name            VARCHAR
  status          ENUM('active','inactive','blocked')
  smsToken        VARCHAR
  conectaSmsToken VARCHAR
  livsonSmsToken  VARCHAR
  infobipRcsApiKey VARCHAR
  infobipRcsBaseUrl VARCHAR
  sinchRcsApiKey  VARCHAR
  sinchRcsSenderId VARCHAR
  zenviaRcsToken  VARCHAR
  -- muitos outros tokens de integracao...
}

Pipelines / Stages / Opportunities (Funil)

Pipelines {
  id        SERIAL PRIMARY KEY
  name      VARCHAR
  tenantId  INT
}
 
Stages {
  id              SERIAL PRIMARY KEY
  name            VARCHAR
  color           VARCHAR
  order           INT
  pipelineId      INTPipelines.id
  notifyQueueId   INTQueues.id    -- notificacao ao avancar
  notifyUserId    INTUsers.id
  tenantId        INT
}
 
Opportunities {
  id              SERIAL PRIMARY KEY
  name            VARCHAR
  value           DECIMAL
  status          ENUM('open','win','lose','pending')
  closingForecast DATE
  contactId       INTContacts.id
  stageId         INTStages.id
  pipelineId      INTPipelines.id
  userId          INTUsers.id (responsavel)
  tenantId        INT
  createdAt       TIMESTAMP
  updatedAt       TIMESTAMP
}

Kanbans / KanbanContacts

Kanbans {
  id        SERIAL PRIMARY KEY
  name      VARCHAR  -- nome da lane/coluna
  color     VARCHAR
  tag       VARCHAR
  tenantId  INT
}
-- Contacts.kanban = KanbanId (FK direto no contato)

Tags

Tags {
  id              SERIAL PRIMARY KEY
  name            VARCHAR
  color           VARCHAR
  tenantId        INT
  isActive        BOOLEAN
  triggerKeyword  VARCHAR  -- para ativar chatbot automaticamente
}

ChatFlows (FlowBuilder)

ChatFlows {
  id              SERIAL PRIMARY KEY
  name            VARCHAR
  status          BOOLEAN
  isActive        BOOLEAN
  celularTeste    VARCHAR
  flow            JSONB   -- { name, nodeList, lineList }
  tenantId        INT
}

Queries críticas frequentes

Tickets abertos por canal

SELECT w.name, COUNT(t.id) as total
FROM "Tickets" t
JOIN "Whatsapps" w ON t."whatsappId" = w.id
WHERE t.status = 'open' AND t."tenantId" = :tenantId
GROUP BY w.name ORDER BY total DESC;

Contatos sem ticket nos ultimos 7 dias

SELECT c.name, c.number, t."lastMessageAt"
FROM "Contacts" c
JOIN "Tickets" t ON t."contactId" = c.id
WHERE t."tenantId" = :tenantId
  AND t."lastMessageAt" < NOW() - INTERVAL '7 days'
  AND t.status = 'open'
ORDER BY t."lastMessageAt" ASC;

Sessions Baileys corrompidas

-- Ver quantidade de sessoes por canal
SELECT "sessionId", COUNT(*) as registros
FROM "BaileysSessions"
WHERE "tenantId" = :tenantId
GROUP BY "sessionId"
ORDER BY registros DESC;

Problema: terminating connection due to administrator command

Visto nos logs: Error: terminating connection due to administrator command

Causa: PgBouncer (pool de conexoes) matando conexoes ociosas apos timeout. Com 6 workers × multiplas conexoes por worker = muitas conexoes ativas.

Impacto: Worker crasha → Baileys daquela instancia desconecta → sessoes perdem sincronismo.

Mitigacao: Reduzir workers de 6 para 4 (ou configurar keepAlive no Sequelize).