Guías de configuración

Tutorial del SDK de Android de Hodhod: soporte nativo en tu app en 10 minutos

Añade chat de soporte y tickets a tu app Kotlin y Compose con el SDK de Android de Hodhod: añade la dependencia, configúrala, abre el chat, identifica a los usuarios con HMAC, ajusta tema e idioma, muestra los mensajes sin leer y resuelve los errores más comunes.

En este artículo
  1. ¿SDK o widget web en un WebView?
  2. Qué incluye el SDK
  3. Requisitos
  4. Paso 1: añade la dependencia
  5. Paso 2: configúralo en tu clase Application
  6. Paso 3: abre el chat
  7. Paso 4: identifica al usuario
  8. Paso 5: tema, idioma y modo oscuro
  9. Paso 6: el contador de mensajes sin leer
  10. Cómo se comportan los tickets y la encuesta en la app
  11. Anuncios y advertencias de la bandeja de entrada
  12. R8 y ProGuard
  13. Solución de problemas
  14. Preguntas frecuentes

Tienes una app Android y tus clientes hacen sus preguntas dentro de ella. La opción más sencilla es abrir el widget web en un WebView. Pero si te importan el aspecto nativo, el teclado y los selectores de archivos de Android, y la sensación de una app de verdad, el SDK de Android de Hodhod hace el mismo trabajo sin WebView. Se comunica con la misma bandeja de entrada y la misma API de widget que el widget web, de modo que las conversaciones y los tickets de la app llegan a la misma bandeja de entrada compartida del equipo.

En este tutorial conectamos paso a paso una app Kotlin con el SDK: instalación, configuración, apertura del chat, identificación del usuario, tema e idioma, y el contador de mensajes sin leer. Al final verás cómo se comportan los tickets y la encuesta de satisfacción, y cómo resolver los errores más comunes.

La versión actual del SDK es una beta (1.0.0-beta04). Los nombres de clases y parámetros que aparecen a continuación corresponden a esa versión y pueden cambiar antes de la versión estable.

¿SDK o widget web en un WebView?

Widget web en un WebView SDK de Android
Aspecto Una página web dentro de la app Interfaz nativa en Compose con el diseño del widget de Hodhod
RTL y fuente persa Depende de la página RTL desde el principio, con la fuente Vazirmatn incluida en el SDK
Archivos adjuntos y cámara Limitados por el WebView Selectores de fotos y archivos del propio Android
Identificación del usuario Mediante JavaScript Una llamada en Kotlin
Contador de mensajes sin leer en el botón No Sí
Tema claro y oscuro Depende de la página Sigue el sistema, o tú eliges
Instalación Un fragmento de código Una dependencia de Gradle

Si tu app es solo una página web dentro de un contenedor Android, el widget web es suficiente. Si tu app es nativa y el soporte forma parte de su experiencia, el SDK es la mejor opción.

Qué incluye el SDK

  • Modos de contacto de la bandeja de entrada: chat en vivo, ticket, ambos, o «ticket fuera de horario». La opción que elegiste para el widget web en el panel también se aplica en la app.
  • Tickets: un formulario de ticket con asunto, descripción, categoría y archivos adjuntos (según la configuración de la bandeja de entrada), una lista de «mis tickets» y el hilo de cada ticket con una insignia de estado y las respuestas.
  • Encuesta de satisfacción (CSAT): tres estilos de presentación, emojis, estrellas y números del 1 al 5, con un cuadro de comentario opcional.
  • Anuncios y advertencias de la bandeja de entrada (desde 1.0.0-beta04): los mismos anuncios que en el widget web aparecen en la parte superior de la pantalla de inicio, sin código adicional.
  • Pre-formulario de chat, horarios y festivos, el aviso de incidencias, finalizar una conversación y enviar la transcripción por correo, igual que en el widget web.
  • Seis idiomas: persa, inglés, árabe, alemán, español y francés. Los demás idiomas recurren al inglés.
  • Tema claro y oscuro, el color de marca de la bandeja de entrada y un color alternativo manual.
  • Pérdida de conexión: cuando se cae la red o el WebSocket, aparece un aviso de «sin conexión», el SDK se reconecta por sí solo y un mensaje que no se pudo enviar permanece con un botón «Reintentar».

