---
title: API
slug: cp-sdconn-android-api
docTags: 
createdAt: 2026-03-11T16:01:42.349Z
---

Referencia completa de la clase principal SDConn y los modelos de datos del SDK.

Paquete: com.mobbex.sdconn

## Clase SDConn

Fachada principal del SDK. Punto de entrada único para todas las operaciones.

### Constructor

:::BlockQuote
SDConn(context: Context, config: SDConnConfig = SDConnConfig())
:::

| Parámetro | Tipo         | Descripción                                  |
| --------- | ------------ | -------------------------------------------- |
| context   | Context      | Contexto de Android (Activity o Application) |
| config    | SDConnConfig | Configuración opcional                       |

### Propiedades

**connectionState: StateFlow\<ConnectionState>**

Estado reactivo de la conexión. Se puede observar con collect o collectLatest.

:::BlockQuote
sdconn.connectionState.collect \{ state -> ... }
:::

**isConnected: Boolean**

true si hay un dispositivo conectado.

**credentialsConfigured: Boolean**

true si las credenciales de Mobbex están guardadas.

### Credenciales

**setCredentials(apiKey: String, accessToken: String)**

Guarda las credenciales de Mobbex de forma cifrada. Persisten entre sesiones.

**clearCredentials()**

Elimina las credenciales almacenadas.

### Conexión Serial

**suspend fun connectSerial()**

Busca y conecta un dispositivo POS por USB serial.

- Escanea todos los drivers USB serial disponibles.
- Envía HELLO a cada puerto y conecta al primero que responda.
- Lanza SDConnException si no encuentra dispositivo.
- Actualiza connectionState a Connected(mode="serial", detail=portName).

### Conexión HTTP

**suspend fun connectHttp(ip: String, port: Int = 5000)**

Conecta directamente a un dispositivo por HTTP (sin emparejamiento).

| Parámetro | Tipo   | Descripción                                 |
| --------- | ------ | ------------------------------------------- |
| ip        | String | Dirección IP del dispositivo                |
| port      | Int    | Puerto HTTP del dispositivo (defecto: 5000) |

Lanza SDConnException si el dispositivo no es alcanzable.

### Emparejamiento

**suspend fun requestPairing(ip: String, port: Int = 5000): Boolean**

Solicita emparejamiento con un dispositivo. El dispositivo muestra un PIN de 6 dígitos en su pantalla.

**Retorna:** true si el dispositivo aceptó la solicitud.

**Lanza:** SDConnException si el dispositivo no es alcanzable.

**suspend fun confirmPairing(ip: String, port: Int = 5000, pin: String): DiscoveredDevice**

Confirma el emparejamiento con el PIN mostrado en el dispositivo.

| Parámetro | Tipo   | Descripción                      |
| --------- | ------ | -------------------------------- |
| ip        | String | IP del dispositivo               |
| port      | Int    | Puerto (defecto: 5000)           |
| pin       | String | PIN de 6 dígitos del dispositivo |

**Retorna:** DiscoveredDevice con los datos del dispositivo emparejado.

**Efectos:**

- Guarda el dispositivo y token de confianza para reconexión.
- Establece la conexión HTTP.
- Pone el dispositivo en modo pinpad.

**Lanza:** SDConnException si el PIN es incorrecto o el dispositivo no responde.

**suspend fun reconnect(): DiscoveredDevice**

Reconecta al último dispositivo emparejado usando el token de confianza guardado. No requiere PIN.

**Retorna:** DiscoveredDevice reconectado.

**Lanza:** SDConnException si no hay dispositivo guardado, no es alcanzable, o el token fue rechazado.

**fun hasSavedDevice(): Boolean**

Indica si hay un dispositivo guardado disponible para reconexión.

### Desconexión

**fun disconnect()**

Desconecta el dispositivo actual. No borra el dispositivo guardado ni el token de confianza.

**fun forgetDevice()**

Desconecta y borra el dispositivo guardado y su token de confianza. La próxima conexión requerirá emparejamiento completo.

### Lectura de tarjeta / QR

**suspend fun read(request: ReadRequest): ReadResult**

Operación completa de lectura de tarjeta o pago por QR.

**Flujo tarjeta estándar** (sin intentToken, sin source):

