Recopilar y procesar información de los usuarios de Google Chat

En esta guía, se describe cómo las apps de Google Chat pueden recopilar y procesar información de los usuarios creando entradas de formulario en interfaces basadas en tarjetas.

Un diálogo con una variedad de widgets diferentes.
Figura 1: Una app de Chat de ejemplo que abre un diálogo para recopilar información de contacto.

Las apps de Chat solicitan información a los usuarios para realizar acciones dentro o fuera de Chat, incluidas las siguientes:

  • Establece la configuración. Por ejemplo, para permitir que los usuarios personalicen la configuración de notificaciones o configuren y agreguen la app de Chat a uno o más espacios.
  • Crear o actualizar información en otras aplicaciones de Google Workspace Por ejemplo, permite que los usuarios creen un evento de Calendario de Google.
  • Permitir que los usuarios accedan a recursos en otras apps o servicios web, y los actualicen Por ejemplo, una app de Chat puede ayudar a los usuarios a actualizar el estado de un ticket de asistencia directamente desde un espacio de Chat.

Requisitos previos

HTTP

Una app de Google Chat que recibe interacciones de los usuarios y responde a ellas Para compilar una, completa la guía de inicio rápido de HTTP.

Apps Script

Una app de Google Chat que recibe interacciones de los usuarios y responde a ellas Para compilar uno, completa la guía de inicio rápido de Apps Script.

Crea formularios con tarjetas

Para recopilar información, las apps de Chat diseñan formularios y sus entradas, y los incorporan en tarjetas. Para mostrar tarjetas a los usuarios, las apps de Chat pueden usar las siguientes interfaces de Chat:

  • Mensajes que contienen una o más tarjetas
  • Páginas principales, que es una tarjeta que aparece en la pestaña Página principal en los mensajes directos con la app de Chat.
  • Diálogos, que son tarjetas que se abren en una ventana nueva desde mensajes y páginas principales

Las apps de Chat pueden compilar las tarjetas con los siguientes widgets:

  • Widgets de entrada de formularios que solicitan información a los usuarios De manera opcional, puedes agregar validación a los widgets de entrada de formularios para asegurarte de que los usuarios ingresen y den formato a la información correctamente. Las apps de Chat pueden usar los siguientes widgets de entrada de formulario:

  • Un widget de botón para que los usuarios puedan enviar los valores que ingresaron en la tarjeta Después de que un usuario hace clic en el botón, la app de Chat puede procesar la información que recibe.

En el siguiente ejemplo, una tarjeta recopila información de contacto con una entrada de texto, un selector de fecha y hora, y una entrada de selección:

Para ver más ejemplos de widgets interactivos que puedes usar para recopilar información, consulta Diseña una tarjeta o un diálogo interactivo.

Cómo agregar un menú desplegable

Para personalizar los elementos de selección o permitir que los usuarios seleccionen un solo elemento de una fuente de datos dinámica, las apps de Chat pueden usar menús desplegables, que son un tipo de widget SelectionInput. Por ejemplo, la siguiente tarjeta muestra un menú desplegable en el que los usuarios pueden seleccionar de forma dinámica de una lista de contactos:

Puedes propagar elementos para un menú desplegable desde las siguientes fuentes de datos:

Cómo propagar elementos desde una fuente de datos de Google Workspace

Para propagar elementos de fuentes de datos de Google Workspace, como usuarios de Google Workspace, especifica el campo platformDataSource dentro de un objeto DataSourceConfig. A diferencia de otros tipos de entradas de selección, omites los objetos SelectionItem, ya que estos elementos de selección se obtienen de forma dinámica de Google Workspace.

En el siguiente código, se muestra un menú desplegable de usuarios de Google Workspace:

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "contacts",
            "type": "DROPDOWN",
            "label": "Select contact from organization",
            "dataSourceConfigs": [
              {
                "platformDataSource": {
                  "commonDataSource": "USER"
                },
                "minCharactersTrigger": 1
              }
            ]
          }
        }
      ]
    }
  ]
}

Propaga elementos desde una fuente de datos externa

Los menús desplegables también pueden propagar elementos de una fuente de datos externa o de terceros. Para usar una fuente de datos externa, especifica el campo remoteDataSource dentro de un objeto DataSourceConfig que contenga la función que consulta y devuelve elementos de la fuente de datos.

Para reducir las solicitudes a una fuente de datos externa, puedes incluir elementos sugeridos que aparezcan en el menú desplegable antes de que los usuarios escriban en el menú. Para propagar elementos sugeridos de una fuente de datos externa, especifica objetos SelectionItem estáticos.

En el siguiente código, se muestra un menú desplegable que consulta y propaga elementos de una fuente de datos externa:

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "crm_leads",
            "type": "DROPDOWN",
            "label": "Select CRM Lead",
            "dataSourceConfigs": [
              {
                "remoteDataSource": {
                  "function": "getCrmLeads"
                },
                "minCharactersTrigger": 2
              }
            ],
            "items": [
              {
                "text": "Suggested Lead 1",
                "value": "lead-1"
              }
            ]
          }
        }
      ]
    }
  ]
}

Para ver un ejemplo completo que muestra cómo devolver elementos sugeridos, consulta la sección Cómo sugerir elementos de selección.

Cómo agregar un menú de selección múltiple

Para personalizar los elementos de selección o permitir que los usuarios seleccionen elementos de una fuente de datos dinámica, las apps de Chat pueden usar menús de selección múltiple, que son un tipo de widget de SelectionInput. Por ejemplo, la siguiente tarjeta muestra un menú de selección múltiple en el que los usuarios pueden seleccionar de forma dinámica de una lista de contactos:

Puedes propagar elementos para un menú de selección múltiple desde las siguientes fuentes de datos:

  • Datos de Google Workspace, que incluyen usuarios o espacios de Chat de los que el usuario es miembro. En el menú, solo se completan los elementos de la misma organización de Google Workspace.
  • Fuentes de datos externas, como una base de datos relacional Por ejemplo, puedes usar menús de selección múltiple para ayudar a un usuario a seleccionar de una lista de leads comerciales de un sistema de administración de relaciones con clientes (CRM).

Cómo propagar elementos desde una fuente de datos de Google Workspace

Para usar fuentes de datos de Google Workspace, especifica el campo platformDataSource en el widget SelectionInput. A diferencia de otros tipos de entradas de selección, omites los objetos SelectionItem, ya que estos elementos de selección se obtienen de forma dinámica de Google Workspace.

En el siguiente código, se muestra un menú de selección múltiple de usuarios de Google Workspace. Para propagar usuarios, la entrada de selección establece commonDataSource en USER:

JSON

{
  "selectionInput": {
    "name": "contacts",
    "type": "MULTI_SELECT",
    "label": "Selected contacts",
    "multiSelectMaxSelectedItems": 5,
    "multiSelectMinQueryLength": 1,
    "platformDataSource": {
      "commonDataSource": "USER"
    }
  }
}

En el siguiente código, se muestra un menú de selección múltiple de espacios de Chat. Para propagar espacios, la entrada de selección especifica el campo hostAppDataSource. El menú de selección múltiple también establece defaultToCurrentSpace en true, lo que hace que el espacio actual sea la selección predeterminada en el menú:

JSON

{
  "selectionInput": {
    "name": "spaces",
    "type": "MULTI_SELECT",
    "label": "Selected contacts",
    "multiSelectMaxSelectedItems": 3,
    "multiSelectMinQueryLength": 1,
    "platformDataSource": {
      "hostAppDataSource": {
        "chatDataSource": {
          "spaceDataSource": {
            "defaultToCurrentSpace": true
          }
        }
      }
    }
  }
}

Propaga elementos desde una fuente de datos externa

Los menús de selección múltiple también pueden propagar elementos de una fuente de datos externa o de terceros. Para usar una fuente de datos externa, especifica el campo externalDataSource en el widget SelectionInput que contiene la función que consulta y devuelve elementos de la fuente de datos.

Para reducir las solicitudes a una fuente de datos externa, puedes incluir elementos sugeridos que aparecen en el menú de selección múltiple antes de que los usuarios escriban en el menú. Por ejemplo, puedes propagar los contactos que el usuario buscó recientemente. Para propagar elementos sugeridos de una fuente de datos externa, especifica objetos SelectionItem estáticos.

En la siguiente muestra de código, se indica un menú de selección múltiple que consulta y propaga elementos de una fuente de datos externa:

Node.js

node/chat/selection-input/index.js
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: FUNCTION_URL },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getSuggestedContact("3")]
}

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Python

python/chat/selection-input/main.py
'selectionInput': {
  'name': "contacts",
  'type': "MULTI_SELECT",
  'label': "Selected contacts",
  'multiSelectMaxSelectedItems': 3,
  'multiSelectMinQueryLength': 1,
  'externalDataSource': { 'function': FUNCTION_URL },
  # Suggested items loaded by default.
  # The list is static here but it could be dynamic.
  'items': [get_suggested_contact("3")]
}

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Java

java/chat/selection-input/src/main/java/com/google/chat/selectionInput/App.java
.setSelectionInput(new GoogleAppsCardV1SelectionInput()
  .setName("contacts")
  .setType("MULTI_SELECT")
  .setLabel("Selected contacts")
  .setMultiSelectMaxSelectedItems(3)
  .setMultiSelectMinQueryLength(1)
  .setExternalDataSource(new GoogleAppsCardV1Action().setFunction(FUNCTION_URL))
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  .setItems(List.of(getSuggestedContact("3")))))))))));

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Apps Script

En este ejemplo, se envía un mensaje de tarjeta con la devolución del JSON de la tarjeta. También puedes usar el servicio de tarjetas de Apps Script.

apps-script/chat/selection-input/selection-input.gs
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: "queryContacts" },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getSuggestedContact("3")]
}

Para ver un ejemplo completo que muestra cómo devolver elementos sugeridos, consulta la sección Cómo sugerir elementos de selección.

Cómo recibir datos de widgets interactivos

Cada vez que los usuarios hacen clic en un botón, se activa su acción de la app de Chat con información sobre la interacción. En el campo commonEventObject de la carga útil del evento, el objeto formInputs contiene los valores que ingresa el usuario.

Puedes recuperar los valores del objeto event.commonEventObject.formInputs.WIDGET_NAME, en el que WIDGET_NAME es el campo name que especificaste para el widget. Los valores se devuelven como un tipo de datos específico para el widget.

A continuación, se muestra una parte de un objeto de evento en el que un usuario ingresó valores para cada widget:

{
  "commonEventObject": { "formInputs": {
    "contactName": { "stringInputs": {
      "value": ["Kai 0"]
    }},
    "contactBirthdate": { "dateInput": {
      "msSinceEpoch": 1000425600000
    }},
    "contactType": { "stringInputs": {
      "value": ["Personal"]
    }}
  }}
}

Para recibir los datos, tu app de Chat controla el objeto de evento para obtener los valores que los usuarios ingresan en los widgets. En la siguiente tabla, se muestra cómo obtener el valor de un widget de entrada de formulario determinado. Para cada widget, la tabla muestra el tipo de datos que acepta el widget, dónde se almacena el valor en el objeto de evento y un valor de ejemplo.

