Files
claude-plugins/wordpress/skills/jetengine/references/automation.md
Emanuel Almeida 9404af7ac9 feat: sync all plugins, skills, agents updates
New plugins: core-tools
New skills: auto-expense, ticket-triage, design, security-check,
  aiktop-tasks, daily-digest, imap-triage, index-update, mindmap,
  notebooklm, proc-creator, tasks-overview, validate-component,
  perfex-module, report, calendar-manager
New agents: design-critic, design-generator, design-lead,
  design-prompt-architect, design-researcher, compliance-auditor,
  metabase-analyst, gitea-integration-specialist
Updated: all plugin configs, knowledge datasets, existing skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:16:32 +00:00

19 KiB

JetEngine — Automação Headless e Integração com Agentes IA

Arquitectura técnica para manipular o JetEngine sem wp-admin: via PHP interno, REST API, SQL e n8n.


1. Estrutura de Dados — Base de Dados

A. Custom Post Types (CPT) — Estrutura Standard WP

-- Dados em wp_posts
SELECT * FROM wp_posts WHERE post_type = 'meu_cpt' AND post_status = 'publish';

-- Meta fields em wp_postmeta
SELECT meta_key, meta_value FROM wp_postmeta WHERE post_id = 123;

Limitação: Queries complexas com múltiplos meta fields são lentas (JOIN por cada meta key).

B. Custom Content Types (CCT) — Tabelas SQL Próprias

Cada CCT tem tabela dedicada: wp_jet_cct_{slug_do_cct}

-- Exemplo: CCT "faturas" → tabela wp_jet_cct_faturas
-- Colunas são os campos definidos no JetEngine

-- Ler todos os itens activos
SELECT * FROM wp_jet_cct_faturas WHERE cct_status = 'active';

-- Filtrar por campo
SELECT * FROM wp_jet_cct_clientes
WHERE cidade = 'Lisboa'
AND cct_status = 'publish'
ORDER BY _created_at DESC
LIMIT 20;

-- Campos especiais em todos os CCT:
-- _ID              → ID numérico único
-- cct_status       → publish / draft
-- cct_author_id    → ID do utilizador criador
-- _created_at      → Unix timestamp de criação
-- _modified        → Unix timestamp de modificação

Vantagem: SQL directo é seguro e extremamente rápido. Ideal para leitura em dashboards e n8n.

C. Relations — Tabelas wp_jet_rel_default e _meta

O JetEngine NÃO guarda relações em wp_postmeta. Tem tabela própria.

-- wp_jet_rel_default: relações básicas
-- parent_object_id | child_object_id | rel_id (hash/slug da relação)

-- Ler filhos de um pai
SELECT child_object_id
FROM wp_jet_rel_default
WHERE parent_object_id = 123
AND rel_id = 'relacao_clientes_projetos';

-- Ler pai a partir de filho
SELECT parent_object_id
FROM wp_jet_rel_default
WHERE child_object_id = 456
AND rel_id = 'relacao_clientes_projetos';

-- Contar relações
SELECT parent_object_id, COUNT(*) as total
FROM wp_jet_rel_default
WHERE rel_id = 'relacao_empresa_colaboradores'
GROUP BY parent_object_id;

wp_jet_rel_default_meta — metadados específicos de uma relação (ex: papel do actor num filme):

-- Exemplo: condição de uso de um veículo numa relação utilizador↔veículo
SELECT meta_key, meta_value
FROM wp_jet_rel_default_meta
WHERE rel_id = 8
AND parent_object_id = 45
AND child_object_id = 1020;

-- UPSERT massivo para sincronizações ETL (com integridade referencial)
START TRANSACTION;
INSERT INTO wp_jet_rel_default (rel_id, parent_object_id, child_object_id)
VALUES (8, 45, 1020), (8, 45, 1021), (8, 92, 1055)
ON DUPLICATE KEY UPDATE parent_object_id = VALUES(parent_object_id);

INSERT INTO wp_jet_rel_default_meta (rel_id, parent_object_id, child_object_id, meta_key, meta_value)
VALUES (8, 45, 1020, 'condicao_uso', 'Alugado')
ON DUPLICATE KEY UPDATE meta_value = VALUES(meta_value);
COMMIT;
-- OBRIGATÓRIO após SQL directo: chamar wp_cache_flush() + jet_smart_filters()->indexer->index_all()

