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 3s

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,121 @@
---
title: Respuesta automática a correos entrantes
description: Crea un flujo de trabajo que utilice IA para clasificar los correos entrantes y enviar respuestas en hilo automáticamente.
---
Responde a los correos entrantes en segundos — no en horas. Este flujo de trabajo utiliza un agente de IA para filtrar el ruido (boletines, spam, respuestas automáticas) y redactar una respuesta personalizada a los mensajes reales. Luego, AI lo envía como una respuesta en hilos dentro de la conversación original.
## Cómo funcionan los hilos de correo electrónico
Cada correo electrónico lleva un encabezado oculto `Message-ID` — un identificador único asignado por el servidor de correo del remitente. Cuando respondes a un correo electrónico, tu cliente de correo establece un encabezado `In-Reply-To` que hace referencia a esa huella. Así es como Gmail, Outlook y cualquier otro cliente agrupan los mensajes en hilos.
En Twenty, esa huella se almacena como `headerMessageId` en el objeto Message. Tu flujo de trabajo la toma y la pasa al campo In-Reply-To de la acción Send Email.
## Crear el flujo de trabajo
### Paso 1: Crear un nuevo flujo de trabajo
Ve a **Settings -> Workflows** y haz clic en **+ New Workflow**.
### Paso 2: Activar con mensajes entrantes
Selecciona **When a Record is Created** y elige **Messages**.
Cada vez que un correo electrónico llega a Twenty, esto se activa.
<img src="/images/user-guide/workflows/auto-reply/workflow-overview.png" style={{width:'100%'}} />
### Paso 3: Buscar quién lo envió
Añade una acción **Search Records**.
La dirección del remitente no está en el propio mensaje — está en el registro relacionado Message Participant.
| Campo | Valor |
| ---------- | ------------------------------- |
| **Objeto** | Participantes del mensaje |
| **Filtro** | Message **es** `{{trigger.id}}` |
| **Filtro** | Role **es** From |
| **Límite** | 1 |
Esto te da el correo electrónico del remitente en `handle` y su nombre en `displayName`.
<img src="/images/user-guide/workflows/auto-reply/find-sender.png" style={{width:'100%'}} />
### Paso 4: Trienal y redactar una respuesta con IA
Añade una acción **AI Agent**. Este único paso hace dos cosas: decide si el correo merece una respuesta y, en ese caso, redacta una.
Usa un 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.
```
El AI Agent genera su respuesta en un campo `response` que los siguientes pasos pueden consultar.
<img src="/images/user-guide/workflows/auto-reply/ai-triage.png" style={{width:'100%'}} />
### Paso 5: Ramificar según la decisión de la IA
Añade una acción **If/Else** para comprobar si la IA decidió responder u omitir.
| Campo | Valor |
| ------------------- | ------------------------------------------- |
| **Condición** | AI Agent `response` **no contiene** `SKIP` |
| **Si es verdadero** | Continuar con Send Email |
| **Si no** | No hacer nada (el flujo de trabajo termina) |
El spam, los boletines y los mensajes autogenerados se descartan. Todo lo demás pasa al siguiente paso.
<img src="/images/user-guide/workflows/auto-reply/should-reply.png" style={{width:'100%'}} />
### Paso 6: Enviar una respuesta en hilo
Añade una acción **Send Email** en la rama "si es verdadero". Haz clic en **Advanced options**, y luego en **Add In-Reply-To**.
| Campo | Valor |
| --------------- | -------------------------------------- |
| **Para** | `{{Find Sender.first.handle}}` |
| **Asunto** | `Re: {{trigger.subject}}` |
| **Cuerpo** | `{{AI Triage & Draft Reply.response}}` |
| **In-Reply-To** | `{{trigger.headerMessageId}}` |
El campo In-Reply-To es lo que hace que esto sea una respuesta en lugar de una conversación nueva. El destinatario la ve en hilo debajo del correo original en Gmail, Outlook o cualquier otro cliente.
<img src="/images/user-guide/workflows/auto-reply/send-email.png" style={{width:'100%'}} />
<Tip>
**In-Reply-To** espera un `message.headerMessageId` del disparador — es la huella única del correo, no una dirección del destinatario. Si lo dejas vacío, el correo igualmente se envía, solo que como un mensaje independiente.
</Tip>
<Warning>
Gmail usa la línea de asunto para agrupar los mensajes en hilos. El asunto **debe** comenzar con `Re:` (incluidos los dos puntos y el espacio) para que Gmail muestre la respuesta dentro del hilo original. Sin eso, la respuesta aparecerá como una conversación separada — incluso si el encabezado In-Reply-To está configurado correctamente.
</Warning>
### Paso 7: Probar y activar
Haz clic en **Test** y luego revisa tu cliente de correo. La respuesta debería aparecer anidada debajo del mensaje original.
Actívalo cuando estés satisfecho con el resultado.
## Ideas para ampliar
* **Responder solo a VIPs** — añade una rama que compruebe el dominio del remitente o si existe como Contact en Twenty
* **Enrutar por intención** — usa prompts de AI Agent separados para tratar las consultas de ventas de forma diferente a las solicitudes de soporte
* **Enriquecer antes de responder** — añade un paso Search Records para incorporar la empresa del remitente o el historial de oportunidades al prompt de IA para respuestas más personalizadas

View File

@@ -0,0 +1,179 @@
---
title: Automatizaciones de Cerrado Ganado
description: Automatiza las actividades posteriores al cierre cuando las oportunidades se cierran como ganadas.
---
Cuando se cierra un negocio, deben ocurrir varias cosas: actualizar el estado de la empresa, notificar a los miembros del equipo, crear tareas de incorporación. Automatiza todo esto con un único flujo de trabajo.
## Problema
Cuando una oportunidad pasa a "Cerrado ganado":
* El tipo de empresa debe cambiar de "Prospect" a "Customer"
* Se deben crear tareas de incorporación
* Se debe notificar al equipo de Customer Success
* El representante de ventas necesita una confirmación
Hacer esto manualmente consume tiempo y es propenso a errores.
## La solución
Crea un flujo de trabajo que gestione automáticamente todas las actividades posteriores al cierre ganado.
## Configuración completa del flujo de trabajo
### Paso 1: Crear el flujo de trabajo
1. Ve a **Configuración → Flujos de trabajo**
2. Haz clic en **+ Nuevo flujo de trabajo**
3. Asígnale el nombre "Deal Won - Post-Win Automation"
### Paso 2: Configurar el disparador
1. Selecciona **Record is Updated**
2. Elige **Oportunidades**
3. En "Campos a supervisar", selecciona **Etapa**
### Paso 3: Agregar filtro de etapa
1. Añade la acción **Filter**
2. Condición: `{{trigger.object.stage}}` es igual a "Closed Won"
### Paso 4: Actualizar el tipo de empresa
1. Añade la acción **Update Record**
2. Configurar:
| Campo | Valor |
| ---------------------------- | ------------------------------- |
| **Objeto** | Empresas |
| **Registro** | `{{trigger.object.company.id}}` |
| **Tipo** | Cliente |
| **Fecha del primer negocio** | `{{trigger.object.closedAt}}` |
| **Propietario de la cuenta** | `{{trigger.object.owner.id}}` |
### Paso 5: Crear tarea de incorporación
1. Añade la acción **Create Record**
2. Configurar:
| Campo | Valor |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **Objeto** | Tareas |
| **Título** | `Incorporación: {{trigger.object.name}}` |
| **Asignado** | Miembro del equipo de Customer Success |
| **Fecha de vencimiento** | Dentro de 3 días |
| **Prioridad** | Alta |
| **Empresa relacionada** | `{{trigger.object.company.id}}` |
| **Oportunidad relacionada** | `{{trigger.object.id}}` |
| **Descripción** | `Incorporación de nuevo cliente para {{trigger.object.company.name}}. Valor del negocio: {{trigger.object.amount}}` |
### Paso 6: Notificar a Customer Success
1. Añade la acción **Send Email**
2. Configurar:
| Campo | Valor |
| ---------- | --------------------------------------------------- |
| **Para** | customer-success@yourcompany.com |
| **Asunto** | `🎉 Nuevo cliente: {{trigger.object.company.name}}` |
| **Cuerpo** | Consulta el ejemplo a continuación |
**Ejemplo de cuerpo del correo**:
```
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!
```
### Paso 7: Confirmar al representante de ventas
1. Añade otra acción **Send Email**
2. Configurar:
| Campo | Valor |
| ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Para** | `{{trigger.object.owner.email}}` |
| **Asunto** | `✅ Negocio cerrado: {{trigger.object.name}}` |
| **Cuerpo** | ¡Felicidades! Tu negocio ha sido procesado. Se ha notificado al equipo de Customer Success y la incorporación ha comenzado. |
### Paso 8: Probar y activar
1. Prueba moviendo una oportunidad de prueba a "Closed Won"
2. Verificar:
* El tipo de empresa cambió a "Customer"
* Tarea de incorporación creada
* El equipo de CS recibió el correo electrónico
* El representante de ventas recibió la confirmación
3. Actívalo cuando esté listo
## Cómo gestionar Closed Lost
Crea un flujo de trabajo similar para negocios perdidos:
### Disparador
* Record is Updated (Oportunidades, Etapa = "Closed Lost")
### Acciones
1. **Create Record**: Tarea para "Análisis de negocio perdido"
2. **Update Record**: Agrega el motivo de pérdida al registro de la empresa
3. **Send Email**: Notifica al gerente sobre el negocio perdido
## Avanzado: incorporación de varios pasos
Para una incorporación compleja, crea varias tareas:
```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 };
};
```
Usa **Iterator** para crear cada tarea a partir del array.
## Ideas de personalización
### Mantén tus otras herramientas actualizadas
* Crea el cliente en el sistema de facturación con una **HTTP Request**
### Acciones condicionales
Usa acciones **Filter** para:
* Incorporación diferente para empresas grandes frente a pymes
* Asignados diferentes según la región
* Omitir notificaciones para negocios pequeños
### Incluye detalles de la oferta
Usa la acción **Code** para formatear:
* Documentos de resumen del negocio
* Notas de traspaso para el equipo de CS
* Listas de verificación de incorporación personalizadas
## Relacionado
* [Acciones de flujos de trabajo](/l/es/user-guide/workflows/capabilities/workflow-actions)
* [Enviar correos electrónicos desde flujos de trabajo](/l/es/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,136 @@
---
title: Detectar oportunidades estancadas
description: Notifica automáticamente a los gerentes cuando las oportunidades no se han actualizado.
---
Mantén tu pipeline saludable informando a los gerentes cuando las oportunidades se estanquen. Este flujo de trabajo revisa las oportunidades que no se han actualizado en un número específico de días.
## Problema
Las oportunidades sin actualizaciones provocan:
* Acuerdos que se enfrían
* Pronósticos poco fiables
* Pérdida de ingresos
## La solución
Crea un flujo de trabajo programado que encuentre oportunidades estancadas y envíe correos electrónicos a sus gerentes.
## Configuración paso a paso
### Paso 1: Crear el flujo de trabajo
1. Ve a **Configuración → Flujos de trabajo**
2. Haz clic en **+ Nuevo flujo de trabajo**
3. Asígnale el nombre "Alerta de oportunidades estancadas"
### Paso 2: Configurar el disparador
1. Selecciona **On a Schedule**
2. Configúralo para ejecutarse diariamente (p. ej., todos los días a las 8 a. m.)
### Paso 3: Buscar oportunidades estancadas
1. Añade la acción **Search Records**
2. Configurar:
| Campo | Valor |
| ---------- | ---------------------------------------------- |
| **Objeto** | Oportunidades |
| **Filtro** | Updated At es anterior a (hoy - 7 días) |
| **Filtro** | Stage no es "Closed Won" Y no es "Closed Lost" |
| **Límite** | 100 |
### Paso 4: Comprobar si se encontró algo
1. Añade la acción **Filter**
2. Condición: `{{searchRecords.length}}` es mayor que 0
3. Si no hay oportunidades estancadas, el flujo de trabajo se detiene aquí
### Paso 5: Formatear la alerta (acción de código)
Añade una acción **Code** para formatear el correo electrónico:
```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
};
};
```
### Paso 6: Enviar correo de alerta
Añade la acción **Send Email**:
| Campo | Valor |
| ---------- | -------------------------------------------------------------------- |
| **Para** | sales-manager@yourcompany.com |
| **Asunto** | `🚨 {{code.totalCount}} oportunidades estancadas requieren atención` |
| **Cuerpo** | `{{code.summary}}` |
### Paso 7: Probar y activar
1. Haz clic en **Test** para ejecutar el flujo de trabajo
2. Verifica que el correo electrónico contenga los datos correctos
3. Actívalo cuando esté listo
## Opciones de personalización
### Cambiar el umbral de estancamiento
Modifica el filtro de Search Records para cambiar de 7 días a tu periodo preferido:
* 3 días para ventas de alto ritmo
* 14 días para acuerdos empresariales
* 30 días para ciclos de ventas largos
### Alertar a los representantes individuales
En lugar de un solo correo para el gerente, usa **Iterator** para enviar correos personalizados a cada representante sobre sus propios acuerdos estancados.
### Agregar escalamiento
Crea varios flujos de trabajo con severidad creciente:
1. Día 7: Correo al representante
2. Día 14: Correo al representante + gerente
3. Día 21: Crear una tarea para que el gerente intervenga
### Incluir en Slack
Usa **HTTP Request** para publicar en un webhook de Slack en lugar del correo electrónico o además de este.
## Relacionado
* [Acciones de flujos de trabajo](/l/es/user-guide/workflows/capabilities/workflow-actions)
* [Enviar correos electrónicos desde flujos de trabajo](/l/es/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,69 @@
---
title: Mostrar el número de correos electrónicos recibidos
description: Cree un flujo de trabajo para contar y mostrar automáticamente el número de correos electrónicos recibidos de cada contacto.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/workflow-number-of-emails-received.png" style={{width:'100%'}} />
## Resumen
Este flujo de trabajo se activa cada vez que se recibe un nuevo correo electrónico y actualiza un campo personalizado en el registro de Person con el recuento total de correos electrónicos de ese remitente.
## Prerrequisitos
Antes de configurar este flujo de trabajo, cree un campo personalizado en el objeto **People**:
1. Vaya a **Configuración → Modelo de datos → People**
2. Añada un nuevo campo **Number**
3. Asígnele un nombre como "Número de correos electrónicos recibidos de esta persona"
## Configuración paso a paso
<VimeoEmbed videoId="1148700336" title="Demostración en video" />
### Paso 1: Configurar el disparador
1. Vaya a **Flujos de trabajo** y cree un nuevo flujo de trabajo
2. Seleccione **Record is Created** como el disparador
3. Elija **Message Participants** (disponible en objetos avanzados)
<Note>
Un Message Participant es una combinación de un ID de mensaje y un ID de persona, creando un registro único por mensaje. Esto es más fácil de rastrear que Messages directamente porque podemos acceder al campo `handle`, que contiene la dirección de correo electrónico del remitente (o del destinatario).
</Note>
### Paso 2: Filtrar por Rol
1. Añada una acción **Filter**
2. Establezca la condición: **Role** es igual a **FROM**
Esto garantiza que solo cuente los mensajes enviados por esta persona, no los mensajes enviados a esa persona.
### Paso 3: Buscar todos los Message Participants con el mismo Handle
1. Añada una acción **Search Records**
2. Seleccione **Message Participants** como el objeto
3. Añada filtros: **Handle** es igual al handle del disparador (la dirección de correo electrónico del remitente) y **Role** es igual a **FROM**
4. Aumente el **Limit** de 1 a **200** (el máximo)
Esto encuentra todos los mensajes de esta dirección de correo electrónico para obtener el recuento total.
<Note>
La acción Search Records está limitada a devolver un máximo de 200 registros. Sin embargo, dado que solo está utilizando el valor `totalCount` (no los registros individuales), este paso devolverá el número total de correos electrónicos enviados por esta persona.
</Note>
### Paso 4: Actualizar el registro de Person con una acción Create or Update Record
1. Añada una acción **Create or Update Record**
2. Seleccione **People** como el objeto
3. Busque a la persona haciendo coincidir su correo electrónico con el `handle` del Message Participant
4. Establezca su campo personalizado "Número de correos electrónicos recibidos" en `{{searchRecords.totalCount}}`
El valor `totalCount` de la acción Search Records representa el número total de correos electrónicos recibidos de esta persona.
## Relacionado
* [Acciones de flujos de trabajo](/l/es/user-guide/workflows/capabilities/workflow-actions)
* [Cómo crear campos personalizados](/l/es/user-guide/data-model/how-tos/customize-your-data-model)
* [Acción Search Records](/l/es/user-guide/workflows/capabilities/workflow-actions#search-records)

View File

@@ -0,0 +1,170 @@
---
title: Mostrar datos de registros relacionados
description: Muestra datos de registros relacionados (p. ej., información de la empresa en Oportunidades) usando flujos de trabajo.
---
Muestra datos de registros relacionados directamente en tus registros — por ejemplo, muestra el recuento de empleados de una Empresa en sus Oportunidades. Esta solución provisional con flujos de trabajo es útil hasta que los campos anidados estén disponibles de forma nativa.
## Casos de Uso Comunes
| Fuente | Destino | Campos a copiar |
| ----------- | ----------- | ----------------------------------------------------------------------- |
| Empresa | Oportunidad | Industria, Tamaño de la empresa, ARR |
| Persona | Oportunidad | Correo electrónico, Teléfono, Cargo |
| Oportunidad | Empresa | Importe de la última oportunidad, Fecha de la última oportunidad ganada |
## Copia básica de campos
### Ejemplo: Copiar el correo electrónico del contacto a la oportunidad
**Objetivo**: Al establecer un Punto de contacto en una oportunidad, copia su correo electrónico a la oportunidad para un acceso sencillo.
### Requisito previo
Crea los campos de destino en **Configuración → Modelo de datos → Oportunidades** antes de crear el flujo de trabajo:
* Correo electrónico del contacto (tipo: Email)
* Teléfono del contacto (tipo: Phone)
### Configuración
1. **Desencadenante**: Se actualiza el registro (Oportunidades, campo Punto de contacto)
2. **Filtro**: Comprueba que el Punto de contacto no esté vacío
3. **Buscar registros**: Encuentra a la persona vinculada
* Objeto: Personas
* Filtro: ID igual a `{{trigger.object.pointOfContact.id}}`
4. **Actualizar registro**:
* Objeto: Oportunidades
* Registro: `{{trigger.object.id}}`
* Correo electrónico del contacto: `{{searchRecords[0].email}}`
* Teléfono del contacto: `{{searchRecords[0].phone}}`
## Copiar varios campos
### Ejemplo: Sincronizar la información de la empresa con todas las oportunidades relacionadas
**Objetivo**: Cuando cambien los detalles de la empresa, actualiza todas las oportunidades relacionadas.
### Configuración
1. **Desencadenante**: Se actualiza el registro (Empresas)
* Campos: Industria, Tamaño de la empresa, Ingresos anuales
2. **Buscar registros**: Encuentra todas las oportunidades de esta empresa
* Objeto: Oportunidades
* Filtro: ID de empresa igual a `{{trigger.object.id}}`
3. **Iterator**: Recorre cada oportunidad
4. **Actualizar registro** (dentro del Iterator):
* Objeto: Oportunidades
* Registro: `{{iterator.currentItem.id}}`
* Industria de la empresa: `{{trigger.object.industry}}`
* Tamaño de la empresa: `{{trigger.object.companySize}}`
* ARR de la empresa: `{{trigger.object.annualRevenue}}`
## Copiar al crear el registro
### Ejemplo: Rellenar previamente la oportunidad con datos de la empresa
**Objetivo**: Al crear una oportunidad vinculada a una empresa, copia automáticamente la información clave de la empresa.
### Requisito previo
Crea los campos de destino en **Configuración → Modelo de datos → Oportunidades**:
* Industria de la empresa (tipo: Texto)
* Tamaño de la empresa (tipo: Número)
### Configuración
1. **Desencadenante**: Se crea el registro (Oportunidades)
* Filtro: La empresa no está vacía
2. **Buscar registros**: Obtén los detalles de la empresa vinculada
* Objeto: Empresas
* Filtro: ID igual a `{{trigger.object.company.id}}`
3. **Actualizar registro**:
* Objeto: Oportunidades
* Registro: `{{trigger.object.id}}`
* Industria de la empresa: `{{searchRecords[0].industry}}`
* Tamaño de la empresa: `{{searchRecords[0].employees}}`
<Note>
**Limitación de Tareas y Notas**: Las relaciones en Tareas y Notas están codificadas como de muchos a muchos y aún no están disponibles en los desencadenantes o acciones de flujos de trabajo. Para acceder a estas relaciones, usa la [API](/l/es/developers/extend/api).
</Note>
## Sincronización bidireccional
### Ejemplo: Mantener sincronizado el contacto principal
**Objetivo**: Cuando cambie el contacto principal de una empresa, actualiza el contacto. Cuando una persona se convierta en contacto principal, actualiza la empresa.
### Flujo de trabajo 1: Empresa → Persona
1. **Desencadenante**: Se actualiza el registro (Empresas, campo Contacto principal)
2. **Actualizar registro**: Establece "Es contacto principal" de la persona en true
3. **Buscar registros**: Encuentra el contacto principal anterior
4. **Actualizar registro**: Establece "Es contacto principal" del contacto anterior en false
### Flujo de trabajo 2: Persona → Empresa
1. **Desencadenante**: Se actualiza el registro (Personas, Es contacto principal = true)
2. **Actualizar registro**: Establece el Contacto principal de la empresa en esta persona
<Note>
Ten cuidado con las sincronizaciones bidireccionales para evitar bucles infinitos. Usa filtros para comprobar si el valor realmente cambió antes de actualizar.
</Note>
## Uso de código para mapeo complejo
### Ejemplo: Transformar datos durante la copia
**Objetivo**: Copiar y formatear el número de teléfono de la persona a la oportunidad.
```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 };
};
```
## Mejores prácticas
### Evitar bucles
* No crees flujos de trabajo que se activen mutuamente sin fin
* Usa condiciones específicas de campo
* Añade comprobaciones para ver si el valor realmente cambió
### Gestionar datos faltantes
* Comprueba siempre que el registro de origen exista antes de copiar
* Proporciona valores predeterminados para los campos opcionales
* Usa filtros para omitir cuando el campo de origen esté vacío
### Rendimiento
* Procesa actualizaciones por lotes al copiar a muchos registros
* Usa flujos de trabajo programados para operaciones de sincronización masiva
* Considera usar Iterator para actualizaciones de múltiples registros
## Relacionado
* [Acciones de flujos de trabajo](/l/es/user-guide/workflows/capabilities/workflow-actions)
* [Disparadores de flujos de trabajo](/l/es/user-guide/workflows/capabilities/workflow-triggers)

View File

@@ -0,0 +1,202 @@
---
title: Campos de fórmula
description: Crea campos de fórmula usando flujos de trabajo hasta que haya soporte nativo disponible.
---
Veinte todavía no soporta campos de fórmula nativos (llegando en 2026), pero puede lograr el mismo resultado usando flujos de trabajo. Esta solución alternativa te permite calcular y rellenar automáticamente valores de campos—desde concatenaciones simples hasta lógica empresarial compleja.
## Casos de Uso Comunes
| Caso de uso | Ejemplo de fórmula |
| ----------------------------- | ---------------------------------- |
| **Nombre completo** | Nombre + " " + Apellido |
| **Importe esperado** | Importe × Probabilidad |
| **Días hasta el vencimiento** | Fecha de vencimiento - Hoy |
| **Días en la etapa** | Hoy - Fecha de entrada a la etapa |
| **Puntuación del lead** | Puntos basados en varios criterios |
<Tip>
Para ver un ejemplo completo de seguimiento del tiempo en etapas del pipeline, consulta [Cuánto tiempo permanecen las oportunidades en cada etapa](/l/es/user-guide/views-pipelines/how-tos/track-time-in-stage).
</Tip>
## Fórmula básica: concatenación
### Ejemplo: rellenar automáticamente el nombre completo
**Objetivo**: Combinar automáticamente nombre y apellido en un campo de nombre completo.
### Configuración
1. **Desencadenante**: Se actualiza o se crea el registro (Personas)
2. **Filtro**: Comprueba que el nombre o el apellido hayan cambiado
3. **Acción de código**:
```javascript
export const main = async (params) => {
const { firstName, lastName } = params;
const fullName = [firstName, lastName]
.filter(Boolean)
.join(' ');
return { fullName };
};
```
4. **Actualizar registro**: Establecer Nombre completo en `{{code.fullName}}`
## Fórmula numérica: importe esperado
### Ejemplo: calcular ingresos esperados
**Objetivo**: Multiplicar el importe de la oportunidad por la probabilidad para obtener el importe esperado.
Consulta [Cómo mostrar el importe esperado en el pipeline](/l/es/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline) para ver el flujo de trabajo completo.
### Configuración
1. **Desencadenante**: Se actualiza el registro (Oportunidades, campo Importe o Probabilidad)
2. **Acción de código**:
```javascript
export const main = async (params) => {
const { amount, probability } = params;
const expectedAmount = (amount || 0) * (probability || 0) / 100;
return { expectedAmount };
};
```
3. **Actualizar registro**: Establecer Importe esperado en `{{code.expectedAmount}}`
## Fórmula de fechas: cálculo de días
### Ejemplo: días hasta el vencimiento de la tarea
**Objetivo**: Calcular cuántos días faltan hasta la fecha de vencimiento de una tarea.
### Configuración
1. **Desencadenante**: Se actualiza o se crea el registro (Tareas, campo Fecha de vencimiento)
2. **Acción 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. **Actualizar registro**: Establecer Días hasta el vencimiento en `{{code.daysUntilDue}}`
<Note>
Los valores negativos indican tareas vencidas. Puedes usar este campo para filtrar o ordenar tareas por urgencia.
</Note>
## Fórmula condicional: puntuación del lead
### Ejemplo: calcular la puntuación del lead según criterios
**Objetivo**: Puntuar leads en función del tamaño de la empresa, el sector y la interacción.
### Configuración
1. **Desencadenante**: Se actualiza el registro (Personas o Empresas)
2. **Acción 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. **Actualizar registro**: Establecer Puntuación del lead en `{{code.leadScore}}`
## Fórmula de texto: extracción de dominio
### Ejemplo: extraer el dominio del correo electrónico
**Objetivo**: Extraer y almacenar automáticamente el dominio del correo electrónico.
### Configuración
1. **Desencadenante**: Se actualiza el registro (Personas, campo Correo electrónico)
2. **Acción 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. **Actualizar registro**: Establecer el campo Dominio en `{{code.domain}}`
## Mejores prácticas
### Rendimiento
* Activa solo ante cambios en campos relevantes
* Usa filtros para omitir registros que no necesiten cálculo
* Evita cálculos complejos en flujos de trabajo de alto volumen
### Manejo de errores
* Comprueba valores null/undefined antes de los cálculos
* Usa valores predeterminados cuando falten datos
* Devuelve mensajes de error claros cuando fallen los cálculos
### Pruebas
* Prueba con casos límite (campos vacíos, valores cero)
* Verifica los cálculos manualmente antes de activar
* Supervisa las ejecuciones de flujos de trabajo para detectar resultados inesperados
## Relacionado
* [Cómo mostrar el importe esperado en el pipeline](/l/es/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline)
* [Cómo realizar el seguimiento del tiempo en la etapa](/l/es/user-guide/views-pipelines/how-tos/track-time-in-stage)
* [Acciones de flujos de trabajo](/l/es/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,127 @@
---
title: Notificar a los compañeros de equipo que hay una nota para revisar
description: Enviar notificaciones de correo electrónico automáticas al asignar a un colega para revisar una nota.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Note>
**Etiquetar directamente a los compañeros de equipo en las notas aún no está disponible.** Esta función está prevista para la segunda mitad de 2026. Mientras tanto, este flujo de trabajo ofrece una solución alternativa práctica.
</Note>
## El desafío
Desea etiquetar a un colega en una nota para que reciba una notificación para revisarla. Actualmente, Twenty no admite @mentions en notas ni tareas.
## La solución alternativa
En lugar de etiquetar en el texto de la nota, puede:
1. Cree un campo de relación **Reviewer** en el objeto Notes
2. Asigne al revisor al crear o editar la nota
3. Haz que un flujo de trabajo envíe automáticamente un correo electrónico al revisor con un enlace a la nota
<Warning>
**Limitación**: Este enfoque permite asignar un revisor por campo. Si necesita notificar a varias personas, cree campos de relación adicionales (p. ej., Reviewer 1, Reviewer 2). Esto no escalará bien si necesita etiquetar a muchos colaboradores a la vez.
</Warning>
## Configuración paso a paso
### Paso 1: Crear el campo Reviewer
<img src="/images/user-guide/workflows/reviewer-field-for-note.png" style={{width:'100%'}} />
1. Vaya a **Configuración → Modelo de datos → Notes**
2. Haga clic en **Add Field**
3. Configurar:
| Configuración | Valor |
| ------------------ | --------------------------------- |
| **Name** | Reviewer |
| **Tipo** | Relación |
| **Related Object** | Miembro del espacio de trabajo |
| **Relation Type** | Many Notes → One Workspace Member |
4. Guarde el campo
### Paso 2: Crear el flujo de trabajo
<VimeoEmbed videoId="1151897168" title="Demostración en video" />
#### Cree el disparador del flujo de trabajo
1. Vaya a la vista principal y cree un nuevo flujo de trabajo
2. Seleccione **Record Updated** como el disparador
3. Elija **Note** como objeto y especifique el campo Reviewer.
4. Esto se activará cada vez que se actualice el revisor de una nota.
#### Agregue un filtro para comprobar que el revisor se acaba de asignar
Añada una acción **Filter** para asegurarse de que el flujo de trabajo solo continúe cuando se asigne un revisor:
| Condición | Valor |
| ------------ | ------------- |
| **Reviewer** | no está vacío |
#### Busque al miembro del espacio de trabajo
1. Añada una acción **Search Records**
2. Seleccione **Workspace Member** (en Advanced Objects)
3. Añada un filtro:
| Condición | Valor |
| --------- | ----------------------------- |
| **ID** | igual a `{{note.reviewerId}}` |
Esto recupera el registro completo del miembro del espacio de trabajo, incluida su dirección de correo electrónico y su nombre.
#### Enviar el correo electrónico de notificación
1. Añada una acción **Send Email**
2. Configurar:
| Campo | Valor |
| ---------- | --------------------------------------------------- |
| **Para** | Search Record → First Workspace Member → User Email |
| **Asunto** | Una nota requiere su revisión |
| **Cuerpo** | Consulte el ejemplo a continuación |
**Ejemplo de cuerpo del correo:**
```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>
Reemplace `yourSubDomain` por el subdominio real de su espacio de trabajo de Twenty.
</Note>
#### Probar y activar
1. Haga clic en **Test** para ejecutar el flujo de trabajo manualmente
2. Cree o edite una nota y asigne un revisor
3. Compruebe la bandeja de entrada del revisor para ver el correo electrónico de notificación
4. Active el flujo de trabajo
## Uso
Una vez configurado, su equipo puede usar este flujo de trabajo de la siguiente manera:
1. Crear una nueva nota (o editar una existente)
2. Seleccionar a un colega en el campo **Reviewer**
3. El colega recibe automáticamente un correo electrónico con un enlace directo a la nota
## Relacionado
* [Disparadores de flujos de trabajo](/l/es/user-guide/workflows/capabilities/workflow-triggers)
* [Enviar correos electrónicos desde flujos de trabajo](/l/es/user-guide/workflows/capabilities/send-emails-from-workflows)
* [Crear campos personalizados](/l/es/user-guide/data-model/how-tos/create-custom-fields)
* [Crear campos de relación](/l/es/user-guide/data-model/how-tos/create-relation-fields)

