Pular para o conteúdo principal

Visão geral das Jornadas

As Jornadas são o recurso para criar serviços de impacto na Turn.io. As Jornadas foram fortemente inspiradas pelas ideias originais do Hypercard e do Hypertalk.

cuidado

As Jornadas eram previamente chamadas de stacks. As palavras "stacks" e "jornadas" podem ser usadas de forma intercambiável em nossa documentação, e você ainda pode ver o uso de stacks em alguns nomes de função.

Na Turn.io, as Jornadas são contêineres que contêm um ou mais cards e os relacionamentos entre eles. Os cards executam as diversas etapas que compõem seu serviço de impacto usando funções e expressões.

As funções e expressões disponíveis são descritas abaixo, com exemplos e erros comuns.

Linguagem de codificação das Jornadas

Serviços de impacto construídos usando jornadas são descritos usando uma linguagem de codificação personalizada. Às vezes, nos referimos a ela como a DSL das Jornadas (Domain Specific Language).

Vamos analisar a estrutura e a sintaxe da linguagem de código.

Bloco de código da Jornada

Pense em uma jornada como um contêiner que contém vários cards.

Exemplo

O exemplo a seguir cria uma jornada vazia para fins ilustrativos.

stack JourneyName do
...
end

Pontos importantes

  • Sua jornada deve ter um nome.
  • O comando do inicia a jornada.
  • O comando end conclui a jornada.
  • Toda a lógica que a jornada deve executar será adicionada entre os comandos do e end.

Você pode omitir o stack do ... end, fazendo com que o sistema assuma implicitamente que uma única jornada descreve todos os cards definidos.

Bloco de código do Card

Pense em um card como executando uma ou mais etapas em seu serviço de impacto. Os cards fazem uso de várias funções e expressões para descrever essas etapas.

Exemplo

O exemplo a seguir cria um card vazio para fins ilustrativos.

card CardName do
...
end

Pontos importantes

  • A estrutura de código para criar um card é semelhante à estrutura de código para criar uma jornada.
  • Seu bloco de código de card sempre será definido dentro de um bloco de código de jornada.
  • Seu card deve ter um nome.
  • O comando do inicia o card.
  • O comando end conclui o card.
  • Toda a lógica do card será adicionada entre os comandos do e end.
  • A lógica consistirá em funções, expressões etc. Veja os exemplos abaixo.

Usando comentários

Qualquer linha de texto em sua jornada prefixada com um sinal # se tornará um comentário. Isso significa que não será uma linha de código executável, sendo ignorada quando a jornada for executada.

card MyCard do
# Este é um comentário
end

Documentando suas Jornadas

Comentários de código são ótimos para documentar linhas individuais dentro de um Card, mas às vezes podem ser limitados. Quando se deseja descrever preocupações mais amplas, como fornecer mais detalhes sobre decisões específicas de design, fazer links para documentação ou pesquisa externas, ou descrever detalhes-chave de implementação, os blocos de documentação em Markdown são o ideal.

Markdown é um formato de texto simples usado para fornecer marcações adicionais ao texto puro. Há um ótimo tutorial de Markdown disponível em https://www.markdowntutorial.com que cobre os detalhes; também existe o Markdown Cheat Sheet se você estiver procurando um guia de referência rápida.

markdown-block

A ideia por trás de combinar cards, para descrever a lógica, e Markdown, para descrever o processo de pensamento e as informações de fundo, é que essas jornadas se tornarão autodescritivas ao longo do tempo. Na verdade, todo o seu serviço de impacto em si é descrito como um grande arquivo de texto em Markdown.

Entre seus cards, você pode adicionar quantos blocos de Markdown forem necessários para descrever a lógica do seu serviço de impacto.

Sua primeira jornada

Agora que sabemos que uma jornada contém um ou mais cards, e cada card contém lógica que descreve seu serviço de impacto, vamos juntar tudo. O exemplo mostra como blocos de jornada e blocos de card se encaixam para criar uma jornada muito simples.

Exemplo

O exemplo a seguir cria uma jornada simples que envia uma única mensagem de texto para um usuário, dizendo "Hello World!". Note como o bloco do card fica dentro do bloco da jornada.

