Pular para conteúdo

Modelo de Dados

Diagrama de Relacionamentos

┌─────────────────┐
│     Tenant      │
└────────┬────────┘
         ├──────────────┬──────────────┬──────────────┬──────────────┐
         │              │              │              │              │
         ▼              ▼              ▼              ▼              ▼
   ┌──────────┐  ┌──────────┐  ┌──────────────┐  ┌──────────┐  ┌──────────────┐
   │   User   │  │  Agent   │  │WhatsAppInstance│ │ LlmUsage │  │ Subscription │
   └──────────┘  └────┬─────┘  └───────┬───────┘  └──────────┘  └──────┬───────┘
                      │                │                                │
         ┌────────────┼────────┬───────┘               ┌───────────────┼───────────────┐
         │            │        │                        │               │               │
         ▼            ▼        ▼                        ▼               ▼               ▼
   ┌──────────┐ ┌──────────┐ ┌──────────┐        ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
   │Knowledge │ │   FAQ    │ │  Agent   │        │   Credit     │ │ Subscription │ │ Subscription │
   │  Block   │ │          │ │   Step   │        │ Transaction  │ │    Event     │ │    Item      │
   └────┬─────┘ └──────────┘ └────┬────┘        └──────────────┘ └──────────────┘ └──────┬───────┘
        │                         │                                                       │
        ▼                    ┌────┴─────┐                                                 ▼
   ┌──────────┐              │ Prompt   │                                           ┌──────────────┐
   │Knowledge │              │ Version  │                                           │ Subscription │
   │  Chunk   │              └──────────┘                                           │   Product    │
   └──────────┘                                                                     └──────────────┘

         Agent                    Agent / AgentStep
           │                            │
           ▼                            ▼
   ┌──────────────┐              ┌──────────────┐
   │  Attachment  │              │ FollowUpConfig│
   └──────────────┘              └──────┬───────┘
                          ┌─────────────┘
                   ┌──────────────────┐
                   │ConversationFollowUp│
                   └──────────────────┘
                   ┌──────┴───────┐
                   │  Conversation │
                   └──────┬───────┘
              ┌───────────┼───────────┐
              ▼           ▼           ▼
       ┌──────────┐ ┌──────────┐ ┌──────────────────┐
       │ Message  │ │  Step    │ │    Pending       │
       └────┬─────┘ │Transition│ │  Aggregation     │
            │       └──────────┘ └──────────────────┘
       ┌──────────┐
       │ Reaction │
       └──────────┘

┌─────────────────┐      ┌─────────────────┐
│InstanceWhitelist│      │InstanceBlacklist│
└─────────────────┘      └─────────────────┘
        ▲                        ▲
        └────────┬───────────────┘
          WhatsAppInstance

Subscription ──N:1──► CreditPackage

Nota: LlmUsage armazena referências (agentId, conversationId, etc.) em metadata JSONB

Entidades

Tenant

Empresa/organização que utiliza a plataforma (multi-tenancy).

Campo Tipo Descrição
Id Guid Identificador único
Name string Nome da empresa
Timezone string? Fuso horário do tenant (IANA timezone ID)
IsActive bool Se o tenant está ativo
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Relacionamentos: - 1:N Users - 1:N Agents - 1:N WhatsAppInstances - 1:1 Subscription (opcional)


User

Usuários do portal administrativo.

Campo Tipo Descrição
Id Guid Identificador único
TenantId Guid FK Tenant (obrigatório)
Email string Login único
PasswordHash string Senha BCrypt
Name string Nome de exibição
IsAdmin bool Admin do tenant
IsSuperAdmin bool Admin global (acesso a páginas admin)
IsActive bool Se pode fazer login
MustChangePassword bool Força troca de senha no próximo login
OnboardingCompleted bool Se completou o onboarding
OnboardingDismissed bool Se dispensou o onboarding
CreatedAt DateTime Data de criação
LastLoginAt DateTime? Último acesso

Regras de acesso: - IsSuperAdmin = true: Acesso a páginas admin (Tenants, LLM Usage, Settings) — dados filtrados pelo seu tenant - IsAdmin = true: Gerencia usuários e recursos do tenant - IsAdmin = false: Acesso limitado ao tenant

Restrições: - Email deve ser único globalmente (não apenas por tenant)


Agent

Agentes IA que processam conversas.