Widget de entrada de formulario Tipo de datos de entrada Es el valor de entrada del objeto de evento. Valor de ejemplo
textInput stringInputs event.commonEventObject.formInputs.contactName.stringInputs.value[0] Kai O
selectionInput stringInputs Para obtener el primer o único valor, event.commonEventObject.formInputs.contactType.stringInputs.value[0] Personal
dateTimePicker que solo acepta fechas. dateInput event.commonEventObject.formInputs.contactBirthdate.dateInput.msSinceEpoch. 1000425600000

Después de que la app de Chat recibe datos, puede realizar cualquiera de las siguientes acciones:

  • En el caso de las tarjetas que contienen un menú de selección múltiple, completa o sugiere elementos según lo que el usuario escriba en el menú.
  • Transfiere los datos a otra tarjeta para que el usuario pueda revisar su información o continuar con la siguiente sección del formulario.
  • Respóndele al usuario para confirmar que completó el formulario correctamente.

Sugerir elementos de selección

Si una tarjeta contiene un menú de selección múltiple o un menú desplegable que propaga elementos de una fuente de datos externa, la app de Chat puede devolver elementos sugeridos en función de lo que los usuarios escriban en el menú. Por ejemplo, si un usuario comienza a escribir Atl en un menú que completa ciudades de Estados Unidos, tu app de Chat puede sugerir automáticamente Atlanta antes de que el usuario termine de escribir. La app de Chat puede sugerir hasta 100 elementos.

Para sugerir y propagar elementos de forma dinámica en una entrada de selección, el widget SelectionInput de la tarjeta debe especificar una función que consulte la fuente de datos externa. Para los menús de selección múltiple, debes especificar el campo externalDataSource. Para los menús desplegables, debes especificar el campo remoteDataSource dentro de un objeto DataSourceConfig.

También puedes configurar la cantidad de caracteres que un usuario escribe antes de que el menú devuelva sugerencias. En el caso de los menús de selección múltiple, configura el campo multiSelectMinQueryLength. Para los menús desplegables, configura el campo min_characters_trigger dentro de DataSourceConfig.

Para devolver los elementos sugeridos, la función debe hacer lo siguiente:

  1. Controlar un objeto de evento, que la app de Chat recibe cuando los usuarios escriben en el menú
  2. En el objeto de evento, obtén el valor que escribe el usuario, que se representa en el campo event.commonEventObject.parameters["autocomplete_widget_query"].
  3. Consulta la fuente de datos con el valor de entrada del usuario para obtener uno o más objetos SelectionItems que se le sugerirán al usuario.
  4. Devuelve los elementos sugeridos con la acción RenderActions con un objeto modifyCard.

En la siguiente muestra de código, se indica cómo una app de Chat sugiere elementos de forma dinámica en el menú de selección múltiple de una tarjeta. Cuando un usuario escribe en el menú, la función o el extremo proporcionados en el campo externalDataSource del widget consulta una fuente de datos externa y sugiere elementos que el usuario puede seleccionar.

Node.js

node/chat/selection-input/index.js
/**
 * Web app that responds to events sent from a Google Chat space.
 *
 * @param {Object} req Request sent from Google Chat space
 * @param {Object} res Response to send back
 */
app.post('/', async (req, res) => {
  // Stores the Google Chat event
  const chatEvent = req.body.chat;

  // Handle user interaction with multiselect.
  if(chatEvent.widgetUpdatedPayload) {
    return res.json(queryContacts(req.body));
  }

  // Replies with a card that contains the multiselect menu.
  return res.json({ hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
    cardsV2: [{
      cardId: "contactSelector",
      card: { sections:[{ widgets: [{
        selectionInput: {
          name: "contacts",
          type: "MULTI_SELECT",
          label: "Selected contacts",
          multiSelectMaxSelectedItems: 3,
          multiSelectMinQueryLength: 1,
          externalDataSource: { function: FUNCTION_URL },
          // Suggested items loaded by default.
          // The list is static here but it could be dynamic.
          items: [getSuggestedContact("3")]
        }
      }]}]}
    }]
  }}}}});
});

/**
 * Get contact suggestions based on text typed by users.
 *
 * @param {Object} event the event object that contains the user's query
 * @return {Object} suggestions
 */
function queryContacts(event) {
  const query = event.commonEventObject.parameters["autocomplete_widget_query"];
  return { action: { modifyOperations: [{ updateWidget: { selectionInputWidgetSuggestions: { suggestions: [
    // The list is static here but it could be dynamic.
    getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("5")
  // Only return items based on the query from the user.
  ].filter(e => !query || e.text.includes(query)) }}}]}};
}

/**
 * Generate a suggested contact given an ID.
 *
 * @param {String} id The ID of the contact to return.
 * @return {Object} The contact formatted as a selection item in the menu.
 */
function getSuggestedContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Python

python/chat/selection-input/main.py
@app.route('/', methods=['POST'])
def post() -> Mapping[str, Any]:
  """Handle requests from Google Chat

  Returns:
      Mapping[str, Any]: The response
  """
  # Stores the Google Chat event
  chatEvent = request.get_json().get('chat')

  # Handle user interaction with multiselect.
  if chatEvent.get('widgetUpdatedPayload') is not None:
    return json.jsonify(query_contacts(request.get_json()))

  # Replies with a card that contains the multiselect menu.
  return json.jsonify({ 'hostAppDataAction': { 'chatDataAction': { 'createMessageAction': {
    'message': { 'cardsV2': [{
      'cardId': "contactSelector",
      'card': { 'sections':[{ 'widgets': [{
        'selectionInput': {
          'name': "contacts",
          'type': "MULTI_SELECT",
          'label': "Selected contacts",
          'multiSelectMaxSelectedItems': 3,
          'multiSelectMinQueryLength': 1,
          'externalDataSource': { 'function': FUNCTION_URL },
          # Suggested items loaded by default.
          # The list is static here but it could be dynamic.
          'items': [get_suggested_contact("3")]
        }
      }]}]}
    }]}
  }}}})


