# OnSMS - Plataforma Operacional

## Overview
OnSMS is an operational platform that mediates between the main OnSMS system and external WhatsApp messaging tools. Its core functions include managing and executing WhatsApp campaigns, generating operational packages for operators, processing and normalizing operator reports, and returning aggregated results to the main OnSMS platform via API. The platform provides robust, scalable, and customizable WhatsApp campaign management, covering campaign creation, contact management, status normalization, and comprehensive reporting. It also features a SaaS module for managing users, services, accounts, and credits. The business vision is to provide a powerful solution for businesses to efficiently manage and execute WhatsApp campaigns, increasing market potential and optimizing communication strategies.

## User Preferences
- **Communication Style**: I prefer clear and concise language.
- **Coding Style**: I favor a modular and maintainable codebase.
- **Workflow Preferences**: I prefer an iterative development approach with regular updates on progress.
- **Interaction Preferences**: Ask for confirmation before implementing major architectural changes.
- **Theming**: The application should have a green/teal color scheme, inspired by WhatsApp.
- **Language**: The application's language should be Brazilian Portuguese (pt-BR).
- **Font**: The primary font should be Inter.

## System Architecture

### Core Technologies
- **Frontend**: React, TypeScript, Vite, shadcn/ui, Tailwind CSS, TanStack Query.
- **Backend**: Express.js with Node.js.
- **Database**: PostgreSQL with Drizzle ORM.

### Data Models and SaaS Module
A SaaS module manages users with hierarchical roles (ADMIN, REVENDA, CLIENTE, OPERADOR, USUARIO), various service types (manual/API with distinct billing rules), user-service linkages, account balances, and credit transactions. Campaigns track internal UUIDs and external identifiers. Media content (images, videos) is stored efficiently.

### Workflow Management
The platform manages the entire campaign lifecycle, including generating operational ZIP packages for operators, assigning campaigns, facilitating report uploads, processing reports (with status normalization to `enviado` or `nao_enviado`), and providing final statistics.

### Service Types
The system supports `WA OFICIAL` (WhatsApp Business API), `WA OFF` (third-party WhatsApp tools), and `WA HUB` (template-based manual dispatch). Campaigns are associated with a specific service type.

### HubSpot Integration
The HubSpot integration supports OAuth and Private App authentication, enabling a custom workflow action for sending WhatsApp messages. It uses a shared dispatch pipeline with error handling and queueing, publishes Timeline Events to HubSpot, includes credit billing, optimizes performance with direct phone number inclusion, and provides duplicate dispatch protection. Configurable email alerts for integration failures are available. Status terminais do WhatsApp (DELIVERED/SEEN/FAILED) recebidos via webhook Infobip são repassados ao HubSpot Timeline como eventos adicionais (best-effort, com idempotência por `{status}:{messageId}`); o token `status` é traduzido para PT-BR ("Enviado"/"Entregue"/"Lido"/"Falhou"/"Na fila") no momento da publicação. Após mudanças na definição do Event Type Template, é necessário re-registrá-lo via rota admin `/api/admin/hubspot/timeline/register-template` para o HubSpot exibir os novos labels de status.

### API Token per Service (Inbound API)
A REST API allows external systems to create campaigns using service-bound API tokens, providing endpoints for listing templates and creating campaigns with per-contact variable customization.

### Infobip WA OFICIAL CRM Integration
This integration handles Infobip credentials, manages multiple senders, and streamlines template approval. A webhook processes delivery events, and templates support `{{N}}` variables. Infobip balance checks are supported. Campaign content is snapshotted for auditing. An automated background job syncs Infobip templates. Template header media is stored in GCS and served via a public route. Campaign lifecycle follows `pending → processing → completed` with a 24h fallback job for stuck campaigns. Template buttons (URL + QUICK_REPLY) are supported, and placeholder count validation prevents dispatch with mismatched variables. Webhook registration with Infobip is automated via API.

