> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.onesignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Confirmación de reserva y recuperación de reserva abandonada

> Use eventos personalizados, Journeys y Data Feeds para enviar correos de confirmación de reserva o recuperación basados en el estado de la reserva en tiempo real.

## Descripción general

En este tutorial, configurará un flujo de trabajo de reserva común:

* Enviar un correo de confirmación de reserva después de que un usuario complete una reserva.
* Enviar un correo de recuperación si un usuario comienza una reserva pero no la completa a tiempo.

Al final, tendrá:

* Dos Eventos Personalizados (`booking_started`, `booking_complete`)
* Un Journey que se ramifica según la finalización vs el abandono
* Un Data Feed de reserva para detalles de confirmación
* Un Data Feed de cupón opcional para incentivos de recuperación

Esta guía se centra en configurar OneSignal. Su sistema de reservas y backend se pueden implementar en cualquier lenguaje o framework.

### Flujo de configuración

1. Su aplicación rastrea un [evento personalizado](./custom-events) `booking_started`.
2. Esto ingresa al usuario en un Journey.
3. El Journey espera un evento `booking_complete` y si no se recibe a tiempo, envía recordatorios de seguimiento.
4. Si la reserva se completa, OneSignal llama a un Data Feed de reserva al momento del envío y envía un correo de confirmación con los últimos detalles de la reserva.
5. Si la reserva no se completa dentro de la ventana de espera, el Journey sigue la ruta de expiración y envía un correo de recuperación.

***

## Configuración

### Requisitos previos

Antes de comenzar, asegúrese de tener:

* Una aplicación OneSignal con el canal de [**Email**](./email-setup) habilitado
* Un endpoint backend que pueda devolver datos de reserva y/o cupón como JSON
* Un identificador de usuario estable compartido entre su aplicación, backend y [ID Externo](./users#external-id) de OneSignal
* Acceso a [Eventos Personalizados](./custom-events)

### 1. Rastrear eventos de reserva

Rastree los siguientes Eventos Personalizados. Estos pueden ser desde su aplicación (usando nuestro SDK) o desde su backend (usando nuestra API REST).

**Nombres de eventos:**

* `booking_started` — cuando el usuario comienza el flujo de reserva
* `booking_complete` — cuando la reserva se completa exitosamente

<Tabs>
  <Tab title="Aplicación - Móvil y Web">
    Use el método `trackEvent()` en nuestro [SDK Móvil](./mobile-sdk-reference) y/o [SDK Web](./web-sdk-reference) para enviar eventos personalizados directamente desde su aplicación/sitio web.

    ```js Ejemplo theme={null}
    OneSignal.User.trackEvent("booking_started");
    OneSignal.User.trackEvent("booking_complete");
    ```
  </Tab>

  <Tab title="Lado del Servidor">
    Si está rastreando eventos desde su backend, use la [API de Crear Eventos Personalizados](/reference/create-custom-events) para enviar eventos a OneSignal.

    ```bash theme={null}
    curl --request POST \
      --url https://api.onesignal.com/apps/{app_id}/custom_events \
      --header 'Authorization: Key YOUR_APP_API_KEY' \
      --header 'Content-Type: application/json' \
      --data '{
        "events": [
          {
            "name": "booking_started",
            "external_id": "user123",
            "properties": {}
          }
        ]
      }'
    ```
  </Tab>
</Tabs>

<Note>
  Use la misma identidad de usuario al rastrear eventos y al devolver datos desde su backend. Los IDs que no coinciden son la causa más común de personalización faltante.
</Note>

### 2. Crear alias de Data Feed

En OneSignal, vaya a **Configuraciones > Data Feeds** y cree los siguientes alias.

**Data Feed de Reserva:**
Use este feed para obtener los últimos detalles de la reserva al momento del envío.

* **Alias:** `booking_data`
* **Método:** GET
* **URL:**

```liquid Ejemplo de endpoint theme={null}
https://your-domain.com/datafeed/booking?user_id={{subscription.external_id}}
```

Ejemplo de respuesta:

```json JSON theme={null}
{
  "first_name": "Sam",
  "last_booking": {
    "service_type": "Consultation",
    "booking_date": "January 22, 2026",
    "booking_time": "2:00 PM",
    "price": 45
  }
}
```

**Data Feed de Cupón (opcional):**

Use este feed opcional si desea incluir un código de cupón en su correo de recuperación.

* **Alias:** `coupon`
* **Método:** GET
* **URL:**

```liquid Ejemplo de endpoint theme={null}
https://your-domain.com/datafeed/coupon?user_id={{subscription.external_id}}
```

Ejemplo de respuesta:

```json JSON theme={null}
{
  "first_name": "Sam",
  "code": "PROMO8F3K2",
  "discount_text": "10%",
  "expires_in_hours": 2,
  "deep_link": "https://your-domain.com/checkout?coupon=PROMO8F3K2"
}
```

<Warning>
  Asegure sus endpoints de Data Feed. En producción, envíe una clave API en los headers de solicitud (por ejemplo `x-api-key`) y configure ese header en **Configuraciones > Data Feeds** en lugar de incrustar secretos en la URL.
</Warning>

### 3. Crear plantillas de email

#### Email de confirmación de reserva:

**Asunto:**

```text theme={null}
Sus detalles de reserva
```

**Cuerpo:**

```liquid theme={null}
Hola {{ data_feed.booking_data.first_name | default: "cliente" }},

¡Gracias por su reserva! Aquí están los detalles de su cita:

Servicio: {{ data_feed.booking_data.last_booking.service_type }}
Fecha: {{ data_feed.booking_data.last_booking.booking_date }}
Hora: {{ data_feed.booking_data.last_booking.booking_time }}
Precio: ${{ data_feed.booking_data.last_booking.price }}

¡Esperamos verle!
```

#### Email de recuperación de reserva

**Asunto:**

```text theme={null}
Complete su reserva y ahorre
```

**Cuerpo:**

<Tabs>
  <Tab title="Usando un Data Feed de Cupón">
    ```liquid theme={null}
    Hola {{ data_feed.coupon.first_name | default: "cliente" }},

    Termine su reserva en las próximas {{ data_feed.coupon.expires_in_hours }} horas y ahorre
    {{ data_feed.coupon.discount_text }} con este código:

    {{ data_feed.coupon.code }}

    Úselo aquí:
    {{ data_feed.coupon.deep_link }}
    ```
  </Tab>

  <Tab title="Sin un Data Feed de Cupón">
    ```text theme={null}
    Hola, 

    ¡Aún no ha completado su reserva! 

    Complétela ahora para ahorrar en su próxima cita.

    Use este enlace para completar su reserva:
    [Insertar enlace profundo aquí]
    ```
  </Tab>
</Tabs>

<Note>
  Siempre incluya filtros `default` en Liquid para prevenir contenido en blanco si falta un campo del Data Feed.
</Note>

### 4. Construir el Journey

1. En OneSignal, vaya a **Mensajes > Journeys > Crear Journey**

2. Configure el **Trigger de Entrada** como:
   * **Evento Personalizado:** `booking_started`

3. Agregue un paso **Esperar Hasta**:
   * **Condición:** Ocurre Evento Personalizado
   * **Nombre del evento:** `booking_complete`
   * **Tiempo máximo de espera:** 10 minutos
   * Habilite la ruta de expiración

4. Configure las ramas:
   * **Completado:** Enviar email de confirmación de reserva
     * **Data Feed:** `booking_data`
   * **Expirado:** Enviar email de recuperación
     * **Data Feed:** `coupon`

<Info>
  La rama de expiración le permite manejar el abandono sin lógica adicional en su aplicación. Vea:

  * [Configuraciones de Journey](./journeys-settings) - para detalles sobre reglas de entrada y salida de Eventos Personalizados
  * [Acciones de Journeys](./journeys-actions) - para detalles sobre pasos de Esperar Hasta y ramas de expiración
</Info>

### 5. Probar y verificar

#### Verificar eventos

Dispare los eventos personalizados desde su aplicación o backend y confirme.

En OneSignal, vaya a **Analytics > Eventos Personalizados** y confirme que ve:

* Eventos `booking_started` aparecen para su ID Externo
* Eventos `booking_complete` aparecen para su ID Externo

#### Verificar Data Feeds

Llame manualmente a sus endpoints de Data Feed usando un ID de usuario conocido y confirme:

* Se devuelve una respuesta 200
* Todos los campos esperados están presentes

#### Verificar emails

Envíe mensajes de prueba desde el editor de Journey y confirme:

* Los emails de reserva contienen detalles de reserva reales
* Los emails de recuperación contienen un cupón válido
* Ninguna variable de Liquid se renderiza vacía

<Check>
  Si falta personalización, confirme que el ID de usuario en la solicitud del Data Feed coincide con el usuario que disparó el Journey.
</Check>

## Ejemplo: Implementación de Data Feed

<Accordion title="Ejemplos de endpoints de Data Feed en Node.js">
  Este ejemplo muestra una implementación mínima de Express para Data Feeds de confirmación de reserva y recuperación. Su lenguaje backend, framework y fuente de datos pueden diferir siempre que la forma de respuesta JSON coincida con sus plantillas de email.

  ### Ejemplo de Data Feed de Reserva

  ```js theme={null}
  import express from "express";

  const app = express();

  function dataFeedAuth(req, res, next) {
    if (req.headers["x-api-key"] !== process.env.DATAFEED_API_KEY) {
      return res.status(401).json({ error: "Unauthorized" });
    }
    next();
  }

  app.get("/datafeed/booking", dataFeedAuth, async (req, res) => {
    const { user_id } = req.query;

    if (!user_id) {
      return res.status(400).json({ error: "Missing user_id" });
    }

    const booking = await getLatestBookingForUser(user_id);

    if (!booking) {
      return res.status(404).json({ error: "No booking found" });
    }

    res.json({
      first_name: booking.first_name,
      last_booking: {
        service_type: booking.service_type,
        booking_date: booking.booking_date,
        booking_time: booking.booking_time,
        price: booking.price
      }
    });
  });
  ```

  ### Ejemplo de Data Feed de Cupón

  ```js theme={null}
  app.get("/datafeed/coupon", dataFeedAuth, async (req, res) => {
    const { user_id } = req.query;

    if (!user_id) {
      return res.status(400).json({ error: "Missing user_id" });
    }

    const coupon = await generateCouponForUser(user_id);

    res.json({
      first_name: coupon.first_name,
      code: coupon.code,
      discount_text: coupon.discount_text,
      expires_in_hours: coupon.expires_in_hours,
      deep_link: coupon.deep_link
    });
  });
  ```

  ### Pautas de implementación

  * Mantenga las respuestas rápidas (los Data Feeds se llaman al momento del envío)
  * Siempre devuelva una estructura JSON predecible
  * Use 404 cuando no existan datos
  * Asegure endpoints con una clave API enviada vía headers de solicitud
</Accordion>

## Problemas comunes

### El email muestra valores vacíos

* El Data Feed devolvió 404
* Los nombres de campo cambiaron en la respuesta JSON
* Discrepancia de identidad de usuario

### El Journey no se ramifica

* Evento `booking_complete` no rastreado
* Discrepancia de nombre de evento (sensible a mayúsculas)
* El evento ocurre fuera de la ventana de espera

### El Data Feed devuelve 401 o 403

* Clave API faltante o inválida
* Header no configurado en las configuraciones del Data Feed

## Próximos pasos

* Agregar propiedades de evento (tipo de servicio, precio) para condiciones de Journey más avanzadas
* Agregar pasos de recuperación adicionales como recordatorios push o SMS
* Usar reglas de salida de Journey para prevenir mensajes de recuperación repetidos

***