Nota: Em relações críticas com muito tráfego, o JetEngine pode isolar por relação em tabela própria (wp_jet_rel_81_meta), reduzindo table locks.

Guardrail: NUNCA inserir directamente via SQL para criar relações em ambientes activos. Usar API PHP (ver secção 2). SQL directo só para sincronizações ETL massivas (centenas de milhares de linhas).

D. Custom Meta Storage para CPT (Tabela Dedicada)

Alternativa ao wp_postmeta para CPTs com muitos campos e consultas complexas.

JetEngine > Post Types > Edit > Custom Meta Storage: Activar
→ Cria tabela dedicada: wp_{cpt_slug}_meta
  (ex: wp_imoveis_meta com colunas tipadas por campo)
-- Com Custom Meta Storage activo, cada campo é uma coluna
SELECT preco, area_m2, tipologia, localizacao
FROM wp_imoveis_meta
WHERE preco BETWEEN 150000 AND 350000
AND tipologia IN ('T2', 'T3')
ORDER BY preco ASC;

-- Joins com wp_posts permanecem necessários para title/date
SELECT p.ID, p.post_title, m.preco, m.area_m2
FROM wp_posts p
JOIN wp_imoveis_meta m ON m.post_id = p.ID
WHERE p.post_status = 'publish'
AND m.preco < 300000;

Quando usar: CPTs com >10 campos numéricos/date, queries com múltiplos filtros simultâneos, alternativa a CCT quando se precisa de taxonomias e comentários nativos WP.

E. Estrutura na wp_options

-- Configurações de CPTs
SELECT option_value FROM wp_options WHERE option_name = 'jet_engine_cpt';

-- Taxonomias
SELECT option_value FROM wp_options WHERE option_name = 'jet_engine_tax';

-- Relações
SELECT option_value FROM wp_options WHERE option_name = 'jet_engine_relations';

-- Meta Boxes
SELECT option_value FROM wp_options WHERE option_name = 'jet_engine_meta_boxes';

Aviso: São PHP serialized arrays. NUNCA editar via SQL string replace — vai corromper. Usar sempre unserialize → edit → serialize em PHP.


2. PHP — API Interna JetEngine

Criar/Actualizar Item CCT

<?php
// Usar CCT Data Handler (mantém hooks, validações e cache)
// Mais seguro que SQL directo para escrita

$cct_slug = 'faturas';
$item_data = [
    'titulo'  => 'Fatura #1024',
    'valor'   => 500.00,
    'status'  => 'pendente',
    'cliente' => 'Empresa X Lda',
    // Para update, incluir _ID existente:
    // '_ID' => 15
];

// Aceder ao módulo CCT
if (function_exists('jet_engine')) {
    $cct_module = jet_engine()->modules->get_module('custom-content-types');
    if ($cct_module) {
        $manager = $cct_module->instance->manager;
        $type    = $manager->get_content_type_instance($cct_slug);
        if ($type) {
            $result = $type->get_handler_instance()->update_item($item_data);
            // $result = ID do item criado, ou false em caso de erro
        }
    }
}

Gerir Relações Programaticamente

<?php
// Método 1: process_relation (simples, para connect/disconnect)
// IMPORTANTE: Executar após o hook 'init' com prioridade > 12

if (class_exists('\Jet_Engine\Relations\Manager')) {
    $relations_manager = \Jet_Engine\Relations\Manager::get_instance();
    $relations_manager->process_relation(
        $rel_id,   // ex: 'relacao_empresa_colaboradores'
        $parent_id,
        $child_id,
        true       // true = conectar, false = desconectar
    );
}

// Método 2: API completa (update com contexto — mais robusto)
function ia_conectar_com_contexto($cliente_id, $documento_id) {
    $rel_id_numerico = 15; // ID numérico da relação (ver JetEngine > Relations)

    $relation = jet_engine()->relations->get_active_relations($rel_id_numerico);

    if ($relation) {
        $relation->set_update_context('parent'); // perspectiva: 'parent' ou 'child'
        $relation->update($cliente_id, $documento_id);

        // Limpar cache do Query Builder após actualizar relações
        \Jet_Engine\Query_Builder\Manager::instance()->listings_cache->clear();
    }
}

Criar/Actualizar CPT Post

<?php
// CPTs usam funções standard do WordPress
$post_data = [
    'post_title'   => 'Novo Projecto',
    'post_content' => '',
    'post_status'  => 'publish',
    'post_type'    => 'projecto',
];

$post_id = wp_insert_post($post_data);