### WA Multi-BM Service
The `wa_multibm` service type orchestrates dispatch across ~100 Business Managers hosted under a single shared Infobip account per service. Credentials (`apiKey`, `baseUrl`, optional `messagesPerSecond`) are stored once per service in `multibm_configs`; individual BMs hold only their own identifiers (WABA/senders), never credentials. Creating a `wa_multibm` service requires the Infobip API Key and Base URL (validated http(s), default `https://api.infobip.com`); the backend creates the service, persists the config, and then auto-registers the webhook subscription (best-effort: a failure does not block service creation). The webhook URL is central (`/webhooks/infobip`) and the token is global (`api_settings.infobip_webhook_token`) — there is no per-service URL field; the handler routes incoming status by `messageId`. `multibm_configs.webhookSubscriptionId` stores the remote subscription id to detect reuse/drift and avoid duplicate subscriptions. Admin routes `GET /api/admin/multibm/webhook/:serviceId/status` and `POST /api/admin/multibm/webhook/:serviceId/register` expose the subscription state and allow manual (re)registration. Senders and APPROVED templates are imported per BM filtered by the BM's WABA (route `POST /api/admin/multibm/bms/:bmId/templates/sync-from-infobip`): templates are isolated between BMs (a BM never overwrites another BM's logical content; each BM keeps its own `infobipTemplateId` in its submission). The `permitirMarketing` flag applies to `wa_multibm` like `wa_oficial`: MARKETING templates are skipped on import and the dispatch gate blocks the campaign's selected Template Base (case-insensitive category check). Regression coverage: `tests/multibmTemplateSync.test.ts`, `tests/multibmDispatchMarketing.test.ts`. `Entity ID`/`Application ID` are exclusive to WA OFICIAL and do not apply to Multi-BM. Spec: `.local/tasks/validacao-credenciais-wa-multibm.md`. Regression coverage: `tests/multibmWebhook.test.ts`.

#### Atribuição por sender (não por BM)
A atribuição cliente↔recursos é **por sender**, não por BM (`bm_sender_assignments`, único `sender_id+user_id`). O admin escolhe, dentro de cada BM, **quais senders** cada cliente pode usar (aba "Por Cliente" → "Gerir senders", com checkbox por sender e marcar todos/nenhum por BM). Assim a **mesma BM é reutilizável entre clientes** com subconjuntos disjuntos de senders, sem vazamento de perfil/sender. As "BMs do cliente" e os clientes exibidos no detalhe da BM são **derivados** dos senders atribuídos (read-only no detalhe da BM). O disparo (`buildSenderPool`) usa **somente** os senders atribuídos ao dono da campanha (`getBmSendersForUser`, restrito a BM ativa/ACTIVE + sender ativo/ACTIVE), agrupando por BM para resolver o físico do cluster. A substituição de atribuições é **escopada por serviço** (`setBmSenderAssignmentsForUserInService`) — salvar não apaga atribuições de outros serviços. A tabela legada `bm_client_assignments` é mantida apenas como fonte da migração de dados (boot migration idempotente keyed `021_task376_bm_sender_assignments`, popula a partir de `bm_client_assignments × bm_senders`). Rotas admin: `GET/PUT /api/admin/multibm/clients/:userId/sender-assignments` (CLIENTE-only, escopadas por serviço). Cobertura: `tests/multibmTemplateCluster.test.ts`.

#### Template Cluster (preencher variáveis uma única vez)
O modelo Multi-BM é **cluster-only**: não há designação de template principal/alternativo por cliente. Os templates físicos reais vêm da importação da Infobip por BM (`POST /api/admin/multibm/bms/:bmId/templates/sync-from-infobip`) **ou** da submissão controlada via **Templates Validados** (ver subseção abaixo). O admin cria um **Template Base** manualmente (definindo a assinatura/prévia mestra) e então **agrupa** as submissões compatíveis sob esse Base. Na campanha, o cliente escolhe um Template Base diretamente.