1. Obtiene información del dispositivo (cacheada).
2. Obtiene ubicación GPS del dispositivo.
3. Crea una operación en la API de Mobbex (/p/pos/operation).
4. Envía el token al dispositivo para lectura de tarjeta.
5. Retorna los datos leídos junto con el intentToken generado.

**Flujo tarjeta con token externo** (con intentToken):

Si se proporciona un intentToken en el ReadRequest, se omite la llamada a la API de Mobbex y se usa ese token directamente. En este caso no se requieren credenciales configuradas y la respuesta incluye la geolocation del dispositivo.

**Flujo QR** (con source):

Si se proporciona un source (ej: "arg.qr"), el SDK:

1. Crea el intent token (o usa el intentToken externo si fue proporcionado).
2. Llama al endpoint de operación (/p/operations/&#x7B;token}) con la fuente.
3. Descarga la imagen QR del resultado y la codifica en base64.
4. Envía la imagen al dispositivo POS para que la muestre en pantalla.
5. Retorna ReadResult con operationId, source y status (sin sourceData).

El flujo QR siempre requiere credenciales configuradas (para llamar al endpoint de operación).

**Retorna:** ReadResult con el resultado de la operación.

**Requiere:**

- Dispositivo conectado.
- Credenciales configuradas (solo si no se envía intentToken, o si se envía source).
- total > 0.

No lanza excepciones — los errores se devuelven en ReadResult.error.

**suspend fun cancelRead()**

Cancela una lectura de tarjeta en progreso. El dispositivo deja de esperar y la operación read() retorna con error CANCELLED.

### Pantalla del dispositivo

**Mostrar resultado — suspend fun display(request: DisplayRequest): DisplayResult?**

Muestra una pantalla de resultado con animación en el dispositivo POS. Es fire-and-forget: retorna null inmediatamente tras enviar el comando.

:::BlockQuote
sdconn.display(DisplayRequest(
&#x20;   status = "success",
&#x20;   title = "Pago Aprobado",
&#x20;   subtitle = "Visa \*\*\*\* 1234",
&#x20;   total = 15.00,
&#x20;   currency = "ARS",
&#x20;   timeout = 10
))
:::

Se puede enviar el status.code del procesador directamente (ej: 200, 400) y el dispositivo resuelve la animación automáticamente.

**Lanza:** SDConnException si no hay dispositivo conectado.

**Mostrar carga — suspend fun display(request: DisplayRequest): DisplayResult?**

Muestra una pantalla de carga con spinner. Es fire-and-forget: retorna null. Se usa para indicar que se está procesando una operación.

:::BlockQuote
sdconn.display(DisplayRequest(
&#x20;   view = "loading",
&#x20;   title = "Procesando pago...",
&#x20;   subtitle = "No retire la tarjeta",
&#x20;   timeout = 30
))
:::

Para quitar la pantalla de carga, llamar a dismissDisplay() o mostrar otra vista.

**Lanza:** SDConnException si no hay dispositivo conectado.

**Mostrar selección — suspend fun display(request: DisplayRequest): DisplayResult?**

Muestra una pantalla con opciones seleccionables. Es **bloqueante**: espera a que el usuario seleccione una opción o se agote el timeout. Retorna DisplayResult.

:::BlockQuote
val result = sdconn.display(DisplayRequest(
&#x20;   view = "choose",
&#x20;   title = "Seleccione cuotas",
&#x20;   subtitle = "Visa \*\*\*\* 1234",
&#x20;   values = listOf(
&#x20;       DisplayOption("1 Pago - $15.00", "1"),
&#x20;       DisplayOption("3 Cuotas - $5.75 c/u", "3"),
&#x20;       DisplayOption("6 Cuotas - $3.08 c/u", "6")
&#x20;   ),
&#x20;   timeout = 60
))

if (result != null && result.result) \{
&#x20;   val selected = result.selected  *// DisplayOption("3 Cuotas...", "3")*
}
:::

**Lanza:** SDConnException si no hay dispositivo conectado.

**Ocultar pantalla — suspend fun dismissDisplay()**

Oculta la pantalla del dispositivo antes de que expire el timeout. El dispositivo vuelve a la pantalla de espera.

### Modo del dispositivo

**suspend fun setMode(mode: String): Map\<String, Any?>**

Cambia el modo de operación del dispositivo.

| Modo      | Descripción                                         |
| --------- | --------------------------------------------------- |
| "normal"  | Modo POS estándar con pantalla de ingreso de monto  |
| "slave"   | Modo cola — recibe trabajos de pago desde una cola  |
| "transit" | Lectura continua de tarjetas para peajes/transporte |
| "pinpad"  | Espera comandos de lectura del controlador          |

**Retorna:** Respuesta del dispositivo como mapa.

### Información del dispositivo

**suspend fun getDeviceInfo(): DeviceInfo**

Obtiene información del hardware y software del dispositivo. Se cachea tras la primera consulta; el caché se limpia al cambiar de conexión.

**suspend fun getDeviceLocation(): DeviceLocation**

Obtiene la ubicación GPS del dispositivo en tiempo real (sin caché).

**Lanza:** SDConnException si la ubicación no está disponible.

## Modelos de datos

### SDConnConfig

:::BlockQuote
data class SDConnConfig(
&#x20;   val serialBaudRate: Int = 115200,
&#x20;   val mobbexApiBase: String = "https\://api.mobbex.com",
&#x20;   val debug: Boolean = false
)
:::

| Campo          | Tipo    | Defecto                   | Descripción                       |
| -------------- | ------- | ------------------------- | --------------------------------- |
| serialBaudRate | Int     | 115200                    | Velocidad de la conexión serial   |
| mobbexApiBase  | String  | "https\://api.mobbex.com" | URL base de la API de Mobbex      |
| debug          | Boolean | false                     | Activa logs detallados en consola |

### ReadRequest

:::BlockQuote
data class ReadRequest(
&#x20;   val total: Double,
&#x20;   val otherAmount: Double = 0.0,
&#x20;   val intent: String? = null,
&#x20;   val currency: String? = null,
&#x20;   val hideAmount: Boolean = false,
&#x20;   val test: Boolean = false,
&#x20;   val intentToken: String? = null,
&#x20;   val timeout: Int = 120,
&#x20;   val source: String? = null,
&#x20;   val description: String? = null,
&#x20;   val reference: String? = null
)
:::

| Campo       | Tipo    | Requerido | Descripción                                                     |
| ----------- | ------- | --------- | --------------------------------------------------------------- |
| total       | Double  | Sí        | Monto total (ej: 15.00)                                         |
| otherAmount | Double  | No        | Monto adicional (propina, etc.)                                 |
| intent      | String? | No        | Tipo de intento (defecto: "payment.v2")                         |
| currency    | String? | No        | Código de moneda (defecto: "ARS")                               |
| hideAmount  | Boolean | No        | Ocultar monto en la pantalla del dispositivo                    |
| test        | Boolean | No        | Enviar operación en modo test a Mobbex                          |
| intentToken | String? | No        | Token externo. Si se envía, omite la llamada a la API de Mobbex |
| timeout     | Int     | No        | Timeout de lectura en segundos (defecto: 120)                   |
| source      | String? | No        | Fuente de pago (ej: "arg.qr"). Si se envía, activa el flujo QR  |
| description | String? | No        | Descripción de la operación (se envía a la API de Mobbex)       |
| reference   | String? | No        | Referencia externa de la operación                              |

### ReadResult

:::BlockQuote
data class ReadResult(
&#x20;   val result: Boolean,
&#x20;   val sourceData: Map\<String, Any?>? = null,
&#x20;   val intentToken: String? = null,
&#x20;   val geolocation: List\<Double>? = null,
&#x20;   val operationId: String? = null,
&#x20;   val source: String? = null,
&#x20;   val status: Map\<String, Any?>? = null,
&#x20;   val error: String? = null
)
:::

| Campo       | Tipo           | Descripción                                                                             |
| ----------- | -------------- | --------------------------------------------------------------------------------------- |
| result      | Boolean        | true si la operación fue exitosa                                                        |
| sourceData  | Map?           | Datos de la tarjeta (pin, emvData, cryptogram, ksn, track2...). Solo en flujo tarjeta.  |
| intentToken | String?        | Token de intento utilizado (generado por Mobbex o el externo proporcionado)             |
| geolocation | List\<Double>? | \[latitude, longitude] del dispositivo. Se incluye con token externo y en flujo QR.     |
| operationId | String?        | ID de la operación. Solo en flujo QR.                                                   |
| source      | String?        | Fuente utilizada (ej: "arg.qr"). Solo en flujo QR.                                      |
| status      | Map?           | Estado de la operación (ej: \{ "code": "100", "text": "Pendiente" }). Solo en flujo QR. |
| error       | String?        | Mensaje de error si result es false                                                     |

**Posibles errores:**

| Error                                         | Descripción                                                       |
| --------------------------------------------- | ----------------------------------------------------------------- |
| total is required and must be > 0             | Falta el monto o es inválido                                      |
| Mobbex credentials not configured             | No se configuraron credenciales                                   |
| credentials required for source operations    | Se envió source pero no hay credenciales configuradas             |
| No device connected                           | No hay dispositivo conectado                                      |
| Card read failed                              | El dispositivo no pudo leer la tarjeta                            |
| TIMEOUT                                       | Se agotó el tiempo de lectura (120s)                              |
| CANCELLED                                     | La lectura fue cancelada                                          |
| NOT\_IN\_PINPAD\_MODE                         | El dispositivo no está en modo pinpad                             |
| Operate response did not contain a QR barcode | La respuesta del endpoint de operación no contiene el QR esperado |

### DisplayRequest

:::BlockQuote
data class DisplayRequest(
&#x20;   val view: String = "result",
&#x20;   val status: Any? = null,
&#x20;   val title: String? = null,
&#x20;   val subtitle: String? = null,
&#x20;   val total: Double? = null,
&#x20;   val currency: String? = null,
&#x20;   val text: String? = null,
&#x20;   val values: List\<DisplayOption>? = null,
&#x20;   val timeout: Int? = null
)
:::

| Campo    | Tipo                  | Requerido | Descripción                                                                                             |
| -------- | --------------------- | --------- | ------------------------------------------------------------------------------------------------------- |
| view     | String                | No        | Tipo de vista: "result" (defecto), "loading", "choose"                                                  |
| status   | Any?                  | No        | Estado: String ("success", "error", "neutral", "none") o Int (código). Solo aplica para vista "result". |
| title    | String?               | No        | Título en pantalla                                                                                      |
| subtitle | String?               | No        | Subtítulo debajo del título                                                                             |
| total    | Double?               | No        | Monto (ej: 15.00)                                                                                       |
| currency | String?               | No        | Código de moneda                                                                                        |
| text     | String?               | No        | Texto adicional debajo del monto                                                                        |
| values   | List\<DisplayOption>? | Sí\*      | Lista de opciones seleccionables. \*Requerido para "choose".                                            |
| timeout  | Int?                  | No        | Segundos de espera. Defecto: 10 para result/loading, 60 para choose. Usar 0 para desactivar.            |

### DisplayOption

:::BlockQuote
data class DisplayOption(
&#x20;   val label: String,
&#x20;   val value: String
)
:::

| Campo | Tipo   | Descripción                                     |
| ----- | ------ | ----------------------------------------------- |
| label | String | Texto visible en la pantalla del dispositivo    |
| value | String | Valor que se retorna al seleccionar esta opción |

### DisplayResult

:::BlockQuote
data class DisplayResult(
&#x20;   val result: Boolean,
&#x20;   val selected: DisplayOption? = null,
&#x20;   val error: String? = null
)
:::

| Campo    | Tipo           | Descripción                                  |
| -------- | -------------- | -------------------------------------------- |
| result   | Boolean        | true si el usuario seleccionó una opción     |
| selected | DisplayOption? | Opción seleccionada (solo si result es true) |
| error    | String?        | Error (ej: "TIMEOUT") si result es false     |

:::BlockQuote
Solo se retorna DisplayResult cuando view es "choose". Para "result" y "loading", display() retorna null.
:::

### DeviceInfo

:::BlockQuote
data class DeviceInfo(
&#x20;   val type: String = "",
&#x20;   val serialNumber: String = "",
&#x20;   val deviceExtras: Map\<String, String> = emptyMap()
)
:::

| Campo        | Tipo   | Descripción                                       |
| ------------ | ------ | ------------------------------------------------- |
| type         | String | Tipo de dispositivo (ej: "POS")                   |
| serialNumber | String | Número de serie                                   |
| deviceExtras | Map    | Información adicional (os, modelo, versión, etc.) |

Ejemplo de deviceExtras:

:::BlockQuote
\{
&#x20; "os": "Android",
&#x20; "osVersion": "11",
&#x20; "manufacturer": "nexgo",
&#x20; "model": "N62",
&#x20; "serialNumber": "SN12345",
&#x20; "bundleId": "com.sugaway.pos",
&#x20; "buildNumber": "42",
&#x20; "appVersion": "1.2.3"
}
:::

### DeviceLocation

:::BlockQuote
data class DeviceLocation(
&#x20;   val latitude: Double,
&#x20;   val longitude: Double,
&#x20;   val accuracy: Double? = null,
&#x20;   val provider: String? = null,
&#x20;   val timestamp: Long? = null
)
:::

| Campo     | Tipo    | Descripción                               |
| --------- | ------- | ----------------------------------------- |
| latitude  | Double  | Latitud                                   |
| longitude | Double  | Longitud                                  |
| accuracy  | Double? | Precisión en metros                       |
| provider  | String? | Proveedor de ubicación ("gps", "network") |
| timestamp | Long?   | Timestamp en milisegundos                 |

### DiscoveredDevice

:::BlockQuote
data class DiscoveredDevice(
&#x20;   val ip: String,
&#x20;   val port: Int = 5000,
&#x20;   val name: String = "Dispositivo POS"
)
:::

Representa un dispositivo encontrado o emparejado por red.

### ConnectionState

:::BlockQuote
sealed class ConnectionState \{
&#x20;   data object Disconnected : ConnectionState()
&#x20;   data object Searching : ConnectionState()
&#x20;   data class Connected(val mode: String, val detail: String) : ConnectionState()
&#x20;   data class Error(val message: String) : ConnectionState()
}
:::

| Estado       | Descripción                                                                  |
| ------------ | ---------------------------------------------------------------------------- |
| Disconnected | Sin conexión activa                                                          |
| Searching    | Buscando dispositivo (serial o HTTP)                                         |
| Connected    | Conectado. mode = "serial" o "http". detail = nombre del puerto o "ip\:port" |
| Error        | Error de conexión. message con la descripción                                |

### SDConnException

:::BlockQuote
class SDConnException(message: String, cause: Throwable? = null) : Exception(message, cause)
:::

Excepción general del SDK. Lanzada por operaciones que fallan (conexión, emparejamiento, etc.).

## Flujo completo

### Sin token externo (flujo estándar)

:::BlockQuote
App Android                     SDConn SDK                      Dispositivo POS
&#x20;   │                              │                                 │
&#x20;   │  setCredentials(key, token)  │                                 │
&#x20;   │ ────────────────────────────>│                                 │
&#x20;   │                              │                                 │
&#x20;   │  requestPairing(ip, port)    │                                 │
&#x20;   │ ────────────────────────────>│  (solicita emparejamiento)      │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │                              │                     (muestra PIN)│
&#x20;   │  true                        │                                 │
&#x20;   │\<────────────────────────────│                                 │
&#x20;   │                              │                                 │
&#x20;   │  confirmPairing(ip, port, pin)│                                │
&#x20;   │ ────────────────────────────>│  (confirma con PIN)             │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │                              │\<─────────── \{ trustToken }      │
&#x20;   │                              │                                 │
&#x20;   │                              │  (cambia a modo pinpad)         │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │  DiscoveredDevice            │                                 │
&#x20;   │\<────────────────────────────│                                 │
&#x20;   │                              │                                 │
&#x20;   │  read(ReadRequest)           │                                 │
&#x20;   │ ────────────────────────────>│                                 │
&#x20;   │                              │  (obtiene info del dispositivo) │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │                              │\<──────────────────────────────│
&#x20;   │                              │                                 │
&#x20;   │                              │  (crea operación en Mobbex)     │
&#x20;   │                              │ ──────────> Mobbex API          │
&#x20;   │                              │\<────────── \{ token }            │
&#x20;   │                              │                                 │
&#x20;   │                              │  (lectura de tarjeta)           │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │                              │          (usuario pasa tarjeta) │
&#x20;   │                              │\<────────── \{ sourceData }       │
&#x20;   │                              │                                 │
&#x20;   │  ReadResult(true,            │                                 │
&#x20;   │    sourceData, intentToken)  │                                 │
&#x20;   │\<────────────────────────────│                                 │
&#x20;   │                              │                                 │
&#x20;   │  display(DisplayRequest)     │                                 │
&#x20;   │ ────────────────────────────>│                                 │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │                              │         (pantalla de resultado) │
:::

### Con token externo

:::BlockQuote
App Android                     SDConn SDK                      Dispositivo POS
&#x20;   │                              │                                 │
&#x20;   │  read(ReadRequest(           │                                 │
&#x20;   │    intentToken="ext\_token")) │                                 │
&#x20;   │ ────────────────────────────>│                                 │
&#x20;   │                              │  (obtiene info del dispositivo) │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │                              │\<──────────────────────────────│
&#x20;   │                              │                                 │
&#x20;   │                              │  (omite llamada a Mobbex API)   │
&#x20;   │                              │                                 │
&#x20;   │                              │  (lectura de tarjeta)           │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │                              │          (usuario pasa tarjeta) │
&#x20;   │                              │\<────────── \{ sourceData }       │
&#x20;   │                              │                                 │
&#x20;   │  ReadResult(true,            │                                 │
&#x20;   │    sourceData, intentToken,  │                                 │
&#x20;   │    geolocation=\[lat, lng])   │                                 │
&#x20;   │\<────────────────────────────│                                 │
:::

### Flujo QR (con source)

:::BlockQuote
App Android                     SDConn SDK                      Dispositivo POS
&#x20;   │                              │                                 │
&#x20;   │  read(ReadRequest(           │                                 │
&#x20;   │    total=15.0,               │                                 │
&#x20;   │    source="arg.qr"))         │                                 │
&#x20;   │ ────────────────────────────>│                                 │
&#x20;   │                              │  (obtiene info del dispositivo) │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │                              │\<──────────────────────────────│
&#x20;   │                              │                                 │
&#x20;   │                              │  (crea operación en Mobbex)     │
&#x20;   │                              │ ──────────> Mobbex API          │
&#x20;   │                              │\<────────── \{ token }            │
&#x20;   │                              │                                 │
&#x20;   │                              │  (obtiene QR de Mobbex)         │
&#x20;   │                              │ ──────────> Mobbex API          │
&#x20;   │                              │\<────────── \{ barcode }          │
&#x20;   │                              │                                 │
&#x20;   │                              │  (muestra QR en dispositivo)    │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │                              │        (QR visible en pantalla) │
&#x20;   │                              │                                 │
&#x20;   │  ReadResult(true,            │                                 │
&#x20;   │    intentToken, operationId, │                                 │
&#x20;   │    source="arg.qr",          │                                 │
&#x20;   │    status=\{code, text})      │                                 │
&#x20;   │\<────────────────────────────│                                 │
&#x20;   │                              │                                 │
&#x20;   │  (espera a que el usuario    │                                 │
&#x20;   │   escanee el QR)             │                                 │
&#x20;   │                              │                                 │
&#x20;   │  cancelRead()                │                                 │
&#x20;   │  (cuando quiera cancelar)    │                                 │
&#x20;   │ ────────────────────────────>│ ───────────────────────────────>│
&#x20;   │                              │                (quita QR)       │
:::

### Flujo con vista choose (cuotas)

:::BlockQuote
App Android                     SDConn SDK                      Dispositivo POS
&#x20;   │                              │                                 │
&#x20;   │  display(DisplayRequest(     │                                 │
&#x20;   │    view="choose",            │                                 │
&#x20;   │    title="Cuotas",           │                                 │
&#x20;   │    values=\[...]))            │                                 │
&#x20;   │ ────────────────────────────>│                                 │
&#x20;   │                              │ ───────────────────────────────>│
&#x20;   │                              │       (usuario toca una opción) │
&#x20;   │                              │\<──────────────────────────────  │
&#x20;   │                              │                                 │
&#x20;   │  DisplayResult(true,         │                                 │
&#x20;   │    selected=DisplayOption(   │                                 │
&#x20;   │      "3 Cuotas", "3"))       │                                 │
&#x20;   │\<────────────────────────────│                                 │
:::