Campo Tipo Descrição
Id Guid Identificador único
TenantId Guid FK Tenant
Name string Nome do agente ("Suporte TI")
Description string? Descrição interna
Prompt string? Prompt principal do agente (texto livre, nullable)
IsActive bool Se está processando mensagens
IsArchived bool Se o agente está arquivado
ArchivedAt DateTime? Data de arquivamento
ErrorMessage string? Mensagem customizada de erro
WelcomeMessage string? Mensagem de boas-vindas
RateLimitMessage string? Mensagem de rate limit
OutOfHoursMessage string? Mensagem fora do horário
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Relacionamentos: - 1:N WhatsAppInstances - 1:N AgentKnowledge - 1:N AgentAttachment - 1:N AgentFaq - 1:N AgentStep - 1:1 FollowUpConfig (opcional)


AgentStep

Etapas de atendimento que guiam a IA por um fluxo estruturado. Quando configuradas, a IA segue as instruções da etapa atual e avança conforme os objetivos são cumpridos.

Campo Tipo Descrição
Id Guid Identificador único
AgentId Guid FK Agent
TenantId Guid FK Tenant (denormalizado do Agent — elimina JOIN nos query filters)
Name string Nome da etapa (máx 100)
Instructions string Instruções da etapa
Order int Posição na sequência
IsActive bool Se está ativa no fluxo
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Comportamento: - Sem etapas: agente responde livremente usando apenas o prompt base - Com etapas: IA segue a etapa atual, pode avançar/voltar conforme necessidade - IA pode pular etapas se todas as anteriores foram atendidas - Ao completar todas as etapas, a conversa é resolvida automaticamente

Relacionamentos: - N:1 Agent (cascade delete) - 1:1 FollowUpConfig (opcional)


PromptVersion

Snapshot automático de prompts e instruções, criado a cada atualização de Agent.Prompt ou AgentStep.Instructions.

Campo Tipo Descrição
Id Guid Identificador único
AgentId Guid FK Agent
AgentStepId Guid? FK AgentStep (null = prompt do Agent)
Version int Número sequencial da versão
Content string Conteúdo do prompt/instrução no momento do snapshot
CreatedAt DateTime Data de criação

Discriminação: - AgentStepId == null: versão do Agent.Prompt - AgentStepId != null: versão do AgentStep.Instructions

Comportamento: - Snapshot criado automaticamente ANTES de cada update (captura estado anterior) - Restaurar uma versão aplica o conteúdo e cria um novo snapshot do estado atual - Versões são sequenciais por escopo (por Agent ou por AgentStep)

Índices: - UNIQUE (agent_id, agent_step_id, version) — sequencialidade - (agent_id) WHERE agent_step_id IS NULL — histórico do agent - (agent_step_id) WHERE agent_step_id IS NOT NULL — histórico do step

Relacionamentos: - N:1 Agent (cascade delete) - N:1 AgentStep (cascade delete, opcional)


AgentKnowledge

Base de conhecimento em blocos para compor o contexto do agente. O conteúdo é dividido em chunks (AgentKnowledgeChunk) para busca semântica com pgvector.

Campo Tipo Descrição
Id Guid Identificador único
AgentId Guid FK Agent
TenantId Guid FK Tenant (denormalizado do Agent — elimina JOIN nos query filters)
Title string Título do bloco
Content string Conteúdo (markdown, máx 25.000 caracteres)
DisplayOrder int Ordem de exibição
IsActive bool Se está incluído no prompt
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Busca Semântica: - O conteúdo é dividido em chunks (AgentKnowledgeChunk) que possuem embeddings individuais - Índice HNSW nos chunks para busca eficiente por similaridade - Retorna os chunks mais relevantes para cada pergunta - Veja knowledge-retrieval.md para detalhes

Relacionamentos: - N:1 Agent (cascade delete) - 1:N AgentKnowledgeChunk (cascade delete)


AgentKnowledgeChunk

Fragmento de um bloco de conhecimento com embedding vetorial para busca semântica.

Campo Tipo Descrição
Id Guid Identificador único
KnowledgeBlockId Guid FK AgentKnowledge
ChunkIndex int Posição do chunk dentro do bloco
Content string Conteúdo textual do chunk
Embedding Vector? Vetor de 1536 dimensões para busca semântica
EmbeddingUpdatedAt DateTime? Última atualização do embedding
CreatedAt DateTime Data de criação

Busca Semântica: - Embeddings gerados automaticamente ao criar/atualizar blocos de conhecimento - Índice HNSW (vector_cosine_ops) para busca eficiente por similaridade - Permite granularidade maior na recuperação de contexto relevante