if (!is_wp_error($post_id)) {
    update_post_meta($post_id, 'cliente_id', 45);
    update_post_meta($post_id, 'valor_contrato', 5000);
    update_post_meta($post_id, 'data_inicio', '2026-03-01');
}

Manipular Estrutura (Onboarding Programático)

<?php
// Adicionar novo CPT à configuração do JetEngine
// CUIDADO: substitui configurações existentes se não fizer merge

$current_cpts = get_option('jet_engine_cpt', []);

$new_cpt = [
    'slug'          => 'imovel',
    'singular_name' => 'Imóvel',
    'name'          => 'Imóveis',
    'show_in_rest'  => true,
    'supports'      => ['title', 'editor', 'thumbnail', 'custom-fields'],
    // ... outras configurações
];

$current_cpts[] = $new_cpt;
update_option('jet_engine_cpt', $current_cpts);

// Obrigatório: regenerar permalinks após alterar CPTs
flush_rewrite_rules();

Registar Meta Box Programaticamente (Hook)

<?php
// Hook: jet-engine/meta-boxes/register-instances
// Ideal para plugins de agência (controlo por código, imutável pelo cliente)

add_action('jet-engine/meta-boxes/register-instances', function($meta_manager) {

    $meta_fields = [
        [
            'title'       => 'ID Remoto de Sincronização',
            'name'        => '_ia_sync_id',
            'object_type' => 'field',
            'type'        => 'text',
            'width'       => '50%',
        ],
        [
            'title'       => 'Score de Auditoria',
            'name'        => '_score_auditoria',
            'object_type' => 'field',
            'type'        => 'number',
            'width'       => '50%',
        ],
    ];

    $post_type = 'documento_legal';

    if (!class_exists('Jet_Engine_CPT_Meta')) {
        require $meta_manager->component_path('post.php');
    }

    $meta_manager->store_fields($post_type, $meta_fields, 'post_type');
    new Jet_Engine_CPT_Meta($post_type, $meta_fields, 'Controlo IA', 'normal', 'high', [
        'object_type'       => 'post',
        'allowed_post_type' => [$post_type],
        'name'              => 'Controlo de Metadados IA',
    ]);
});

WP-CLI Batch Import para CCT

<?php
// Ficheiro: import_cct_data.php
// Executar: wp eval-file import_cct_data.php

if (!class_exists('\Jet_Engine\Modules\Custom_Content_Types\Module')) {
    WP_CLI::error('Módulo CCT não está activo.');
}

$cct_slug   = 'veiculos';
$manager    = \Jet_Engine\Modules\Custom_Content_Types\Module::instance()->manager;
$content_type = $manager->get_content_types($cct_slug);

if (!$content_type) {
    WP_CLI::error("CCT '{$cct_slug}' não encontrado.");
}

$registos = json_decode(file_get_contents('/tmp/veiculos.json'), true);
$sucesso  = 0;

foreach ($registos as $dados) {
    // db->insert() respeita sanitizações e integridade do CCT
    if ($content_type->db->insert($dados)) {
        $sucesso++;
    }
    // Libertar memória em loops grandes
    if ($sucesso % 500 === 0) {
        wp_cache_flush();
    }
}

wp_cache_flush();
WP_CLI::success("Importação concluída. Registos criados: {$sucesso}");
# Execução no servidor CWP:
PHP="/opt/alt/php-fpm83/usr/bin/php"
WP="$PHP /usr/local/bin/wp --allow-root --path=/home/USER/public_html"
$WP eval-file /tmp/import_cct_data.php

3. REST API JetEngine

Endpoints

Tipo Endpoint Método
CPT /wp-json/wp/v2/{cpt_slug} GET / POST / PUT / DELETE
CCT /wp-json/jet-cct/{cct_slug} GET / POST / PUT / DELETE
Relação /wp-json/jet-rel/{relation_id} GET / POST

Pré-requisito: Activar REST API nas configurações do CPT/CCT/Relação em JetEngine.

Autenticação

# Application Passwords (WP 5.6+) — recomendado para n8n
# Criar em: WP Admin > Users > Edit > Application Passwords

# Header de autenticação
Authorization: Basic $(echo -n 'username:app-password' | base64)

Criar/Ler Item CCT via REST