View File

@@ -0,0 +1,106 @@
---
title: Enviar alertas por correo electrónico sobre tareas con fecha de vencimiento
description: Notifica automáticamente a los miembros del equipo sobre sus tareas próximas o vencidas.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/email_tasks_due.png" style={{width:'100%'}} />
Enviar recordatorios diarios por correo electrónico a cada miembro del equipo sobre sus tareas que vencen hoy.
## Resumen
Este flujo de trabajo se ejecuta según una programación y:
1. Obtiene todos los miembros del espacio de trabajo
2. Itera por cada miembro
3. Encuentra sus tareas que vencen hoy
4. Da formato y envía un correo electrónico personalizado
## Configuración paso a paso
<VimeoEmbed videoId="1147463514" title="Demostración en video" />
### Paso 1: Configurar el disparador
1. Ve a **Configuración → Flujos de trabajo** y crea un nuevo flujo de trabajo
2. Selecciona **On a Schedule** como el disparador
3. Usa una expresión cron para ejecutar diariamente a las 8:00 a. m.: `0 8 * * *`
### Paso 2: Buscar a todos los miembros del espacio de trabajo
1. Añade la acción **Search Records**
2. Selecciona **Workspace Members** (en objetos avanzados)
3. No se necesitan filtros — esto devuelve a todos los miembros
### Paso 3: Añadir un iterador
1. Añade la acción **Iterator**
2. Configura el arreglo de entrada con los miembros del espacio de trabajo del paso anterior
3. Todas las acciones dentro del iterador se ejecutarán una vez por miembro
### Paso 4: Buscar tareas que vencen hoy (dentro del iterador)
1. Dentro del iterador, añade una acción **Search Records**
2. Selecciona **Tasks** como el objeto
3. Agregar filtros:
* **Assignee** = miembro actual del espacio de trabajo (del iterador)
* **Due Date** = hoy
### Paso 5: Formatear las tareas para el cuerpo del correo (dentro del iterador)
Añade una acción **Code** para dar formato a las tareas como una lista legible con enlaces:
```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>
Reemplaza `yourSubDomain` por el subdominio real de tu espacio de trabajo de Twenty.
</Note>
### Paso 6: Enviar correo electrónico (dentro del iterador)
1. Añade una acción **Send Email** (aún dentro del iterador)
2. Configurar:
| Campo | Valor |
| ---------- | -------------------------------------------------------------------------------------------- |
| **Para** | `{{iterator.currentItem.userEmail}}` (correo electrónico del miembro del espacio de trabajo) |
| **Asunto** | Tus tareas que vencen hoy |
| **Cuerpo** | `{{code.formattedTasks}}` |
### Paso 7: Probar y activar
1. Haz clic en **Test** para ejecutar el flujo de trabajo manualmente
2. Revisa las bandejas de entrada para ver los correos electrónicos
3. Activa el flujo de trabajo
## Relacionado
* [Acciones de flujos de trabajo](/l/es/user-guide/workflows/capabilities/workflow-actions)
* [Enviar correos electrónicos desde flujos de trabajo](/l/es/user-guide/workflows/capabilities/send-emails-from-workflows)
* [Manejar arreglos en acciones de código](/l/es/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)