> ## 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.

# Construir un carrusel de onboarding con botones

> Crea un flujo de onboarding de múltiples pasos usando Mensajes In-App HTML con navegación por botones y cierre automático.

## Descripción general

Este tutorial te muestra cómo crear un **carrusel de onboarding de múltiples pasos** usando un único Mensaje In-App HTML. A diferencia de los carruseles tradicionales que dependen de gestos de deslizamiento, este enfoque usa **navegación por botones** y mantiene todos los pasos dentro de un solo mensaje.

**Lo que construirás:**

* Un flujo de onboarding de dos pasos con imágenes, texto y botones
* Navegación por botones (toca "Siguiente" para avanzar, toca "Comenzar" para cerrar)
* Puntos indicadores de progreso
* Transiciones suaves de desvanecimiento entre pasos

<Frame caption="Ejemplo de carrusel de onboarding con navegación por botones">
  <img src="https://mintcdn.com/onesignal/t_Vr4vLAhranGx-O/images/docs/onboarding-carousel-demo.gif?s=39418d8af9896323694f46f9cb6eebec" alt="Carrusel de onboarding mostrando pantalla de bienvenida con imagen, texto y botón Siguiente" style={{width: '200px', maxWidth: '100%'}} width="768" height="1660" data-path="images/docs/onboarding-carousel-demo.gif" />
</Frame>

**Usa este enfoque cuando quieras:**

* Guiar a los usuarios a través de un **flujo corto de onboarding o educación** (2-5 pasos)
* Requerir que los usuarios **toquen explícitamente un botón** para continuar (sin gestos de deslizamiento)
* Mantener todo dentro de **un único Mensaje In-App HTML** para simplificar
* Cerrar el mensaje automáticamente cuando el flujo se complete

