snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
---
title: Tratar arrays em ações de Código
description: Aprenda a tratar corretamente entradas de array em ações de Código em fluxos de trabalho.
---
Ao trabalhar com arrays em ações de Código, você pode encontrar dois desafios comuns:
1. **Arrays passados como strings** — dados de sistemas externos ou de etapas anteriores chegam como uma string em vez de um array real
2. **Não é possível selecionar itens individualmente** — você só consegue selecionar o array inteiro, não campos específicos dentro dele
Ambos podem ser resolvidos com um nó de Código.
## Analisar arrays a partir de strings
Com frequência, arrays são passados entre etapas do fluxo de trabalho como strings ou JSON, em vez de arrays nativos. Isso acontece quando:
* Recebendo dados de APIs externas via HTTP Request
* Processando payloads de webhooks
* Transmitindo dados entre etapas do fluxo de trabalho
**Solução**: Adicione este padrão no início da sua ação de Código:
```javascript
export const main = async (params: {
users: any;
}): Promise<object> => {
const { users } = params;
// Handle input that may come as a string or an array
const usersFormatted = typeof users === "string" ? JSON.parse(users) : users;
// Now you can safely work with usersFormatted as an array
return {
users: usersFormatted.map((user) => ({
...user,
activityStatus: String(user.activityStatus).toUpperCase(),
})),
};
};
```
A linha chave `typeof users === "string" ? JSON.parse(users) : users` verifica se a entrada é uma string, faz o parse se necessário ou a utiliza diretamente se já for um array.
## Extraindo campos individuais de arrays
Um webhook pode retornar um array como `answers: [...]`, mas, nas etapas seguintes do fluxo de trabalho, você só consegue selecionar o **array inteiro** — não itens individuais dentro dele.
**Solução**: Adicione um nó de Código para extrair campos específicos e retorná-los como um objeto estruturado:
```javascript
export const main = async (params: {
answers: any;
}): Promise<object> => {
const { answers } = params;
// Handle input that may come as a string or an array
const answersFormatted = typeof answers === "string"
? JSON.parse(answers)
: answers;
// Extract specific fields from the array
const firstname = answersFormatted[0]?.text || "";
const name = answersFormatted[1]?.text || "";
return {
answer: {
firstname,
name
}
};
};
```
O nó de Código retorna um objeto estruturado em vez de um array. Nas etapas subsequentes, agora você pode selecionar campos individuais como `answer.firstname` e `answer.name` no seletor de variáveis.
<Tip>
**Quer iterar sobre o array em vez de extrair campos?** Quando uma etapa Code ou Logic Function retorna um array de nível superior, você pode passá-lo diretamente para um [Iterator](/l/pt/user-guide/workflows/capabilities/use-iterator): selecione a opção **Whole list** da etapa como entrada do Iterator e, em seguida, faça referência a cada elemento com `{{iterator.currentItem}}` dentro do loop. Nesse caso, você não precisa reestruturar o array em um objeto.
</Tip>
<Tip>
Clique no ícone quadrado no canto superior direito do editor de código para exibi-lo em tela cheia — útil, já que a largura padrão do editor é limitada.
</Tip>

View File