Requisitos

  • Una bandeja de entrada de «sitio web» en el panel de Hodhod. Si aún no tienes una, sigue la guía de instalación del chat en vivo hasta la creación de la bandeja de entrada.
  • El websiteToken de la bandeja de entrada. Es el mismo valor que aparece en el fragmento del widget.
  • minSdk 24 o superior, Kotlin y Java 17. El SDK se compila con compileSdk 35 y su interfaz usa Jetpack Compose. Si tu app todavía usa Views, llama a Hodhod.open para usar la Activity ya preparada; las dependencias de Compose se añaden automáticamente.

Paso 1: añade la dependencia

El SDK tiene dos módulos: hodhod-core (sin interfaz: API, conexión en vivo y lógica) y hodhod-ui (la interfaz en Compose y una Activity ya preparada). hodhod-ui incluye hodhod-core, así que para usar la interfaz ya preparada basta con ese.

El SDK es de código abierto (MIT) en github.com/HodHodChat/hodhod-android-sdk y se instala mediante JitPack, que lo compila a partir de la etiqueta git 1.0.0-beta04 del repositorio. Primero añade el repositorio de JitPack a settings.gradle.kts:

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

Después añade la dependencia:

// app/build.gradle.kts
dependencies {
    implementation("com.github.HodHodChat.hodhod-android-sdk:hodhod-ui:1.0.0-beta04")
}

Las coordenadas son com.github.HodHodChat.hodhod-android-sdk:hodhod-ui:1.0.0-beta04 (y ...:hodhod-core:1.0.0-beta04 si solo necesitas el core sin interfaz). Los nombres de paquete de Kotlin en tu código siguen siendo chat.hodhod.sdk.

Paso 2: configúralo en tu clase Application

Configura el SDK una sola vez, en Application.onCreate. Hasta que se llame a Hodhod.start(), Hodhod.open() o Hodhod.identify(), el SDK no envía ninguna solicitud de red.

import android.app.Application
import chat.hodhod.sdk.Hodhod
import chat.hodhod.sdk.HodhodConfig

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        Hodhod.configure(
            this,
            HodhodConfig(
                baseUrl = "https://hodhod.chat",          // tu servidor de Hodhod, sin ruta adicional
                websiteToken = "YOUR_WEBSITE_TOKEN",      // el token de la bandeja de entrada de sitio web
            ),
        )
        Hodhod.start() // opcional: para que el contador de mensajes sin leer funcione antes de abrir el chat
    }
}

Registra la clase en AndroidManifest.xml con android:name=".MyApp" en la etiqueta <application>. La URL base debe usar https://; la única excepción es un servidor local en compilaciones de depuración, que se explica en «Solución de problemas».

Paso 3: abre el chat

Hay cuatro formas, empezando por la más sencilla.

1. La Activity ya preparada. Una sola línea desde cualquier Activity o Fragment:

Hodhod.open(context)

Esto abre HodhodChatActivity: una página a pantalla completa, con un tema aislado del tema de tu app, que gestiona por sí misma el botón de retroceso. La misma página también se abre mediante el enlace profundo hodhod://chat.

2. Insértalo en tu propia pantalla de Compose. Si quieres el chat dentro de la navegación de tu app:

@Composable
fun SupportScreen(onBack: () -> Unit) {
    HodhodChat(modifier = Modifier.fillMaxSize(), onClose = onBack)
}

HodhodChat muestra la pantalla de inicio, el chat, los tickets y el pre-formulario de chat según los ajustes de la bandeja de entrada, y llama a onClose cuando el usuario sale de la primera pantalla o pulsa el botón de cerrar. Si necesitas respetar la barra de estado y la barra de navegación, gestiona los insets en modifier; HodhodChatActivity lo hace con windowInsetsPadding(WindowInsets.systemBars).

3. Un botón flotante con una insignia de mensajes sin leer. HodhodBubble es un botón redondo que muestra el número de mensajes sin leer y llama a Hodhod.open al pulsarlo:

Box(Modifier.fillMaxSize()) {
    // contenido de la pantalla
    HodhodBubble(Modifier.align(Alignment.BottomEnd).padding(16.dp))
}

Pasa onClick para un comportamiento distinto. El color del botón proviene de la bandeja de entrada, o puedes cambiarlo con el parámetro accent.

4. Tu propio botón. Cualquier botón con el diseño de tu app, como «Soporte» en la pantalla de perfil, solo tiene que llamar a Hodhod.open(context).