<Info>
  Esta guía usa un Mensaje In-App HTML para control total. También puedes [construir flujos de onboarding basados en tarjetas con el editor de arrastrar y soltar](./design-your-in-app-message#carousels)—esas tarjetas son deslizables pero ofrecen menos personalización.
</Info>

***

## Requisitos previos

Antes de comenzar, asegúrate de tener:

* Una aplicación OneSignal activa con Mensajes In-App habilitados
* [Permiso para crear o editar Mensajes In-App HTML](./manage-team-members#team-members)
* [Mobile SDK](./mobile-sdk-setup) instalado en tu aplicación móvil
* Comprensión básica de HTML, CSS y JavaScript

***

## Cómo funciona el flujo de múltiples pasos

Antes de profundizar en el código, es importante entender el enfoque técnico. Esta implementación usa **un único Mensaje In-App HTML** que cambia entre pasos **mostrando y ocultando contenido**, no cargando múltiples mensajes separados.

La arquitectura se basa en **cuatro componentes principales**:

<Steps>
  <Step title="Contenedores de tarjeta para cada paso">
    Cada paso está envuelto en un `<div>` con la clase `card` y un ID único:

    ```html theme={null}
    <div id="card-0" class="card active">...</div>
    <div id="card-1" class="card">...</div>
    ```

    * Todas las tarjetas existen simultáneamente en el DOM
    * Solo una tarjeta es visible a la vez (controlada por la clase `active`)
  </Step>

  <Step title="Control de visibilidad con CSS">
    CSS maneja la lógica de mostrar/ocultar usando opacidad y eventos de puntero:

    ```css theme={null}
    .card {
      opacity: 0;
      pointer-events: none;  /* Previene interacción con tarjetas ocultas */
      transition: opacity .25s ease;
    }

    .card.active {
      opacity: 1;
      pointer-events: auto;  /* Permite interacción con tarjeta visible */
    }
    ```

    **Por qué esto importa:**

    * `opacity: 0` oculta la tarjeta visualmente pero la mantiene en el diseño
    * `pointer-events: none` previene clics accidentales en tarjetas ocultas
    * `transition` crea efectos suaves de desvanecimiento
  </Step>

  <Step title="Gestión de estado con JavaScript">
    La función `setActive(i)` controla qué tarjeta es visible:

    ```javascript theme={null}
    function setActive(i) {
      // Actualizar visibilidad de tarjetas
      document.getElementById("card-0").className = i === 0 ? "card active" : "card";
      document.getElementById("card-1").className = i === 1 ? "card active" : "card";

      // Actualizar puntos de progreso
      var dots = document.getElementById("dots").children;
      dots[0].classList.toggle("active", i === 0);
      dots[1].classList.toggle("active", i === 1);
    }
    ```

    Esta función:

    * Elimina `active` de todas las tarjetas
    * Agrega `active` a la tarjeta objetivo
    * Actualiza los puntos indicadores de progreso
  </Step>

  <Step title="Listeners de eventos de botón">
    Los botones activan la navegación o el cierre:

    ```javascript theme={null}
    // Avanzar al siguiente paso
    document.getElementById("next-0").addEventListener("click", function () {
      setActive(1);
    });

    // Cerrar el Mensaje In-App
    document.getElementById("done").addEventListener("click", function (e) {
      if (window.OneSignalIamApi && OneSignalIamApi.close) {
        OneSignalIamApi.close(e);
      }
    });
    ```

    **Importante:** `OneSignalIamApi.close(e)` es el método del SDK de OneSignal que cierra el Mensaje In-App desde dentro del HTML.
  </Step>
</Steps>

<Note>
  **Información clave:** Este es un patrón de **aplicación de página única (SPA)** dentro de un Mensaje In-App. Todo el contenido se carga una vez, y JavaScript gestiona los cambios de estado sin recargar.
</Note>

***

## Paso 1: Crear un nuevo Mensaje In-App HTML

1. En el panel de OneSignal, ve a **Messages → In-App Messages**
2. Haz clic en **New In-App Message**
3. Selecciona **HTML** como tipo de mensaje
4. Elige un diseño **Full Screen** o **Large** (recomendado para onboarding para maximizar el impacto visual)
5. Continúa al editor HTML

<Note>
  La vista previa del editor HTML puede no reflejar completamente el comportamiento en tiempo de ejecución. Siempre prueba en un dispositivo real o usuario de prueba para verificar animaciones, comportamiento de botones y la acción de cierre.
</Note>

***

## Paso 2: Agregar la plantilla HTML

Reemplaza el contenido del editor con la plantilla a continuación. Esta plantilla incluye:

* **Código autocontenido:** Todo el HTML, CSS y JavaScript en un archivo
* **Navegación por botones:** Sin gestos de deslizamiento (más confiable en diferentes dispositivos)
* **Transiciones de desvanecimiento:** Cambios suaves de opacidad entre pasos
* **Integración con SDK de OneSignal:** Usa `OneSignalIamApi.close(e)` para cerrar el mensaje
* **Optimizado para móvil:** Diseño responsive con meta tag de viewport

<Accordion title="Ver plantilla HTML completa">
  ```html theme={null}
  <!doctype html>
  <html>
  <head>
    <meta charset="UTF-8" />
    <!-- viewport-fit=cover asegura cobertura de área segura en dispositivos con notch -->
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
    <style>
      /* Estilos base - reset y fuente del sistema */
      html, body {
        margin: 0;
        padding: 0;
        background: #ffffff;
        font-family: -apple-system, system-ui;
      }

      /* Contenedor principal con padding */
      .wrap {
        padding: 28px 22px 24px;
      }

      /* Contenedor de escenario - mantiene todas las tarjetas en la misma posición */
      .stage {
        position: relative;
        min-height: 74vh;  /* Asegura suficiente espacio vertical */
      }

      /* Tarjeta - cada paso del flujo de onboarding */
      .card {
        position: absolute;  /* Todas las tarjetas se superponen en la misma posición */
        inset: 0;            /* Cobertura completa del escenario */
        display: flex;
        flex-direction: column;
        align-items: center;
        opacity: 0;               /* Oculta por defecto */
        pointer-events: none;     /* Previene clics cuando está oculta */
        transition: opacity .25s ease;  /* Efecto suave de desvanecimiento */
      }

      /* Tarjeta activa es visible e interactiva */
      .card.active {
        opacity: 1;
        pointer-events: auto;
      }

      /* Tipografía */
      h1 {
        margin: 44px 0 12px;
        font-size: 26px;
        text-align: center;
      }

      p {
        margin: 0;
        color: #6b7280;
        text-align: center;
        max-width: 260px;
        line-height: 1.35;
      }

      /* Contenedor de imagen - cuadrado con esquinas redondeadas */
      .image {
        width: 240px;
        height: 240px;
        border-radius: 16px;
        margin: 24px 0 12px;
        background-size: cover;
        background-position: center;
      }

      /* Botón principal */
      .btn {
        margin-top: auto;  /* Empuja el botón al fondo de la tarjeta */
        width: 100%;
        max-width: 260px;
        height: 52px;
        border: 0;
        border-radius: 12px;
        background: #3b82f6;  /* Azul - personaliza según tu marca */
        color: #fff;
        font-size: 18px;
        font-weight: 600;
      }

      /* Puntos indicadores de progreso */
      .dots {
        display: flex;
        justify-content: center;
        gap: 8px;
        padding: 12px 0 8px;
      }

      .dot {
        width: 8px;
        height: 8px;
        border-radius: 999px;
        background: #d1d5db;  /* Color de punto inactivo */
      }

      .dot.active {
        background: #6b7280;  /* Color de punto activo */
        transform: scale(1.15);  /* Ligeramente más grande cuando está activo */
      }
    </style>
  </head>

  <body>
    <div class="wrap">
      <div class="stage">

        <!-- PASO 1: Tarjeta de bienvenida (comienza visible con clase "active") -->
        <div id="card-0" class="card active">
          <h1>Bienvenido</h1>
          <div
            class="image"
            style="background-image: url('https://images.pexels.com/photos/6153129/pexels-photo-6153129.jpeg');">
          </div>
          <p>Construye un hábito diario tranquilo en minutos.</p>
          <button
            id="next-0"
            class="btn"
            data-onesignal-unique-label="onboarding_next_0">
            Siguiente
          </button>
        </div>

        <!-- PASO 2: Tarjeta de respiración (comienza oculta, se muestra cuando el usuario toca "Siguiente") -->
        <div id="card-1" class="card">
          <h1>Respira</h1>
          <div
            class="image"
            style="background-image: url('https://images.pexels.com/photos/417173/pexels-photo-417173.jpeg');">
          </div>
          <p>Respiración guiada cuando necesites reiniciarte.</p>
          <button
            id="done"
            class="btn"
            data-onesignal-unique-label="onboarding_done">
            Comenzar
          </button>
        </div>

      </div>

      <!-- Indicador de progreso: 2 puntos, el primero comienza activo -->
      <div class="dots" id="dots">
        <div class="dot active"></div>
        <div class="dot"></div>
      </div>
    </div>

    <script>
      (function () {
        /**
         * Cambiar entre tarjetas alternando la clase "active"
         * @param {number} i - Índice de la tarjeta a mostrar (0 o 1)
         */
        function setActive(i) {
          // Actualizar visibilidad de tarjetas
          document.getElementById("card-0").className = i === 0 ? "card active" : "card";
          document.getElementById("card-1").className = i === 1 ? "card active" : "card";

          // Actualizar puntos de progreso
          var dots = document.getElementById("dots").children;
          dots[0].classList.toggle("active", i === 0);
          dots[1].classList.toggle("active", i === 1);
        }

        // Botón: Siguiente (tarjeta 0 → tarjeta 1)
        document.getElementById("next-0").addEventListener("click", function () {
          setActive(1);
        });

        // Botón: Comenzar (cierra el Mensaje In-App)
        document.getElementById("done").addEventListener("click", function (e) {
          // Verificar si la API IAM de OneSignal está disponible
          if (window.OneSignalIamApi && OneSignalIamApi.close) {
            OneSignalIamApi.close(e);  // Cerrar el mensaje
          }
        });
      })();
    </script>
  </body>
  </html>
  ```
</Accordion>

***

## Paso 3: Personalizar tu contenido

### Seguro para personalizar

Puedes modificar estos elementos sin romper la funcionalidad:

**Contenido:**

* Texto del título en etiquetas `<h1>`
* Texto del cuerpo en etiquetas `<p>`
* Etiquetas de botones (`Siguiente`, `Comenzar`)
* URLs de imágenes en los estilos `background-image: url('...')`

**Estilo visual:**

* Colores: Cambia el fondo de `.btn`, color de texto o colores de puntos
* Espaciado: Ajusta padding y márgenes
* Tipografía: Modifica font-family, font-size, font-weight
* Radio de borde: Actualiza valores de `border-radius` para botones e imágenes

### Agregar más pasos

Para agregar un tercer paso, sigue este patrón:

1. **Agregar la tarjeta HTML:**

```html theme={null}
<div id="card-2" class="card">
  <h1>Tu título</h1>
  <div class="image" style="background-image: url('tu-url-de-imagen');"></div>
  <p>Tu descripción</p>
  <button id="next-2" class="btn">Siguiente</button>
</div>
```

2. **Agregar un punto de progreso:**

```html theme={null}
<div class="dots" id="dots">
  <div class="dot active"></div>
  <div class="dot"></div>
  <div class="dot"></div> <!-- Nuevo punto -->
</div>
```

3. **Actualizar la función `setActive()`:**

```javascript theme={null}
function setActive(i) {
  document.getElementById("card-0").className = i === 0 ? "card active" : "card";
  document.getElementById("card-1").className = i === 1 ? "card active" : "card";
  document.getElementById("card-2").className = i === 2 ? "card active" : "card"; // Nueva tarjeta

  var dots = document.getElementById("dots").children;
  dots[0].classList.toggle("active", i === 0);
  dots[1].classList.toggle("active", i === 1);
  dots[2].classList.toggle("active", i === 2); // Nuevo punto
}
```

4. **Actualizar el ID del botón del paso anterior:**
   Cambia `id="done"` a `id="next-1"` en el botón de la tarjeta 1, luego agrega un listener de clic:

```javascript theme={null}
document.getElementById("next-1").addEventListener("click", function () {
  setActive(2);
});
```

5. **Agregar el botón de cierre a la nueva última tarjeta (card-2):**

```javascript theme={null}
document.getElementById("done").addEventListener("click", function (e) {
  if (window.OneSignalIamApi && OneSignalIamApi.close) {
    OneSignalIamApi.close(e);
  }
});
```

<Warning>
  Mantén los flujos de onboarding cortos (máximo 2-4 pasos). Los usuarios abandonan rápidamente en flujos más largos. Prueba las tasas de finalización con [seguimiento de clics](./in-app-message-api#click-name).
</Warning>

***

## Paso 4: Probar el Mensaje In-App

### Lista de verificación de pruebas

1. **Guarda** el mensaje en el panel de OneSignal
2. **Configura los ajustes de entrega:**
   * Establece condiciones de activación (ej., inicio de sesión, vista de página específica)
   * Elige tu audiencia objetivo o selecciona un usuario de prueba
3. **Envía a un dispositivo de prueba:**
   * Usa [Usuarios de prueba](./test-users) para previsualizar sin afectar usuarios de producción
   * Instala tu app en un dispositivo físico (recomendado sobre simuladores para comportamiento preciso)
4. **Verifica la funcionalidad:**
   * ✓ La primera tarjeta aparece con contenido correcto
   * ✓ El botón "Siguiente" avanza a la tarjeta 2
   * ✓ Los puntos de progreso se actualizan correctamente
   * ✓ Las transiciones de desvanecimiento son suaves
   * ✓ El botón "Comenzar" cierra el mensaje
   * ✓ El mensaje no reaparece inmediatamente (verifica configuración de límite de frecuencia)

<Note>
  Los simuladores/emuladores pueden no reflejar con precisión el comportamiento del dispositivo real, especialmente para interacciones táctiles e integraciones de SDK. Siempre prueba en dispositivos físicos antes de lanzar a producción.
</Note>

### Solución de problemas comunes

| Problema                             | Causa probable                           | Solución                                                                                                                |
| ------------------------------------ | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| El mensaje no aparece                | Condiciones de activación no cumplidas   | Verifica los [Disparadores de Mensajes In-App](./iam-triggers) y confirma que tu usuario de prueba cumple los criterios |
| Los botones no funcionan             | Errores de JavaScript o IDs no coinciden | Revisa la consola del navegador por errores; verifica que los IDs de botones coincidan con los IDs de listeners         |
| Las imágenes no cargan               | Problemas de CORS o URLs inválidas       | Usa URLs HTTPS; prueba las URLs de imágenes en un navegador primero                                                     |
| El mensaje aparece pero no se cierra | SDK de OneSignal no cargado              | Verifica que la [configuración del Mobile SDK](./mobile-sdk-setup) esté completa                                        |

***

## Próximos pasos

**Rastrear engagement de usuarios:**

* Agrega seguimiento de clics usando atributos [`data-onesignal-unique-label`](./in-app-message-api#click-name) (ya incluidos en la plantilla) para medir abandono entre pasos
* Ve las analíticas de clics en **Messages → In-App Messages → \[Tu mensaje] → Analytics**

**Personalizar la experiencia:**

* [Etiqueta usuarios](./in-app-message-api#tag-user) que completen el onboarding (ej., `onboarding_completed: true`)
* Usa etiquetas para [segmentar usuarios](./segmentation) y prevenir que se muestre nuevamente el flujo de onboarding
* [Agrega datos de usuario](./add-user-data-tags) para personalizar contenido en mensajes futuros

**Personalización avanzada:**

* [Deep link](./deep-linking#send-in-app-messages-with-deep-links) a usuarios a una pantalla específica después del cierre
* Usa [sintaxis Liquid](./using-liquid-syntax) para personalizar títulos con nombres de usuario o atributos
* Implementa pruebas A/B con diferentes flujos de onboarding para optimizar tasas de finalización