# Criar registo no CCT "clientes"
curl -X POST https://site.pt/wp-json/jet-cct/clientes \
  -H "Authorization: Basic dXNlcjpwYXNz..." \
  -H "Content-Type: application/json" \
  -d '{
    "nome_cliente": "Empresa X Lda",
    "email": "contacto@empresax.com",
    "nif": "123456789",
    "cct_status": "publish"
  }'
# Resposta: { "_ID": 42, ... }

# Pesquisa com filtros — parâmetros CCT REST API
# _limit, _offset: paginação
# _cct_search: pesquisa de texto
# _cct_search_by: campo específico
curl "https://site.pt/wp-json/jet-cct/clientes?_limit=20&_offset=0&_cct_search=Lisboa&_cct_search_by=cidade"
# Header de resposta inclui: Jet-Query-Total (total de registos para paginação)

Actualizar Relação via REST API

# POST para /wp-json/jet-rel/{relation_id}
# store_items_type: "update" (aditivo) | "replace" (destrutivo!) | "disconnect" (remover)
curl -X POST https://site.pt/wp-json/jet-rel/22 \
  -H "Authorization: Basic dXNlcjpwYXNz..." \
  -H "Content-Type: application/json" \
  -d '{
    "parent_id": 4500,
    "child_id": 1020,
    "context": "child",
    "store_items_type": "update",
    "meta": {
      "etapa_validacao": "Aprovado",
      "score_auditoria": 88.5,
      "data_processamento": "2026-02-18T08:30"
    }
  }'

Atenção store_items_type:

  • update — adiciona à relação existente (safe)
  • replaceDESTRÓI todas as relações existentes e substitui (perigoso!)
  • disconnect — remove a relação específica

Criar Post (CPT) via REST

curl -X POST https://site.pt/wp-json/wp/v2/projecto \
  -H "Authorization: Basic dXNlcjpwYXNz..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Projecto Website ABC",
    "status": "publish",
    "meta": {
      "cliente_id": 45,
      "valor_contrato": 5000
    }
  }'

Campos Repeater via REST

{
  "galeria_servicos": [
    { "titulo": "Serviço A", "descricao": "Descrição A" },
    { "titulo": "Serviço B", "descricao": "Descrição B" }
  ]
}

4. Integração n8n

Leitura (MySQL Node — mais rápido)

// n8n MySQL Node — ler CCT directamente da BD
// Muito mais rápido que REST API para leitura em massa

// Query example:
SELECT
  _ID,
  nome_cliente,
  email,
  cidade,
  cct_status,
  _created_at
FROM wp_jet_cct_clientes
WHERE cct_status = 'publish'
ORDER BY _created_at DESC
LIMIT 100;

Vantagem: 100x mais rápido que HTTP Request ao WP REST API.

Escrita (HTTP Request Node — via REST)

// n8n HTTP Request Node
// Method: POST
// URL: https://site.pt/wp-json/jet-cct/faturas
// Authentication: Basic Auth
// Body (JSON):
{
  "numero": "{{ $json.fatura_numero }}",
  "cliente_id": "{{ $json.cliente_id }}",
  "valor": "{{ $json.total }}",
  "cct_status": "publish"
}

Usar REST para escrita (garante hooks JetEngine e validações).

Webhooks via JetFormBuilder

JetFormBuilder pode disparar Webhook para n8n:
1. Criar formulário no JetFormBuilder
2. Post-submit Action: Webhook
3. URL: https://n8n.descomplicar.pt/webhook/jetengine
4. Formulários silenciosos: receber POST externos que passam pelas validações JetEngine

5. Tabela de Métodos

Método Complexidade Performance Risco Caso de Uso
REST API Baixa Média Baixo Inserir dados externos via n8n
PHP (Classes Internas) Média Alta Baixo Plugins custom, scripts de migração
SQL Directo (CCT) Alta Muito Alta Médio Bulk updates, leitura analytics
SQL Directo (Meta) Alta Baixa Alto Evitar — corrupção de arrays
WP-CLI Custom Média Alta Baixo Manutenção, cron jobs

6. Guardrails Críticos

Cache JetEngine

# Após alterar estruturas via PHP/SQL na wp_options,
# forçar regeneração de cache:

PHP="/opt/alt/php-fpm83/usr/bin/php"
WP="$PHP /usr/local/bin/wp --allow-root --path=/home/USER/public_html"

$WP cache flush
$WP rewrite flush
$WP elementor flush-css --regenerate

Cache do Query Builder — obrigatório após alterações SQL directas:

<?php
// Limpar cache de listagens após SQL bulk update
\Jet_Engine\Query_Builder\Manager::instance()->listings_cache->clear();