Paso 4: identifica al usuario

Si el usuario ha iniciado sesión en tu app, preséntalo a Hodhod para que las conversaciones y los tickets se registren a su nombre y también puedan encontrarse desde otro dispositivo:

Hodhod.identify(
    HodhodUser(
        identifier = user.id,                // un id de usuario estable en tu sistema
        identifierHash = hashFromBackend,    // HMAC calculado en tu backend
        name = user.name,
        email = user.email,
        phone = user.phone,
        customAttributes = mapOf("plan" to "pro"),
    )
) { result ->
    result.onFailure { Log.w("Support", "identify failed: ${it.message}") }
}

Sin un identifier, el usuario es un contacto anónimo. El resultado se entrega en el hilo principal.

¿Por qué identifierHash y por qué en el backend?

Si cualquiera pudiera enviar el identificador de otra persona, podría leer sus conversaciones. Para evitarlo, Hodhod guarda una clave HMAC secreta para cada bandeja de entrada (en los ajustes de la bandeja de entrada, en «Validación de identidad de usuario») y comprueba identifierHash así: un HMAC con SHA-256 del identifier usando esa clave, como cadena hexadecimal.

La clave nunca debe estar dentro de la app. Cuando el usuario inicia sesión, la app obtiene de tu propio backend únicamente el valor de identifierHash. Dos ejemplos:

// Node.js
import { createHmac } from 'node:crypto';

const identifierHash = createHmac('sha256', process.env.HODHOD_HMAC_KEY)
  .update(String(user.id))
  .digest('hex');
# Python
import hashlib, hmac, os

identifier_hash = hmac.new(
    os.environ["HODHOD_HMAC_KEY"].encode(),
    str(user.id).encode(),
    hashlib.sha256,
).hexdigest()

En PHP es igual: hash_hmac('sha256', (string) $user->id, $key). La cadena identifier debe ser exactamente la misma en la app y en el backend.

Si activas «Forzar validación de identidad de usuario» en los ajustes de la bandeja de entrada, se rechazan las solicitudes sin identifierHash. Recomendamos activarlo para que nadie pueda acceder a las conversaciones de otra persona adivinando un identificador.

Atributos personalizados y cierre de sesión

Envía atributos personalizados (por ejemplo, la versión de la app o el plan) con HodhodUser o por separado. Quedan en cola hasta que exista la sesión:

Hodhod.setCustomAttributes(mapOf("app_version" to BuildConfig.VERSION_NAME))

Cuando el usuario cierra sesión en tu app:

Hodhod.logout()

Esto borra la sesión y los tokens almacenados y cierra la conexión; el siguiente uso crea un contacto anónimo nuevo. Así, la siguiente persona que inicie sesión en el mismo teléfono no ve las conversaciones del usuario anterior.

Paso 5: tema, idioma y modo oscuro

Todo esto se define en HodhodConfig:

HodhodConfig(
    baseUrl = "https://hodhod.chat",
    websiteToken = "YOUR_WEBSITE_TOKEN",
    locale = "fa",                       // fa, en, ar, de, es o fr
    darkMode = DarkMode.AUTO,            // AUTO (sistema), LIGHT o DARK
    accentColorOverride = 0xFF6A2BC4,    // un color ARGB en lugar del color del widget de la bandeja de entrada
)
  • Idioma: locale en la configuración tiene prioridad; después, el idioma de la cuenta en el servidor y, por último, el idioma del dispositivo. Un idioma no admitido recurre al inglés. La dirección del diseño (RTL para persa y árabe) se establece automáticamente.
  • Tema: AUTO sigue el ajuste del sistema.
  • Color: si no pasas accentColorOverride, se usa el color de widget que definiste para la bandeja de entrada en el panel.

Paso 6: el contador de mensajes sin leer

El número de mensajes sin leer es un StateFlow<Int>. Se mantiene en cero hasta que se llama a Hodhod.start() o Hodhod.open(), y por eso añadimos start() en el paso 2. El contador se actualiza en vivo mientras tu app está en ejecución.

val unread by Hodhod.unreadCount.collectAsState()

BadgedBox(badge = { if (unread > 0) Badge { Text("$unread") } }) {
    Icon(Icons.Outlined.SupportAgent, contentDescription = "Soporte")
}