stack MyJourney do
card MyCard do
text("Hello world!")
end
end

Relacionando cards

Os cards descrevem as etapas do seu serviço de impacto. Cada card descreve um ponto específico em uma série de interações.

O primeiro card é sempre o que a jornada inicia. Depois disso, a ordem em que os cards aparecem no código da jornada não é importante. O que é importante é descrever os relacionamentos entre os cards, pois isso determina como os usuários navegarão entre eles.

Existem duas formas principais de descrever o que acontece após cada card, ou seja, a then ou a condição when.

A condição then

O relacionamento then é usado para conectar um card a outro.

Sintaxe

card FirstCard, then: NextCard do
...
end

Exemplo

Este exemplo mostra como usar a palavra-chave then: para indicar quando um card deve ser imediatamente seguido por outro card. Neste caso, duas mensagens de texto serão enviadas uma após a outra, primeiro "this is the first card" e depois "this is the second card".

card One, then: Two do
text("this is the first card")
end

card Two do
text("this is the second card")
end

Pontos importantes

  • Sem then:, apenas a primeira mensagem de texto será enviada.

A condição when

A cláusula when é usada para adicionar condições que controlam a ordem de execução de um card.

Sintaxe

card CardName when some_condition do
...
end
info

Observe que com a sintaxe then usamos uma vírgula e dois pontos (CardName, then:). No entanto, when deve ocorrer imediatamente após o cardName sem vírgula ou dois pontos.

Operadores Booleanos

Esta é uma lista das funções de comparação que podem ser usadas nas Jornadas:

  • < - menor que
  • > - maior que
  • >= - maior ou igual a
  • <= - menor ou igual a
  • = - igual a
  • == - igual a
  • != - diferente de
  • <> - diferente de

Eles podem ser combinados com os operadores padrão and e or, bem como a palavra-chave not.

Você pode usar parênteses para determinar a ordem de avaliação:

  • (true or false) and false será avaliado como false
  • true or (false and false) será avaliado como true

Por fim, no que diz respeito a valores truthy e falsey:

  • variáveis que são nil ou null serão avaliadas como false
  • simplesmente verificar se uma variável não é o valor booleano true resultará em false. Então qualquer uso de when some_variable do será avaliado como false. Caso contrário, você deve incluir uma comparação. Por exemplo, algumas linguagens podem declarar que uma string não vazia ou uma lista não vazia é avaliada como verdadeiro, mas não é o caso nas Jornadas.
info

Se você não tiver certeza sobre o valor de uma expressão, pode usar a função log para visualizar a saída no simulador.

Exemplo

O exemplo a seguir elabora um pouco o exemplo anterior, introduzindo condições entre cards.

Você notará que o card One segue para o card Two usando a palavra-chave then:. Uma diferença importante aqui é que estamos usando a função ask(), que aguarda a entrada do usuário antes de prosseguir.

O card Two é definido várias vezes, dois deles têm condições que protegem o card. Dependendo da resposta do usuário, o sistema selecionará automaticamente o card Three para o qual a condição seja avaliada como true. O último card Two não tem condição, o que significa que sempre será avaliado como true, funcionando como um fallback padrão.

card One, then: Two do
text("this is the second card")
age = ask("What is your age?")
end

card Two when age > 18 do
text("Hello boomer")
end

card Two when age > 16 and age <= 18 do
text("Hey there!")
end

card Two do
text("This service is too cool for you")
end

Gatilhos (Triggers)

Os Gatilhos (Triggers) são definidos no início da sua jornada e especificam quando sua jornada deve ser executada. Por exemplo, você pode usar o Gatilho (Trigger) a seguir para iniciar sua jornada quando um usuário enviar a mensagem "hi" para o seu serviço:

trigger(on: "MESSAGE RECEIVED") when has_phrase(event.message.text.body, "hi")

card FirstCard do
text("Welcome!")
end

Uma vez acionada (triggered), a jornada começará a ser executada a partir de seu primeiro card.

Um Gatilho (Trigger) é composto por um evento, como MESSAGE RECEIVED, e uma expressão opcional (especificada após a palavra-chave when) que pode ser usada para corresponder às propriedades da mensagem recebida e/ou do contato.

