--- name: fluent-crm description: Conhecimento profundo do FluentCRM (WPManageNinja, plugin `fluent-crm` + addon `fluentcampaign-pro`) — arquitectura, schema de BD (`fc_*`), API de desenvolvimento oficial (`FluentCrmApi()`), modelo de estados de contacto e as suas guardas reais, wp-admin (Subscribers/Tags/Lists/Companies/Campaigns/Automations), automações (Funnels, triggers/actions), Dynamic Segments (Pro), e o estado real em descomplicar.pt (291k contactos). Usar quando "fluentcrm", "email marketing wordpress", "automação de contactos", "funnels fluentcrm", "segmentos dinâmicos", "status de subscritor", "unsubscribe", "empresas crm wordpress", "campanha email estatísticas", ou qualquer trabalho de configuração/estratégia FluentCRM que não seja apenas chamar o MCP. Para execução programática (ler/escrever contactos, tags, listas, empresas, notas, campanhas), usar a skill `mcp-fluent-crm`. layer: wiki --- # /fluent-crm — FluentCRM (WPManageNinja) — conhecimento de plugin Plugin `fluent-crm` (Free, `FLUENTCRM_PLUGIN_VERSION` 3.1.10) + addon comercial `fluentcampaign-pro` (2.9.86, "FluentCRM Pro"), instalados e activos em **descomplicar.pt** (produção). Mapeamento feito por leitura completa do código-fonte (`wp-content/plugins/fluent-crm/app/` e `wp-content/plugins/fluentcampaign-pro/app/`) via SSH, 19-08-2026, antes de construir o MCP `mcp-fluent-crm`. **Não confundir com FluentBooking** (`fluent-booking`/`fluent-booking-pro`, mesmo fabricante WPManageNinja, também activo em descomplicar.pt) — é um produto de agendamento separado, sem relação com contactos/email marketing além de estar na mesma "família Fluent". --- ## 0. As duas camadas — Free vs Pro | | Free (`fluent-crm`) | Pro (`fluentcampaign-pro`) | |---|---|---| | Contactos, tags, listas, empresas | ✅ core | — | | Campanhas de email | ✅ core | — | | Automações (Funnels) — motor + triggers/actions base | ✅ core (`FluentFormSubmissionTrigger`, `UserRegistrationTrigger`, apply/detach tag/list/company, send email, wait) | Acrescenta dezenas de triggers/actions por integração (ver §5) | | Dynamic Segments | — | ✅ (`WooCustomerSegment`, `EddActiveCustomerSegment`, `PMProMembersSegment`, `AffiliateWPSegment`, `WpUserSegment`, `CustomSegment`, `WooSubscriptionActiveSegment`) | | Abandon Cart | — | ✅ (WooCommerce) | | Integrações de terceiros (WooCommerce, EDD, SureCart, LearnDash, LifterLMS, TutorLMS, MemberPress, RCP, PMPro, AffiliateWP, WishlistMember, Voxel) | — | ✅ | **Achado real (descomplicar.pt, 19-08-2026):** nenhuma das plataformas-alvo das integrações Pro (WooCommerce, EDD, LearnDash, LifterLMS, TutorLMS, MemberPress, RCP, PMPro, AffiliateWP, WishlistMember) está instalada no site — só **FluentForm** está activo. Logo, das dezenas de triggers/actions que o Pro acrescenta, **só os de FluentForm têm alvo real hoje**; o resto do addon Pro está dormente (código carregado, sem integração activável). Reavaliar se algum destes plugins for instalado no futuro. --- ## 1. Onde vivem os dados ### 1.1 `wp_options` (config do plugin) | Opção | Conteúdo | |---|---| | `_fluentcrm_db_version` | versão do schema de BD instalado | | `fluentcrm-global-settings` | array serializado: `campaign.from.{name,email}`, `email.emails_per_second` | | `fluentcrm_is_sending_emails` | flag de lock durante envio de campanha | | `_fluentcrm_lock_minute_scheduler` / `_five_minute_scheduler` / `_hourly_scheduler` | locks dos cron schedulers internos | ### 1.2 Tabelas `fc_*` (prefixo do site + `fc_`, ex. `wpah_fc_subscribers` em descomplicar.pt) Confirmado por `DESCRIBE`/`SHOW TABLES` em descomplicar.pt (19-08-2026): | Tabela | Papel | |---|---| | `fc_subscribers` | contactos — `email` UNIQUE, `status`, `contact_type` (`lead`/`customer`), `company_id`, `hash`, `total_points`, `life_time_value`, geo (`ip`/`latitude`/`longitude`/`country`/...) | | `fc_subscriber_meta` | custom fields por contacto | | `fc_subscriber_pivot` | associações contacto↔tag/lista — `object_type` = FQCN literal (`FluentCrm\App\Models\Tag` ou `FluentCrm\App\Models\Lists`), `object_id` | | `fc_subscriber_notes` | notas por contacto — `type` normal é `note`; `_company_note_`/`_system_log_` são tipos internos excluídos por omissão da UI (global scope no modelo) | | `fc_tags` / `fc_lists` | taxonomias reais de segmentação (81 tags / 9 lists em descomplicar.pt) | | `fc_terms` / `fc_term_relations` | **tabelas vazias em descomplicar.pt (0 linhas)** — schema preparado para uma futura unificação de taxonomias que ainda não está em uso; não confiar nelas, usar `fc_tags`/`fc_lists`/`fc_subscriber_pivot` | | `fc_companies` | empresas (CRM B2B) — `name`, `industry`, `owner_id` (contacto responsável), `meta` (JSON com `custom_values`) | | `fc_campaigns` | campanhas de email — `type='campaign'`, `status` (`draft`/`published`/`paused`/`archived`), `email_body`, `recipients_count` | | `fc_campaign_emails` | um registo por (campanha × destinatário) — `is_open`, `click_counter`, `status` (`sent`/...) — é aqui que vivem as estatísticas reais de abertura/clique | | `fc_campaign_url_metrics` | cliques agregados por URL dentro de uma campanha | | `fc_funnels` / `fc_funnel_sequences` / `fc_funnel_subscribers` / `fc_funnel_metrics` | automações (ver §5) | | `fc_event_tracking` | eventos custom rastreados via `event_tracker` API (§2) | | `fc_smart_links` | links rastreáveis reutilizáveis (Pro) | | `fc_sequence_tracker` | progresso de contactos dentro de sequências de email (automações/campanhas em sequência) | | `fc_url_stores` | URLs longas por trás de smart links/tracking | **Gotcha real confirmado (descomplicar.pt):** existem linhas em `fc_subscriber_pivot` com `object_type` literalmente diferente do FQCN correcto (deteção via `SELECT object_type, COUNT(*) GROUP BY object_type` — 107 linhas com um padrão de escaping incorrecto vs 325k+342k linhas correctas). São dados órfãos de alguma migração/import antiga, invisíveis à própria UI do FluentCRM (que também filtra por FQCN exacto) — não é bug do MCP nem motivo de alarme, só não contar com essas 107 linhas em auditorias de integridade. --- ## 2. API de desenvolvimento oficial — `FluentCrmApi()` **Único caminho seguro para escritas programáticas.** Nunca fazer `INSERT`/`UPDATE` directo nas tabelas `fc_*` — perde-se guardas de estado, `do_action()` hooks (usados por automações/ integrações de terceiros) e invalidação de cache. ```php FluentCrmApi('contacts') // Contacts — createOrUpdate, getContact, getContactByUserRef, query() FluentCrmApi('tags') // Tags — importBulk (cria/actualiza por slug) FluentCrmApi('lists') // Lists — importBulk (idem) FluentCrmApi('companies') // Companies — createOrUpdate, attachContactsByIds, detachContactsByIds FluentCrmApi('event_tracker') // Tracker — track($eventData, $isUnique) FluentCrmApi('extender') // ponto de extensão para integrações de terceiros ``` Fonte: `app/Api/Classes/*.php`, registadas em `app/Api/config.php`. Cada classe expõe também os métodos `all`/`get`/`find`/`first`/`paginate` do modelo Eloquent-like subjacente via `__call()`. Métodos de instância directos no modelo `Subscriber` (não passam pela fábrica `FluentCrmApi()`, chamam-se sobre um objecto `Subscriber` já resolvido): `attachTags($ids)`, `detachTags($ids)`, `attachLists($ids)`, `detachLists($ids)`, `attachCompanies($ids)`, `detachCompanies($ids)`, `updateStatus($status)` (mudança directa, sem guardas — ver §3). --- ## 3. Modelo de estados de contacto — e a guarda real contra escalada indevida ```php fluentcrm_subscriber_statuses() // ['subscribed','pending','unsubscribed','transactional','bounced','complained','spammed'] fluentcrm_strict_statues() // ['unsubscribed','bounced','complained','spammed'] — "suprimidos" ``` `Contacts::createOrUpdate($data, $forceUpdate=false, ...)` → `Subscriber::updateOrCreate()` implementa uma guarda documentada no próprio código-fonte (comentário explícito no ficheiro, descrevendo a correcção de uma vulnerabilidade real): **sem `$forceUpdate=true` explícito, um payload nunca rebaixa um contacto `subscribed` nem ressuscita um contacto suprimido** — protege contra webhooks/migradores/formulários que resSuscitassem silenciosamente contactos que se desinscreveram ou tiveram bounce/complaint. Mover **para** um estado suprimido a partir de qualquer estado é sempre permitido (direcção "fail-safe"). `Subscriber::updateStatus($status)` é o método directo, **sem essas guardas** — dispara sempre `do_action('fluent_crm/subscriber_status_changed', ...)` e `do_action('fluentcrm_subscriber_status_to_' . $status, ...)`. É a via correcta quando a intenção de mudar o status é deliberada e já confirmada pelo chamador (ex. um admin a reactivar manualmente um contacto). **Implicação prática:** qualquer automação/import que crie/actualize contactos em massa deve usar `createOrUpdate()` sem `forceUpdate`, salvo decisão explícita e documentada de contornar a guarda. --- ## 4. wp-admin — páginas principais | Página | Conteúdo | |---|---| | **FluentCRM → Subscribers** | lista/pesquisa/filtra contactos, vista de detalhe com tags/listas/empresa/notas/timeline de actividade | | **FluentCRM → Contact Companies** | CRM B2B — empresas, dono (owner_id), contactos associados (Pro-visível, dados core) | | **FluentCRM → Email Campaigns** | criar/agendar/enviar campanhas, relatório de abertura/clique/bounce por campanha | | **FluentCRM → Automations** | construtor visual de Funnels (trigger → sequência de actions/benchmarks/condições) | | **FluentCRM → Forms** (se FluentForm activo) | mapeamento formulário↔lista/tag na submissão | | **FluentCRM → Reports** | dashboards agregados (crescimento de lista, performance de campanhas) | | **FluentCRM → Settings** | remetente por omissão, limite de emails/segundo, dupla-opt-in, campos custom | | **FluentCRM → Dynamic Segments** (Pro) | segmentos calculados em tempo real a partir de dados externos (§6) | --- ## 5. Automações (Funnels) — arquitectura Motor em `app/Services/Funnel/` (`FunnelProcessor.php`, `BaseTrigger.php`, `BaseAction.php`, `BaseBenchMark.php`, `SequencePoints.php`). Um Funnel = 1 trigger + sequência ordenada de actions/wait-steps/condições, persistida em `fc_funnels` (definição) + `fc_funnel_sequences` (passos) + `fc_funnel_subscribers` (progresso por contacto) + `fc_funnel_metrics` (contadores agregados). **Triggers/Actions core (Free), confirmados no código:** - Triggers: `FluentFormSubmissionTrigger`, `FluentFormSubscriptionCancelledTrigger`, `FluentFormSubscriptionPaymentReceivedTrigger`, `UserRegistrationTrigger`. - Actions: `ApplyTagAction`/`DetachTagAction`, `ApplyListAction`/`DetachListAction`, `ApplyCompanyAction`/`DetachCompanyAction`, `SendEmailAction`, `WaitTimeAction`. **Triggers/Actions internas do CRM (Pro, mas sem dependência de plugin externo — sempre disponíveis com o addon activo):** `ContactCreatedTrigger`, `TagAppliedTrigger`/ `RemoveFromTagTrigger`, `ListAppliedTrigger`/`RemoveFromListTrigger`, `CompanyAppliedTrigger`/ `RemoveFromCompanyTrigger` (`app/Services/Integrations/CRM/`) — permitem encadear automações a partir de mudanças de tag/lista/empresa feitas por outra automação ou pelo MCP. **Triggers/Actions Pro dependentes de plugin externo (dormentes em descomplicar.pt — nenhum dos plugins-alvo está instalado):** WooCommerce (order success/refund/complete, subscription renew/start/cancel/expire, add order note, create coupon), EDD (payment success/refund, license expired, recurring payment), SureCart, RCP, PMPro, AffiliateWP, LearnDash/LifterLMS/TutorLMS (course/lesson/topic completed, enroll, group), MemberPress, WishlistMember, Voxel. O MCP `mcp-fluent-crm` **não gere Funnels** (fora do v1, deliberado — sequências JSON complexas, alto risco de erro de configuração). Gerir Funnels via wp-admin. --- ## 6. Dynamic Segments (Pro) `app/Services/DynamicSegments/` — segmentos calculados em tempo real (não persistidos como lista estática), 7 tipos confirmados no código: `CustomSegment` (query builder livre), `WpUserSegment` (por role/meta WP), `WooCustomerSegment`, `WooSubscriptionActiveSegment`, `EddActiveCustomerSegment`, `PMProMembersSegment`, `AffiliateWPSegment`. Os 5 últimos dependem do plugin-alvo estar instalado (nenhum está, hoje). Não coberto pelo MCP — gerir via wp-admin. --- ## 7. Estado real em descomplicar.pt (verificado 19-08-2026) - **291 441 contactos**: 81 881 `subscribed`, 209 559 `unsubscribed`, 1 `transactional`. - **291 428 `lead`, 13 `customer`** (`contact_type`). - **81 tags, 9 lists, 0 empresas** (tabela `fc_companies` vazia — CRM B2B nunca usado neste site). - **37 campanhas** (31 `archived`/enviadas, 2 `draft`, 2 `paused`, 2 `published`). - Nenhuma licença Pro visível em `cfLicenseKey`-equivalente verificada nesta sessão — addon `fluentcampaign-pro` estava **instalado mas inactivo** até ser activado nesta mesma sessão via `emcp-tools/activate-plugin` (não requer autorização de infra — só mutações de ficheiro por SSH precisam do `infra-auth.sh`). --- ## 8. Gotchas | Sintoma | Causa | Nota | |---|---|---| | `wp fluent_crm ` não tem `create-contact`/`add-tag`/etc. | O WP-CLI do plugin só cobre operações administrativas (`license_status`, `stats`, `sync_woo_customers`, `sync_edd_customers`, `reindex_wp_user_ids`, `reset_db`, `simulate_funnel`, `cli_send`) — nunca CRUD de contactos | Usar `FluentCrmApi()` (via MCP `mcp-fluent-crm`) ou o wp-admin | | API REST (`/wp-json/fluent-crm/v2/...`) não responde/autentica | Precisa de uma API key gerada manualmente em **Settings → REST API** — não vinha configurada em nenhum site do bundle nesta verificação | Gerar a key no wp-admin se for necessário REST em vez de SSH; o MCP `mcp-fluent-crm` não depende disto | | Query a `fc_terms`/`fc_term_relations` devolve sempre vazio | Tabelas preparadas para uma futura unificação de taxonomias, nunca populadas nesta versão | Usar sempre `fc_tags`/`fc_lists` + `fc_subscriber_pivot` | | `object_type` em `fc_subscriber_pivot` não bate certo com o FQCN esperado numa pequena fracção de linhas | Dados órfãos de import/migração antiga (107 de ~650k linhas em descomplicar.pt) | Ignorar essas linhas — a própria UI do FluentCRM também as ignora | | Contacto criado/actualizado via automação não muda de status apesar do payload trazer `status` | Guarda anti-escalada do `Contacts::createOrUpdate()` (§3) — comportamento correcto, não bug | Passar `forceUpdate=true` só com intenção explícita, ou usar `updateStatus()` directamente | --- ## Fonte Leitura completa do código-fonte `fluent-crm` 3.1.10 + `fluentcampaign-pro` 2.9.86 via SSH em descomplicar.pt (`app/Api/Classes/*.php`, `app/Models/Subscriber.php`, `app/Models/Company.php`, `app/Models/SubscriberNote.php`, `app/Functions/helpers.php`, `app/Services/Funnel/*`, `app/Services/Integrations/*`, `app/Services/DynamicSegments/*`), cruzada com `DESCRIBE`/ `SHOW TABLES`/agregações reais via SQL, 19-08-2026 — sessão de construção do MCP `mcp-fluent-crm`. Ver essa skill para a lista completa de 20 tools programáticas.