// Recalcular Indexer do JetSmartFilters (se filtros activos)
if (function_exists('jet_smart_filters')) {
    jet_smart_filters()->indexer->index_all();
}

// Cache do WordPress
wp_cache_flush();

Regra crítica: Alterações SQL directas em wp_jet_cct_* ou wp_jet_rel_default NÃO invalidam automaticamente o Object Cache nem os transients do Query Builder. O site apresentará dados obsoletos até ao flush.

NUNCA Editar Campos Serializados via SQL String Replace

-- ERRADO — corrompe o array PHP serializado
UPDATE wp_options
SET option_value = REPLACE(option_value, 'Imóveis', 'Propriedades')
WHERE option_name = 'jet_engine_cpt';

-- CORRECTO — usar PHP
$cpts = get_option('jet_engine_cpt');
// Fazer alterações via PHP
update_option('jet_engine_cpt', $cpts);

IDs de Relações em Migrações

Problema: Ao migrar dados entre ambientes, IDs podem mudar → relações quebram
Solução: Usar Slugs como referência no código; converter para ID antes de gravar
Usar: get_posts(['name' => 'slug-do-post', 'post_type' => 'meu_cpt'])

Repeater Fields via SQL

Repeater fields são PHP serialize() ou JSON dependendo da configuração.
NUNCA fazer string replace em SQL.
Usar sempre: unserialize() → editar array → serialize() → update_post_meta()

7. MU-Plugin: Wrapper de Endpoints Custom

Para expor endpoints seguros ao n8n sem passar pela REST API standard:

<?php
/**
 * Plugin Name: JetEngine AI Agent Endpoints
 * Description: Endpoints seguros para criação de CCTs e Relações via n8n
 * Must Use: sim — colocar em /wp-content/mu-plugins/
 */

add_action('rest_api_init', function() {
    // Endpoint para criar item CCT
    register_rest_route('jet-agent/v1', '/cct/(?P<slug>[a-z_]+)', [
        'methods'             => 'POST',
        'callback'            => 'jet_agent_create_cct_item',
        'permission_callback' => function() {
            return current_user_can('edit_posts');
        },
        'args' => [
            'slug' => ['required' => true, 'sanitize_callback' => 'sanitize_key'],
        ]
    ]);

    // Endpoint para gerir relações
    register_rest_route('jet-agent/v1', '/relation', [
        'methods'             => 'POST',
        'callback'            => 'jet_agent_manage_relation',
        'permission_callback' => function() {
            return current_user_can('edit_posts');
        }
    ]);
});

function jet_agent_create_cct_item(WP_REST_Request $request) {
    $slug = $request->get_param('slug');
    $data = $request->get_json_params();

    // Sanitizar dados
    $data = array_map('sanitize_text_field', $data);

    // Usar handler interno do JetEngine
    if (!function_exists('jet_engine')) {
        return new WP_Error('jet_engine_missing', 'JetEngine não activo', ['status' => 500]);
    }

    $cct_module = jet_engine()->modules->get_module('custom-content-types');
    $type       = $cct_module->instance->manager->get_content_type_instance($slug);

    if (!$type) {
        return new WP_Error('cct_not_found', "CCT '{$slug}' não encontrado", ['status' => 404]);
    }

    $result = $type->get_handler_instance()->update_item($data);

    return rest_ensure_response([
        'success' => (bool)$result,
        'id'      => $result,
    ]);
}

function jet_agent_manage_relation(WP_REST_Request $request) {
    $params    = $request->get_json_params();
    $rel_id    = sanitize_text_field($params['rel_id'] ?? '');
    $parent_id = absint($params['parent_id'] ?? 0);
    $child_id  = absint($params['child_id'] ?? 0);
    $connect   = (bool)($params['connect'] ?? true);

    if (!$rel_id || !$parent_id || !$child_id) {
        return new WP_Error('missing_params', 'rel_id, parent_id e child_id são obrigatórios', ['status' => 400]);
    }

    if (class_exists('\Jet_Engine\Relations\Manager')) {
        \Jet_Engine\Relations\Manager::get_instance()->process_relation(
            $rel_id, $parent_id, $child_id, $connect
        );
        return rest_ensure_response(['success' => true]);
    }

    return new WP_Error('relations_missing', 'Módulo Relations não disponível', ['status' => 500]);
}

JetEngine Automation | Descomplicar® | 18-02-2026