Si usas HodhodBubble, este muestra el contador por sí mismo. El estado de la carga de los ajustes está en Hodhod.state (Idle, Loading, Ready o Failed).

Cómo se comportan los tickets y la encuesta en la app

Estos comportamientos dependen de los ajustes de la bandeja de entrada en el panel y no requieren código:

  • Modo de contacto: en el modo «chat», el cliente entra directamente en una conversación; en el modo «ticket», ve el formulario de ticket; y en «ambos», elige una de las dos opciones. En «ticket fuera de horario», se muestra el chat en vivo mientras el equipo está trabajando y el formulario de ticket se abre fuera del horario laboral. Este modo depende de los horarios definidos en la bandeja de entrada.
  • Tickets: después de enviarlo, se muestra el número del ticket y, desde «Mis tickets», el cliente ve el estado (abierto, en curso, esperando su respuesta, resuelto, cerrado) y responde en el mismo hilo. Para conocer la diferencia entre tickets y chat, lee el artículo sobre ticketing frente a chat en vivo.
  • Encuesta: cuando termina una conversación, si la encuesta de la bandeja de entrada está activada, aparece una tarjeta de valoración del 1 al 5 con el estilo definido para la bandeja de entrada (emojis, estrellas o números). Si te interesa el resultado, consulta qué es el CSAT.
  • Después de terminar una conversación: el cliente vuelve a la pantalla de inicio y, según el modo de contacto, puede abrir un nuevo chat o ticket.
  • Pre-formulario de chat: si está activado en la bandeja de entrada, se muestra antes de la primera conversación. Consulta el pre-formulario de chat.

Flujos de chatbot

Los flujos de chatbot se ejecutan de forma nativa en el SDK de Android, sin código por tu parte. Si la bandeja de entrada tiene un flujo activo, la app lo muestra en la pantalla de inicio: los 17 tipos de nodo, variables y condiciones, validación de entradas y valoraciones. El visitante puede pasar a un agente en vivo o a un ticket, y se registran las mismas analíticas de flujo que en el widget web. Si la opción require_flow del flujo está activada, la tarjeta directa «Iniciar conversación» permanece oculta mientras el flujo esté disponible (vuelve si el flujo no se puede cargar). El motor se comprobó contra el motor web con 40 escenarios de paridad (mismos pasos, variables y eventos), y las pantallas se verificaron en un emulador en persa (RTL) e inglés. El flujo solo se edita en el panel.

Anuncios y advertencias de la bandeja de entrada

Desde 1.0.0-beta04, el SDK muestra por sí solo, sin código, los anuncios que configuraste para la bandeja de entrada en el panel en la parte superior de la pantalla de inicio de la app. Los ajustes son los mismos que para el widget web (ajustes de la bandeja de entrada, sección «Widget announcements»); para saber cómo crearlos y redactarlos, lee anuncios y advertencias en el widget de soporte.

  • Dónde aparecen: en la parte superior de la pantalla de inicio, de la pantalla de tickets cuando hace de pantalla de inicio y del pre-formulario de chat, por encima del ejecutor de flujos y de las tarjetas de inicio. El aviso de incidencias va después de ellos.
  • Hasta dos, con dos niveles: un «anuncio» amarillo con un icono de información y una «advertencia» roja con un icono de advertencia, en los temas claro y oscuro y de derecha a izquierda.
  • Texto y enlaces: partes en negrita y enlaces en parte del texto. Solo se abren http, https, mailto y tel, en la app correspondiente (navegador, marcador, correo) y nunca en un WebView.
  • Imagen: una imagen https con una descripción para TalkBack y un enlace opcional; se oculta si no se carga.
  • Cierre: un anuncio que se puede cerrar tiene un botón de cerrar y la decisión del usuario se recuerda en ese dispositivo. Si cambias el texto, el nivel o la opción «Visitors can close it» en el panel, el anuncio se vuelve a mostrar.
  • Programación: el servidor solo envía los anuncios que están activados y dentro de su ventana de tiempo; el SDK no tiene que hacer nada para ello.
  • Servidores antiguos: si el servidor no tiene esta función, no se muestra ningún anuncio y el resto del SDK funciona como siempre.

Si has creado tu propia interfaz, los anuncios que no se han cerrado están en un StateFlow. HodhodChat y Hodhod.open ya lo hacen por sí mismos y no necesitan el código siguiente:

val items by Hodhod.repository.announcements.collectAsState()   // not closed yet, refreshed with the widget config

// e.g. from a close button of your own UI; ignored for announcements visitors cannot close
items.firstOrNull()?.let { Hodhod.repository.dismissAnnouncement(it.id) }

R8 y ProGuard

Las reglas necesarias se incluyen dentro de las bibliotecas (consumer-rules.pro), así que no necesitas añadir nada para una compilación de producción. Estas reglas conservan los serializadores de los modelos del SDK y HodhodChatActivity, que Hodhod.open busca por su nombre. Si ya tienes una regla para la Activity, repetirla no causa ningún problema. Si activas shrinkResources, las cadenas y las fuentes del SDK se conservan gracias al archivo keep que se distribuye con él.

Solución de problemas

La pantalla del chat muestra un error o no carga. Consulta Hodhod.state; en el estado Failed incluye un code:

  • not_found: el websiteToken o el baseUrl es incorrecto.
  • network: el teléfono no puede llegar al servidor.
  • suspended: la cuenta de Hodhod está suspendida.
  • server: un error del servidor; inténtalo de nuevo en un momento.
  • cleartext: pasaste una dirección http://.

Un servidor local (10.0.2.2) no se conecta. En el emulador, el localhost de su máquina es 10.0.2.2. Como la dirección es http://, se necesitan dos cosas, ambas solo para compilaciones de depuración: pasa allowCleartext = true en HodhodConfig y, en un network_security_config de depuración, permitir el tráfico sin cifrar únicamente para 10.0.2.2 (la app de ejemplo del SDK incluye un ejemplo). No uses ninguna de las dos en compilaciones de producción.

Los mensajes no llegan en vivo y aparece el aviso de «sin conexión». Las actualizaciones en vivo funcionan mediante un WebSocket en la ruta /cable. Un proxy corporativo, un cortafuegos o una CDN pueden bloquear la actualización a WebSocket. El SDK se reconecta con un retardo creciente; prueba la misma ruta en otra red.

El idioma es incorrecto. Pasa locale de forma explícita en HodhodConfig. Sin él, se usa el idioma de la cuenta de Hodhod (en el servidor) y, después, el idioma del dispositivo. Códigos como fa-IR se aceptan y se reducen a fa.

identify falla. Normalmente el identifierHash es incorrecto. Comprueba que usaste la clave HMAC de la bandeja de entrada (no el websiteToken), que el identifier es idéntico en la app y en el backend, y que el hash está escrito en hexadecimal.

Preguntas frecuentes

¿En qué se diferencia el SDK de Android del widget web?

El widget web es una página web que se coloca en tu sitio con un solo fragmento de código. El SDK es una interfaz nativa de Android creada con Jetpack Compose que se conecta a la misma bandeja de entrada y a la misma API. En ambos casos, las conversaciones llegan a una única bandeja de entrada compartida.

¿Mi app tiene que estar escrita en Compose?

No. Hodhod.open(context) funciona desde cualquier Activity. Solo insertar HodhodChat y el botón HodhodBubble requieren Compose.

¿Qué versiones de Android son compatibles?

minSdk es 24 (Android 7.0 y posteriores).

¿Necesito una bandeja de entrada aparte?

No. Se usa la misma bandeja de entrada de «sitio web», con el mismo websiteToken. Si quieres mantener separadas las conversaciones de la app, crea otra bandeja de entrada de sitio web para la app.

¿Los flujos de chatbot funcionan en la app?

Sí. Si la bandeja de entrada tiene un flujo de chatbot activo, el SDK lo ejecuta de forma nativa en la pantalla de inicio, con traspaso a un agente en vivo o como ticket. No hace falta código más allá de la configuración normal.

¿Cómo se distingue a los contactos de los usuarios de la app?

Por el identifier que proporcionas. Para evitar la suplantación, calcula identifierHash en tu backend con la clave HMAC de la bandeja de entrada y activa la validación de identidad obligatoria.

¿Los anuncios de la bandeja de entrada también se muestran en la app?

Sí, desde 1.0.0-beta04. El SDK muestra los anuncios y advertencias configurados para la bandeja de entrada en la parte superior de la pantalla de inicio; no hace falta código y el cierre de uno se recuerda en ese dispositivo. Cómo crearlos se explica en anuncios y advertencias en el widget de soporte.