`multibm_templates` atua como **Template Base (cluster) — esqueleto puro**: carrega apenas uma *assinatura* (`varCount` + `buttonUrlPrefix` + `headerType`) e uma prévia mestra. O Base **nunca** persiste rodapé nem conteúdo de cabeçalho real (`rodape`/`headerContent` são sempre `null`, forçados no create/patch); o conteúdo físico real de cada BM (corpo, botões, header, nome do template) vive em `multibm_template_submissions`. O formulário admin do Base expõe só: nome/idioma/categoria, corpo (define o nº de variáveis), botões (definem o prefixo do botão de URL) e o **tipo de cabeçalho** (Sem cabeçalho / Imagem / Vídeo / Documento — sem campo de texto ou URL). Templates reais por BM são agrupados sob um Base apenas quando suas assinaturas coincidem. O cliente preenche o conjunto de variáveis **uma única vez** (dimensionado pela assinatura do Template Base escolhido na campanha); o disparo resolve o conteúdo físico de cada BM e injeta esse mesmo conjunto. Uma BM só é elegível ao disparo se o físico que ela enviaria casar com a assinatura do Base — caso contrário a BM é excluída (log `campaign_multibm_signature_incompatible`), evitando injeção de número errado de variáveis. Rotas admin: coverage (`GET .../templates/:id/coverage`), agrupar (`POST .../templates/:id/group`), desagrupar (`POST .../submissions/:id/ungroup`). Helpers de assinatura em `server/multibmSignature.ts`. Cobertura: `tests/multibmSignature.test.ts`, `tests/multibmTemplateCluster.test.ts`. A tabela `multibm_client_templates` foi removida (migração `020_task366_drop_multibm_client_templates.sql`).

##### Mídia de cabeçalho na campanha (Task #369)
Quando o Template Base marca um cabeçalho de **mídia** (`headerType` ∈ imagem/vídeo/documento), o `headerType` faz parte da assinatura do cluster — só BMs cujo físico tenha o mesmo formato de cabeçalho entram no pool. O cliente envia **uma única mídia** no momento da campanha (form de nova campanha, controle dedicado por tipo: imagem JPG/PNG ≤5MB, vídeo MP4 ≤16MB, documento PDF ≤100MB), via `POST /api/multibm/campaigns/upload-media` (Object Storage/CDN, pois a URL precisa ser acessível pelos servidores do Meta/WhatsApp). A URL é persistida em `campaign.headerImageUrl` e injetada no disparo para **todas as BMs elegíveis** — `enviarMensagemTemplate` constrói o componente de header com o tipo correto (IMAGE/VIDEO/DOCUMENT). O backend (`POST /api/multibm/campaigns`) exige `headerMediaUrl` quando o Base marca header de mídia. Cobertura de assinatura com `headerType`: `tests/multibmSignature.test.ts`.

##### Templates Validados (Task #2) — submissão em massa controlada
Recurso exclusivo do serviço Multi-BM, ADMIN only, na tabela **isolada** `multibm_validated_templates` (nunca reaproveita o esqueleto de `multibm_templates`). Reintroduz — de forma controlada — a composição manual de um template completo (estilo Meta: corpo com `{{N}}` + exemplo por variável obrigatório, cabeçalho sem mídia/imagem/vídeo com mídia de exemplo, rodapé, botões incluindo CTA de URL com **prefixo fixo + sufixo dinâmico `{{1}}`**), submetido primeiro a **uma BM principal** (`business_managers.is_principal`, no máximo uma por serviço) para validação da composição pela Meta.

Máquina de estados: `RASCUNHO → EM_VALIDACAO (POST .../:id/validate, cria a submissão física via criarTemplate) → VALIDADO | REJEITADO (POST .../:id/sync-status, deriva do status da submissão na BM de validação)`. Só com status **VALIDADO** o botão "Enviar às BMs" (`POST .../:id/rollout`, todas do serviço ou selecionadas) libera o envio em massa às demais BMs — sequencial (rate-limit Infobip), pulando BMs já submetidas (idempotente por `bmId+templateName`) e sem sender ativo, sem abortar o lote por erro isolado por BM. Editar o conteúdo de um template VALIDADO/REJEITADO o devolve a RASCUNHO (revalidação obrigatória). `criarTemplate` (`server/infobip.ts`) ganhou o campo aditivo/opcional `bodyExamples` (nunca usado pelo fluxo WA OFICIAL legado) para enviar `body.examples` à Infobip. As submissões geradas aqui vivem em `multibm_template_submissions` (mesma tabela do cluster, `templateId=null` até serem agrupadas manualmente sob um Base). Rotas: `GET/POST/PATCH/DELETE /api/admin/multibm/validated-templates`, `POST .../:id/validate`, `POST .../:id/sync-status`, `POST .../:id/rollout`, `POST /api/admin/multibm/bms/:id/set-principal`. Cobertura: `tests/multibmValidatedTemplates.test.ts`.