Índices: - (knowledge_block_id) — lookup por bloco - (knowledge_block_id, chunk_index) — ordem dos chunks - HNSW (embedding vector_cosine_ops) — busca semântica

Relacionamentos: - N:1 AgentKnowledge (cascade delete)


AgentAttachment

Arquivos anexados a um agente, armazenados no R2. Enviados junto com as respostas da IA quando relevantes.

Campo Tipo Descrição
Id Guid Identificador único
AgentId Guid FK Agent
TenantId Guid FK Tenant (denormalizado do Agent — elimina JOIN nos query filters)
FileName string Nome do arquivo (sanitizado)
MimeType string MIME type do arquivo
FileSizeBytes long Tamanho em bytes
StorageUrl string URL no R2
DisplayName string Nome de exibição
Description string? Descrição do anexo
Tags string? Tags para categorização
IsActive bool Se está disponível para uso
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Relacionamentos: - N:1 Agent (cascade delete)


AgentFaq

Perguntas frequentes do agente. Cada FAQ é um par pergunta/resposta atômico que participa da busca semântica junto com a base de conhecimento.

Campo Tipo Descrição
Id Guid Identificador único
AgentId Guid FK Agent
TenantId Guid FK Tenant (denormalizado do Agent — elimina JOIN nos query filters)
Question string Pergunta (máx 500 caracteres)
Answer string Resposta
Embedding Vector? Vetor de 1536 dimensões para busca semântica
EmbeddingUpdatedAt DateTime? Última atualização do embedding
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Diferença de AgentKnowledge: - AgentKnowledge: Blocos de texto/documentos que são divididos em chunks para embedding - AgentFaq: Pares Q&A atômicos com embedding direto na entidade (não precisa de chunks)

Busca Semântica: - Embeddings gerados no formato: "Pergunta: {Question}\nResposta: {Answer}" - Participam da mesma busca semântica que AgentKnowledgeChunk - Resultados combinados por relevância (distância de cosseno)


FollowUpConfig

Configuração de follow-up automático para um agente ou etapa. Exatamente um dono (agente OU etapa, nunca ambos).

Campo Tipo Descrição
Id Guid Identificador único
AgentId Guid? FK Agent (mutuamente exclusivo com AgentStepId)
AgentStepId Guid? FK AgentStep (mutuamente exclusivo com AgentId)
Message string Mensagem de follow-up (máx 2000)
DelayMinutes int Delay em minutos antes do envio
MaxSends int Número máximo de envios (default 1)
AllowedHoursJson string? Horários permitidos para envio (JSONB)
IsActive bool Se está ativo
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Restrição de integridade: - CHECK: (agent_id IS NOT NULL AND agent_step_id IS NULL) OR (agent_id IS NULL AND agent_step_id IS NOT NULL) - UNIQUE filtrado em agent_id (no máximo um por agente) - UNIQUE filtrado em agent_step_id (no máximo um por etapa)

Relacionamentos: - 1:1 Agent (cascade delete, opcional) - 1:1 AgentStep (cascade delete, opcional)


ConversationFollowUp

Instância de follow-up agendado para uma conversa específica. Rastreia envios e agendamento via TickerQ.

Campo Tipo Descrição
Id Guid Identificador único
ConversationId Guid FK Conversation
FollowUpConfigId Guid FK FollowUpConfig
TenantId Guid FK Tenant (para query filter)
SendCount int Número de envios realizados (default 0)
TickerJobId Guid? ID do job agendado no TickerQ
NextScheduledAt DateTime? Próximo envio agendado
LastSentAt DateTime? Último envio realizado
Status ConversationFollowUpStatus Scheduled, Completed, Cancelled
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Relacionamentos: - N:1 Conversation (cascade delete) - N:1 FollowUpConfig (cascade delete)


StepTransition

Registro de transição de etapa em uma conversa. Usado para auditoria e analytics do fluxo de atendimento.

Campo Tipo Descrição
Id Guid Identificador único
ConversationId Guid FK Conversation
AgentId Guid FK Agent
FromStepId Guid? FK AgentStep de origem (null = início)
ToStepId Guid FK AgentStep de destino
TransitionedAt DateTime Quando ocorreu a transição

Relacionamentos: - N:1 Conversation (cascade delete) - N:1 Agent (cascade delete) - N:1 AgentStep (FromStep, SetNull on delete, opcional) - N:1 AgentStep (ToStep, Restrict on delete)