@@ -0,0 +1,182 @@
---
title: Traga dados de produtos para o Twenty
description: Sincronize, de forma agendada, os dados do catálogo de produtos do seu armazém de dados para o seu CRM.
---
Use este padrão para manter o Twenty sincronizado com os dados de produtos do seu armazém de dados (por exemplo, Snowflake, BigQuery, PostgreSQL).
## Estrutura do Fluxo de Trabalho
1. **Gatilho**: De forma agendada
2. **Código**: Consultar seu armazém de dados
3. **Código** (opcional): Formatar os dados como array
4. **Iterador**: Percorrer cada produto
5. **Inserir/atualizar registro**: Criar ou atualizar no Twenty
<img src="/images/user-guide/workflows/create_users_workflow.png" style={{width:'100%'}} />
## Etapa 1: Agendar o gatilho
Defina o fluxo de trabalho para executar em uma frequência que atenda às suas necessidades de atualização dos dados:
* A cada 5 minutos para sincronização quase em tempo real
* A cada hora para dados menos críticos
* Diariamente para atualizações em lote
## Etapa 2: Consultar seu armazém de dados
Adicione uma ação de **Código** para buscar dados recentes:
```javascript
export const main = async () => {
const intervalMinutes = 10; // Match your schedule frequency
const cutoffTime = new Date(Date.now() - intervalMinutes * 60 * 1000).toISOString();
// Replace with your actual data warehouse connection
const response = await fetch("https://your-warehouse-api.com/query", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
query: `
SELECT id, name, sku, price, stock_quantity, updated_at
FROM products
WHERE updated_at >= '${cutoffTime}'
`
})
});
const data = await response.json();
return { products: data.results };
};
```
<Tip>
Filtre por `updated_at >= last X minutes` para recuperar apenas registros alterados recentemente. Isso mantém a sincronização eficiente.
</Tip>
## Etapa 3: Formatar os dados (opcional)
Se o seu armazém de dados retornar dados em um formato que precise de transformação, adicione outra ação de **Código**. Transformações comuns incluem conversões de tipo, renomeação de campos e limpeza de dados.
### Exemplo: Dados de usuário com campos booleanos e de status
```javascript
export const main = async (params: {
users: any;
}): Promise<object> => {
const { users } = params;
const usersFormatted = typeof users === "string" ? JSON.parse(users) : users;
// Convert string "true"/"false" to actual booleans
const toBool = (v: any) => v === true || v === "true";
return {
users: usersFormatted.map((user) => ({
...user,
activityStatus: String(user.activityStatus).toUpperCase(),
isActiveLast30d: toBool(user.isActiveLast30d),
isActiveLast7d: toBool(user.isActiveLast7d),
isActiveLast24h: toBool(user.isActiveLast24h),
isTwenty: toBool(user.isTwenty),
})),
};
};
```
### Exemplo: Dados de produto com conversões de tipo
```javascript
export const main = async (params: { products: any }) => {
const products = typeof params.products === "string"
? JSON.parse(params.products)
: params.products;
return {
products: products.map(product => ({
externalId: product.id,
name: product.name,
sku: product.sku,
price: parseFloat(product.price), // String → Number
stockQuantity: parseInt(product.stock_quantity),
isActive: product.status === "active" // String → Boolean
}))
};
};
```
### Exemplo: Formatação de data e moeda
```javascript
export const main = async (params: { deals: any }) => {
const deals = typeof params.deals === "string"
? JSON.parse(params.deals)
: params.deals;
return {
deals: deals.map(deal => ({
...deal,
// Convert Unix timestamp to ISO date
closedAt: deal.closed_timestamp
? new Date(deal.closed_timestamp * 1000).toISOString()
: null,
// Ensure amount is a number (remove currency symbols)
amount: parseFloat(String(deal.amount).replace(/[^0-9.-]/g, "")),
// Normalize stage names
stage: deal.stage?.toLowerCase().replace(/_/g, " ")
}))
};
};
```
### Transformações comuns
| Formato de origem | Formato de destino | Código |
| -------------------- | ------------------ | ---------------------------------------- |
| `"true"` / `"false"` | `true` / `false` | `v === true \|\| v === "true"` |
| `"123.45"` | `123.45` | `parseFloat(value)` |
| `"active"` | `"ACTIVE"` | `value.toUpperCase()` |
| `1704067200` (Unix) | Data ISO | `new Date(v * 1000).toISOString()` |
| `"$1,234.56"` | `1234.56` | `parseFloat(v.replace(/[^0-9.-]/g, ""))` |
| `null` / `undefined` | `""` | `value \|\| ""` |
## Etapa 4: Iterar pelos produtos
Adicione uma ação de **Iterador**:
* Entrada: `{{code.products}}`
Isso percorre cada produto no array.
## Etapa 5: Inserir/atualizar cada registro
Dentro do iterador, adicione uma ação de **Inserir/atualizar registro**:
| Configuração | Valor |
| ----------------------- | --------------------------------------- |
| **Objeto** | Seu objeto Produto personalizado |
| **Correspondência por** | ID externo ou SKU (identificador único) |
| **Nome** | `{{iterator.item.name}}` |
| **SKU** | `{{iterator.item.sku}}` |
| **Preço** | `{{iterator.item.price}}` |
<Tip>
Use **inserir/atualizar** (atualizar ou criar) em vez de construir ramificações separadas para criação vs. atualização. É mais rápido de construir e mais fácil de depurar.
</Tip>
## Casos de uso de exemplo
| Fonte | Data |
| ---------------------------- | ------------------------------------------- |
| **Sistema ERP** | Catálogo de produtos, preços, inventário |
| **Plataforma de e-commerce** | Pedidos, clientes, atualizações de produtos |
| **Armazém de dados** | Métricas agregadas, dados enriquecidos |
| **Sistema de inventário** | Níveis de estoque, alertas de reposição |
## Relacionados
* [Gatilhos de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-triggers)
* [Ações de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-actions)
* [Lidar com arrays em ações de código](/l/pt/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,130 @@
---
title: Importar envios do Typeform para o Twenty
description: Trate a carga útil de webhook do Typeform para criar leads a partir de envios de formulários.
---
Para a configuração padrão de webhook, consulte [Configurar um gatilho de Webhook](/l/pt/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger). Este artigo aborda o tratamento específico necessário para a estrutura de carga útil personalizada do Typeform.
### Etapa 1: Criar um fluxo de trabalho de Webhook
1. Vá para **Configurações → Fluxos de trabalho**
2. Clique em **+ Novo fluxo de trabalho**
3. Selecione **Webhook** como o gatilho
4. Copie a URL do webhook
### Etapa 2: Configurar o Typeform
1. No Typeform, abra seu formulário
2. Vá para **Connect → Webhooks**
3. Cole a URL do webhook do Twenty
4. Salvar
### Etapa 3: Entender a carga útil do Typeform
O Typeform envia uma estrutura JSON aninhada. Aqui está um exemplo simplificado:
```json
{
"event_type": "form_response",
"form_response": {
"form_id": "abc123",
"submitted_at": "2025-01-15T10:30:00Z",
"answers": [
{
"text": "Jane",
"type": "text",
"field": { "id": "field1", "type": "short_text", "title": "First Name" }
},
{
"text": "Smith",
"type": "text",
"field": { "id": "field2", "type": "short_text", "title": "Last Name" }
},
{
"text": "Acme Corp",
"type": "text",
"field": { "id": "field3", "type": "short_text", "title": "Company" }
},
{
"email": "jane@acme.com",
"type": "email",
"field": { "id": "field4", "type": "email", "title": "Email" }
},
{
"type": "choice",
"field": { "id": "field5", "type": "dropdown", "title": "Team Size" },
"choice": { "label": "10-50" }
}
]
}
}
```
Pontos importantes a observar:
* Os dados do formulário estão aninhados em `form_response`
* **As respostas são retornadas como um array**, não como campos nomeados
* Cada resposta inclui o tipo e o título do campo para referência
### Etapa 4: Extrair campos do array de respostas
Como `answers` é um array, você só pode selecionar o array inteiro nas etapas subsequentes — não campos individuais. Adicione uma ação de **Código** para extrair os campos necessários:
```javascript
export const main = async (params: {
answers: any;
}): Promise<object> => {
const { answers } = params;
// Handle input that may come as a string or an array
const answersFormatted = typeof answers === "string"
? JSON.parse(answers)
: answers;
// Extract fields by position or by finding the field type
const firstName = answersFormatted[0]?.text || "";
const lastName = answersFormatted[1]?.text || "";
const company = answersFormatted[2]?.text || "";
const email = answersFormatted.find(a => a.type === "email")?.email || "";
const teamSize = answersFormatted.find(a => a.type === "choice")?.choice?.label || "";
return {
contact: {
firstName,
lastName,
company,
email,
teamSize
}
};
};
```
Nas etapas subsequentes, agora você pode selecionar campos individuais como `contact.firstName` e `contact.email` no seletor de variáveis.
<Tip>
Para mais detalhes sobre como lidar com arrays, consulte [Tratar arrays em ações de código](/l/pt/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions).
</Tip>
### Etapa 5: Criar o registro
Adicione a ação **Criar registro**:
| Campo | Valor |
| ----------- | --------------------------------------------------------- |
| **Objeto** | Pessoas |
| **Nome** | `{{code.contact.firstName}}` |
| **Apelido** | `{{code.contact.lastName}}` |
| **Email** | `{{code.contact.email}}` |
| **Empresa** | Pesquisar ou criar com base em `{{code.contact.company}}` |
### Etapa 6: Testar e ativar
1. Envie uma resposta de teste no Typeform
2. Verifique a execução do fluxo de trabalho para verificar se os dados foram capturados
3. Ative o fluxo de trabalho
## Relacionados
* [Configurar um gatilho de Webhook](/l/pt/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger)
* [Lidar com arrays em ações de código](/l/pt/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,263 @@
---
title: Gerar um PDF a partir do Twenty
description: Crie um fluxo de trabalho para gerar e anexar um PDF (como um orçamento) a um registo.
---
Gere automaticamente ou obtenha um PDF e anexe-o a um registo no Twenty. Isto é normalmente utilizado para criar orçamentos, faturas ou relatórios que estão associados a Empresas, Oportunidades ou outros objetos.
## Visão Geral
Este fluxo de trabalho utiliza um **Gatilho Manual** para que os utilizadores possam gerar um PDF a pedido para qualquer registo selecionado. Uma **Função lógica** trata de:
1. Transferir o PDF a partir de um URL (de um serviço de geração de PDF)
2. Carregar o ficheiro para o Twenty
3. Criar um Anexo ligado ao registo
## Pré-requisitos
Antes de configurar o fluxo de trabalho:
1. **Crie uma Chave de API**: Vá a **Definições → APIs** e crie uma nova chave de API. Você precisará deste token para a função lógica.
2. **Configure um serviço de geração de PDF** (opcional): Se quiser gerar PDFs dinamicamente (por exemplo, orçamentos), utilize um serviço como o Carbone, o PDFMonkey ou o DocuSeal para criar o PDF e obter um URL de transferência.
## Configuração passo a passo
### Etapa 1: Configurar o gatilho
1. Vá a **Fluxos de Trabalho** e crie um novo fluxo de trabalho
2. Selecione **Gatilho Manual**
3. Escolha o objeto ao qual pretende anexar PDFs (por exemplo, **Empresa** ou **Oportunidade**)
<Tip>
Com um disparador Manual usuários podem executar este fluxo de trabalho usando um botão que aparece no topo direito, depois que um registro é selecionado — isso executa o fluxo de trabalho para gerar e anexar um PDF.
</Tip>
### Passo 2: Adicionar uma Função lógica
1. Adicionar uma ação **Código** (função lógica)
2. Crie uma nova função com o código abaixo
3. Configure os parâmetros de entrada
#### Parâmetros de Entrada
| Parâmetro | Valor |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
Se estiver a anexar a um objeto diferente (Pessoa, Oportunidade, etc.), renomeie o parâmetro em conformidade (por exemplo, `personId`, `opportunityId`) e atualize a função lógica.
</Note>
#### Código da Função lógica
```typescript
export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
// Replace with your PDF URL
// This could be from a PDF generation service or a static URL
const pdfUrl = 'https://your-pdf-service.com/generated-quote.pdf';
const filename = 'quote.pdf';
// Step 1: Download the PDF file
const pdfResponse = await fetch(pdfUrl);
if (!pdfResponse.ok) {
throw new Error(`Failed to download PDF: ${pdfResponse.status}`);
}
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
});
const uploadResult = await uploadResponse.json();
if (uploadResult.errors?.length) {
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
}
}
`;
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: attachmentMutation,
variables: {
data: {
name: filename,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
});
const attachmentResult = await attachmentResponse.json();
if (attachmentResult.errors?.length) {
throw new Error(`Attachment creation failed: ${attachmentResult.errors[0].message}`);
}
return attachmentResult.data?.createAttachment;
};
```
### Passo 3: Personalizar para o seu caso de utilização
#### Para anexar a um objeto diferente
Substitua `targetCompanyId` pelo campo apropriado:
| Objeto | Nome do Campo |
| -------------------- | -------------------------- |
| Empresa | `targetCompanyId` |
| Pessoa | `targetPersonId` |
| Oportunidade | `targetOpportunityId` |
| Objeto personalizado | `targetYourCustomObjectId` |
Atualize tanto o parâmetro da função como o objeto `variables.data` na mutação de anexo.
#### Para utilizar um URL de PDF dinâmico
Se utilizar um serviço de geração de PDF, pode:
1. Primeiro, faça uma ação de Pedido HTTP para gerar o PDF
2. Passe o URL do PDF devolvido para a função lógica como parâmetro
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### Etapa 4: Testar e ativar
1. Guarde o fluxo de trabalho
2. Aceda a um registo de Empresa
3. Clique no menu **⋮** e selecione o seu fluxo de trabalho
4. Verifique a secção **Anexos** no registo para confirmar que o PDF foi anexado
5. Ative o fluxo de trabalho
## Combinar com Serviços de Geração de PDF
Para criar orçamentos ou faturas dinâmicos:
### Exemplo: Gerar Orçamento → Anexar PDF
| Etapa | Ação | Finalidade |
| ----- | ------------------------ | ------------------------------------------------------ |
| 1 | Gatilho Manual (Empresa) | O utilizador inicia num registo |
| 2 | Pesquisar Registo | Obter detalhes da Oportunidade ou dos itens de linha |
| 3 | Requisição HTTP | Chamar a API de geração de PDF com os dados do registo |
| 4 | Função lógica | Transferir e anexar o PDF gerado |
### Serviços Populares de Geração de PDF
* **Carbone** - Geração de documentos baseada em modelos
* **PDFMonkey** - Criação dinâmica de PDF a partir de modelos
* **DocuSeal** - Plataforma de automatização de documentos
* **Documint** - Geração de documentos orientada a API
Cada serviço fornece uma API que retorna uma URL em PDF, que você pode passar para a função lógica.
## Resolução de Problemas
| Problema | Solução |
| --------------------------- | ---------------------------------------------------------------------------- |
| "Falha ao transferir PDF" | Verifique se o URL do PDF é acessível e devolve um PDF válido |
| "Falha ao carregar" | Verifique se a sua chave de API é válida e tem permissões de escrita |
| "Falha na criação do anexo" | Garanta que o nome do campo de ID do objeto corresponde ao objeto de destino |
## Relacionados
* [Gatilhos de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-triggers)
* [Funções lógicas](/l/pt/user-guide/workflows/capabilities/workflow-actions#code)
* [Gerar um Orçamento ou Fatura a partir do Twenty](/l/pt/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)

View File

@@ -0,0 +1,144 @@
---
title: Gerar um orçamento ou fatura no Twenty
description: Crie faturas automaticamente em ferramentas externas quando os negócios forem fechados.
---
Envie automaticamente os dados do negócio para o seu sistema de faturação (Stripe, QuickBooks, Xero, etc.) quando uma oportunidade é ganha.
## Estrutura do Fluxo de Trabalho
1. **Gatilho**: Registo é atualizado (Oportunidade)
2. **Filtro**: Etapa = Closed Won
3. **Pesquisar Registo**: Obter detalhes da empresa
4. **Código** (opcional): Formatar o payload
5. **Pedido HTTP**: Enviar para o sistema de faturação
## Etapa 1: Configurar o Gatilho
1. Crie um novo fluxo de trabalho
2. Selecione o gatilho **Registo é atualizado**
3. Escolha **Oportunidade** como o objeto
## Etapa 2: Filtrar por Closed Won
Adicione uma ação de **Filtro** para só continuar quando o negócio for ganho:
| Configuração | Valor |
| ------------ | ------------------------------------- |
| **Campo** | Etapa |
| **Condição** | Igual a |
| **Valor** | `CLOSED_WON` (ou o nome da sua etapa) |
<Tip>
O gatilho é acionado em qualquer atualização de Oportunidade. O Filtro garante que o fluxo de trabalho só continue quando a etapa mudar para Closed Won.
</Tip>
## Etapa 3: Obter detalhes da empresa
O registo de Oportunidade pode não incluir todos os campos da Empresa de que precisa para a fatura. Adicione uma ação de **Pesquisar Registo**:
| Configuração | Valor |
| ----------------------- | ----------------------------------------- |
| **Objeto** | Empresa |
| **Correspondência por** | ID igual a `{{trigger.object.companyId}}` |
Isto obtém o registo completo da Empresa com morada de faturação, NIF, etc.
## Etapa 4: Formatar o Payload (Opcional)
Se o seu sistema de faturação esperar um formato específico, adicione uma ação de **Código**:
```javascript
export const main = async (params: {
opportunity: any;
company: any;
}): Promise<object> => {
const { opportunity, company } = params;
return {
invoice: {
// Customer info from Company
customer_name: company.name,
customer_email: company.email || "",
billing_address: {
line1: company.address?.street || "",
city: company.address?.city || "",
postal_code: company.address?.postalCode || "",
country: company.address?.country || ""
},
tax_id: company.taxId || null,
// Invoice details from Opportunity
amount: opportunity.amount,
currency: opportunity.currency || "USD",
description: `Invoice for ${opportunity.name}`,
due_days: 30,
// Reference back to Twenty
metadata: {
opportunity_id: opportunity.id,
company_id: company.id
}
}
};
};
```
## Etapa 5: Enviar para o Sistema de Faturação
Adicione uma ação de **Pedido HTTP**:
| Configuração | Valor |
| -------------- | -------------------------------------------------------------- |
| **Método** | POST |
| **URL** | O endpoint da API do seu sistema de faturação |
| **Cabeçalhos** | `Authorization: Bearer YOUR_API_KEY` |
| **Corpo** | `{{code.invoice}}` ou faça o mapeamento dos campos diretamente |
### Exemplo: Fatura Stripe
```
POST https://api.stripe.com/v1/invoices
Headers:
Authorization: Bearer sk_live_xxx
Content-Type: application/x-www-form-urlencoded
Body:
customer: {{company.stripeCustomerId}}
collection_method: send_invoice
days_until_due: 30
```
### Exemplo: Fatura QuickBooks
```
POST https://quickbooks.api.intuit.com/v3/company/{realmId}/invoice
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Body: {{code.invoice}}
```
## Resumo Completo do Fluxo de Trabalho
| Etapa | Ação | Finalidade |
| ----- | --------------------------- | -------------------------------------------------- |
| 1 | Gatilho: Registo atualizado | Dispara quando qualquer Oportunidade é alterada |
| 2 | Filtro | Só prossiga se Etapa = Closed Won |
| 3 | Pesquisar Registo | Obter detalhes completos da Empresa para faturação |
| 4 | Código | Formatar dados para a API de faturação |
| 5 | Requisição HTTP | Criar fatura no sistema externo |
## Dicas
* **Armazene IDs externos**: Guarde o ID da fatura retornado pela API na Oportunidade usando uma ação de **Atualizar Registo**
* **Tratamento de erros**: Adicione uma ramificação para enviar uma notificação se o pedido HTTP falhar
* **Teste primeiro**: Use o modo sandbox/de teste do seu sistema de faturação antes de ir para produção
## Relacionados
* [Gerar um PDF a partir do Twenty](/l/pt/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — anexar PDFs gerados aos registros
* [Gatilhos de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-triggers)
* [Ações de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-actions)
* [Automações de Closed Won](/l/pt/user-guide/workflows/how-tos/crm-automations/closed-won-automations)

View File

@@ -0,0 +1,170 @@
---
title: Configurar um gatilho de Webhook
description: Receba dados de serviços externos para acionar fluxos de trabalho.
---
Gatilhos de Webhook permitem que serviços externos iniciem seus fluxos de trabalho enviando dados para uma URL exclusiva. Use-os para conectar formulários, aplicativos de terceiros e integrações personalizadas.
## Quando usar Webhooks
| Caso de uso | Exemplo |
| ------------------------------ | ----------------------------------------------- |
| **Formulários da web** | Envios de formulários de contato geram leads |
| **Aplicativos de terceiros** | Pagamento no Stripe → criar registro de cliente |
| **Integrações personalizadas** | Seu aplicativo → automação no Twenty |
| **Ferramentas no-code** | Conexões com Zapier, Make, n8n |
## Configuração passo a passo
### Etapa 1: Criar o fluxo de trabalho
1. Vá para **Configurações → Fluxos de trabalho**
2. Clique em **+ Novo fluxo de trabalho**
3. Dê um nome (por exemplo, "Envio de formulário do site")
### Etapa 2: Configurar o gatilho de Webhook
1. Clique no bloco de gatilho
2. Selecione **Webhook**
3. Você receberá uma URL de webhook exclusiva, como:
```
https://api.twenty.com/webhooks/workflow/abc123...
```
4. Copie esta URL—você vai precisar dela no seu serviço externo
### Etapa 3: Definir a estrutura de dados esperada
Para solicitações **POST**, defina a estrutura esperada do corpo:
1. Clique em **Definir corpo esperado**
2. Insira um JSON de exemplo que corresponda ao que o seu serviço enviará:
```json
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"company": "Acme Inc",
"message": "Interested in your product"
}
```
3. Clique em **Salvar**—isso cria variáveis que você pode usar nas etapas subsequentes
### Etapa 4: Adicionar ações
Agora adicione ações que usem os dados do webhook:
**Exemplo: Criar um registro de Pessoa**
1. Adicione a ação **Criar registro**
2. Selecione o objeto **Pessoas**
3. Mapeie os campos:
| Campo | Valor |
| --------- | --------------------------------------------------------- |
| Nome | `{{trigger.body.firstName}}` |
| Sobrenome | `{{trigger.body.lastName}}` |
| E-mail | `{{trigger.body.email}}` |
| Empresa | Pesquisar ou criar com base em `{{trigger.body.company}}` |
### Etapa 5: Testar o Webhook
Antes de ativar, teste o seu webhook:
**Usando cURL**:
```bash
curl -X POST https://api.twenty.com/webhooks/workflow/abc123... \
-H "Content-Type: application/json" \
-d '{"firstName":"Test","lastName":"User","email":"test@example.com"}'
```
**Usando o Postman ou similar**:
1. Crie uma solicitação POST para a sua URL de webhook
2. Defina o cabeçalho Content-Type como `application/json`
3. Adicione o corpo JSON de teste
4. Envie e verifique as execuções do fluxo de trabalho
### Etapa 6: Ativar
Depois de testar, clique em **Ativar** para ativar o fluxo de trabalho.
## Como lidar com diferentes estruturas de dados
### Dados aninhados
Se o seu webhook enviar dados aninhados:
```json
{
"contact": {
"name": "John Doe",
"email": "john@example.com"
},
"source": "website"
}
```
Referencie com: `{{trigger.body.contact.email}}`
### Arrays
Se os dados incluírem arrays:
```json
{
"items": [
{"name": "Product A", "qty": 2},
{"name": "Product B", "qty": 1}
]
}
```
Como você lida com arrays depende do seu caso de uso:
**Número desconhecido de itens → Use Iterator**
Se você precisar processar cada item no array (por exemplo, criar um registro para cada), adicione uma ação **Code** para analisar o array e, em seguida, use **Iterator**:
```javascript
export const main = async (params: { items: any }) => {
const items = typeof params.items === "string"
? JSON.parse(params.items)
: params.items;
return { items };
};
```
Em seguida, use Iterator para percorrer: `{{code.items}}`
**Campos conhecidos/específicos → Extraia para campos nomeados**
Se o array contiver campos específicos aos quais você deseja acessar individualmente (por exemplo, respostas de formulário em que a posição 0 é sempre "nome", a posição 1 é sempre "sobrenome"), adicione uma ação **Code** para extraí-los:
```javascript
export const main = async (params: { items: any }) => {
const items = typeof params.items === "string"
? JSON.parse(params.items)
: params.items;
return {
product: {
name: items[0]?.name || "",
qty: items[0]?.qty || 0
}
};
};
```
Agora você pode selecionar `product.name` e `product.qty` individualmente nas etapas subsequentes.
<Tip>
Para mais detalhes sobre como lidar com arrays, consulte [Tratar arrays em ações de código](/l/pt/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions).
</Tip>
## Relacionados
* [Gatilhos de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-triggers)
* [Ações de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,121 @@
---
title: Resposta automática a e-mails de entrada
description: Crie um fluxo de trabalho que use IA para fazer a triagem dos e-mails de entrada e enviar respostas encadeadas automaticamente.
---
Responda a e-mails de entrada em segundos — não em horas. Este fluxo de trabalho usa um Agente IA para filtrar ruído (boletins informativos, spam, respostas automáticas) e rascunhar uma resposta personalizada para mensagens reais. Então, AI o envia como uma resposta em tópico dentro da conversa original.
## Como funciona o encadeamento de e-mails
Cada e-mail contém um cabeçalho oculto `Message-ID` — uma impressão digital única atribuída pelo servidor de e-mail do remetente. Quando você responde a um e-mail, seu cliente de e-mail define um cabeçalho `In-Reply-To` que faz referência a essa impressão digital. É assim que o Gmail, o Outlook e todos os outros clientes agrupam as mensagens em conversas.
No Twenty, essa impressão digital é armazenada como `headerMessageId` no objeto Message. Seu fluxo de trabalho obtém isso e o passa para o campo In-Reply-To da ação Enviar e-mail.
## Criando o fluxo de trabalho
### Etapa 1: Criar um novo fluxo de trabalho
Vá para **Configurações -> Fluxos de trabalho** e clique em **+ Novo fluxo de trabalho**.
### Etapa 2: Acionar com mensagens recebidas
Escolha **When a Record is Created** e selecione **Messages**.
Sempre que um e-mail chega ao Twenty, isto é acionado.
<img src="/images/user-guide/workflows/auto-reply/workflow-overview.png" style={{width:'100%'}} />
### Etapa 3: Procurar quem enviou
Adicione uma ação **Pesquisar Registros**.
O endereço do remetente não está na própria mensagem — está no registro relacionado Message Participant.
| Campo | Valor |
| ---------- | ------------------------------ |
| **Objeto** | Participantes da Mensagem |
| **Filtro** | Message **é** `{{trigger.id}}` |
| **Filtro** | Função **é** From |
| **Limite** | 1 |
Isso fornece o e-mail do remetente em `handle` e o nome dele em `displayName`.
<img src="/images/user-guide/workflows/auto-reply/find-sender.png" style={{width:'100%'}} />
### Passo 4: Triage e rascunhar uma resposta com IA
Adicione uma ação **Agente de IA**. Esta única etapa faz duas coisas: decide se o e-mail merece uma resposta e, se sim, redige uma.
Use um prompt como:
```
You are an email triage assistant for a sales team. Read the following
inbound email and decide if it deserves a reply.
Subject: {{trigger.subject}}
Body: {{trigger.text}}
From: {{Find Sender.first.displayName}} ({{Find Sender.first.handle}})
If this email is spam, a newsletter, an automated notification, or
otherwise does not need a human reply, respond with exactly: SKIP
Otherwise, write a short, professional reply (3-4 sentences max) that:
- Acknowledges their specific message
- Lets them know someone from the team will follow up shortly
- Is warm but not overly casual
Respond with only the reply text, no subject line or greeting prefix.
```
O Agente de IA produz sua resposta em um campo `response` ao qual as próximas etapas podem se referir.
<img src="/images/user-guide/workflows/auto-reply/ai-triage.png" style={{width:'100%'}} />
### Etapa 5: Ramificar com base na decisão da IA
Adicione uma ação **If/Else** para verificar se a IA decidiu responder ou pular.
| Campo | Valor |
| ------------------ | -------------------------------------------------- |
| **Condição** | A `response` do Agente de IA **não contém** `SKIP` |
| **Se verdadeiro** | Continuar para Enviar e-mail |
| **Caso contrário** | Não fazer nada (o fluxo de trabalho termina) |
Spam, boletins informativos e mensagens geradas automaticamente são descartados. Todo o restante segue para a próxima etapa.
<img src="/images/user-guide/workflows/auto-reply/should-reply.png" style={{width:'100%'}} />
### Etapa 6: Enviar uma resposta encadeada
Adicione uma ação **Enviar e-mail** no ramo "se verdadeiro". Clique em **Opções avançadas** e depois em **Adicionar In-Reply-To**.
| Campo | Valor |
| --------------- | -------------------------------------- |
| **Para** | `{{Find Sender.first.handle}}` |
| **Assunto** | `Re: {{trigger.subject}}` |
| **Corpo** | `{{AI Triage & Draft Reply.response}}` |
| **In-Reply-To** | `{{trigger.headerMessageId}}` |
O campo In-Reply-To é o que faz disso uma resposta em vez de uma nova conversa. O destinatário a vê encadeada sob o e-mail original no Gmail, Outlook ou qualquer outro cliente.
<img src="/images/user-guide/workflows/auto-reply/send-email.png" style={{width:'100%'}} />
<Tip>
**In-Reply-To** espera um `message.headerMessageId` do acionador — é a impressão digital única do e-mail, não um endereço do destinatário. Se você deixá-lo vazio, o e-mail ainda será enviado, apenas como uma mensagem independente.
</Tip>
<Warning>
O Gmail usa a linha de assunto para agrupar mensagens em conversas. O assunto **deve** começar com `Re:` (incluindo os dois-pontos e o espaço) para que o Gmail exiba a resposta dentro da conversa original. Sem isso, a resposta aparecerá como uma conversa separada — mesmo que o cabeçalho In-Reply-To esteja definido corretamente.
</Warning>
### Etapa 7: Testar e ativar
Clique em **Test** e, em seguida, verifique seu cliente de e-mail. A resposta deve aparecer aninhada sob a mensagem original.
Ative quando estiver satisfeito com isso.
## Ideias para desenvolver
* **Responder apenas a VIPs** — adicione um ramo que verifique o domínio do remetente ou se ele existe como um Contato no Twenty
* **Roteie por intenção** — use prompts separados do Agente de IA para lidar com consultas de vendas de forma diferente de solicitações de suporte
* **Enriquecer antes de responder** — adicione uma etapa Pesquisar Registros para trazer a empresa do remetente ou o histórico de negócios para o prompt de IA, obtendo respostas mais personalizadas

View File

@@ -0,0 +1,179 @@
---
title: Automatizações para Closed Won
description: Automatize as atividades pós-ganho quando as oportunidades forem encerradas.
---
Quando um negócio é encerrado, várias coisas precisam acontecer: atualizar o status da empresa, notificar os membros da equipe, criar tarefas de onboarding. Automatize tudo isso com um único fluxo de trabalho.
## O Problema
Quando uma oportunidade passa para "Closed Won":
* O tipo de empresa precisa mudar de "Prospect" para "Customer"
* Tarefas de onboarding precisam ser criadas
* A equipe de Customer Success precisa ser notificada
* O representante de vendas precisa de confirmação
Fazer isso manualmente consome tempo e é suscetível a erros.
## A Solução
Crie um fluxo de trabalho que trate automaticamente todas as atividades pós-ganho.
## Configuração Completa do Fluxo de Trabalho
### Etapa 1: Criar o fluxo de trabalho
1. Vá para **Configurações → Fluxos de trabalho**
2. Clique em **+ Novo fluxo de trabalho**
3. Nomeie como "Deal Won - Post-Win Automation"
### Etapa 2: Configurar o gatilho
1. Selecione **Record is Updated**
2. Escolha **Oportunidades**
3. Em "Campos para monitorar", selecione **Estágio**
### Etapa 3: Adicionar filtro de estágio
1. Adicione a ação **Filtro**
2. Condição: `{{trigger.object.stage}}` é igual a "Closed Won"
### Etapa 4: Atualizar o tipo de empresa
1. Adicione a ação **Atualizar registro**
2. Configurar:
| Campo | Valor |
| ---------------------------- | ------------------------------- |
| **Objeto** | Empresas |
| **Registro** | `{{trigger.object.company.id}}` |
| **Tipo** | Cliente |
| **Data do primeiro negócio** | `{{trigger.object.closedAt}}` |
| **Proprietário da Conta** | `{{trigger.object.owner.id}}` |
### Etapa 5: Criar tarefa de onboarding
1. Adicione a ação **Criar registro**
2. Configurar:
| Campo | Valor |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------- |
| **Objeto** | Tarefas |
| **Título** | `Onboarding: {{trigger.object.name}}` |
| **Responsável** | Membro da equipe de Customer Success |
| **Data de vencimento** | Daqui a 3 dias |
| **Prioridade** | Alta |
| **Empresa relacionada** | `{{trigger.object.company.id}}` |
| **Oportunidade relacionada** | `{{trigger.object.id}}` |
| **Descrição** | `Onboarding de novo cliente para {{trigger.object.company.name}}. Valor do negócio: {{trigger.object.amount}}` |
### Etapa 6: Notificar Customer Success
1. Adicione a ação **Enviar e-mail**
2. Configurar:
| Campo | Valor |
| ----------- | -------------------------------------------------- |
| **Para** | customer-success@yourcompany.com |
| **Assunto** | `🎉 New Customer: {{trigger.object.company.name}}` |
| **Corpo** | Veja o exemplo abaixo |
**Exemplo de corpo do e-mail**:
```
Hi CS Team,
We have a new customer!
Company: {{trigger.object.company.name}}
Deal: {{trigger.object.name}}
Value: {{trigger.object.amount}}
Sales Rep: {{trigger.object.owner.name}}
Close Date: {{trigger.object.closedAt}}
An onboarding task has been created automatically.
Let's give them a great start!
```
### Etapa 7: Confirmar para o representante de vendas
1. Adicione outra ação **Enviar e-mail**
2. Configurar:
| Campo | Valor |
| ----------- | -------------------------------------------------------------------------------------------------------------- |
| **Para** | `{{trigger.object.owner.email}}` |
| **Assunto** | `✅ Deal Closed: {{trigger.object.name}}` |
| **Corpo** | Parabéns! Seu negócio foi processado. A equipe de Customer Success foi notificada e o onboarding foi iniciado. |
### Etapa 8: Testar e ativar
1. Teste movendo uma oportunidade de teste para "Closed Won"
2. Verificar:
* Tipo de empresa alterado para "Customer"
* Tarefa de onboarding criada
* A equipe de CS recebeu o e-mail
* O representante de vendas recebeu a confirmação
3. Ative quando estiver pronto
## Tratamento de Closed Lost
Crie um fluxo de trabalho semelhante para negócios perdidos:
### Disparador
* Registro é atualizado (Oportunidades, Estágio = "Closed Lost")
### Ações
1. **Criar registro**: Tarefa para "Análise de negócio perdido"
2. **Atualizar registro**: Adicionar o motivo da perda ao registro da empresa
3. **Enviar e-mail**: Notificar o gerente sobre o negócio perdido
## Avançado: Onboarding em várias etapas
Para um onboarding complexo, crie várias tarefas:
```javascript
export const main = async (params) => {
const tasks = [
{ title: "Welcome call", daysFromNow: 1, assignee: "CS" },
{ title: "Send onboarding materials", daysFromNow: 2, assignee: "CS" },
{ title: "Technical setup", daysFromNow: 5, assignee: "Support" },
{ title: "30-day check-in", daysFromNow: 30, assignee: "CS" }
];
return { tasks };
};
```
Use **Iterator** para criar cada tarefa a partir do array.
## Ideias de personalização
### Mantenha suas outras ferramentas atualizadas
* Crie o cliente no sistema de faturamento com uma **Requisição HTTP**
### Ações condicionais
Use ações **Filtro** para:
* Onboarding diferente para enterprise vs. SMB
* Responsáveis diferentes com base na região
* Ignorar notificações para negócios pequenos
### Incluir detalhes da oferta
Use a ação **Código** para formatar:
* Documentos de resumo do negócio
* Notas de repasse para a equipe de CS
* Checklists de onboarding personalizadas
## Relacionados
* [Ações de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-actions)
* [Enviar e-mails a partir de Fluxos de Trabalho](/l/pt/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,136 @@
---
title: Detectar oportunidades estagnadas
description: Notifique automaticamente os gestores quando as oportunidades não tiverem sido atualizadas.
---
Mantenha o seu pipeline saudável alertando os gestores quando as oportunidades ficarem estagnadas. Este fluxo de trabalho verifica oportunidades que não foram atualizadas há um número específico de dias.
## O Problema
Oportunidades sem atualizações levam a:
* Negócios a arrefecer
* Previsões pouco fiáveis
* Perda de receita
## A Solução
Crie um fluxo de trabalho agendado que encontre oportunidades estagnadas e envie e-mails aos respetivos gestores.
## Configuração passo a passo
### Etapa 1: Criar o fluxo de trabalho
1. Vá para **Configurações → Fluxos de trabalho**
2. Clique em **+ Novo fluxo de trabalho**
3. Dê o nome "Alerta de Oportunidade Estagnada"
### Etapa 2: Configurar o gatilho
1. Selecione **Agendado**
2. Defina para executar diariamente (por exemplo, todos os dias às 08:00)
### Etapa 3: Pesquisar oportunidades estagnadas
1. Adicione a ação **Pesquisar Registos**
2. Configurar:
| Campo | Valor |
| ---------- | --------------------------------------------- |
| **Objeto** | Oportunidades |
| **Filtro** | Atualizado em é anterior a (hoje - 7 dias) |
| **Filtro** | Fase não é "Closed Won" E não é "Closed Lost" |
| **Limite** | 100 |
### Etapa 4: Verificar se algum foi encontrado
1. Adicione a ação **Filtro**
2. Condição: `{{searchRecords.length}}` é maior do que 0
3. Se não houver oportunidades estagnadas, o fluxo de trabalho termina aqui
### Etapa 5: Formatar o alerta (Ação de Código)
Adicione uma ação de **Código** para formatar o e-mail:
```javascript
export const main = async (params) => {
const opportunities = params.opportunities;
// Group opportunities by owner
const byOwner = {};
opportunities.forEach(opp => {
const ownerEmail = opp.owner?.email || 'unassigned';
if (!byOwner[ownerEmail]) {
byOwner[ownerEmail] = [];
}
byOwner[ownerEmail].push({
name: opp.name,
amount: opp.amount,
lastUpdated: opp.updatedAt,
stage: opp.stage
});
});
// Format summary for manager
let summary = "Stale Opportunities Report\n\n";
Object.entries(byOwner).forEach(([owner, opps]) => {
summary += `${owner}: ${opps.length} stale opportunities\n`;
opps.forEach(opp => {
summary += ` - ${opp.name} (${opp.stage})\n`;
});
summary += "\n";
});
return {
summary,
totalCount: opportunities.length
};
};
```
### Etapa 6: Enviar e-mail de alerta
Adicione a ação **Enviar e-mail**:
| Campo | Valor |
| ----------- | --------------------------------------------------------------------- |
| **Para** | sales-manager@yourcompany.com |
| **Assunto** | `🚨 {{code.totalCount}} oportunidades estagnadas precisam de atenção` |
| **Corpo** | `{{code.summary}}` |
### Etapa 7: Testar e ativar
1. Clique em **Testar** para executar o fluxo de trabalho
2. Verifique se o e-mail contém os dados corretos
3. Ative quando estiver pronto
## Opções de personalização
### Alterar o limite de estagnação
Modifique o filtro de Pesquisar Registos para alterar de 7 dias para o período da sua preferência:
* 3 dias para vendas de alto ritmo
* 14 dias para negócios empresariais
* 30 dias para ciclos de vendas longos
### Alertar representantes individualmente
Em vez de um e-mail único para o gestor, use **Iterator** para enviar e-mails personalizados a cada representante sobre os seus próprios negócios estagnados.
### Adicionar escalonamento
Crie vários fluxos de trabalho com gravidade crescente:
1. Dia 7: E-mail para o representante
2. Dia 14: E-mail para o representante + gestor
3. Dia 21: Criar tarefa para o gestor intervir
### Incluir no Slack
Use **HTTP Request** para enviar para um webhook do Slack em vez de ou além do e-mail.
## Relacionados
* [Ações de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-actions)
* [Enviar e-mails a partir de Fluxos de Trabalho](/l/pt/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,69 @@
---
title: Exibir o número de e-mails recebidos
description: Crie um fluxo de trabalho para contar e exibir automaticamente o número de e-mails recebidos de cada contato.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/workflow-number-of-emails-received.png" style={{width:'100%'}} />
## Visão Geral
Esse fluxo de trabalho é acionado sempre que um novo e-mail é recebido e atualiza um campo personalizado no registro de Pessoa com a contagem total de e-mails desse remetente.
## Pré-requisitos
Antes de configurar esse fluxo de trabalho, crie um campo personalizado no objeto **Pessoas**:
1. Vá para **Configurações → Modelo de Dados → Pessoas**
2. Adicione um novo campo **Número**
3. Dê a ele um nome como "Número de e-mails recebidos desta pessoa"
## Configuração passo a passo
<VimeoEmbed videoId="1148700336" title="Demonstração em vídeo" />
### Etapa 1: Configurar o gatilho
1. Vá para **Fluxos de Trabalho** e crie um novo fluxo de trabalho
2. Selecione **Registro é criado** como o gatilho
3. Escolha **Participantes da Mensagem** (disponível em Objetos avançados)
<Note>
Um Participante da Mensagem é a combinação de um ID de mensagem e um ID de pessoa, criando um registro exclusivo por mensagem. Isso é mais fácil de acompanhar do que Mensagens diretamente porque podemos acessar o campo `handle`, que contém o endereço de e-mail do remetente (ou do destinatário).
</Note>
### Etapa 2: Filtrar por função
1. Adicione a ação **Filtro**
2. Defina a condição: **Role** igual a **FROM**
Isso garante que você conte apenas as mensagens enviadas por essa pessoa, e não as mensagens enviadas para ela.
### Etapa 3: Pesquisar todos os Participantes da Mensagem com o mesmo handle
1. Adicione a ação **Pesquisar Registros**
2. Selecione **Participantes da Mensagem** como o objeto
3. Adicione filtros: **Handle** igual ao handle do gatilho (o endereço de e-mail do remetente) e **Role** igual a **FROM**
4. Aumente o **Limite** de 1 para **200** (o máximo)
Isso encontra todas as mensagens desse endereço de e-mail para obter a contagem total.
<Note>
A ação Pesquisar Registros é limitada a retornar no máximo 200 registros. No entanto, como você está usando apenas o valor `totalCount` (não os registros individuais), esta etapa retornará o número total de e-mails enviados por essa pessoa.
</Note>
### Etapa 4: Atualizar o registro de Pessoa com uma ação Criar ou Atualizar Registro
1. Adicione a ação **Criar ou Atualizar Registro**
2. Selecione **Pessoas** como o objeto
3. Encontre a pessoa cujo e-mail corresponde ao `handle` do Participante da Mensagem
4. Defina seu campo personalizado "Número de e-mails recebidos" como `{{searchRecords.totalCount}}`
O valor `totalCount` da ação Pesquisar Registros representa o número total de e-mails recebidos dessa pessoa.
## Relacionados
* [Ações de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-actions)
* [Criar campos personalizados](/l/pt/user-guide/data-model/how-tos/customize-your-data-model)
* [Ação Pesquisar Registros](/l/pt/user-guide/workflows/capabilities/workflow-actions#search-records)

View File

@@ -0,0 +1,170 @@
---
title: Apresentar dados de registos relacionados
description: Mostrar dados de registos relacionados (por exemplo, informações da Empresa em Oportunidades) usando fluxos de trabalho.
---
Apresente dados de registos relacionados diretamente nos seus registos — por exemplo, mostre o número de funcionários de uma Empresa nas suas Oportunidades. Esta solução alternativa com fluxos de trabalho é útil até que os campos aninhados estejam disponíveis nativamente.
## Casos de Uso Comuns
| Fonte | Destino | Campos a Copiar |
| ------------ | ------------ | ------------------------------------------------------- |
| Empresa | Oportunidade | Setor, Tamanho da Empresa, ARR |
| Pessoa | Oportunidade | Email, Telefone, Cargo |
| Oportunidade | Empresa | Valor do Último Negócio, Data do Último Negócio Ganhado |
## Cópia Básica de Campo
### Exemplo: Copiar Email do Contacto para a Oportunidade
**Objetivo**: Ao definir um Ponto de Contacto numa oportunidade, copiar o email dessa pessoa para a oportunidade para acesso facilitado.
### Pré-requisito
Crie os campos de destino em **Definições → Modelo de Dados → Oportunidades** antes de construir o fluxo de trabalho:
* Email do Contacto (tipo: Email)
* Telefone do Contacto (tipo: Telefone)
### Configuração
1. **Gatilho**: Registo é atualizado (Oportunidades, campo Ponto de Contacto)
2. **Filtro**: Verificar que o Ponto de Contacto não está vazio
3. **Pesquisar Registos**: Encontrar a pessoa associada
* Objeto: Pessoas
* Filtro: ID igual a `{{trigger.object.pointOfContact.id}}`
4. **Atualizar Registo**:
* Objeto: Oportunidades
* Registo: `{{trigger.object.id}}`
* Email do Contacto: `{{searchRecords[0].email}}`
* Telefone do Contacto: `{{searchRecords[0].phone}}`
## Copiar Vários Campos
### Exemplo: Sincronizar Informações da Empresa com Todas as Oportunidades Relacionadas
**Objetivo**: Quando os detalhes da empresa mudarem, atualizar todas as oportunidades relacionadas.
### Configuração
1. **Gatilho**: Registo é atualizado (Empresas)
* Campos: Setor, Tamanho da Empresa, Receita Anual
2. **Pesquisar Registos**: Encontrar todas as oportunidades desta empresa
* Objeto: Oportunidades
* Filtro: ID da Empresa igual a `{{trigger.object.id}}`
3. **Iterador**: Percorrer cada oportunidade
4. **Atualizar Registo** (dentro do iterador):
* Objeto: Oportunidades
* Registo: `{{iterator.currentItem.id}}`
* Setor da Empresa: `{{trigger.object.industry}}`
* Tamanho da Empresa: `{{trigger.object.companySize}}`
* ARR da Empresa: `{{trigger.object.annualRevenue}}`
## Copiar na Criação do Registo
### Exemplo: Preencher a Oportunidade com Dados da Empresa
**Objetivo**: Ao criar uma oportunidade vinculada a uma empresa, copiar automaticamente as informações principais da empresa.
### Pré-requisito
Crie os campos de destino em **Definições → Modelo de Dados → Oportunidades**:
* Setor da Empresa (tipo: Texto)
* Tamanho da Empresa (tipo: Número)
### Configuração
1. **Gatilho**: Registo é criado (Oportunidades)
* Filtro: Empresa não está vazia
2. **Pesquisar Registos**: Obter os detalhes da empresa associada
* Objeto: Empresas
* Filtro: ID igual a `{{trigger.object.company.id}}`
3. **Atualizar Registo**:
* Objeto: Oportunidades
* Registo: `{{trigger.object.id}}`
* Setor da Empresa: `{{searchRecords[0].industry}}`
* Tamanho da Empresa: `{{searchRecords[0].employees}}`
<Note>
**Limitação de Tarefas e Notas**: As relações em Tarefas e Notas são pré-definidas como muitos-para-muitos e ainda não estão disponíveis em gatilhos ou ações de fluxos de trabalho. Para aceder a estas relações, use antes a [API](/l/pt/developers/extend/api).
</Note>
## Sincronização Bidirecional
### Exemplo: Manter o Contacto Principal Sincronizado
**Objetivo**: Quando o contacto principal de uma empresa mudar, atualizar o contacto. Quando uma pessoa se tornar principal, atualizar a empresa.
### Fluxo de Trabalho 1: Empresa → Pessoa
1. **Gatilho**: Registo é atualizado (Empresas, campo Contacto Principal)
2. **Atualizar Registo**: Definir "É Contacto Principal" da pessoa como verdadeiro
3. **Pesquisar Registos**: Encontrar o contacto principal anterior
4. **Atualizar Registo**: Definir "É Contacto Principal" do contacto anterior como falso
### Fluxo de Trabalho 2: Pessoa → Empresa
1. **Gatilho**: Registo é atualizado (Pessoas, É Contacto Principal = verdadeiro)
2. **Atualizar Registo**: Definir o Contacto Principal da empresa para esta pessoa
<Note>
Tenha cuidado com sincronizações bidirecionais para evitar ciclos infinitos. Use filtros para verificar se o valor realmente mudou antes de atualizar.
</Note>
## Usar Código para Mapeamento Complexo
### Exemplo: Transformar Dados Durante a Cópia
**Objetivo**: Copiar e formatar o número de telefone da pessoa para a oportunidade.
```javascript
export const main = async (params) => {
const { phone } = params;
if (!phone) return { formattedPhone: null };
// Remove non-numeric characters
const digits = phone.replace(/\D/g, '');
// Format as (XXX) XXX-XXXX
const formatted = digits.length === 10
? `(${digits.slice(0,3)}) ${digits.slice(3,6)}-${digits.slice(6)}`
: phone;
return { formattedPhone: formatted };
};
```
## Melhores Práticas
### Evitar Ciclos
* Não crie fluxos de trabalho que disparem uns aos outros indefinidamente
* Use condições específicas de campo
* Adicione verificações para ver se o valor realmente mudou
### Tratar Dados em Falta
* Verifique sempre se o registo de origem existe antes de copiar
* Forneça valores predefinidos para campos opcionais
* Use filtros para ignorar quando o campo de origem estiver vazio
### Desempenho
* Agrupe atualizações ao copiar para muitos registos
* Use fluxos de trabalho agendados para operações de sincronização em massa
* Considere usar o Iterador para atualizações de múltiplos registos
## Relacionados
* [Ações de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-actions)
* [Gatilhos de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-triggers)

View File

@@ -0,0 +1,202 @@
---
title: Campos de Fórmula
description: Crie campos de fórmula usando fluxos de trabalho até que o suporte nativo esteja disponível.
---
A Twenty ainda não oferece suporte nativo a campos de fórmula (chegando em 2026), mas você pode obter o mesmo resultado usando fluxos de trabalho. Esta solução alternativa permite calcular e preencher automaticamente os valores dos campos — de concatenações simples a lógica de negócios complexa.
## Casos de Uso Comuns
| Caso de uso | Exemplo de fórmula |
| ------------------------- | ----------------------------------- |
| **Nome completo** | Nome + " " + Sobrenome |
| **Valor esperado** | Valor × Probabilidade |
| **Dias até o vencimento** | Data de vencimento - hoje |
| **Dias na etapa** | Hoje - Data de entrada na etapa |
| **Pontuação de lead** | Pontos com base em vários critérios |
<Tip>
Para um exemplo completo de rastreamento do tempo nas etapas do pipeline, consulte [Acompanhar por quanto tempo as oportunidades permanecem em cada etapa](/l/pt/user-guide/views-pipelines/how-tos/track-time-in-stage).
</Tip>
## Fórmula básica: Concatenação
### Exemplo: Preencher automaticamente o nome completo
**Objetivo**: Combinar automaticamente nome e sobrenome em um campo de nome completo.
### Configuração
1. **Gatilho**: Registro é atualizado ou criado (Pessoas)
2. **Filtro**: Verifique se o nome ou o sobrenome foi alterado
3. **Ação de código**:
```javascript
export const main = async (params) => {
const { firstName, lastName } = params;
const fullName = [firstName, lastName]
.filter(Boolean)
.join(' ');
return { fullName };
};
```
4. **Atualizar registro**: Definir Nome completo como `{{code.fullName}}`
## Fórmula numérica: Valor esperado
### Exemplo: Calcular a receita esperada
**Objetivo**: Multiplicar o valor da oportunidade pela probabilidade para obter o valor esperado.
Consulte [Como mostrar o valor esperado no pipeline](/l/pt/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline) para o fluxo de trabalho completo.
### Configuração
1. **Gatilho**: Registro é atualizado (Oportunidades, campo Valor OU Probabilidade)
2. **Ação de código**:
```javascript
export const main = async (params) => {
const { amount, probability } = params;
const expectedAmount = (amount || 0) * (probability || 0) / 100;
return { expectedAmount };
};
```
3. **Atualizar registro**: Definir Valor esperado como `{{code.expectedAmount}}`
## Fórmula de data: Cálculo de dias
### Exemplo: Dias até o vencimento da tarefa
**Objetivo**: Calcular quantos dias faltam até a data de vencimento de uma tarefa.
### Configuração
1. **Gatilho**: Registro é atualizado ou criado (Tarefas, campo Data de vencimento)
2. **Ação de código**:
```javascript
export const main = async (params) => {
const { dueDate } = params;
if (!dueDate) {
return { daysUntilDue: null };
}
const due = new Date(dueDate);
const today = new Date();
const diffTime = due - today;
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return { daysUntilDue: diffDays };
};
```
3. **Atualizar registro**: Definir Dias até o vencimento como `{{code.daysUntilDue}}`
<Note>
Valores negativos indicam tarefas em atraso. Você pode usar este campo para filtrar ou classificar tarefas por urgência.
</Note>
## Fórmula condicional: Pontuação de lead
### Exemplo: Calcular a pontuação de lead com base em critérios
**Objetivo**: Pontuar leads com base no porte da empresa, setor e engajamento.
### Configuração
1. **Gatilho**: Registro é atualizado (Pessoas ou Empresas)
2. **Ação de código**:
```javascript
export const main = async (params) => {
const { companySize, industry, hasEmail, hasPhone, source } = params;
let score = 0;
// Company size scoring
if (companySize === 'Enterprise') score += 30;
else if (companySize === 'Mid-Market') score += 20;
else if (companySize === 'SMB') score += 10;
// Industry scoring
const targetIndustries = ['Technology', 'Finance', 'Healthcare'];
if (targetIndustries.includes(industry)) score += 25;
// Contact info scoring
if (hasEmail) score += 10;
if (hasPhone) score += 15;
// Source scoring
if (source === 'Referral') score += 20;
else if (source === 'Website') score += 10;
return { leadScore: score };
};
```
3. **Atualizar registro**: Definir Pontuação de lead como `{{code.leadScore}}`
## Fórmula de texto: Extração de domínio
### Exemplo: Extrair domínio do e-mail
**Objetivo**: Extrair e armazenar automaticamente o domínio do e-mail.
### Configuração
1. **Gatilho**: Registro é atualizado (Pessoas, campo E-mail)
2. **Ação de código**:
```javascript
export const main = async (params) => {
const { email } = params;
if (!email) return { domain: null };
const domain = email.split('@')[1]?.toLowerCase();
return { domain };
};
```
3. **Atualizar registro**: Definir o campo Domínio como `{{code.domain}}`
## Melhores Práticas
### Desempenho
* Acione apenas em alterações de campos relevantes
* Use filtros para ignorar registros que não precisam de cálculo
* Evite cálculos complexos em fluxos de trabalho de alto volume
### Tratamento de Erros
* Verifique valores null/undefined antes dos cálculos
* Use valores padrão quando faltarem dados
* Retorne mensagens de erro claras quando os cálculos falharem
### Testes
* Teste com casos extremos (campos vazios, valores zero)
* Verifique os cálculos manualmente antes de ativar
* Monitore as execuções de fluxos de trabalho para resultados inesperados
## Relacionados
* [Como mostrar o valor esperado no pipeline](/l/pt/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline)
* [Como acompanhar o tempo na etapa](/l/pt/user-guide/views-pipelines/how-tos/track-time-in-stage)
* [Ações de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,127 @@
---
title: Notificar colegas de equipe sobre uma nota para revisão
description: Envie notificações automáticas por e-mail ao atribuir um colega para revisar uma nota.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Note>
**Marcar colegas diretamente nas notas ainda não é suportado.** Esse recurso está planejado para o segundo semestre de 2026. Enquanto isso, este fluxo de trabalho oferece uma solução alternativa prática.
</Note>
## O desafio
Você quer marcar um colega em uma nota para que receba uma notificação para revisá-la. Hoje, a Twenty não oferece suporte a @mentions em notas ou tarefas.
## A solução alternativa
Em vez de marcar no texto da nota, você pode:
1. Crie um campo de relação **Revisor** no objeto Notas
2. Atribua o revisor ao criar ou editar a nota
3. Ter um fluxo de trabalho automaticamente enviar um e-mail ao revisor com um link para a nota
<Warning>
**Limitação**: Esta abordagem permite atribuir um revisor por campo. Se você precisar notificar várias pessoas, crie campos de relação adicionais (por exemplo, Revisor 1, Revisor 2). Isso não escala bem se você precisar marcar muitos colaboradores de uma só vez.
</Warning>
## Configuração passo a passo
### Etapa 1: Criar o campo Revisor
<img src="/images/user-guide/workflows/reviewer-field-for-note.png" style={{width:'100%'}} />
1. Vá para **Configurações → Modelo de Dados → Notas**
2. Clique em **Adicionar campo**
3. Configurar:
| Configuração | Valor |
| ---------------------- | ---------------------------------------------- |
| **Nome** | Revisor |
| **Tipo** | Relação |
| **Objeto Relacionado** | Membro do Workspace |
| **Tipo de Relação** | Muitas Notas → Um Membro do Espaço de Trabalho |
4. Salve o campo
### Etapa 2: Criar o fluxo de trabalho
<VimeoEmbed videoId="1151897168" title="Demonstração em vídeo" />
#### Criar o gatilho do fluxo de trabalho
1. Vá para a visualização principal e crie um novo fluxo de trabalho
2. Selecione **Registro atualizado** como o gatilho
3. Escolha **Nota** como o objeto e especifique o campo Revisor.
4. Isso será acionado sempre que o revisor de uma nota for atualizado.
#### Adicione um filtro para verificar se o revisor acabou de ser atribuído
Adicione uma ação de **Filtro** para garantir que o fluxo de trabalho só continue quando um revisor for atribuído:
| Condição | Valor |
| ----------- | -------------- |
| **Revisor** | não está vazio |
#### Pesquisar o Membro do Espaço de Trabalho
1. Adicione uma ação **Pesquisar Registros**
2. Selecione **Membro do Espaço de Trabalho** (em Objetos Avançados)
3. Adicione um filtro:
| Condição | Valor |
| -------- | ------------------------------- |
| **ID** | é igual a `{{note.reviewerId}}` |
Isso recupera o registro completo do membro do espaço de trabalho, incluindo seu endereço de e-mail e nome.
#### Enviar o e-mail de notificação
1. Adicione uma ação **Enviar e-mail**
2. Configurar:
| Campo | Valor |
| ----------- | -------------------------------------------------------------------------------- |
| **Para** | Registro de pesquisa → Primeiro Membro do Espaço de Trabalho → E-mail do usuário |
| **Assunto** | Uma nota precisa da sua revisão |
| **Corpo** | Veja o exemplo abaixo |
**Exemplo de corpo do e-mail:**
```text
Hi {{searchRecord.firstWorkspaceMember.name.firstName}},
You've been assigned to review a note.
View the note here:
https://yourSubDomain.twenty.com/object/note/{{recordIsUpdated.id}}
Best,
Twenty
```
<Note>
Substitua `yourSubDomain` pelo subdomínio real do seu espaço de trabalho Twenty.
</Note>
#### Testar e ativar
1. Clique em **Testar** para executar o fluxo de trabalho manualmente
2. Crie ou edite uma nota e atribua um revisor
3. Verifique a caixa de entrada do revisor para o e-mail de notificação
4. Ative o fluxo de trabalho
## Uso
Depois de configurado, sua equipe pode usar este fluxo de trabalho:
1. Criando uma nova nota (ou editando uma existente)
2. Selecionando um colega no campo **Revisor**
3. O colega recebe automaticamente um e-mail com um link direto para a nota
## Relacionados
* [Gatilhos de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-triggers)
* [Enviar e-mails a partir de Fluxos de Trabalho](/l/pt/user-guide/workflows/capabilities/send-emails-from-workflows)
* [Criar campos personalizados](/l/pt/user-guide/data-model/how-tos/create-custom-fields)
* [Criar campos de relação](/l/pt/user-guide/data-model/how-tos/create-relation-fields)

View File

@@ -0,0 +1,106 @@
---
title: Enviar alertas por e-mail sobre tarefas com vencimento
description: Notifique automaticamente os membros da equipe sobre suas tarefas próximas ou em atraso.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/email_tasks_due.png" style={{width:'100%'}} />
Envie lembretes diários por e-mail para cada membro da equipe sobre suas tarefas com vencimento hoje.
## Visão Geral
Este fluxo de trabalho é executado conforme um cronograma e:
1. Busca todos os membros do espaço de trabalho
2. Percorre cada membro
3. Localiza suas tarefas com vencimento hoje
4. Formata e envia um e-mail personalizado
## Configuração passo a passo
<VimeoEmbed videoId="1147463514" title="Demonstração em vídeo" />
### Etapa 1: Configurar o gatilho
1. Vá até **Configurações → Fluxos de Trabalho** e crie um novo fluxo de trabalho
2. Selecione **Agendado** como o gatilho
3. Use uma expressão cron para executar diariamente às 8:00: `0 8 * * *`
### Etapa 2: Pesquisar todos os membros do espaço de trabalho
1. Adicione a ação **Pesquisar Registros**
2. Selecione **Membros do Espaço de Trabalho** (em objetos avançados)
3. Não são necessários filtros — isso retorna todos os membros
### Etapa 3: Adicionar um Iterador
1. Adicione a ação **Iterador**
2. Defina o array de entrada como os membros do espaço de trabalho da etapa anterior
3. Todas as ações dentro do iterador serão executadas uma vez por membro
### Etapa 4: Pesquisar tarefas com vencimento hoje (dentro do iterador)
1. Dentro do iterador, adicione a ação **Pesquisar Registros**
2. Selecione **Tarefas** como o objeto
3. Adicione filtros:
* **Responsável** = membro atual do espaço de trabalho (do iterador)
* **Data de vencimento** = hoje
### Etapa 5: Formatar as tarefas no corpo do e-mail (dentro do iterador)
Adicione uma ação **Código** para formatar as tarefas em uma lista legível com links:
```javascript
export const main = async (params: {
tasksDue?: Array<{ id: string; title: string }> | null | string;
}) => {
const tasksDue =
typeof params.tasksDue === "string"
? JSON.parse(params.tasksDue)
: params.tasksDue;
if (!Array.isArray(tasksDue) || tasksDue.length === 0) {
return {
formattedTasks: "No tasks due today."
};
}
const formattedTasks = tasksDue
.map(
t =>
`${t.title}\nhttps://yourSubDomain.twenty.com/object/task/${t.id}`
)
.join("\n\n");
return { formattedTasks };
};
```
<Note>
Substitua `yourSubDomain` pelo subdomínio real do seu espaço de trabalho Twenty.
</Note>
### Etapa 6: Enviar E-mail (dentro do iterador)
1. Adicione a ação **Enviar E-mail** (ainda dentro do iterador)
2. Configurar:
| Campo | Valor |
| ----------- | ----------------------------------------------------------------------------- |
| **Para** | `{{iterator.currentItem.userEmail}}` (e-mail do membro do espaço de trabalho) |
| **Assunto** | Suas tarefas com vencimento hoje |
| **Corpo** | `{{code.formattedTasks}}` |
### Etapa 7: Testar e ativar
1. Clique em **Testar** para executar o fluxo de trabalho manualmente
2. Verifique as caixas de entrada para os e-mails
3. Ative o fluxo de trabalho
## Relacionados
* [Ações de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-actions)
* [Enviar e-mails a partir de Fluxos de Trabalho](/l/pt/user-guide/workflows/capabilities/send-emails-from-workflows)
* [Lidar com arrays em ações de código](/l/pt/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,21 @@
---
title: Serviços Profissionais
description: Obtenha ajuda profissional para construir fluxos de trabalho complexos e automações com a equipe do Twenty e parceiros certificados.
---
## Quando Você Precisa de Ajuda Profissional?
Considere serviços profissionais para:
* Integrações complexas de múltiplos sistemas
* Lógica de negócios avançada e regras de automação
* Fluxos de trabalho de processamento de dados em larga escala
* Desenvolvimento de API personalizado
* Treinamento de equipe e otimização de fluxo de trabalho
* Quando você não tem recursos internos
## Opções de Serviço
### Parceiros de Implementação
Trabalhe com parceiros certificados para personalizações avançadas. Contacte-nos em [contact@twenty.com](mailto:contact@twenty.com) para entrar em contacto com os nossos [parceiros de implementação](https://twenty.com/partners).

View File

@@ -0,0 +1,170 @@
---
title: Solução de Problemas do Workflow
description: Problemas comuns de fluxos de trabalho e como resolvê-los.
---
## Problemas Comuns e Soluções
### Workflow Não Dispara
**Sintomas**: Seu fluxo de trabalho não é executado quando você espera.
**Possíveis causas**:
1. **Fluxo de trabalho não ativado**: Garanta que o fluxo de trabalho esteja definido como "Active", não "Draft"
2. **Condições do gatilho não atendidas**: Verifique se o gatilho corresponde ao evento esperado
3. **Campo não monitorado**: Para gatilhos "Record is Updated", garanta que o campo específico esteja sendo monitorado
4. **Permissões**: Verifique se você tem permissão para executar fluxos de trabalho
**Soluções**:
* Verifique o status do fluxo de trabalho na lista de fluxos de trabalho
* Teste com a ação específica que você espera que o dispare
* Revise a configuração do gatilho
* Entre em contato com o seu administrador sobre permissões
### Gatilhos do fluxo de trabalho ocorrem cedo demais (campos vazios)
**Sintomas**: Ao criar um registro manualmente na interface do usuário (UI), o seu fluxo de trabalho é acionado antes de você ter tempo de preencher todos os campos. O fluxo de trabalho é executado com a maioria dos campos vazios.
**Por que isso acontece**: Twenty salva tudo em tempo real — não há um modo separado de "edit" vs "read". Quando você cria um registro, ele é salvo imediatamente, acionando o evento "Record is created" antes de você preencher os campos adicionais.
**Quando "Record is created" funciona bem**:
* Registros criados por chamadas de API (os campos são preenchidos em uma única requisição)
* Registros criados por importação
* Criação automática de registros a partir de outros fluxos de trabalho
**Solução**: Para registros criados manualmente na UI, use **"Record is created or updated"** como seu gatilho em vez disso. Dessa forma:
* O fluxo de trabalho é acionado depois que o usuário termina de preencher e salvar os campos
* Você obtém os dados completos em vez de valores vazios
<Tip>
Se você quer que o fluxo de trabalho seja executado apenas uma vez por registro, adicione uma ação Filter para verificar um campo como `createdAt equals updatedAt` (primeiro salvamento) ou use um campo de caixa de seleção personalizado para acompanhar se o fluxo de trabalho já foi executado.
</Tip>
### Ações com falha
**Sintomas**: O fluxo de trabalho é executado, mas algumas ações falham.
**Possíveis causas**:
1. **Dados ausentes**: Campos obrigatórios estão vazios
2. **Referências inválidas**: Variáveis de etapas anteriores não existem
3. **Erros de API**: Serviços externos retornando erros
4. **Problemas de permissão**: A ação requer permissões que você não possui
**Soluções**:
* Verifique os detalhes da execução do fluxo de trabalho em busca de mensagens de erro
* Verifique se todos os campos obrigatórios têm valores
* Teste as conexões de API de forma independente
* Revise as permissões de função
### Erros de requisição HTTP
**Sintomas**: Ações de requisição HTTP falham ou retornam resultados inesperados.
**Códigos de erro comuns**:
* **400**: Solicitação inválida - verifique o formato do corpo da requisição
* **401**: Não autorizado - verifique a chave da API
* **403**: Proibido - verifique as permissões da API
* **404**: Não encontrado - verifique a URL do endpoint
* **429**: Muitas requisições - implemente limitação de taxa
* **500**: Erro do servidor - problema no serviço externo
**Soluções**:
* Verifique a URL do endpoint da API
* Verifique os cabeçalhos de autenticação
* Teste a chamada de API primeiro fora do Twenty
* Adicione tratamento de erros em Ações de Código
### Erros em Ações de Código
**Sintomas**: O código JavaScript não é executado.
**Problemas comuns**:
1. **Erros de sintaxe**: Erros de digitação ou JavaScript inválido
2. **Variáveis indefinidas**: Referenciando variáveis que não existem
3. **Erros de tipo**: Operações com tipos de dados incorretos
4. **Tempo limite excedido**: Código demorando muito para executar
**Soluções**:
* Use a validação integrada do editor de código
* Teste a lógica do código primeiro em um console JavaScript
* Adicione instruções console.log para depuração
* Simplifique operações complexas
### E-mail não enviado
**Sintomas**: A ação Send Email não entrega os e-mails.
**Possíveis causas**:
1. **Nenhuma conta de e-mail conectada**: Verifique Configurações → Contas
2. **Endereço de e-mail inválido**: O e-mail do destinatário está com formato inválido
3. **Limites de envio**: Limites de taxa do provedor de e-mail atingidos
4. **Filtros de spam**: E-mails sendo bloqueados
**Soluções**:
* Verifique a conexão da conta de e-mail
* Valide os endereços de e-mail dos destinatários
* Verifique os limites do provedor de e-mail
* Revise o conteúdo do e-mail para gatilhos de spam
## Depuração de fluxos de trabalho
### Usando execuções de fluxo de trabalho
1. Vá para o editor de fluxo de trabalho
2. Abra o painel **Runs**
3. Encontre a execução com falha
4. Clique para ver os detalhes passo a passo
5. Revise as mensagens de erro e os dados de saída
### Testando etapas individuais
1. Para Ações de Código, use o botão **Test**
2. Para requisições HTTP, teste o endpoint separadamente
3. Crie registros de teste para acionar fluxos de trabalho
4. Use gatilhos manuais para testes controlados
### Padrões comuns de depuração
**Adicione logs**:
Use Ações de Código para registrar valores intermediários para depuração.
**Isole etapas**:
Teste cada etapa de forma independente para identificar falhas.
**Verifique o fluxo de dados**:
Confirme que cada etapa recebe os dados de entrada esperados.
## Boas práticas para evitar problemas
### Antes da ativação
* Teste cuidadosamente no modo de rascunho
* Valide todas as conexões de API
* Revise cuidadosamente as condições do gatilho
* Documente o comportamento esperado
### Durante o desenvolvimento
* Use nomes descritivos para as etapas
* Adicione comentários nas Ações de Código
* Teste com dados realistas
* Planeje casos-limite
### Após a ativação
* Monitore de perto as execuções iniciais
* Configure alertas para falhas
* Revise regularmente o histórico de execuções
* Mantenha os fluxos de trabalho simples quando possível

View File

@@ -0,0 +1,236 @@
---
title: Perguntas frequentes sobre fluxos de trabalho
description: Perguntas frequentes sobre fluxos de trabalho na Twenty.
---
<AccordionGroup>
<Accordion title="Por que não consigo ativar um fluxo de trabalho?">
Isso provavelmente é um problema de permissões. Você precisa de acesso a fluxos de trabalho para criá-los e ativá-los.
**Solução**: Entre em contato com o administrador do seu espaço de trabalho para conceder acesso a fluxos de trabalho em **Configurações → Membros → Funções**.
Se você não vê a seção de fluxos de trabalho na sua barra lateral, isso confirma que é um problema de permissões.
</Accordion>
<Accordion title="Por que não vejo o botão do meu fluxo de trabalho manual na barra de navegação superior?">
Fluxos de trabalho manuais só aparecem na barra de navegação se estiverem corretamente configurados:
1. O fluxo de trabalho deve estar **ativado** (não em modo rascunho)
2. A posição na barra de navegação deve estar definida como **Pinned**
3. Para gatilhos Single/Bulk, você deve estar na página do objeto correta
**Para verificar**: Abra o fluxo de trabalho → clique no gatilho → verifique se "Navbar placement" está definido como "Pinned".
Você sempre pode acessar fluxos de trabalho manuais via **Cmd + K** (ou **Ctrl + K**), independentemente das configurações da barra de navegação.
</Accordion>
<Accordion title="Qual é a diferença entre gatilhos manuais Single, Global e Bulk?">
| Tipo | Registros necessários | Execuções de Workflow |
| ---- | --------------------- | --------------------- |
\| **Global** | Nenhum | Uma vez, sem entrada de registro |
\| **Single** | Um ou mais selecionados | Uma vez por registro selecionado |
\| **Bulk** | Um ou mais selecionados | Uma vez, com todos os registros como array |
* **Global**: Use quando o fluxo de trabalho não precisa de nenhum contexto de registro (por exemplo, gerar um relatório)
* **Single**: Use quando você quer processar cada registro selecionado de forma independente (por exemplo, enviar e-mails individuais)
* **Bulk**: Use quando você precisa processar registros em conjunto ou otimizar o uso de créditos (requer a ação Iterator)
Veja [Gatilhos de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-triggers) para obter detalhes.
</Accordion>
<Accordion title="Não vejo um nó If/Else. O que devo fazer?">
Um nó If/Else explícito ainda não está disponível, mas está em nosso roadmap.
**Solução alternativa atual**: Crie várias ramificações a partir da sua etapa, cada uma começando com uma ação **Filter**:
```
Step 1
│
├── Branch A: Filter (condition = true) → Actions...
│
└── Branch B: Filter (condition = false) → Actions...
```
Apenas a ramificação em que a condição do filtro for atendida executará suas ações subsequentes.
Veja [Como usar ramificações](/l/pt/user-guide/workflows/capabilities/workflow-branches) para um guia passo a passo.
</Accordion>
<Accordion title="As ramificações do fluxo de trabalho são executadas em paralelo?">
**Sim**, as ramificações são executadas em paralelo por padrão.
Se você quiser que apenas uma ramificação seja executada:
* Adicione uma ação **Filter** no início de cada ramificação
* Defina condições opostas (por exemplo, Ramificação A: status = "Open", Ramificação B: status ≠ "Open")
Ramificações que não atendem à sua condição de filtro param de executar, enquanto as outras continuam.
</Accordion>
<Accordion title="Posso unir as ramificações novamente?">
**Sim**. Após a conclusão das ramificações em paralelo, você pode adicionar uma etapa à qual ambas as ramificações se conectam.
No editor de fluxo de trabalho:
1. Conclua suas ações ramificadas
2. Adicione uma nova etapa após as ramificações
3. Arraste conexões do final de cada ramificação para essa nova etapa
A etapa mesclada será executada após a conclusão de todas as ramificações conectadas.
</Accordion>
<Accordion title="Qual é o limite nos resultados de Search Records?">
**Search Records retorna no máximo 200 registros.**
Se você precisar processar mais:
* Adicione filtros mais específicos para reduzir os resultados
* Use fluxos de trabalho agendados para processar em lotes
* Considere usar a API para operações em massa
Para a maioria dos fluxos de trabalho, 200 registros são suficientes. Se você atingir esse limite regularmente, considere reestruturar sua automação.
</Accordion>
<Accordion title="Posso adicionar pessoas em CC nos e-mails do fluxo de trabalho?">
**Ainda não.** Os campos CC e BCC para a ação Send Email estão em nosso roadmap.
**Solução alternativa atual**: Adicione várias ações Send Email para enviar a destinatários adicionais, ou use um HTTP Request para enviar por meio de um serviço de e-mail externo que dê suporte a CC.
</Accordion>
<Accordion title="Como uso as saídas de etapas anteriores?">
Cada ação produz dados de saída que podem ser usados em etapas subsequentes.
**Para referenciar dados de etapas anteriores**:
* Use o seletor de variáveis ao configurar um campo
* Ou digite `{{stepName.fieldName}}` diretamente
**Exemplos**:
* Dados do gatilho: `{{trigger.object.email}}`
* Resultados da pesquisa: `{{searchRecords[0].name}}`
* Saída do código: `{{code.calculatedValue}}`
Passe o mouse sobre qualquer campo na configuração da ação para ver as variáveis disponíveis das etapas anteriores.
</Accordion>
<Accordion title="Por que meu Iterator falha?">
**Iterator requer uma entrada do tipo array.** Problemas comuns:
1. **A entrada não é um array**: garanta que você está passando resultados de Search Records ou outra ação que retorne um array
2. **O array está vazio**: adicione um filtro antes do Iterator para verificar `{{searchRecords.length}} > 0`
3. **Variável errada selecionada**: certifique-se de selecionar o próprio array, não um único registro
**Configuração correta**:
1. Search Records (retorna um array)
2. Filter: length > 0
3. Iterator: selecione `{{searchRecords}}`
4. Ações dentro do Iterator usam `{{iterator.currentItem.fieldName}}`
</Accordion>
<Accordion title="Qual é o tempo máximo de execução para ações Code?">
As ações Code (funções lógicas) têm um **tempo limite padrão de 5 minutos** (300 segundos).
O tempo limite máximo configurável é de **15 minutos** (900 segundos).
Se seu código exceder esse limite, a ação falhará com um erro de tempo limite.
**Dicas para evitar estouros de tempo**:
* Divida operações grandes em partes menores usando o Iterator
* Evite computações pesadas; use serviços externos via HTTP Request para processamento intensivo
* Otimize seu código para reduzir o tempo de execução
* Se você precisar de um processamento mais longo, considere usar fluxos de trabalho agendados que processem dados em lotes
</Accordion>
<Accordion title="Como leio as execuções de fluxo de trabalho?">
As execuções de fluxo de trabalho mostram o histórico de execução e ajudam você a depurar problemas.
**Acessar execuções**:
* No editor de fluxo de trabalho → painel **Execuções** à direita
* Ou vá para **Execuções de Fluxo de Trabalho** na barra lateral
**Entendendo uma execução**:
* **Status**: Em execução, Concluída, Falhou, Aguardando
* **Etapas**: Veja quais etapas foram executadas e suas saídas
* **Erros**: Clique nas etapas com falha para ver as mensagens de erro
* **Dados**: Veja os dados de entrada/saída em cada etapa
Veja [Execuções de Fluxo de Trabalho](/l/pt/user-guide/workflows/capabilities/workflow-runs) para obter detalhes.
</Accordion>
<Accordion title="Por que não vejo minhas execuções de fluxo de trabalho?">
As execuções de fluxo de trabalho podem estar falhando imediatamente devido a limites de taxa. Twenty impõe um limite rígido de 5.000 execuções por hora por espaço de trabalho.
Se você exceder esse limite, os fluxos de trabalho são marcados imediatamente como falhos e não aparecerão na sua lista de execuções.
**Cenários comuns que atingem esse limite**:
* Selecionar mais de 5.000 registros com um gatilho manual Single
* Vários fluxos de trabalho executando simultaneamente em todo o seu espaço de trabalho
* Gatilhos automatizados de alta frequência (por exemplo, Record Updated em um objeto movimentado)
**Soluções**:
* Use gatilhos **Bulk** em vez de Single para processar muitos registros em uma única execução
* Espaçe as operações em lotes grandes
* Use filtros para reduzir a frequência dos gatilhos
* Agende fluxos de trabalho pesados fora do horário de pico
</Accordion>
<Accordion title="Quais são os limites de taxa dos fluxos de trabalho?">
A Twenty tem dois limites de taxa para garantir a estabilidade do sistema:
| Limite | Valor | Comportamento |
| ------ | ----- | ------------- |
\| **Limite flexível** | 100 execuções/minuto | Execuções entram na fila com status "Not Started", processadas gradualmente |
\| **Limite rígido** | 5.000 execuções/hora | Execuções falham imediatamente |
**Limite flexível (100/min)**: Seus fluxos de trabalho não falharão — eles apenas aguardam na fila e são processados ao longo do tempo. Você pode acionar mais de 100 registros; a execução será mais lenta.
**Limite rígido (5.000/h)**: Isso se aplica a todo o seu espaço de trabalho. Se todos os seus fluxos de trabalho combinados excederem 5.000 execuções em uma hora, execuções adicionais falharão imediatamente.
**Dicas para permanecer dentro dos limites**:
* Use gatilhos Bulk com Iterator em vez de gatilhos Single para grandes lotes
* Combine automações relacionadas em menos fluxos de trabalho
* Use fluxos de trabalho agendados para distribuir a carga ao longo do tempo
</Accordion>
<Accordion title="Posso tentar novamente uma execução de fluxo de trabalho com falha?">
**Não, no momento não há funcionalidade de nova tentativa automática.**
Se uma execução de fluxo de trabalho falhar, você precisará:
1. Revise o erro em **Configurações → Fluxos de trabalho → [Seu Fluxo de Trabalho] → Execuções**
2. Corrija o problema (dados, configuração ou serviço externo)
3. Dispare manualmente o fluxo de trabalho novamente no(s) registro(s) afetado(s)
**Dicas para reduzir falhas**:
* Adicione nós **Filter** para validar dados antes das ações
* Use **Search Records** para verificar se existem registros relacionados
* Teste profundamente com alguns registros antes de operações em massa
A funcionalidade de nova tentativa automática está em nosso roadmap para uma versão futura.
</Accordion>
<Accordion title="Devo desativar fluxos de trabalho antes de importações CSV?">
**Sim, se seus fluxos de trabalho forem acionados por criação ou atualização de registros.**
Quando você importa dados via CSV, cada registro criado ou atualizado pode acionar fluxos de trabalho. Uma importação grande (milhares de registros) pode:
* Atingir o limite de 5.000 execuções/hora
* Consumir uma quantidade significativa de créditos de fluxo de trabalho
* Enviar e-mails ou notificações inesperadas
* Criar tarefas ou registros duplicados
**Antes de uma importação em massa**:
1. Vá para **Configurações → Fluxos de trabalho**
2. Identifique os fluxos de trabalho acionados pelo objeto que você está importando
3. **Desative**-os temporariamente
4. Execute sua importação CSV
5. **Reative** os fluxos de trabalho quando terminar
**Alternativa**: Se você precisar que os fluxos de trabalho sejam executados nos dados importados, importe em lotes menores para permanecer dentro dos limites de taxa.
</Accordion>
<Accordion title="Como organizar o layout do meu fluxo de trabalho?">
Se sua tela de fluxo de trabalho estiver desorganizada com nós espalhados, você pode organizá-la automaticamente:
1. Clique com o botão direito em qualquer lugar na tela do fluxo de trabalho
2. Clique em **Tidy up workflow**
Isso reorganizará automaticamente todos os nós em um layout limpo e organizado.
</Accordion>
</AccordionGroup>