def query_contacts(event: dict) -> dict:
  """Get contact suggestions based on text typed by users.

  Args:
      event (Mapping[str, Any]): The event object that contains the user's query

  Returns:
      Mapping[str, Any]: The response with contact suggestions.
  """
  query = event.get("commonEventObject").get("parameters").get("autocomplete_widget_query")
  return { 'action': { 'modifyOperations': [{ 'updateWidget': { 'selectionInputWidgetSuggestions': { 'suggestions': list(
    filter(lambda e: query is None or query in e["text"], [
      # The list is static here but it could be dynamic.
      get_suggested_contact("1"), get_suggested_contact("2"), get_suggested_contact("3"), get_suggested_contact("4"), get_suggested_contact("5")
    # Only return items based on the query from the user
    ])
  )}}}]}}


def get_suggested_contact(id: str) -> dict:
  """Generate a suggested contact given an ID.

  Args:
      id (str): The ID of the contact to return.

  Returns:
      Mapping[str, Any]: The contact formatted as a selection item in the menu.
  """
  return {
    'value': id,
    'startIconUri': "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    'text': "Contact " + id
  }

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Java

java/chat/selection-input/src/main/java/com/google/chat/selectionInput/App.java
@SpringBootApplication
@RestController
// Web app that responds to events sent from a Google Chat space.
public class App {
  private static final String FUNCTION_URL = "your-function-url";

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }

  /**
   * Handle requests from Google Chat
   * 
   * @param event the event object sent by Google Chat
   * @return The response to be sent back to Google Chat
   */
  @PostMapping("/")
  @ResponseBody
  public GenericJson onEvent(@RequestBody JsonNode event) throws Exception {
    // Stores the Google Chat event
    JsonNode chatEvent = event.at("/chat");

    // Handle user interaction with multiselect.
    if (!chatEvent.at("/widgetUpdatedPayload").isEmpty()) {
      return queryContacts(event);
    }

    // Replies with a card that contains the multiselect menu.
    Message message = new Message().setCardsV2(List.of(new CardWithId()
      .setCardId("contactSelector")
      .setCard(new GoogleAppsCardV1Card()
        .setSections(List.of(new GoogleAppsCardV1Section().setWidgets(List.of(new GoogleAppsCardV1Widget()
          .setSelectionInput(new GoogleAppsCardV1SelectionInput()
            .setName("contacts")
            .setType("MULTI_SELECT")
            .setLabel("Selected contacts")
            .setMultiSelectMaxSelectedItems(3)
            .setMultiSelectMinQueryLength(1)
            .setExternalDataSource(new GoogleAppsCardV1Action().setFunction(FUNCTION_URL))
            // Suggested items loaded by default.
            // The list is static here but it could be dynamic.
            .setItems(List.of(getSuggestedContact("3")))))))))));

    return new GenericJson() {{
      put("hostAppDataAction", new GenericJson() {{
        put("chatDataAction", new GenericJson() {{
          put("createMessageAction", new GenericJson() {{
            put("message", message);
          }});
        }});
      }});
    }};
  }

  /**
   * Get contact suggestions based on text typed by users.
   *
   * @param event the event object that contains the user's query.
   * @return The response with contact suggestions.
   */
  GenericJson queryContacts(JsonNode event) throws Exception {
    String query = event.at("/commonEventObject/parameters/autocomplete_widget_query").asText();
    List<GoogleAppsCardV1SelectionItem> suggestions = List.of(
      // The list is static here but it could be dynamic.
      getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("5")
    // Only return items based on the query from the user
    ).stream().filter(e -> query == null || e.getText().indexOf(query) > -1).toList();

    return new GenericJson() {{
      put("action", new GenericJson() {{
        put("modifyOperations", List.of(new GenericJson() {{
          put("updateWidget", new GenericJson() {{
            put("selectionInputWidgetSuggestions", new GenericJson() {{
              put("suggestions", suggestions);
            }});
          }});
        }}));
      }});
    }};
  }

  /**
   * Generate a suggested contact given an ID.
   * 
   * @param id The ID of the contact to return.
   * @return The contact formatted as a selection item in the menu.
   */
  GoogleAppsCardV1SelectionItem getSuggestedContact(String id) {
    return new GoogleAppsCardV1SelectionItem()
      .setValue(id)
      .setStartIconUri("https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png")
      .setText("Contact " + id);
  }
}

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Apps Script

En este ejemplo, se envía un mensaje de tarjeta con la devolución del JSON de la tarjeta. También puedes usar el servicio de tarjetas de Apps Script.

apps-script/chat/selection-input/selection-input.gs
/**
* Responds to a Message trigger in Google Chat.
*
* @param {Object} event the event object from Google Chat
* @return {Object} Response from the Chat app.
*/
function onMessage(event) {
  // Replies with a card that contains the multiselect menu.
  return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
    cardsV2: [{
      cardId: "contactSelector",
      card: { sections:[{ widgets: [{
        selectionInput: {
          name: "contacts",
          type: "MULTI_SELECT",
          label: "Selected contacts",
          multiSelectMaxSelectedItems: 3,
          multiSelectMinQueryLength: 1,
          externalDataSource: { function: "queryContacts" },
          // Suggested items loaded by default.
          // The list is static here but it could be dynamic.
          items: [getSuggestedContact("3")]
        }
      }]}]}
    }]
  }}}}};
}