WhatsAppInstance

Instância de conexão com WhatsApp via Evolution API.

Campo Tipo Descrição
Id Guid Identificador único (usado como nome da instância na Evolution API)
TenantId Guid FK Tenant
Name string Nome da instância
PhoneNumber string? Número conectado
Status ConnectionStatus Estado da conexão
AgentId Guid? FK Agent vinculado
StartsWithAI bool Inicia conversas em modo IA (default true)
AiAvailabilityHoursJson string? Horários de atendimento da IA (JSONB)
SignatureText string? Texto da assinatura nas mensagens
SignaturePosition SignaturePosition Posição da assinatura (default Disabled)
ShowTyping bool Mostra indicador de digitação (default true)
DelaySeconds int Delay antes de enviar resposta (0-10, default 0)
ReopenWindowMinutes int Janela em minutos para reabrir conversa resolvida (0-1440, default 10)
MarkAsReadOnAiReply bool Marca mensagem como lida após resposta da IA (default true)
MaxAiResponsesPerConversation int? Limite de respostas IA por conversa (null = default 50, 0 = ilimitado)
AllowImageAnalysis bool Habilita análise de imagem via Claude Vision (default true)
AllowAudioAnalysis bool Habilita análise de áudio via Whisper (default true)
MaxMediaPerMessage int? Limite de mídias analisadas por mensagem (1-10, null = default 3)
IsArchived bool Se a instância está arquivada
ArchivedAt DateTime? Data de arquivamento
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Análise de Mídia: - AllowImageAnalysis e AllowAudioAnalysis controlam se a instância processa mídia recebida - MaxMediaPerMessage limita quantas mídias são analisadas por mensagem (excedentes são salvas mas não processadas). Null usa o padrão do sistema (3) - Quando desabilitado, a instância envia uma resposta padrão e nenhum crédito é consumido - Custo: 1 crédito base (resposta IA) + 3 créditos por mídia processada. Ex: 2 imagens = 7 créditos (1 + 3 + 3) - Veja media-processing.md e subscription-enforcement.md para detalhes

Relacionamentos: - N:1 Tenant (cascade delete) - N:1 Agent (SetNull on delete) - 1:N Conversations - 1:N InstanceWhitelist - 1:N InstanceBlacklist


InstanceWhitelist

Lista de contatos autorizados a interagir com a instância.

Campo Tipo Descrição
Id Guid Identificador único
InstanceId Guid FK WhatsAppInstance
PhoneNumber string Número no formato E.164
ContactName string? Nome do contato
Notes string? Observações
IsActive bool Se está ativo
CreatedAt DateTime Data de criação

Lógica: Se a whitelist estiver vazia ou todos desativados, aceita qualquer número.


InstanceBlacklist

Lista de contatos bloqueados (mensagens ignoradas).

Campo Tipo Descrição
Id Guid Identificador único
InstanceId Guid FK WhatsAppInstance
PhoneNumber string Número no formato E.164
Reason string? Motivo do bloqueio
BlockedAt DateTime Quando foi bloqueado
BlockedBy Guid? FK User que bloqueou

Prioridade: Blacklist é verificada ANTES da whitelist.


Conversation

Sessão de atendimento entre um contato e a instância.

Campo Tipo Descrição
Id Guid Identificador único
InstanceId Guid FK WhatsAppInstance
AgentId Guid? FK Agent (pode ser null, SetNull on delete)
ContactPhone string Número do contato
ContactName string? Nome (pushName)
ContactProfilePictureUrl string? URL da foto de perfil
Status ConversationStatus Active, Resolved
Mode ConversationMode AI ou Manual
ModeChangedAt DateTime? Quando mudou de modo
StartedAt DateTime Início da conversa
LastMessageAt DateTime Última mensagem
LastReadAt DateTime? Última leitura do operador
ResolvedAt DateTime? Quando foi resolvida
Summary string? Resumo da conversa
CurrentStepId Guid? FK AgentStep — etapa atual do atendimento
StartedHour int Hora de início (0-23, pré-computado para dashboard)
StartedDayOfWeek int Dia da semana de início (0-6, pré-computado para dashboard)
ResolutionTimeMinutes double? Tempo de resolução em minutos (pré-computado ao resolver)
AiResponseCount int Contador de respostas IA na conversa (default 0, reseta ao trocar para AI ou reabrir)