Existem dois tipos de Gatilhos (Triggers):

  • Mensagens Gatilho de Entrada: estes Gatilhos (Triggers) são executados quando um usuário envia uma mensagem para o seu serviço.
  • Gatilhos Temporais: estes Gatilhos (Triggers) são executados em uma data/hora específica ou algum tempo antes/depois de um evento específico (como "2 horas após o cadastro"). Eles também podem ser configurados para serem executados periodicamente em uma programação (como "toda segunda-feira às 15:00").

Dividimos os aspectos de código dos gatilhos nas seções a seguir, mas você também pode aprender fazendo: crie um gatilho (trigger) no canvas sem código e depois mude para a visualização de código para ver o código gerado. Observe que o código é somente leitura e você não pode editá-lo diretamente:

demo of using no-code to generate trigger code

Mensagens Gatilho de Entrada

Esses Gatilhos (Triggers) são executados quando um contato envia uma mensagem para o seu serviço. Os seguintes eventos são suportados:

  • MESSAGE RECEIVED: aciona quando um contato envia uma mensagem para o seu serviço.
  • FIRST TIME: aciona quando um contato envia uma mensagem para o seu serviço pela primeira vez.
  • CATCH ALL: aciona se nenhum Gatilho (Trigger) (ou Automação) tratou a mensagem recebida.

Você pode refinar ainda mais o Gatilho (Trigger) fornecendo uma expressão que corresponda às propriedades da mensagem recebida e/ou do contato que enviou a mensagem.

O exemplo a seguir corresponde a mensagens que contêm a palavra "hi", mas somente para contatos que optaram por participar (opted in):

trigger(on: "MESSAGE RECEIVED")
when has_phrase(event.message.text.body, "hi") and contact.opted_in == true


card FirstCard do
text("Welcome opted-in user!")
end

Na expressão, você pode se referir à mensagem recebida com event.message (e seu conteúdo com event.message.text.body) e ao contato que enviou a mensagem com contact. Para se referir a campos de perfil de contato, você pode usar a sintaxe contact.field_name_here.

As seguintes Expressões são suportadas em Mensagens Gatilho de Entrada:

Todos os operadores booleanos e sintaxe discutidos para declarações when são suportados em um gatilho de mensagem.

Gatilhos Temporais

Os Gatilhos Temporais são executados em uma data/hora específica ou algum tempo antes/depois de um evento específico (como "2 horas após o cadastro"). Eles também podem ser configurados para serem executados periodicamente em um cronograma (como "toda segunda-feira às 15:00").

Os seguintes Gatilhos Temporais são suportados:

  • Data específica: aciona em um datetime específico, por exemplo: trigger(at: "2024-09-13T15:45:00Z").
  • Recorrente: aciona em uma programação recorrente de estilo cron, por exemplo: trigger(every: "30 15 * * MON").
  • Relativo a um campo de perfil: aciona algum tempo antes/depois do valor datetime contido em um campo de perfil de contato, por exemplo: trigger(interval: "+3d", relative_to: "contact.due_date").

Assim como nos Mensagens Gatilho de Entrada, é possível refinar ainda mais o Gatilho (Trigger) fornecendo uma expressão após a palavra-chave when. A expressão atua como um filtro para seus contatos: o gatilho (trigger) será executado para todos os contatos para os quais a expressão retornar true.

Como exemplo, o seguinte Gatilho (Trigger) será executado todas as terças-feiras às 10:30 para todos os contatos que tiverem o campo de perfil is_pregnant definido como true:

trigger(every: "30 10 * * TUE") when contact.is_pregnant == true

As seguintes Expressões são suportadas em Gatilhos Temporais:

Todos os operadores booleanos e sintaxe discutidos para declarações when são suportados em um gatilho temporal.

Uma observação importante sobre modelos (templates) de mensagem: ao usar Gatilhos Temporais, muitas vezes é difícil dizer se o gatilho (trigger) será executado dentro da janela de serviço de 24 horas do WhatsApp para um contato. Como o WhatsApp só permite o envio de modelos (templates) de mensagem fora da janela de serviço de 24 horas, recomendamos fortemente o uso de modelos (templates) de mensagem (em vez de mensagens normais) em jornadas que usem Gatilhos Temporais.