/**
* Get contact suggestions based on text typed by users.
*
* @param {Object} event the event object that contains the user's query
* @return {Object} suggestions
*/
function queryContacts(event) {
  const query = event.commonEventObject.parameters["autocomplete_widget_query"];
  return { action: { modifyOperations: [{ updateWidget: { selectionInputWidgetSuggestions: { suggestions: [
    // The list is static here but it could be dynamic.
    getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("5")
  // Only return items based on the query from the user.
  ].filter(e => !query || e.text.includes(query)) }}}]}};
}

/**
* Generate a suggested contact given an ID.
*
* @param {String} id The ID of the contact to return.
* @return {Object} The contact formatted as a selection item in the menu.
*/
function getSuggestedContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

Transfiere datos a otra tarjeta

Después de que un usuario envíe información de una tarjeta, es posible que debas devolver tarjetas adicionales para realizar cualquiera de las siguientes acciones:

  • Ayuda a los usuarios a completar formularios más largos creando secciones distintas.
  • Permite que los usuarios obtengan una vista previa de la información de la tarjeta inicial y la confirmen para que puedan revisar sus respuestas antes de enviarlas.
  • Completa de forma dinámica las partes restantes del formulario. Por ejemplo, para pedirles a los usuarios que creen una cita, una app de Chat podría mostrar una tarjeta inicial en la que se solicite el motivo de la cita y, luego, completar otra tarjeta en la que se proporcionen los horarios disponibles según el tipo de cita.

Para transferir la entrada de datos de la tarjeta inicial, puedes compilar el widget button con actionParameters que contengan el name del widget y el valor que ingresa el usuario, como se muestra en el siguiente ejemplo:

Node.js

node/chat/contact-form-app/index.js
{ buttonList: { buttons: [{
  text: "SUBMIT",
  onClick: { action: {
    function: FUNCTION_URL,
    parameters: [
      { key: "actionName", value: "submitDialog" },
      // Pass input values as parameters for last dialog step (submission)
      { key: "contactName", value: name },
      { key: "contactBirthdate", value: birthdate },
      { key: "contactType", value: type }
    ]
  }}
}]}}

Reemplaza FUNCTION_URL por el extremo HTTP que controla los clics en el botón.

Python

python/chat/contact-form-app/main.py
{ 'buttonList': { 'buttons': [{
  'text': "SUBMIT",
  'onClick': { 'action': {
    'function': FUNCTION_URL,
    'parameters': [
      { 'key': "actionName", 'value': "submitDialog" },
      # Pass input values as parameters for last dialog step (submission)
      { 'key': "contactName", 'value': name },
      { 'key': "contactBirthdate", 'value': birthdate },
      { 'key': "contactType", 'value': type }
    ]
  }}
}]}}

Reemplaza FUNCTION_URL por el extremo HTTP que controla los clics en el botón.

Java

java/chat/contact-form-app/src/main/java/com/google/chat/contact/App.java
new GoogleAppsCardV1Widget().setButtonList(new GoogleAppsCardV1ButtonList().setButtons(List.of(
  new GoogleAppsCardV1Button()
    .setText("SUBMIT")
    .setOnClick(new GoogleAppsCardV1OnClick().setAction(new GoogleAppsCardV1Action()
      .setFunction(FUNCTION_URL)
      .setParameters(List.of(
        new GoogleAppsCardV1ActionParameter().setKey("actionName").setValue("submitDialog"),
        // Pass input values as parameters for last dialog step (submission)
        new GoogleAppsCardV1ActionParameter().setKey("contactName").setValue(name),
        new GoogleAppsCardV1ActionParameter().setKey("contactBirthdate").setValue(birthdate),
        new GoogleAppsCardV1ActionParameter().setKey("contactType").setValue(type))))))))))));

Reemplaza FUNCTION_URL por el extremo HTTP que controla los clics en el botón.

Apps Script

En este ejemplo, se envía un mensaje de tarjeta con la devolución del JSON de la tarjeta. También puedes usar el servicio de tarjetas de Apps Script.

apps-script/chat/contact-form-app/Code.gs
{ buttonList: { buttons: [{
  text: "SUBMIT",
  onClick: { action: {
    function: "submitDialog",
    // Pass input values as parameters for last dialog step (submission)
    parameters: [
      { key: "contactName", value: name },
      { key: "contactBirthdate", value: birthdate },
      { key: "contactType", value: type }
    ]
  }}
}]}}

Cuando un usuario hace clic en el botón, tu app de Chat recibe un objeto de evento del que puedes recibir datos.

Cómo responder un envío de formulario

Después de recibir los datos de un mensaje de tarjeta o un diálogo, la app de Chat responde confirmando la recepción o devolviendo un error.

En el siguiente ejemplo, una app de Chat envía un mensaje de texto para confirmar que recibió correctamente un formulario enviado desde un mensaje de tarjeta.

Node.js

node/chat/contact-form-app/index.js
return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
  text: "✅ " + event.commonEventObject.parameters["contactName"] + " has been added to your contacts."
}}}}};

Python

python/chat/contact-form-app/main.py
return { 'hostAppDataAction': { 'chatDataAction': { 'createMessageAction': { 'message': {
  'text': "✅ " + event.get('commonEventObject').get('parameters')["contactName"] + " has been added to your contacts."
}}}}}

