# Configuración de Pushwoosh InboxKit para iOS

*Disponible desde el SDK de iOS [7.0.40](https://github.com/Pushwoosh/pushwoosh-ios-sdk/releases/tag/7.0.40).*

Pushwoosh InboxKit ofrece una pantalla de bandeja de entrada moderna de UIKit sobre el backend de bandeja de entrada existente. Seis diseños de celda predeterminados cubren las formas comunes de tarjetas de contenido, desde banners simples hasta carruseles de imágenes, video en línea y pases de Apple Wallet. Los botones de CTA en línea manejan las interacciones más comunes, y toda la superficie está abierta para la creación de subclases si necesita una apariencia personalizada.

<img src="/setting-up-pushwoosh-inboxkit-ios-feed.webp" alt="Feed de InboxKit mostrando tarjetas de banner, con leyenda, clásicas, de carrusel, de video y de Apple Wallet" width="300" style="display: block; margin: 0 auto;"/>

<p style="text-align: center; opacity: 0.7; font-size: 0.875rem; margin-top: 0.5rem;">Feed predeterminado de InboxKit con tarjetas de banner, con leyenda, clásicas, de carrusel, de video y de Apple Wallet.</p>

## Cuándo usar InboxKit

Use InboxKit para cualquier nueva integración de iOS. Es el reemplazo recomendado para el módulo más antiguo de Objective-C [PushwooshInboxUI](/es/developer/pushwoosh-sdk/ios-sdk/setting-up-pushwoosh-inboxui-ios/).

InboxKit le ofrece:

- Seis tipos de celdas integrados — banner, con leyenda, clásica, carrusel, video y Apple Wallet — seleccionados por mensaje a través del `displayType` del payload, o forzados desde el código a través de `attributes.forceCellKind`. Consulte [Tipos de tarjeta](#tipos-de-tarjeta) para la lista completa. (La tarjeta de Apple Wallet es solo para iOS).
- Botones de CTA en línea con una enumeración `PushwooshInboxButtonAction` tipada (`openURL`, `dismiss`, `markRead`, `custom`). El SDK maneja los tres primeros automáticamente; su delegado enruta `custom` a su propia lógica.
- Soporte de anclaje: los mensajes con `actionParams["pinned"] == true` flotan en la parte superior del feed y muestran un glifo de pin.
- Deslizar para eliminar, tirar para actualizar, marcar como leído automáticamente al desaparecer — todo conmutable a través de `PushwooshInboxKitAttributes`.
- Almacenamiento persistente: las eliminaciones y el estado de lectura sobreviven a un reinicio del proceso incluso si la llamada de red aún no ha sido confirmada.
- Una clase base `PushwooshInboxCell` abierta para diseños totalmente personalizados.

El contrato del servidor no ha cambiado — el mismo backend de bandeja de entrada de Pushwoosh, los payloads y las herramientas del panel de control funcionan como antes.

## Elija su método de integración

- [**Configurar InboxKit con Swift Package Manager**](/es/developer/pushwoosh-sdk/ios-sdk/setting-up-pushwoosh-inboxkit-ios/set-up-inboxkit-with-swift-package-manager/) — recomendado para proyectos nuevos.
- [**Configurar InboxKit con CocoaPods**](/es/developer/pushwoosh-sdk/ios-sdk/setting-up-pushwoosh-inboxkit-ios/set-up-inboxkit-with-cocoapods/) — para proyectos que ya usan CocoaPods.

## Tipos de tarjeta

InboxKit elige un diseño de celda por mensaje. El solucionador predeterminado lee `displayType` del payload del push — póngalo dentro del objeto `data`, que el SDK entrega bajo `actionParams`. Cuando falta `displayType`, el solucionador recurre a una heurística: imagen + sin título → banner, imagen + título → con leyenda, de lo contrario clásica. Para forzar un diseño para todo el feed desde el código, establezca `attributes.forceCellKind`.

Cada diseño enriquecido se degrada elegantemente: si el payload requerido está ausente o mal formado, la tarjeta recurre a `classic` en lugar de renderizar un marcador de posición vacío (y se registra un `WARN`).

| `displayType` | Diseño | Campo de payload requerido | Se degrada a |
|---|---|---|---|
| `banner` | Imagen a sangre completa, sin texto | imagen (`inbox_image` o `data.image`) | `classic` cuando no hay imagen |
| `captioned` | Imagen en la parte superior, título + cuerpo debajo | imagen (`inbox_image` o `data.image`) | `classic` cuando no hay imagen |
| `classic` | Avatar inicial coloreado + título + cuerpo | — | — |
| `carousel` | Galería de múltiples imágenes deslizable | `data.carousel` (array de diapositivas) | `classic` cuando no hay diapositivas |
| `video` | Póster con insignia de reproducción, reproductor a pantalla completa al tocar | `data.video` (`url` + `poster` opcional) | `classic` cuando no hay descriptor |
| `wallet` | Botón "Añadir a Apple Wallet" (solo iOS) | `data.wallet` (URL de `.pkpass`) | `classic` cuando no hay URL de pase |

{/* TODO(screenshot): replace each placeholder below with a per-card screenshot. Suggested filenames: setting-up-pushwoosh-inboxkit-ios-card-banner.webp, -captioned.webp, -classic.webp, -carousel.webp, -video.webp, -wallet.webp — then swap each <div> for an <img src="/<filename>" width="240" .../>. */}
<div style="display: flex; flex-wrap: wrap; gap: 1rem; justify-content: center; margin: 1.5rem 0;">
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-banner.webp" alt="Tarjeta de banner de InboxKit" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Tarjeta de banner</strong></figcaption></figure>
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-captioned.webp" alt="Tarjeta con leyenda de InboxKit" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Tarjeta con leyenda</strong></figcaption></figure>
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-classic.webp" alt="Tarjeta clásica de InboxKit" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Tarjeta clásica</strong></figcaption></figure>
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-carousel.webp" alt="Tarjeta de carrusel de InboxKit" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Tarjeta de carrusel</strong></figcaption></figure>
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-video.webp" alt="Tarjeta de video de InboxKit" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Tarjeta de video</strong></figcaption></figure>
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-wallet.webp" alt="Tarjeta de Apple Wallet de InboxKit" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Tarjeta de Apple Wallet</strong></figcaption></figure>
</div>

Las tarjetas de banner, con leyenda y clásicas se basan en los campos de mensaje estándar (imagen, título, cuerpo) más el array opcional `buttons` — consulte [Añadir botones de CTA en línea](#añadir-botones-de-cta-en-línea). Las tarjetas de carrusel, video y Apple Wallet llevan datos estructurados adicionales dentro de `data`, documentados a continuación.

### Tarjeta de carrusel

Un carrusel renderiza varias imágenes de un solo mensaje — una galería deslizable con leyendas opcionales por diapositiva y destinos al tocar. Las diapositivas se encuentran en `data.carousel`. Cada diapositiva necesita una `image`; `title` (superposición de leyenda) y `url` (enlace profundo que se abre al tocar) son opcionales. Una diapositiva sin imagen se descarta; un toque en una diapositiva sin `url` recurre a la acción de fila predeterminada del mensaje.

```json title="POST https://api.pushwoosh.com/json/1.3/createMessage"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "API_TOKEN",
    "notifications": [{
      "send_date": "now",
      "ios_title": "New arrivals",
      "content": "Swipe through this week's drops",
      "inbox_days": 7,
      "data": {
        "displayType": "carousel",
        "carousel": [
          { "image": "https://cdn.example.com/inbox/1.jpg", "title": "New in", "url": "myapp://product/1" },
          { "image": "https://cdn.example.com/inbox/2.jpg", "title": "On sale", "url": "myapp://product/2" },
          { "image": "https://cdn.example.com/inbox/3.jpg" }
        ]
      },
      "platforms": [1]
    }]
  }
}
```

### Tarjeta de video

Una tarjeta de video muestra una imagen de póster con una insignia de reproducción; al tocarla se abre un reproductor a pantalla completa (con sonido, incluso con el interruptor de silencio activado). El descriptor se encuentra en `data.video`: `url` es obligatorio y debe ser una transmisión o archivo `http`/`https`; `poster` es una imagen de vista previa opcional.

```json title="POST https://api.pushwoosh.com/json/1.3/createMessage"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "API_TOKEN",
    "notifications": [{
      "send_date": "now",
      "ios_title": "Watch the reveal",
      "content": "Tap to play",
      "inbox_days": 7,
      "data": {
        "displayType": "video",
        "video": {
          "url": "https://cdn.example.com/inbox/clip.mp4",
          "poster": "https://cdn.example.com/inbox/poster.jpg"
        }
      },
      "platforms": [1]
    }]
  }
}
```

### Tarjeta de Apple Wallet

La tarjeta de Apple Wallet muestra una imagen principal opcional, título y cuerpo sobre el botón oficial **Añadir a Apple Wallet**. Al tocar el botón se descarga el `.pkpass` y se presenta la hoja del sistema para agregar pases. Úsela para entregar cupones, tarjetas de fidelidad, boletos o tarjetas de embarque directamente desde la bandeja de entrada. La tarjeta es solo para iOS / Mac Catalyst — en otras plataformas, el mensaje se renderiza como una tarjeta clásica.

La URL del pase se encuentra en `data.wallet`, ya sea como una cadena simple o como un objeto con un campo `pass`. Un `data.image` opcional añade la imagen principal. El botón se oculta automáticamente cuando no hay URL de pase o el dispositivo no puede agregar pases.

```json title="POST https://api.pushwoosh.com/json/1.3/createMessage"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "API_TOKEN",
    "notifications": [{
      "send_date": "now",
      "ios_title": "Your loyalty card is ready",
      "content": "Add it to Apple Wallet in one tap",
      "inbox_days": 7,
      "data": {
        "displayType": "wallet",
        "image": "https://cdn.example.com/inbox/loyalty.png",
        "wallet": "https://passes.example.com/v1/passes/pass.com.example.loyalty/abc123?token=…"
      },
      "platforms": [1]
    }]
  }
}
```

El resultado se informa a su delegado:

```swift
extension MyInboxHost: PushwooshInboxKitDelegate {

    func inboxKit(_ vc: PushwooshInboxKitViewController,
                  didAddWalletPassFor message: PWInboxMessageProtocol) {
        // The pass is now in the user's Wallet — show a confirmation if you like.
    }

    func inboxKit(_ vc: PushwooshInboxKitViewController,
                  didFailToAddWalletPassFor message: PWInboxMessageProtocol,
                  error: Error?) {
        // Download failed — surface a retry, log, etc.
    }
}
```

Ambas devoluciones de llamada son opcionales (llevan implementaciones vacías predeterminadas). Que un usuario cancele la hoja del sistema no es ni un éxito ni un fracaso, por lo que no se activa ninguna devolución de llamada en ese caso.

## Accesibilidad

Las celdas de InboxKit están listas para VoiceOver desde el primer momento. Las tarjetas de banner, con leyenda y clásicas exponen su título, cuerpo y fecha a través de las etiquetas subyacentes, y los botones de CTA en línea leen sus propios títulos. Las tarjetas enriquecidas añaden semántica explícita:

- **Video** — el póster se expone como un único elemento de botón etiquetado "Reproducir video" (rasgos `.button` + `.startsMediaSession`), por lo que VoiceOver lo anuncia como un control de medios en lugar de una imagen simple.
- **Carrusel** — cada diapositiva es un elemento de botón cuya etiqueta de accesibilidad es la leyenda de la diapositiva, o "Diapositiva" cuando no tiene ninguna. El indicador de página anuncia la posición actual como "*n* de *total*".
- **Apple Wallet** — el botón **Añadir a Apple Wallet** es el `PKAddPassButton` estándar de Apple, que lleva su propia etiqueta de VoiceOver localizada.

Para pruebas de UI y automatización, se establecen dos `accessibilityIdentifier` estables: `inboxkit.video.play` en el póster de video y `inboxkit.wallet.add` en el botón de Wallet.

## Leer datos personalizados de un mensaje

Para que un push aparezca en la bandeja de entrada, la solicitud `createMessage` de la [API de Mensajes](/es/developer/api-reference/messages-api/) debe incluir `inbox_image`, `inbox_date` o `inbox_days` — sin uno de esos campos, el push se entrega como una notificación regular y nunca llega al feed de la bandeja de entrada. Los datos personalizados de formato libre van bajo la clave `data`, que el SDK entrega al cliente como el parámetro `u`:

```json title="POST https://api.pushwoosh.com/json/1.3/createMessage"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "API_TOKEN",
    "notifications": [{
      "send_date": "now",
      "ios_title": "Summer sale",
      "content": "30% off everything — limited time only",
      "inbox_image": "https://cdn.example.com/inbox/summer.png",
      "inbox_days": 7,
      "data": {
        "displayType": "captioned",
        "promo_id": "SUMMER2026",
        "screen": "promo_details"
      },
      "platforms": [1]
    }]
  }
}
```

El SDK expone ese objeto en el mensaje de la bandeja de entrada a través de `actionParams`. Léalo desde el delegado cuando el usuario toque la fila o un CTA en línea:

```swift
extension MyInboxHost: PushwooshInboxKitDelegate {

    func inboxKit(_ vc: PushwooshInboxKitViewController,
                  didSelect message: PWInboxMessageProtocol) -> Bool {
        guard let params = message.actionParams as? [String: Any] else { return true }

        // The custom `data` object arrives under the "u" key —
        // either as a nested dictionary or as a JSON-encoded string,
        // depending on how the payload was built upstream.
        let custom: [String: Any]? = {
            if let dict = params["u"] as? [String: Any] { return dict }
            if let raw = params["u"] as? String,
               let bytes = raw.data(using: .utf8),
               let parsed = try? JSONSerialization.jsonObject(with: bytes) as? [String: Any] {
                return parsed
            }
            return nil
        }()

        if let promoId = custom?["promo_id"] as? String {
            navigateToPromo(promoId)
            return false   // we handled the tap; SDK should not run the default action
        }
        return true
    }
}
```

La misma búsqueda `actionParams["u"]` funciona dentro de `inboxKit(_:didTapButton:onMessage:)` para los botones de CTA en línea. Para los casos de CTA tipados (`openURL`, `dismiss`, `markRead`) el SDK ya realiza la acción predeterminada — devuelva `true` para mantener ese comportamiento, o `false` para suprimirlo y ejecutar el suyo propio.

## Añadir botones de CTA en línea

Un mensaje puede llevar hasta tres botones de llamada a la acción en línea. Los botones se encuentran junto con otros datos personalizados dentro de `data` como un array `buttons`. El SDK los renderiza automáticamente dentro de las celdas con leyenda y clásicas:

```json title="POST https://api.pushwoosh.com/json/1.3/createMessage"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "API_TOKEN",
    "notifications": [{
      "send_date": "now",
      "ios_title": "New promo card",
      "content": "Tap a button to claim or save",
      "inbox_image": "https://cdn.example.com/inbox/promo.png",
      "inbox_days": 7,
      "data": {
        "displayType": "captioned",
        "promo_id": "SUMMER2026",
        "buttons": [
          { "title": "Claim", "url": "https://example.com/promo/SUMMER2026" },
          { "title": "Read",  "action": "markRead" },
          { "title": "Save",  "action": "custom", "tag": "save_promo" }
        ]
      },
      "platforms": [1]
    }]
  }
}
```

Cada objeto de botón tiene estos campos:

| Campo | Tipo | Cuándo |
|---|---|---|
| `title` | string | Requerido. Etiqueta visible del botón. |
| `url` | string | Una URL no vacía y analizable produce una acción `openURL`. El SDK la abre a través de `UIApplication.shared.open` a menos que su delegado la suprima. |
| `action` | string | Token de acción explícito: `dismiss` (elimina el mensaje del feed), `markRead` (marca el mensaje como leído), o `custom` (manejado por el host). No distingue entre mayúsculas y minúsculas. |
| Cualquier otra cosa | any | Cuando `action` es `custom`, cada clave en el objeto del botón excepto `title` y `action` se reenvía a su delegado como el payload personalizado — acuerde una clave con el comercializador (p. ej. `tag`) y despache en base a ella. |

Prioridad de resolución: primero el token `action` explícito, luego `url` si no está vacío, de lo contrario el botón cae en `custom` llevando el payload completo (menos `title` y `action`).

Intercepte los toques desde su delegado. La propiedad `button.action` es la enumeración tipada `PushwooshInboxButtonAction`:

```swift
extension MyInboxHost: PushwooshInboxKitDelegate {

    func inboxKit(_ vc: PushwooshInboxKitViewController,
                  didTapButton button: PushwooshInboxButton,
                  onMessage message: PWInboxMessageProtocol) -> Bool {
        switch button.action {
        case .openURL(let url):
            // Default behavior is fine — let SDK open the URL.
            return true

        case .dismiss, .markRead:
            // SDK handles both. Return false if you want to override.
            return true

        case .custom(let payload):
            // Marketer-defined custom button. Dispatch on a key you agreed on.
            if let tag = payload["tag"] as? String {
                switch tag {
                case "save_promo":
                    saveCurrentPromoLocally(message: message)
                default:
                    break
                }
            }
            return true   // ignored for custom — SDK never runs a default action here
        }
    }
}
```