Gatilhos de data específica

Gatilhos de data específica serão executados na datetime especificada para todos os contatos para os quais a expressão fornecida for avaliada como true.

O datetime é fornecido com o parâmetro at: e deve ser uma string datetime válida em formato ISO8601.

Por exemplo, a jornada a seguir será acionada às 15:45 em 13 de setembro de 2024 para todos os contatos que tiverem o campo opted_in definido como true ou o campo registration_status definido como "completed".

trigger(at: "2024-09-13T15:45:00Z") when
contact.opted_in == true or contact.registration_status == "completed"

Gatilhos Recorrentes

Gatilhos recorrentes serão executados em uma programação recorrente especificada usando uma cron schedule expression.

Recomendamos usar o site Crontab Guru para construir e validar sua expressão cron. Observe que as expressões cron serão avaliadas no fuso horário UTC.

Também é possível especificar uma data de término para o gatilho recorrente informando o parâmetro opcional until. O parâmetro until precisa ser um valor datetime válido no formato ISO8601.

Por exemplo, o seguinte gatilho será executado toda segunda-feira às 15:30 (até 13 de setembro de 2025) para todos os contatos que tiverem o campo language definido como qualquer valor diferente de "en".

trigger(every: "30 15 * * MON", until: "2025-09-13T15:45:00Z") when
not has_phrase(contact.language, "en")

Gatilhos relativos a um campo de perfil

Esses gatilhos são executados em um intervalo de tempo especificado em relação ao valor datetime contido em um campo de perfil.

Por exemplo, podem ser usados para acionar uma jornada 2 horas após um contato se inscrever em um programa:

trigger(interval: "+2h", relative_to: "contact.enrolled_at") when
has_phrase(contact.project_enrolled, "project_a")

O intervalo de tempo relativo é fornecido como o argumento interval e deve ser uma string composta pelos seguintes elementos:

  • Sinal + ou -, onde + significa depois e - significa antes.
  • Um inteiro
  • Uma unidade de tempo das seguintes suportadas:
    • m para minutos.
    • h para horas.
    • d para dias.
    • w para semanas.
    • M para meses.

O campo de contato deve ser do tipo data e deve ser especificado como o argumento relative_to usando a sintaxe "contact.field_name".

O exemplo a seguir envia uma mensagem ao usuário 3 dias antes do aniversário dele:

trigger(interval: "-3d", relative_to: "contact.birthday")

card UpcomingBirthday do
send_message_template("birthday_3d_before", "en", [])
end

Também é possível substituir a hora em que o gatilho (trigger) deve ser executado usando a opção target_time (especificada como uma string de hora UTC ISO8601 válida). Isso é útil quando se usam unidades de intervalo como dias/semanas/meses para evitar enviar mensagens em horários inconvenientes.

Por exemplo, o seguinte Gatilho (Trigger) será executado 2 semanas antes do valor datetime do campo due_date do contato, exatamente às 15:30:

trigger(interval: "-2w", relative_to: "contact.due_date", target_time: "15:30:00") when
contact.is_expecting == true

Variáveis

Como em muitas linguagens de programação, variáveis podem ser usadas para armazenar valores. Todas as variáveis declaradas em uma jornada estão disponíveis para todos os cards da jornada, e não apenas no card em que a variável foi criada.

info

Os valores atribuídos às variáveis estão disponíveis apenas durante a execução da jornada e serão perdidos quando o usuário terminar de interagir com a jornada. Se você precisar salvar dados para recuperação posterior (por exemplo, para recuperá-los de outras jornadas no futuro), consulte sobre contato profiles.

card CardOne, then CardTwo do
first = 2
end

card CardTwo, then CardThree do
second = 3
result = first + second
end

card CardThree do
# Isso envia a mensagem "The result is: 5"
text("The result is: @result")
end

Tabelas de conteúdo (Content Tables)

Um caso de uso comum ao escrever jornadas é armazenar algum conteúdo de forma que seja fácil interagir com ele a partir de um card.