### AI Agent (Automatic WhatsApp Replies)
`server/aiAgent.ts` answers inbound WhatsApp messages (MO) on behalf of the client, gated by a global switch, per-service `aiEnabled`, and per-client `ai_client_settings.aiEnabled`. It replies free-form through the same sender that received the MO (sticky 24h session), guarded against sending two consecutive automatic free-form messages (`server/messageGuard.ts`), and caps replies per contact per dispatch with a configurable fallback message.

**Isolation contract (do not relax):** the agent's system prompt is composed from EXACTLY two account-scoped sources — the client's Knowledge Base (`knowledge_base_items`) and the Company Profile below (`client_business_profiles`). No other account data (login credentials, document/CPF-CNPJ, balances) is ever read into the prompt.

#### Company Profile (Task #4)
`client_business_profiles` — structured fields the CLIENTE fills about their own business (segment, description, products/services, differentiators, target audience, hours, address, site, contact channels, social media, payment methods, policies, desired tone of voice, and free-form extra instructions for the AI), plus `aiUseProfile` to toggle whether the agent uses it. Scoped by `userId` (always the CLIENTE; USUARIO/OPERADOR resolve to the parent via the same `resolveAiOwnerId` helper used across the AI module — never accept an arbitrary `userId` from a non-admin request body).

`buildProfileSection()` in `server/aiAgent.ts` renders the prompt block: merges only the already-public `users` fields (nomeFantasia/razaoSocial/cidade — never document/username), omits empty fields, truncates to ~2000 chars, and returns `null` when `aiUseProfile=false` or nothing is filled in (tone/instructions alone still count as content). It's injected between the base instructions and the client persona, ahead of the Knowledge Base. Routes: `GET/PUT /api/profile/business` (self-service, mirrors `/api/ai/client-settings`'s admin-override-via-`userId` pattern; ADMIN PUT requires the target to be a CLIENTE). Document is masked for non-admin responses. UI: sidebar "Perfil da Empresa" under "Atendente IA" (`client/src/pages/company-profile.tsx`). Coverage: `tests/clientBusinessProfile.test.ts` (buildProfileSection edge cases + route-level cross-client isolation).

### Operational Diagnostics
Administrative endpoints provide tools for Infobip webhook token management (generation/regeneration), diagnosing message status by comparing local data with Infobip Logs API, and manually forcing completion of stuck WA OFICIAL campaigns with a 60-minute safety window. Per-contact activity logs are recorded for failed Infobip messages, and detailed logs track Infobip send requests and webhook events.

### Sender Management and Daily Limits
An administrative interface allows centralizing Infobip sender configuration, including `dailyLimit` and `preferred` status. The dispatch pipeline respects these limits, queueing messages if capacity is exceeded, with a worker processing pending messages with retry mechanisms.

### Service Management
An admin interface provides full CRUD capabilities for services, allowing definition of service type, integration details, billing rules, and active status, including a `modoTeste` flag for API-type services.

### API Configuration
A web interface allows configuring callback URLs, API Keys, timeout settings, and retry logic for integration with the main OnSMS platform.

### Security
The system employs session-based authentication, bcrypt hashing for passwords, and role-based access control (ADMIN, REVENDA, CLIENTE, OPERADOR). Security measures include account lockout, rate limiting, and protected API routes. Report uploads are validated for file type and size.

### Report Processing
The platform processes CSV or TXT reports, handling various separators and time formats. It normalizes phone numbers and standardizes statuses while preserving original details. Duplicate entries are handled by prioritizing the last occurrence.

### Media Storage
Campaign media (banner images, profile photos, videos) is stored on the local server disk, managed by `server/mediaStorage.ts`, with its directory configured via `MEDIA_UPLOAD_DIR`. Files are served via `/api/object-media/*`.

## External Dependencies
- **PostgreSQL**: Primary relational database.
- **WhatsApp Business API**: For "WA OFICIAL" service type.
- **Third-party WhatsApp Tools**: For "WA OFF" service type integrations.
- **OnSMS Platform**: Core system receiving campaign results via callback API.
- **HubSpot API**: For CRM integration and workflow automation.
- **Infobip API**: For WhatsApp Business API messaging and template management.