# Bit Integrations — modelo de dados completo Confirmado por leitura directa de `wp-content/plugins/bit-integrations/backend/Core/Database/DB.php` (schema DDL) e `backend/Flow/FlowController.php` (semântica de `status`), mais inspecção SQL ao vivo em produção (`descomplicar.pt`) 19-08-2026. ## Prefixo de tabela `Config::VAR_PREFIX = 'bit_integrations_'` (`backend/Config.php`) só prefixa **hooks e `wp_options`**. As 4 tabelas de dados usam o prefixo curto e independente `btcbi_` (`{$wpdb->prefix}btcbi_flow`, etc. — literal no `DB.php`, não deriva de `Config::VAR_PREFIX`). `DB::fallbackDB()` mostra que o plugin já mudou de prefixo uma vez no passado (`btcfi_` → `btcbi_`, provavelmente um rename do produto), com uma migração de `RENAME TABLE` + renomear as options correspondentes — não há vestígios de tabelas `btcfi_*` nos sites verificados (migração já aplicada há muito). O prefixo WordPress em si (`$wpdb->prefix`) varia por site: `wpah_` em `descomplicar.pt`, `wpbk_` em `starter.descomplicar.pt`, `wpv4_` em `care.descomplicar.pt`. Nunca hardcoded — `db.ts` resolve via `wp db prefix --path= --allow-root` com cache em memória por processo. ## `btcbi_flow` ``` id bigint(20) unsigned PK auto_increment name varchar(255) nullable triggered_entity varchar(50) NOT NULL — origem do trigger (ex.: "WPF"=WPForms, "FluentBooking") triggered_entity_id varchar(100) nullable — ID da origem (ex.: "16446" = ID do formulário WPForms; "fluentBooking-1") flow_details longtext nullable — JSON, ver secção abaixo status tinyint(1) default 1 — 0=disabled, 1=enabled, 2=trashed (comentário no schema SQL) user_id bigint(20) unsigned nullable — utilizador WP que gravou a última alteração user_ip int(11) unsigned nullable — IP empacotado (ip2long) de quem gravou created_at datetime updated_at datetime ``` `FlowController::updateStatus($id, $status)` (única mutação suportada por este MCP via `bi_set_flow_status`) só grava `status` + `user_id` + `user_ip` + `updated_at`; o MCP replica isto mas omite `user_id`/`user_ip` (não há utilizador WP autenticado numa sessão WP-CLI/SSH — o `updateStatus` real do plugin corre dentro de uma sessão wp-admin). ### `flow_details` — forma observada por app (produção, 19-08-2026) O blob mistura **configuração do mapeamento** e **credenciais em claro da app terceira** — os nomes de chave da credencial variam por app, não há uma coluna/chave fixa a excluir. Dois exemplos reais completos: **PerfexCRM/Desk (flow id=4)** — `JSON_KEYS`: `name, type, api_token, domain, field_map, actionName, selectedLeadSourceId, selectedLeadStatusId, actionId, customerFields, contactFields, leadFields, projectFields, actions, condition, customers, perfexCRMFields, isAuthorized, staffs, selectedStaff, trigger_type`. - Credencial: `api_token` (string em claro) + `domain` (URL da instância, não sensível por si só). - Config: `field_map` (array `{formField, perfexCRMFormField}`), `customers`/`staffs` (listas cacheadas do Desk CRM para preencher dropdowns na UI), `actionName` (ex. `"lead"`). **Google Contacts (flow id=7)** — `JSON_KEYS`: `name, type, mainAction, clientId, clientSecret, field_map, default, allActions, actions, condition, tokenDetails, isAuthorized`. - Credenciais: `clientId` + `clientSecret` (OAuth2 app) + `tokenDetails` (token de acesso/refresh já trocado). - Config: `field_map` (array `{formField, googleContactsFormField}`), `mainAction` (`"1"` = create, `"2"` = update, ver `allActions`). Padrão geral: `type`/`name` identificam a app; `field_map` (ou equivalente) faz o mapeamento de campos; `isAuthorized` é uma flag booleana de UI (não uma credencial); `condition` é um bloco de lógica condicional genérico do plugin, igual em ambos os exemplos. ## `btcbi_log` ``` id int(11) unsigned PK auto_increment flow_id bigint(20) nullable, indexado — FK lógica para btcbi_flow.id job_id bigint(20) nullable — usado só por integrações assíncronas/em fila (NULL nas 75 linhas observadas) api_type varchar(255) nullable — JSON pequeno, ex. {"type":"Lead","type_name":"Lead creating"} response_type varchar(50) nullable — string directa "success" | "error" (SEM enum numérico) response_obj longtext nullable — resposta da app terceira; por vezes JSON, por vezes HTML de erro PHP cru (ver exemplo abaixo) field_data longtext nullable — valores do trigger nesta execução (adicionado numa migração posterior — coluna "after response_obj") parent_id bigint(20) nullable, indexado — liga um log re-executado (filho) ao log original (pai) created_at datetime NOT NULL ``` `field_data`/`parent_id` foram adicionados numa migração idempotente (`DB::addFieldDataColumn`/ `addParentIdColumn`), suportando "voltar a executar" um log falhado a partir da UI — o `parent_id` liga a nova tentativa ao log original. Exemplo real de `response_obj` de erro (log id=142, flow 4/PerfexCRM, `response_type="error"`): HTML cru de exception não apanhada do CodeIgniter (`desk.descomplicar.pt`), mensagem `Unknown column 'rate_limit_checked' in 'INSERT INTO'` — um bug real no lado do Desk CRM, não um problema de credenciais. `parseJsonField` faz fallback para string quando o `JSON.parse` falha (caso normal para este tipo de payload). ## `btcbi_connections` ``` id bigint(20) unsigned PK auto_increment app_slug varchar(191) NOT NULL, indexado auth_type varchar(50) NOT NULL, default 'oauth2' connection_name varchar(255) nullable account_name varchar(255) nullable, indexado encrypt_keys text nullable — NUNCA seleccionado pelo MCP auth_details longtext nullable — NUNCA seleccionado pelo MCP (tokens OAuth) status tinyint(1) default 1, indexado user_id bigint(20) unsigned nullable created_at datetime updated_at datetime ``` Feature mais recente do plugin — permite reutilizar uma ligação OAuth/API entre vários flows em vez de embutir a credencial em cada `flow_details`. **Vazia nos dois sites principais** (0 linhas em `descomplicar` e `starter`, 19-08-2026): os 10 flows de produção são anteriores a esta feature ou foram configurados antes de ela existir, e continuam a guardar a credencial embutida. ## `btcbi_auth` ``` id bigint(20) unsigned PK auto_increment action_name varchar(255) nullable tokenDetails longtext nullable — token OAuth em claro userInfo longtext nullable — perfil OAuth devolvido pela app terceira created_at datetime updated_at datetime ``` Armazém interno usado pelo fluxo OAuth (`OAuth2Authorization.php`). Vazio nos sites verificados. Sem tool dedicada neste MCP — o único conteúdo não-segredo seria `action_name`/`created_at`, sem valor de diagnóstico suficiente para justificar expor mais uma tabela puramente de credenciais. ## `wp_options` relevantes | Option | Forma | Uso | |---|---|---| | `btcbi_version` / `btcbi_pro_version` | string | Versão instalada (free/Pro) — **preferir `wp plugin list` para "activo"**, esta option não muda quando o Pro é desactivado | | `btcbi_db_version` / `btcbi_pro_db_version` | string | Versão do schema da BD | | `btcbi_installed` / `btcbi_pro_installed` | int (unix timestamp) | Quando foi instalado | | `btcbi_integrate_key_data` | array PHP (`key`, `status`, `expireIn`) | Licença Pro — `key` sempre mascarado no MCP (`license_key_masked`, últimos 4 caracteres) | | `btcbi_webhook_` | array PHP vazio nos sites verificados | Um por webhook registado; sem tool dedicada (nenhum webhook configurado nos sites verificados) | | `bit_integrations_allow_tracking` etc. | escalares | Telemetria do plugin para a BitApps, sem valor de diagnóstico | Todas as options `btcbi_*`/`bit_integrations_*` usadas por este MCP são escalares ou arrays PHP nativos (confirmado com `wp option get ` sem `--format=json`, mostrando `array(...)` desestruturado ou um valor escalar simples) — nunca uma string com JSON lá dentro. `--format=json` é portanto seguro em leitura, sem risco de dupla codificação (ao contrário do WP Fastest Cache, ver skill `wp-fastest-cache`).