Java

java/chat/contact-form-app/src/main/java/com/google/chat/contact/App.java
return new GenericJson() {{
  put("hostAppDataAction", new GenericJson() {{
    put("chatDataAction", new GenericJson() {{
      put("createMessageAction", new GenericJson() {{
        put("message", new Message()
          .setText( "✅ " + event.at("/commonEventObject/parameters/contactName").asText() +
                    " has been added to your contacts."));
      }});
    }});
  }});
}};

Apps Script

En este ejemplo, se envía un mensaje de tarjeta con la devolución del JSON de la tarjeta. También puedes usar el servicio de tarjetas de Apps Script.

apps-script/chat/contact-form-app/Code.gs
return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
  text: "✅ " + event.commonEventObject.parameters["contactName"] + " has been added to your contacts."
}}}}};

Para procesar y cerrar un diálogo, debes devolver un objeto RenderActions que especifique si deseas enviar un mensaje de confirmación, actualizar el mensaje o la tarjeta original, o simplemente cerrar el diálogo. Para conocer los pasos, consulta Cómo cerrar un diálogo.

Solucionar problemas

En esta sección, se proporcionan pasos para solucionar problemas relacionados con códigos de error y comportamientos de entorno de ejecución específicos cuando se interactúa con diálogos en Chat.

Las interacciones de diálogo devuelven el mensaje "Error no especificado al invocar el complemento"

Cuando interactúas con un diálogo, si ves registros de errores con el mensaje "Error no especificado al invocar el complemento" y el código 13, por lo general, indica un error interno o que el extremo HTTP de la app de Chat no pudo procesar la solicitud o devolver una respuesta válida.

Para solucionar este error, haz lo siguiente:

  • Revisa los registros de tu extremo HTTP para ver si hay excepciones no controladas o fallas.
  • Verifica que tu extremo responda a las solicitudes en un plazo de 30 segundos. Si el extremo tarda más de 30 segundos en ejecutarse, Chat no puede procesar la respuesta y la interacción falla. Para obtener más detalles, consulta Límites de frecuencia y prácticas recomendadas.
  • Asegúrate de que tu extremo devuelva una respuesta válida. Para los envíos de diálogos, el extremo debe devolver un objeto RenderActions en el formato JSON correcto. Si la respuesta tiene un formato incorrecto o no contiene los campos obligatorios, es posible que falle la interacción del diálogo.

Cuando una app de Google Chat o una tarjeta devuelven un error, la interfaz de Chat muestra el mensaje "Se produjo un error". o "No se puede procesar tu solicitud". A veces, la IU de Chat no muestra ningún mensaje de error, pero la app o tarjeta de Chat produce un resultado inesperado; por ejemplo, es posible que no aparezca un mensaje de tarjeta.

Aunque es posible que no se muestre un mensaje de error en la IU de Chat, hay mensajes de error descriptivos y datos de registro disponibles para ayudarte a corregir errores cuando se activa el registro de errores para las apps de Chat. Para obtener ayuda con la visualización, la depuración y la corrección de errores, consulta Cómo solucionar y corregir errores de Google Chat.

Apps de Chat que no son complementos: Lee los datos de formularios que ingresan los usuarios en las tarjetas

La siguiente documentación se aplica a las apps de Chat que no son complementos de Google Workspace. Para migrar una app de Chat que no sea un complemento, consulta Cómo convertir una app de Google Chat en un complemento de Google Workspace.

Crea formularios con tarjetas

Para ver un ejemplo de una app de Chat que no es un complemento y que usa un formulario de contacto con una entrada de texto, un selector de fecha y hora, y una entrada de selección, consulta el siguiente código:

Node.js

node/contact-form-app/index.js
/**
 * The section of the contact card that contains the form input widgets. Used in a dialog and card message.
 * To add and preview widgets, use the Card Builder: https://addons.gsuite.google.com/uikit/builder
 */
const CONTACT_FORM_WIDGETS = [
  {
    "textInput": {
      "name": "contactName",
      "label": "First and last name",
      "type": "SINGLE_LINE"
    }
  },
  {
    "dateTimePicker": {
      "name": "contactBirthdate",
      "label": "Birthdate",
      "type": "DATE_ONLY"
    }
  },
  {
    "selectionInput": {
      "name": "contactType",
      "label": "Contact type",
      "type": "RADIO_BUTTON",
      "items": [
        {
          "text": "Work",
          "value": "Work",
          "selected": false
        },
        {
          "text": "Personal",
          "value": "Personal",
          "selected": false
        }
      ]
    }
  }
];

Python

python/contact-form-app/main.py
# The section of the contact card that contains the form input widgets. Used in a dialog and card message.
# To add and preview widgets, use the Card Builder: https://addons.gsuite.google.com/uikit/builder
CONTACT_FORM_WIDGETS = [
  {
    "textInput": {
      "name": "contactName",
      "label": "First and last name",
      "type": "SINGLE_LINE"
    }
  },
  {
    "dateTimePicker": {
      "name": "contactBirthdate",
      "label": "Birthdate",
      "type": "DATE_ONLY"
    }
  },
  {
    "selectionInput": {
      "name": "contactType",
      "label": "Contact type",
      "type": "RADIO_BUTTON",
      "items": [
        {
          "text": "Work",
          "value": "Work",
          "selected": False
        },
        {
          "text": "Personal",
          "value": "Personal",
          "selected": False
        }
      ]
    }
  }
]

Java