Um exemplo é um questionário de 10 perguntas. A lógica descrita nos cards não precisa mudar se o conteúdo for atualizado para ter menos ou mais perguntas. Simplesmente queremos percorrer todas as perguntas disponíveis, uma a uma, até que todas sejam respondidas.

Content Tables são ótimas para esses tipos de casos. Elas têm um nome exclusivo e funcionam como uma planilha, permitindo que se especifiquem colunas com nomes e linhas para cada.

content-table

Dentro de um card, as linhas da content table podem ser lidas uma a uma usando Expressions. As tabelas têm um atributo de lista chamado rows que dá acesso a todas as linhas da tabela. Cada valor de linha individual pode ser acessado usando o nome da coluna em minúsculas.

card TextCard do
row = table_0.rows[0]
text("""
The value of the first rows is: @row.column_1, @row.column_2, and @row.column_3
""")
end

content-table-example

Parâmetros

Frequentemente, ao escrever jornadas, haverá partes comuns de texto e valores que são reutilizadas entre os cards. Pense em coisas como o texto de boas-vindas em um botão que leva de volta ao menu principal, por exemplo.

Muitas vezes é desejável apresentar esses elementos comuns em uma interface facilmente editável fora de objetos de código em uma jornada.

Parameters são ótimos para isso, pois permitem atribuir um nome a um elemento comumente reutilizado e fornecer um valor padrão para ele. Ao escrever cards, você pode então se referir a esses valores pelo nome usando Expressions

Os parâmetros são semelhantes às Content Tables, exceto que são otimizados para a leitura de valores individuais em vez de linhas combinadas.

Os valores em parameters são armazenados em um atributo chamado items, de onde as entradas individuais podem ser lidas pelo nome em minúsculas.

card TextCard do
text("""
@parameters_0.items.welcome_text

@parameters_0.items.intro
""")
end

parameters

Uso de texto multilinha

O texto multilinha é delimitado por aspas triplas (""") e permite representar facilmente texto que se estende por várias linhas.

card QuestionCard do
ask("""
Welcome!
Is "Jane" your name?
""")
end

Ele também permite que você use aspas (") dentro sem precisar escapá-las:

card Card do
data = parse_json("""
{
"my": "json",
"structure": [1, 2, 3]
}
""")
end
info

Aspas simples não são mais suportadas para valores de string. Isso era suportado anteriormente, mas, de agora em diante, gerará um erro ao salvar uma jornada.

Se você estiver usando aspas simples como no exemplo a seguir:

map(0..10, &concatenate(&1, ', ')

Use aspas duplas em vez disso:

map(0..10, &concatenate(&1, ", ")

Se você precisar usar aspas em uma string, pode escapá-las com uma barra invertida: \".

map(0..10, &concatenate(&1, "\","))

O bloco de código avisará quando você estiver usando aspas simples na próxima vez que você salvar seu código:

single quote validation screenshot

Erros comuns

  • Todas as linhas, incluindo as aspas triplas, devem estar indentadas no mesmo nível, conforme o exemplo abaixo.
  • Não coloque nenhum texto na mesma linha que as aspas triplas. Por exemplo, não faça ask("""Welcome, apenas ask(""" e o texto na linha seguinte.

Formatando texto

O WhatsApp permite que você formate seu texto em negrito, itálico, tachado e monospace.

FormatoSintaxeExemplo
Itálicocoloque um sublinhado (_) em ambos os lados do texto_seu texto_
Negritocoloque um asterisco (*) em ambos os lados do texto*seu texto*
Tachadocoloque uma til (~) em ambos os lados do texto~seu texto~
Monospacecoloque três crases (```) em ambos os lados do texto seu texto

Desempenho da Jornada

Ao criar jornadas, incentivamos manter em mente o desempenho; criando serviços rápidos e responsivos para seus usuários finais no WhatsApp. Uma das coisas que fazemos para incentivar um serviço responsivo é definir um tempo limite de 30 segundos para que as jornadas concluam uma ação em resposta à mensagem de um usuário. Por exemplo, se você depende de várias chamadas de webhook externas para complementar seu serviço, certifique-se de que elas respondam dentro de um tempo razoável para permitir que as conversas fluam de forma natural.