Colunas pré-computadas: - StartedHour, StartedDayOfWeek e ResolutionTimeMinutes existem porque c.StartedAt.Date, (int)c.DayOfWeek e TimeSpan.TotalMinutes não são traduzíveis pelo Npgsql em GroupBy - Preenchidos automaticamente no Create() e Resolve()

Relacionamentos: - N:1 WhatsAppInstance (cascade delete) - N:1 Agent (SetNull on delete, opcional) - N:1 AgentStep (CurrentStep, SetNull on delete, opcional) - 1:N Messages (cascade delete) - 1:N ConversationFollowUp (cascade delete)


Message

Mensagem individual em uma conversa.

Campo Tipo Descrição
Id Guid Identificador único
ConversationId Guid FK Conversation
Role MessageRole User, Assistant, System
Type MessageType Text, Image, Audio, etc.
Content string? Conteúdo textual / caption original
ExtractedText string? Texto extraído (transcrição ou descrição de imagem)
MediaUrl string? URL da mídia (R2)
MediaMimeType string? MIME type do arquivo
MediaFileName string? Nome do arquivo
SystemEventType SystemEventType? Tipo do evento de sistema (null para mensagens normais)
ActorName string? Quem causou o evento (nome do operador, agente IA, ou null para automático)
WhatsAppMessageId string? ID do WhatsApp (para status)
Status MessageStatus Pending, Sent, Delivered, Read, Failed
SentAt DateTime? Quando foi enviada
DeliveredAt DateTime? Quando foi entregue
ReadAt DateTime? Quando foi lida
CreatedAt DateTime Data de criação

Tracking de Tokens: - O tracking de tokens foi movido para a entidade LlmUsage - Permite tracking granular por operação, modelo e tipo de uso - Veja llm-usage.md para detalhes

Mensagens de Sistema: - Role = System identifica mensagens de evento interno (nunca enviadas ao WhatsApp) - SystemEventType categoriza o evento (cap atingido, falha IA, créditos esgotados, jailbreak bloqueado, etc.) - ActorName registra quem causou: nome do operador (JWT), nome do agente IA, ou null (automático) - Status = Read (sem lifecycle de entrega) - Excluídas do histórico enviado à IA (LoadMessageHistoryStep) - Visíveis apenas no portal Ciba

Processamento de Mídia: - Áudios são transcritos via OpenAI Whisper → resultado em ExtractedText - Imagens são descritas via Claude Vision → resultado em ExtractedText - Veja media-processing.md para detalhes

Relacionamentos: - N:1 Conversation (cascade delete) - 1:N Reactions


Reaction

Reação (emoji) a uma mensagem.

Campo Tipo Descrição
Id Guid Identificador único
MessageId Guid FK Message
Emoji string Emoji da reação
SenderPhone string Quem reagiu
FromMe bool Se foi enviada pelo sistema
CreatedAt DateTime Data de criação

PendingAggregation

Mensagens pendentes aguardando agregação antes do processamento. Usa PostgreSQL para garantias ACID e coordenação multi-instância.

Campo Tipo Descrição
Id Guid Identificador único
ConversationId Guid FK Conversation
InstanceId Guid FK WhatsAppInstance
AgentId Guid? FK Agent
ContactPhone string Número do contato
MessagesJson string Array JSON de mensagens pendentes (JSONB, default [])
FirstMessageAt DateTime Timestamp da primeira mensagem
LastMessageAt DateTime Timestamp da última mensagem
ProcessAt DateTime Quando processar (debounce — resetado a cada nova mensagem)
MaxWaitUntil DateTime Tempo máximo de espera (não resetado)
Status AggregationStatus Pending ou Processing
CreatedAt DateTime Data de criação

Comportamento: - ProcessAt é resetado a cada nova mensagem (debounce) - MaxWaitUntil não é resetado, mantém o deadline original - Status = Processing impede que outras instâncias processem simultaneamente

Relacionamentos: - N:1 Conversation (cascade delete) - N:1 WhatsAppInstance (cascade delete) - N:1 Agent (SetNull on delete, opcional)


LlmUsage

Registro de uso de LLM para tracking de tokens e custos.