java/contact-form-app/src/main/java/com/google/chat/contact/App.java
// The section of the contact card that contains the form input widgets. Used in a dialog and card message.
// To add and preview widgets, use the Card Builder: https://addons.gsuite.google.com/uikit/builder
final static private List<GoogleAppsCardV1Widget> CONTACT_FORM_WIDGETS = List.of(
  new GoogleAppsCardV1Widget().setTextInput(new GoogleAppsCardV1TextInput()
    .setName("contactName")
    .setLabel("First and last name")
    .setType("SINGLE_LINE")),
  new GoogleAppsCardV1Widget().setDateTimePicker(new GoogleAppsCardV1DateTimePicker()
    .setName("contactBirthdate")
    .setLabel("Birthdate")
    .setType("DATE_ONLY")),
  new GoogleAppsCardV1Widget().setSelectionInput(new GoogleAppsCardV1SelectionInput()
    .setName("contactType")
    .setLabel("Contact type")
    .setType("RADIO_BUTTON")
    .setItems(List.of(
      new GoogleAppsCardV1SelectionItem()
        .setText("Work")
        .setValue("Work")
        .setSelected(false),
      new GoogleAppsCardV1SelectionItem()
        .setText("Personal")
        .setValue("Personal")
        .setSelected(false)))));

Apps Script

apps-script/contact-form-app/contactForm.gs
/**
 * The section of the contact card that contains the form input widgets. Used in a dialog and card message.
 * To add and preview widgets, use the Card Builder: https://addons.gsuite.google.com/uikit/builder
 */
const CONTACT_FORM_WIDGETS = [
  {
    "textInput": {
      "name": "contactName",
      "label": "First and last name",
      "type": "SINGLE_LINE"
    }
  },
  {
    "dateTimePicker": {
      "name": "contactBirthdate",
      "label": "Birthdate",
      "type": "DATE_ONLY"
    }
  },
  {
    "selectionInput": {
      "name": "contactType",
      "label": "Contact type",
      "type": "RADIO_BUTTON",
      "items": [
        {
          "text": "Work",
          "value": "Work",
          "selected": false
        },
        {
          "text": "Personal",
          "value": "Personal",
          "selected": false
        }
      ]
    }
  }
];

Cómo recibir datos de widgets interactivos

Cada vez que los usuarios hacen clic en un botón, las apps de Chat que no son complementos reciben un evento de interacción que depende de la ubicación del botón:

  • Si el botón está en un mensaje o diálogo, las apps de Chat que no son complementos reciben un evento de interacción CARD_CLICKED que contiene información sobre la interacción. La carga útil de los eventos de interacción CARD_CLICKED contiene un objeto common.formInputs (event.common.formInputs) con los valores que ingresa el usuario.

    Puedes recuperar los valores del objeto common.formInputs.WIDGET_NAME, en el que WIDGET_NAME es el campo name que especificaste para el widget. Los valores se devuelven como un tipo de datos específico para el widget (representado como un objeto Inputs).

    A continuación, se muestra una parte de un evento de interacción de CARD_CLICKED en el que un usuario ingresó valores para cada widget:

    HTTP

    {
      "type": "CARD_CLICKED",
      "common": { "formInputs": {
        "contactName": { "stringInputs": {
          "value": ["Kai 0"]
        }},
        "contactBirthdate": { "dateInput": {
          "msSinceEpoch": 1000425600000
        }},
        "contactType": { "stringInputs": {
          "value": ["Personal"]
        }}
      }}
    }
    

    Apps Script

    {
      "type": "CARD_CLICKED",
      "common": { "formInputs": {
        "contactName": { "": { "stringInputs": {
          "value": ["Kai 0"]
        }}},
        "contactBirthdate": { "": { "dateInput": {
          "msSinceEpoch": 1000425600000
        }}},
          "contactType": { "": { "stringInputs": {
          "value": ["Personal"]
        }}}
      }}
    }
    
  • Si el botón está en una página principal, las apps de Chat que no son complementos reciben un evento de interacción SUBMIT_FORM. La carga útil del evento de interacción contiene un objeto commonEventObject.formInputs (event.commonEventObject.formInputs) con los valores que ingresa el usuario.

    Puedes recuperar los valores del objeto commonEventObject.formInputs.WIDGET_NAME, en el que WIDGET_NAME es el campo name que especificaste para el widget. Los valores se devuelven como un tipo de datos específico para el widget (representado como un objeto Inputs).

    A continuación, se muestra una parte de un evento de interacción de SUBMIT_FORM en el que un usuario ingresó valores para cada widget:

    HTTP

    {
      "type": "SUBMIT_FORM",
      "commonEventObject": { "formInputs": {
        "contactName": { "stringInputs": {
          "value": ["Kai 0"]
        }},
        "contactBirthdate": { "dateInput": {
          "msSinceEpoch": 1000425600000
        }},
        "contactType": { "stringInputs": {
          "value": ["Personal"]
        }}
      }}
    }
    

    Apps Script

    {
      "type": "SUBMIT_FORM",
      "commonEventObject": { "formInputs": {
        "contactName": { "": { "stringInputs": {
          "value": ["Kai 0"]
        }}},
        "contactBirthdate": { "": { "dateInput": {
          "msSinceEpoch": 1000425600000
        }}},
          "contactType": { "": { "stringInputs": {
          "value": ["Personal"]
        }}}
      }}
    }
    

Para recibir los datos, una app de Chat que no sea un complemento controla el evento de interacción para obtener los valores que los usuarios ingresan en los widgets. En la siguiente tabla, se muestra cómo obtener el valor de un widget de entrada de formulario determinado. Para cada widget, la tabla muestra el tipo de datos que acepta el widget, dónde se almacena el valor en el evento de interacción y un valor de ejemplo.

