---
title: "Procesar datos de flows en el SDK de Capacitor"
description: "Guarda y utiliza los datos que tus usuarios introducen en los flows de tu app Capacitor con el SDK de Adapty."
---

> **AI agents**: to search Adapty docs faster and with fewer tokens, install the Adapty skill. Claude Code (self-updating via plugin): `claude plugin marketplace add adaptyteam/adapty-skills && claude plugin install adapty-skills@adapty` — other tools: `npx skills add adaptyteam/adapty-skills --all`

Cuando un usuario escribe en un campo de texto, responde un cuestionario o activa un interruptor en un [flow](adapty-flow-builder), el SDK pasa el valor a tu app a través de su callback de análisis.

Las apps suelen usar esos datos para:

- **Registrar usuarios en su propio backend**: Toma el correo electrónico y el nombre que el usuario introdujo en tu flow de onboarding, y crea su cuenta cuando el flow se cierre.
- **Guardar respuestas y preferencias**: Registra lo que el usuario seleccionó para que tu app pueda actuar en consecuencia más adelante — por ejemplo, [escríbelo en su perfil de Adapty](capacitor-setting-user-attributes) como atributos personalizados.
- **Personalizar flows futuros**: Guarda las respuestas del cuestionario como atributos personalizados y luego apunta a un placement posterior para que cada segmento vea un flow diferente, o un paywall distinto dentro de él.
- **Alimentar plataformas de analítica de terceros**: Reenvía las respuestas a Amplitude, Mixpanel o cualquier otra herramienta de analítica de producto que utilices.

Los inputs y los grupos seleccionables reportan sus valores automáticamente. Para distinguirlos en tu código, asigna un **Element ID** significativo a cada input y un **Group ID** a cada grupo seleccionable en el builder.

## Antes de comenzar \{#before-you-start\}

Necesitas:

- **Adapty SDK v4 o posterior**: Los callbacks de flow no existen en versiones anteriores.
- **Un flow creado en el [Flow & Paywall Builder](adapty-flow-builder)**: Solo los flows reportan valores de entrada a través de este callback.
- **Una versión de flow publicada recientemente**: Un flow solo reporta valores de entrada si lo publicaste después de que esta función estuviera disponible. Si no llega nada a tu app, publica una nueva versión del flow e inténtalo de nuevo.

## Recibir valores de entrada \{#receive-input-values\}

Los valores de entrada llegan al mismo handler que cualquier otro evento de analítica de un flow, con el nombre de evento `flow_user_input`. Registra `onAnalytics` junto con el resto de tus handlers de eventos del flow:

```typescript showLineNumbers title="Capacitor"
view.setEventHandlers({
  onAnalytics(name, params) {
    handleFlowInput(name, params);
    return false; // keep the flow open
  },
});
```

El callback `onAnalytics` entrega todos los eventos de analíticas de un flow, incluidas las [vistas de pantalla](capacitor-flow-screen-views).
- El parámetro `name` contiene el nombre del evento. Para filtrar los eventos de entrada del usuario, compara `name` con `flow_user_input`.
- El parámetro `element_type` indica la categoría del elemento.
- El valor de la entrada se almacena en distintos parámetros según el tipo de elemento:
     - Los campos de texto, selectores y alternadores guardan la entrada del usuario en `value`
     - Los grupos seleccionables informan las opciones activas en `item_ids` e `item_titles`

```typescript showLineNumbers title="Capacitor"
function handleFlowInput(name: string, params: Record<string, unknown>) {
  if (name !== 'flow_user_input') return;

  // The screen the input sits on. Pair it with element_id to tell apart
  // two fields that share an Element ID on different screens.
  const screenId = params.instanceId as string;

  switch (params.element_type) {
    case 'text_input':
    case 'email_input':
    case 'number_input':
    case 'phone_input': {
      const text = params.value as string;
      break;
    }
    case 'date_picker':
    case 'time_picker':
    case 'date_time_picker': {
      // Unix time in milliseconds.
      const date = new Date(params.value as number);
      break;
    }
    case 'single_choice': {
      const optionId = (params.item_ids as string[])[0];
      break;
    }
    case 'multi_choice': {
      const optionIds = params.item_ids as string[];
      break;
    }
    case 'toggle': {
      const isOn = params.value as boolean;
      break;
    }
  }
}
```