Campo Tipo Descrição
Id Guid Identificador único
TenantId Guid FK Tenant
Provider LlmProvider Anthropic, OpenAI
Model string ID do modelo (claude-3-5-sonnet-20241022)
OperationType LlmOperationType Tipo da operação
InputTokens int Tokens de entrada
OutputTokens int Tokens de saída
CacheCreationInputTokens int Tokens de criação de cache (prompt caching)
CacheReadInputTokens int Tokens lidos do cache (prompt caching)
EstimatedCost decimal? Custo estimado em USD (inclui custo de cache)
Metadata string? Metadados estruturados (JSONB) — contém agentId, traceId, conversationId, whatsAppMessageIds e campos específicos por operação
CreatedAt DateTime Data de criação

Relacionamentos: - N:1 Tenant

Índices: - (tenant_id, created_at) - Consultas por período - (tenant_id, operation_type, created_at) - Consultas por tipo - (created_at) - Consultas globais por período


Subscription

Assinatura de um tenant. Controla limites de recursos e créditos de IA via modelo modular de precificação. Relação 1:1 com Tenant.

Campo Tipo Descrição
Id Guid Identificador único
TenantId Guid FK Tenant (unique)
Instances int Número de instâncias contratadas
ExtraAgents int Agentes adicionais além dos gratuitos
ExtraUsers int Usuários adicionais
ExtraKbBlocks int Blocos adicionais de base de conhecimento
ExtraStepBlocks int Blocos adicionais de etapas
ExtraAttachmentBlocks int Blocos adicionais de storage para anexos
ExtraFaqBlocks int Blocos adicionais de FAQs
MonthlyCreditPackage int Franquia mensal de créditos
CreditPackageId Guid? FK CreditPackage contratado
MonthlyPriceBrl decimal Preço mensal calculado em BRL
MaxAgents int Limite calculado de agentes
MaxUsers int Limite calculado de usuários
MaxKnowledgeDocs int Limite calculado de blocos de conhecimento
MaxSteps int Limite calculado de etapas (global, não por agente)
MaxFaqs int Limite calculado de FAQs
MaxAttachmentStorageMb int Limite calculado de storage para anexos (MB)
CreditBalance int Saldo de créditos (atualizado atomicamente)
Status SubscriptionStatus Estado do lifecycle
PastDueAt DateTime? Quando entrou em PastDue
SuspendedAt DateTime? Quando foi suspenso
CancelledAt DateTime? Quando foi cancelado
CurrentPeriodStart DateTime Início do ciclo atual
CurrentPeriodEnd DateTime Fim do ciclo atual
RowVersion uint Concurrency token (xmin)
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Fórmulas de limites (modelo modular):

Constantes base: - FreeAgentCount = 2 (agentes gratuitos inclusos) - KbPerAgent = 5, StepsPerAgent = 5, FaqsPerAgent = 100, AttachmentMbPerAgent = 100 - KbPerBlock = 5, StepsPerBlock = 5, FaqsPerBlock = 100, AttachmentMbPerBlock = 200 - UsersPerInstance = 2

Cálculos:

totalAgents = FreeAgentCount + ExtraAgents
MaxAgents = totalAgents
MaxUsers = (Instances × UsersPerInstance) + ExtraUsers
MaxKnowledgeDocs = (totalAgents × KbPerAgent) + (ExtraKbBlocks × KbPerBlock)
MaxSteps = (totalAgents × StepsPerAgent) + (ExtraStepBlocks × StepsPerBlock)
MaxAttachmentStorageMb = (totalAgents × AttachmentMbPerAgent) + (ExtraAttachmentBlocks × AttachmentMbPerBlock)
MaxFaqs = (totalAgents × FaqsPerAgent) + (ExtraFaqBlocks × FaqsPerBlock)

Enforcement (soft): - Tenant sem subscription: sem restrições (graceful degradation) - Downgrade: recursos existentes mantidos, novos bloqueados até count < limit - Créditos: consumo atômico via SQL (credit_balance - N WHERE credit_balance >= N) - Custo variável por tipo de operação: texto = 1 crédito, imagem = 3, áudio = 3 (configurado em CreditCostConfig)

Relacionamentos: - 1:1 Tenant - N:1 CreditPackage (Restrict on delete, opcional) - 1:N SubscriptionItem (cascade delete) - 1:N CreditTransactions - 1:N SubscriptionEvents

Índices: - UNIQUE (tenant_id) - (status, past_due_at) WHERE past_due_at IS NOT NULL — background job (grace period) - (status, suspended_at) WHERE suspended_at IS NOT NULL — background job (suspension) - (current_period_end) — consultas de renovação de período


SubscriptionItem

Item de linha de uma assinatura, representando um produto contratado com quantidade e preço.