Widget de entrada de formulario Tipo de datos de entrada Es el valor de entrada del evento de interacción. Valor de ejemplo
textInput stringInputs event.common.formInputs.contactName.stringInputs.value[0] Kai O
selectionInput stringInputs Para obtener el primer o único valor, event.common.formInputs.contactType.stringInputs.value[0] Personal
dateTimePicker que solo acepta fechas. dateInput event.common.formInputs.contactBirthdate.dateInput.msSinceEpoch. 1000425600000

Transfiere datos a otra tarjeta

Para transferir la entrada de datos de la tarjeta inicial en una app de Chat que no sea un complemento, compila el widget button con actionParameters que contengan el name del widget y el valor que ingresa el usuario, como se muestra en el siguiente ejemplo:

Node.js

node/contact-form-app/index.js
buttonList: { buttons: [{
  text: "Submit",
  onClick: { action: {
    function: "submitForm",
    parameters: [{
      key: "contactName", value: name }, {
      key: "contactBirthdate", value: birthdate }, {
      key: "contactType", value: type
    }]
  }}
}]}

Python

python/contact-form-app/main.py
'buttonList': { 'buttons': [{
  'text': "Submit",
  'onClick': { 'action': {
    'function': "submitForm",
    'parameters': [{
      'key': "contactName", 'value': name }, {
      'key': "contactBirthdate", 'value': birthdate }, {
      'key': "contactType", 'value': type
    }]
  }}
}]}

Java

java/contact-form-app/src/main/java/com/google/chat/contact/App.java
new GoogleAppsCardV1Widget().setButtonList(new GoogleAppsCardV1ButtonList().setButtons(List.of(new GoogleAppsCardV1Button()
  .setText("Submit")
  .setOnClick(new GoogleAppsCardV1OnClick().setAction(new GoogleAppsCardV1Action()
    .setFunction("submitForm")
    .setParameters(List.of(
      new GoogleAppsCardV1ActionParameter().setKey("contactName").setValue(name),
      new GoogleAppsCardV1ActionParameter().setKey("contactBirthdate").setValue(birthdate),
      new GoogleAppsCardV1ActionParameter().setKey("contactType").setValue(type))))))))));

Apps Script

apps-script/contact-form-app/main.gs
buttonList: { buttons: [{
  text: "Submit",
  onClick: { action: {
    function: "submitForm",
    parameters: [{
      key: "contactName", value: name }, {
      key: "contactBirthdate", value: birthdate }, {
      key: "contactType", value: type
    }]
  }}
}]}

Cuando un usuario hace clic en el botón, la app de Chat que no es un complemento recibe un evento de interacción CARD_CLICKED del que puedes recibir datos.

Cómo responder un envío de formulario

En el siguiente ejemplo, una app de Chat que no es un complemento envía un mensaje de texto para confirmar que recibió correctamente un formulario enviado desde un mensaje de diálogo o de tarjeta:

Node.js

node/contact-form-app/index.js
const contactName = event.common.parameters["contactName"];
// Checks to make sure the user entered a contact name.
// If no name value detected, returns an error message.
const errorMessage = "Don't forget to name your new contact!";
if (!contactName && event.dialogEventType === "SUBMIT_DIALOG") {
  return { actionResponse: {
    type: "DIALOG",
    dialogAction: { actionStatus: {
      statusCode: "INVALID_ARGUMENT",
      userFacingMessage: errorMessage
    }}
  }};
}

Python

python/contact-form-app/main.py
contact_name = event.get('common').get('parameters')["contactName"]
# Checks to make sure the user entered a contact name.
# If no name value detected, returns an error message.
error_message = "Don't forget to name your new contact!"
if contact_name == "" and "SUBMIT_DIALOG" == event.get('dialogEventType'):
  return { 'actionResponse': {
    'type': "DIALOG",
    'dialogAction': { 'actionStatus': {
      'statusCode': "INVALID_ARGUMENT",
      'userFacingMessage': error_message
    }}
  }}

Java

java/contact-form-app/src/main/java/com/google/chat/contact/App.java
String contactName = event.at("/common/parameters/contactName").asText();
// Checks to make sure the user entered a contact name.
// If no name value detected, returns an error message.
String errorMessage = "Don't forget to name your new contact!";
if (contactName.isEmpty() && event.at("/dialogEventType") != null && "SUBMIT_DIALOG".equals(event.at("/dialogEventType").asText())) {
  return new Message().setActionResponse(new ActionResponse()
    .setType("DIALOG")
    .setDialogAction(new DialogAction().setActionStatus(new ActionStatus()
      .setStatusCode("INVALID_ARGUMENT")
      .setUserFacingMessage(errorMessage))));
}

Apps Script

apps-script/contact-form-app/main.gs
const contactName = event.common.parameters["contactName"];
// Checks to make sure the user entered a contact name.
// If no name value detected, returns an error message.
const errorMessage = "Don't forget to name your new contact!";
if (!contactName && event.dialogEventType === "SUBMIT_DIALOG") {
  return { actionResponse: {
    type: "DIALOG",
    dialogAction: { actionStatus: {
      statusCode: "INVALID_ARGUMENT",
      userFacingMessage: errorMessage
    }}
  }};
}

Para procesar y cerrar un diálogo en una app de Chat que no sea un complemento, devuelve un objeto ActionResponse que especifique si quieres enviar un mensaje de confirmación, actualizar el mensaje o la tarjeta original, o simplemente cerrar el diálogo. Para conocer los pasos, consulta Cómo cerrar un diálogo.