Para confirmar que el callback se ejecuta, interactúa con el input en una versión de prueba de tu propia app. Si tu callback handler no recibe ningún evento, revisa los [prerequisitos](#before-you-start). Asegúrate de que el flow fue publicado después de que esta función estuviera disponible.

## Cuando tu app recibe el input \{#when-your-app-receives-the-input\}

:::important
El valor predeterminado de entrada o selección nunca llega a tu aplicación a través de este callback. Si un usuario acepta la opción marcada como **Set as default** y continúa, no se dispara ningún evento. No interpretes la ausencia del evento como "sin respuesta": el usuario simplemente dejó el valor predeterminado tal como estaba.
:::

**Los siguientes elementos disparan este evento:**

- Campos de texto, email, número y teléfono
- Selectores de fecha, hora y fecha-hora
- Grupos seleccionables de opción única y múltiple, y toggles

**Los siguientes no lo hacen:**

- Campos de contraseña, selecciones de productos y cambios de pestaña
- Inputs dentro de un elemento Header, que se comparte entre pantallas
- Grupos seleccionables con IDs de opción duplicados o faltantes, o con un ID de grupo reutilizado en otra pantalla. Ese tipo de grupo no envía nada en absoluto, en lugar de enviar parte de la respuesta.

**El evento se dispara cuando:**

- Un campo pierde el foco. Un campo vaciado reporta una cadena vacía; un campo que el usuario nunca editó no reporta nada. Un marcador de posición no es un valor. Si el usuario vuelve al campo, lo edita y lo abandona de nuevo, se genera un segundo evento.
- El usuario cierra un selector tras elegir un nuevo valor. Cerrarlo sin cambios no reporta nada.
- El usuario pulsa una opción o un toggle. Un evento de opción múltiple lista todas las opciones seleccionadas, por lo que deseleccionar la última envía dos arrays vacíos.

**El evento no se dispara cuando:**

- El usuario escribe. No hay flujo de pulsaciones de teclas, solo el valor que tiene el campo cuando pierde el foco.
- El usuario envía o cierra el flow. Un valor que aún se está editando en ese momento puede perderse; la sección de [limitaciones de entrega](#delivery-and-limitations) explica cómo diseñar la última pantalla teniendo esto en cuenta.
- Se establece un valor sin interacción del usuario. Una opción marcada como **Set as default** se preselecciona al abrirse la pantalla, y una acción **Set Variable** puede seleccionar una opción o rellenar un input a partir de otra interacción. Ninguna de las dos envía un evento; un input prerrellenado solo se reporta una vez que el usuario lo edita.

## Qué recibes \{#what-you-receive\}

El callback entrega dos eventos. Filtra por `name` para mostrar únicamente los eventos `flow_user_input`. El payload JSON de respuesta tiene este aspecto:

```json
{
  "name": "flow_user_input",
  "instanceId": "scr_registration",
  "isBackendEvent": false,
  "isCustomerEvent": true,
  "element_id": "email",
  "element_type": "email_input",
  "value": "jane@example.com"
}
```

| Parámetro         | Descripción |
|:------------------|:------------|
| `name`            | `flow_user_input` para eventos de entrada, `flow_screen_showed` para vistas de pantalla. |
| `instanceId`      | El ID de la pantalla del input. Los IDs de elemento son únicos dentro de una pantalla, no en todo el flow. Si tu flow tiene inputs en más de una pantalla, combina `instanceId` con `element_id` al filtrar eventos. |
| `element_id`      | El **Element ID** del [input](builder-inputs-and-forms), o el **Group ID** del [grupo seleccionable](flow-selectable-elements). |
| `element_type`    | El tipo de elemento que envió el evento. Determina cuál de los parámetros siguientes contiene el valor del input. |
| `value`           | **Solo campos de texto, selectores y toggles.** El valor del input: una cadena para campos de texto, un entero para selectores, un booleano para toggles. |
| `item_ids`        | **Solo grupos seleccionables de elección única y múltiple.** Los **Element IDs** de las opciones seleccionadas, en el orden en que aparecen en el builder. Una entrada para un grupo de elección única; cualquier número para un grupo de elección múltiple. |
| `item_titles`     | **Solo grupos seleccionables de elección única y múltiple.** Los títulos de las opciones listadas en `item_ids`, en el mismo orden. Nunca está vacío: una opción sin título informa su ID en su lugar. |
| `isCustomerEvent` | Indicador de utilidad, siempre `true` para este evento. Marca los eventos que el SDK entrega a tu callback. Resulta útil si un mismo handler reenvía todos los eventos del flow a tu sistema de análisis y filtras por este indicador en lugar de por `name`. |
| `isBackendEvent`  | Indicador de utilidad, siempre `false` para este evento. Marca los eventos que Adapty también registra para su propio análisis. `false` confirma que lo que los usuarios introducen llega a tu app y solo a ella — Adapty no lo recibe ni lo almacena. |

Lo que reporta cada elemento:

| En el builder | `element_type` | Parámetro que almacena el valor | Qué contiene |
|:--------------|:---------------|:--------------------------------|:-------------|
| Input de **Texto**, **Número**, **Número de teléfono** | `text_input`, `number_input`, `phone_input` | `value` | La cadena literal que escribió el usuario. Los números llegan como cadenas, no como tipos numéricos. |
| Input de **E-mail** | `email_input` | `value` | La cadena literal que escribió el usuario, aunque no haya superado la validación de formato del builder. Valídala en tu lado antes de usarla. |
| Input de **Contraseña** | ninguno | ninguno | No envía ningún evento. |
| Input de **Fecha** | `date_picker` | `value` | Tiempo Unix en milisegundos, como entero, a medianoche local de la fecha seleccionada. |
| Input de **Hora** | `time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. |
| Input de **Fecha y hora** | `date_picker` y `time_picker` | `value` | Dos elementos, un selector de fecha y un selector de hora. Cada uno envía su propio evento. |
| Input cambiado a **Fecha y hora** en el desplegable **Type** | `date_time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. |
| Grupo de **elección única** | `single_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: un array con el Element ID de la opción seleccionada. `item_titles`: un array con el título de esa opción. |
| Grupo de **elección múltiple** | `multi_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: los Element IDs de todas las opciones seleccionadas, en el orden en que aparecen en el builder. `item_titles`: sus títulos, en el mismo orden. Ambos arrays están vacíos cuando no hay nada seleccionado. |
| Grupo de **Toggle** | `toggle` | `value` | Un booleano. |

Para bifurcar según una respuesta, compara `item_ids`, no `item_titles`. El título es derivado: el **Element Title** de la opción si lo definiste, si no su texto en tu idioma predeterminado, y si no su Element ID. Un usuario que vio el flow en otro idioma vio un texto diferente.

## Ejemplos de eventos \{#event-examples\}

Estos ejemplos muestran las propiedades disponibles en cada evento, con valores ilustrativos en los comentarios.

<Details>
<summary>Entrada de texto, email, número y teléfono (Haz clic para expandir)</summary>

```typescript
onAnalytics(name, params) {
    name;                      // 'flow_user_input'
    params.name;               // 'flow_user_input'
    params.instanceId;         // 'scr_J260KU5q'
    params.isCustomerEvent;    // true
    params.isBackendEvent;     // false
    params.element_id;         // 'email'
    params.element_type;       // 'email_input'
    params.value;              // 'jane@example.com'   (string)
}
```
</Details>

<Details>
<summary>Selectores de fecha, hora y fecha-hora (Click to expand)</summary>

```typescript
onAnalytics(name, params) {
    params.element_id;         // 'birthday'
    params.element_type;       // 'date_picker'
    params.value;              // 645408000000   (Unix milliseconds — 1990-06-15, local midnight)
}
```
</Details>

<Details>
<summary>Single choice (Click to expand)</summary>

```typescript
onAnalytics(name, params) {
    params.element_id;         // 'experience'
    params.element_type;       // 'single_choice'
    params.item_ids;           // ['pro']
    params.item_titles;        // ['I train professionally']
}
```
</Details>

<Details>
<summary>Multi choice (Haz clic para expandir)</summary>

```typescript
onAnalytics(name, params) {
    params.element_id;         // 'interests'
    params.element_type;       // 'multi_choice'
    params.item_ids;           // ['sports', 'music']
    params.item_titles;        // ['Sports', 'Music']
}
```
</Details>

<Details>
<summary>Toggle (Haz clic para expandir)</summary>

```typescript
onAnalytics(name, params) {
    params.element_id;         // 'reminders'
    params.element_type;       // 'toggle'
    params.value;              // true   (boolean)
}
```
</Details>

## Entrega y limitaciones \{#delivery-and-limitations\}

:::warning
Los flows envían valores sin procesar: direcciones de correo electrónico, números de teléfono y cualquier otra cosa que el usuario escriba. Trata todo lo que entregue el callback de analíticas como datos personales y no los almacenes en ningún lugar donde no guardarías el correo electrónico de un usuario.
:::

- **El último valor prevalece**: recibes un evento por campo con el valor con el que el usuario se quedó, no una secuencia tecla a tecla. Si edita un campo, solo llega la última versión.
- **Sin garantía de envío**: los valores te llegan a medida que el usuario avanza por el flow, y puede salir en cualquier momento. Espera a que el flow se cierre antes de considerar un conjunto de respuestas como completo.
- **Entrega de mejor esfuerzo**: si un usuario cierra el flow o minimiza la app mientras un campo tiene el foco o un selector está abierto, ese valor puede perderse.

Para que el valor del último campo sea fiable, termina el flow con una pantalla que no tenga entradas ni selectores, y cierra el flow mediante una acción explícita del usuario en lugar de hacerlo automáticamente al aparecer esa pantalla. Pasar a la pantalla final quita el foco del campo anterior, que es lo que envía su valor.

Guarda cada valor de entrada tal como lo recibe tu handler y envía el conjunto completo cuando el flow se cierre. Para detectar ese momento, registra un handler `onDisappeared` junto a `onAnalytics`. Se ejecuta cuando la vista del flow se cierra, ya sea porque el usuario completó el flow o lo cerró a medias.

## Casos de uso \{#use-cases\}

### Registrar usuarios en tu backend \{#register-users-on-your-backend\}

Recoge los valores a medida que llegan y envíalos en cuanto el flow se cierre, para que una sola petición contenga el conjunto completo de respuestas.

El flow desaparece tanto si el usuario lo completó como si lo abandonó a mitad. Comprueba que tienes los campos que necesitas antes de llamar a tu backend.

El flow no puede mostrar errores procedentes de tu backend. El valor de retorno del handler solo cierra la vista del flow, y el SDK no dispone de ningún método para enviar datos a un flow en ejecución. Si el registro falla —por ejemplo, porque el email ya está en uso—, muestra el error en tu propia UI después de que el flow se cierre.

```typescript showLineNumbers title="Capacitor"
const flowAnswers: Record<string, string> = {};

view.setEventHandlers({
  onAnalytics(name, params) {
    if (name === 'flow_user_input' && typeof params.value === 'string') {
      flowAnswers[params.element_id as string] = params.value;
    }
    return false;
  },
  onDisappeared() {
    if (flowAnswers.email) {
      // Send flowAnswers to your backend here to create the account.
    }
    return false;
  },
});
```

### Enriquece los perfiles de usuario con datos \{#enrich-user-profiles-with-data\}

Para vincular lo que un usuario introduce con su perfil y evitar pedirle los mismos datos dos veces, [actualiza el perfil de usuario](capacitor-setting-user-attributes) a medida que lleguen los valores.

Por ejemplo, si tu flow tiene un campo de texto con el ID de elemento `name` y un campo de correo electrónico con el ID de elemento `email`:

```typescript showLineNumbers title="Capacitor"
function handleFlowInput(name: string, params: Record<string, unknown>) {
  if (name !== 'flow_user_input') return;
  if (typeof params.value !== 'string') return;

  const profileParams: Partial<AdaptyProfileParameters> = {};

  switch (params.element_id) {
    case 'name':
      profileParams.firstName = params.value;
      break;
    case 'email':
      profileParams.email = params.value;
      break;
    default:
      return;
  }

  adapty.updateProfile(profileParams).catch((error) => {
    // handle the error
  });
}
```

### Personalizar flows mostrados más adelante \{#customize-flows-shown-later\}

Las respuestas del quiz también pueden decidir qué ve un usuario en un [placement](placements) posterior: un flow diferente o un paywall distinto dentro de él.

Por ejemplo, pregunta a los usuarios sobre su experiencia con el deporte en tu flow de onboarding y luego muestra a cada grupo su propio flow con productos y textos diferentes.

1. Añade un [quiz](onboarding-quizzes) a tu flow. Asigna al [grupo seleccionable](flow-selectable-elements) el Group ID `experience` y a cada opción un Element ID significativo.
2. Gestiona las respuestas y [establece atributos personalizados](capacitor-setting-user-attributes) para el usuario.

```typescript showLineNumbers title="Capacitor"
function handleFlowInput(name: string, params: Record<string, unknown>) {
  if (name !== 'flow_user_input') return;
  if (params.element_id !== 'experience') return;

  const optionId = (params.item_ids as string[])[0];

  adapty
    .updateProfile({
      // Set the custom attribute 'experience' to the option the user selected
      // (beginner, amateur, or pro).
      codableCustomAttributes: { experience: optionId },
    })
    .catch((error) => {
      // handle the error
    });
}
```

3. [Crea un segmento](segments) para cada valor de atributo personalizado.
4. Crea un [placement](placements) y añade una [audiencia](audience) para cada segmento.
5. [Muestra el flow](capacitor-present-paywalls) de ese placement en tu app.