Campo Tipo Descrição
Id Guid Identificador único
SubscriptionId Guid FK Subscription
ProductId Guid FK SubscriptionProduct
Quantity int Quantidade contratada (mínimo 1)
UnitPriceBrl decimal Preço unitário em BRL
UnitPriceUsd decimal Preço unitário em USD
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Relacionamentos: - N:1 Subscription (cascade delete) - N:1 SubscriptionProduct (Restrict on delete)


SubscriptionProduct

Catálogo de produtos disponíveis para composição de assinaturas.

Campo Tipo Descrição
Id Guid Identificador único
Code string Código único do produto (máx 50)
Name string Nome do produto (máx 100)
Description string? Descrição (máx 500)
PriceBrl decimal Preço de tabela em BRL
PriceUsd decimal Preço de tabela em USD
SortOrder int Ordem de exibição
IsActive bool Se está disponível para contratação
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Índices: - UNIQUE (code) - (sort_order)

Relacionamentos: - 1:N SubscriptionItem (Restrict on delete)


CreditPackage

Pacote de créditos disponível para contratação (mensal ou add-on).

Campo Tipo Descrição
Id Guid Identificador único
Credits int Quantidade de créditos no pacote
PriceBrl decimal Preço em BRL
PriceUsd decimal Preço em USD
DiscountPercent int Percentual de desconto (0-100)
IsAddon bool Se é pacote adicional (vs. pacote mensal)
SortOrder int Ordem de exibição
IsActive bool Se está disponível para contratação
CreatedAt DateTime Data de criação
UpdatedAt DateTime Última atualização

Índices: - (is_active, is_addon, sort_order) — listagem filtrada

Relacionamentos: - 1:N Subscription (Restrict on delete)


CreditTransaction

Registro de auditoria de movimentação de créditos. Append-only (sem UpdatedAt).

Campo Tipo Descrição
Id Guid Identificador único
SubscriptionId Guid FK Subscription
TenantId Guid FK Tenant (para query filter)
Type CreditTransactionType Tipo da transação
Amount int Quantidade (negativo = consumo)
BalanceAfter int Saldo após transação
Description string? Descrição (máx 500)
ConversationId Guid? Conversa que originou o consumo
MessageId Guid? Mensagem AI que gerou o consumo (vinculada após persist)
CreatedAt DateTime Data de criação

Relacionamentos: - N:1 Subscription

Índices: - (tenant_id, created_at) — consultas por período - (subscription_id, created_at) — consultas por subscription - (type) — consultas por tipo de transação - (conversation_id, created_at) WHERE conversation_id IS NOT NULL — consultas por conversa - UNIQUE (message_id) WHERE message_id IS NOT NULL — lookup por mensagem (1:1)


SubscriptionEvent

Registro de eventos do lifecycle da assinatura. Append-only (sem UpdatedAt). Usado para auditoria e histórico de alterações.

Campo Tipo Descrição
Id Guid Identificador único
SubscriptionId Guid FK Subscription
TenantId Guid FK Tenant (para query filter)
Type SubscriptionEventType Tipo do evento
FromStatus string? Status anterior (em transições de estado)
ToStatus string? Status novo (em transições de estado)
Description string? Descrição legível do evento
Metadata string? Dados adicionais (JSON)
CreatedAt DateTime Data de criação

Relacionamentos: - N:1 Subscription

Índices: - (subscription_id, created_at) — consultas por subscription - (type) — consultas por tipo de evento - (subscription_id, type, created_at) — consultas compostas


Enums

SubscriptionStatus

Estado do lifecycle da assinatura.

Valor Descrição
Active Assinatura ativa, todos os recursos disponíveis
AiQuotaExhausted Créditos de IA esgotados, sistema funciona sem IA
PastDue Pagamento vencido, grace period de 24h
Suspended Suspenso após grace period, instâncias desconectadas
Cancelled Cancelado após 30 dias de suspensão, créditos zerados

CreditTransactionType

Tipo de movimentação de créditos.

Valor Descrição
MonthlyRenewal Franquia mensal adicionada
AddOnPurchase Compra de pacote adicional
AiConsumption Consumo por resposta de IA
ManualAdjustment Ajuste manual pelo admin
SuspensionZeroing Zeramento por cancelamento

SubscriptionEventType

Tipo de evento do lifecycle da assinatura.

Valor Descrição
Created Assinatura criada
ConfigurationUpdated Limites ou pacote alterados
CreditsAdded Créditos adicionados (renovação ou compra)
AiQuotaExhausted Créditos de IA esgotados
MarkedPastDue Marcado como inadimplente
Suspended Assinatura suspensa
Cancelled Assinatura cancelada
Reactivated Assinatura reativada
PeriodRenewed Período renovado

LlmProvider

Provider de LLM.

Valor Descrição
Anthropic Claude API
OpenAI OpenAI API

LlmOperationType

Tipo de operação com LLM.

Valor Descrição
Chat Resposta em conversa
Vision Descrição de imagem
Transcription Transcrição de áudio
Embedding Geração de embeddings
Chunking Processamento de knowledge
Playground Testes no playground
QueryRewrite Reescrita de query para busca semântica
AiAssistant Assistente IA interno do portal
IntentClassification Classificação de intenção da mensagem

ConnectionStatus

Status de conexão da instância WhatsApp.

Valor Descrição
Disconnected Desconectado
Connecting Conectando
Connected Conectado
Open Conexão aberta
Close Conexão fechada

ConversationStatus

Status da conversa.

Valor Descrição
Active Conversa ativa
Resolved Conversa resolvida

ConversationMode

Modo de atendimento da conversa.

Valor Descrição
AI IA está atendendo
Manual Humano está atendendo

MessageRole

Papel do autor da mensagem.

Valor Descrição
User Mensagem do usuário (cliente)
Assistant Mensagem do assistente (IA ou operador)
System Mensagem do sistema

MessageType

Tipo de conteúdo da mensagem.

Valor Descrição
Text Texto
Image Imagem
Audio Áudio (voice message)
Video Vídeo
Document Documento
Sticker Sticker

MessageStatus

Status de entrega da mensagem no WhatsApp.

Valor Descrição
Pending Aguardando envio
Sent Enviada (check)
Delivered Entregue (check check)
Read Lida (check check azul)
Failed Falhou

SystemEventType

Tipo de evento de sistema registrado em mensagens internas.

Valor Descrição
AiCapReached Limite de respostas IA por conversa atingido
AiFailure Falha na API do LLM (timeout, erro, parse)
CreditsExhausted Créditos de IA esgotados
EscalatedByAi IA decidiu escalar para humano
ResolvedByAi IA resolveu a conversa
ResolvedByOperator Operador resolveu a conversa
ModeChangedToManual Modo mudou para manual (por operador)
ModeChangedToAi Modo mudou para IA (por operador)
JailbreakBlocked Tentativa de jailbreak bloqueada pelo guardrail
OffTopicBlocked Mensagem fora do tópico bloqueada pelo guardrail

SignaturePosition

Posição da assinatura nas mensagens.

Valor Descrição
Disabled Sem assinatura (padrão)
StartSameLine Início da mensagem, mesma linha
StartNewLine Início da mensagem, nova linha
EndSameLine Final da mensagem, mesma linha
EndNewLine Final da mensagem, nova linha

AggregationStatus

Status de uma agregação de mensagens pendentes.

Valor Descrição
Pending Aguardando expiração do debounce/max-wait
Processing Sendo processada pelo pipeline

ConversationFollowUpStatus

Status de um follow-up de conversa.

Valor Descrição
Scheduled Agendado para envio
Completed Todos os envios realizados
Cancelled Cancelado

IntentCategory

Categoria de intenção classificada para mensagens recebidas.

Valor Descrição
FaqSimple Pergunta simples respondível por FAQ
ComplexQuery Consulta complexa que requer raciocínio
ActionRequest Solicitação de ação/operação
OffTopic Fora do tópico do agente
Jailbreak Tentativa de manipulação/jailbreak

Multi-tenancy

O sistema implementa multi-tenancy via query filters globais no EF Core:

// AppDbContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // Filtro global por TenantId
    modelBuilder.Entity<Agent>()
        .HasQueryFilter(a => a.TenantId == _tenantProvider.TenantId);
}
  • Todas as entidades com TenantId são automaticamente filtradas
  • Todo usuário (incluindo SuperAdmin) tem TenantId obrigatório — dados filtrados pelo tenant do JWT
  • TenantId é extraído do JWT via ITenantProvider

Convenções de Banco

Contexto Convenção
Tabelas e colunas snake_case (via NamingConventions)
Chaves primárias id (Guid)
Chaves estrangeiras {entidade}_id
Timestamps created_at, updated_at
Soft delete is_active (não usa deleted_at)