Menú de guías

Flutter

El paquete kavchat_flutter añade a tu app de Flutter un chat de soporte basado en guiones: botones, reconocimiento de texto libre, un formulario que abre un ticket, una copia del bot que funciona sin conexión y una interfaz de derecha a izquierda en hebreo y árabe.

Instalación

pubspec.yaml
dependencies:
  kavchat_flutter: ^0.0.11

El paquete incluye http, shared_preferences (caché de la versión y conversación guardada) y url_launcher (enlaces en los guiones). Para abrir enlaces tel: en Android 11 y versiones posteriores, añade a AndroidManifest.xml una entrada <queries> para el esquema tel y otra para https para los enlaces a sitios web (consulta el README de url_launcher). En iOS, solo necesitas LSApplicationQueriesSchemes si llamas a canLaunchUrl directamente.

Uso

Dart
final kavchat = KavchatController(
  publicKey: 'pk_live_...',
  // apiBase: 'https://api.kavchat.com', // default
  // locale: 'he',                        // default: device language if translated, else the bot language
  onScreen: (screen, targetId) => navigatorKey.currentState?.pushNamed('/$screen', arguments: targetId),
);

Scaffold(
  floatingActionButton: KavchatLauncher(controller: kavchat), // hidden while the bot is off
);

// or open it yourself
showKavchat(context, kavchat);

// or embed the screen in your own route
KavchatChat(controller: kavchat, onClose: () => Navigator.pop(context));

Crea el controlador una sola vez (por ejemplo, en un State o un provider) y llama a dispose() al liberar el componente que lo contiene.

KavchatLauncher es un FloatingActionButton y se posiciona con Scaffold.floatingActionButtonLocation. El ajuste kavchat.settings?.position (start o end) indica el lado elegido en el panel de control.

Cuando configuras en el panel de administración un mensaje junto al botón del chat (una burbuja de diálogo, texto curvo sobre el botón, el propio botón que se expande en forma de cápsula para mostrar un mensaje o una línea que sube desde el botón y muestra las primeras opciones del bot), KavchatLauncher lo muestra tras la demora configurada, como máximo una vez al día. Al tocar una opción de la línea, se abre el chat con esa opción seleccionada, como si se hubiera elegido dentro del chat. Cerrar el mensaje o abrir el chat oculta el mensaje hasta el día siguiente. El mensaje no se muestra mientras el chat está abierto. Si el dispositivo tiene activada la reducción de movimiento, el mensaje aparece sin animación. Cuando está activada la opción "No mostrar en teléfonos", el mensaje solo aparece en tablets (cuando el lado más corto de la pantalla mide 600 puntos o más). En teléfonos, la línea muestra hasta dos opciones. KavchatLauncher(controller: kavchat, attention: false) desactiva el mensaje.

Cuando el icono del botón en el tema es una cara, parpadea de vez en cuando mientras el botón está en reposo. La cara no parpadea cuando el chat está abierto, la app está en segundo plano o está activada la reducción de movimiento.

El bot no se muestra (el botón flotante no aparece y KavchatChat muestra "No disponible") cuando la versión tiene enabled: false: el bot está desactivado, está disponible solo para usuarios de prueba y el usuario no es uno de ellos, o se ha agotado la cuota (reason: 'quota').

Token de identidad

Para los usuarios que han iniciado sesión, firma un JWT corto en tu servidor con la clave secreta (nunca en la app; consulta Identificar usuarios) y pásalo así:

Dart
KavchatController(publicKey: 'pk_live_...', userToken: jwt, userName: 'Dana');
await kavchat.setUser(token: newJwt, name: 'Dana'); // after login
await kavchat.setUser(token: null);                 // after logout

El token se envía en el encabezado Authorization: Bearer <token> en cada llamada. Con un token, el formulario de contacto no exige correo electrónico ni teléfono, y el grupo de usuarios de prueba puede reconocer al usuario. setUser vuelve a cargar la versión y, si el usuario cambia, inicia una conversación nueva. Así, una conversación guardada en el dispositivo nunca pasa a otra cuenta.

Enlaces, pantallas y formulario de contacto

  • Los botones de tipo link se abren con url_launcher, solo para direcciones https: y tel:. Los enlaces dentro de los mensajes del bot solo admiten https:.
  • Los botones de tipo screen llaman a onScreen(screen, targetId). Sin esa función, no hacen nada.
  • El botón "Contactar al equipo" abre el formulario integrado (nombre, correo electrónico, teléfono, asunto y mensaje, con la conversación adjunta) y, tras enviarlo, muestra el número del ticket.

Lista de pantallas en el editor

Declara una sola vez las pantallas que tu app puede abrir en KavchatController(screens: ...). El SDK envía la lista a Kavchat automáticamente y el editor de guiones ofrece esas pantallas en una lista desplegable con sus nombres, en lugar de un campo de texto libre. El editor también pide un identificador de elemento cuando la pantalla lo necesita y avisa si un botón lleva a una pantalla no declarada. No hace falta escribir manualmente la lista de pantallas en el panel de control.

Dart
final kavchat = KavchatController(
  publicKey: 'pk_live_...',
  screens: const [
    KavchatScreen(id: 'orders', label: 'My orders'),
    KavchatScreen(id: 'product', label: 'Product page', needsTarget: true), // the button carries a targetId
  ],
  onScreen: (screen, targetId) => navigatorKey.currentState?.pushNamed('/$screen', arguments: targetId),
);
// later, to replace the list: await kavchat.registerScreens([...]);
  • id es el valor que recibe onScreen: letras latinas, dígitos, _, . y -, hasta 40 caracteres. label es el nombre que ve el administrador, hasta 60 caracteres. Se admiten hasta 100 pantallas. Se omiten las entradas no válidas y los identificadores duplicados.
  • needsTarget: el botón también incluye un identificador de elemento (por ejemplo, el de un producto), que llega a onScreen como targetId.
  • La lista se envía en segundo plano después de cargar la versión, solo si ha cambiado o una vez por semana. Si el envío falla, no se muestra ningún aviso y se vuelve a intentar al iniciar la app de nuevo. El usuario no ve nada.
  • Las pantallas registradas aparecen en el panel de control, en Bot, Ajustes, "Pantallas de la app", junto con las plataformas que han informado de cada pantalla. Si has quitado una pantalla de la app, puedes eliminarla allí.

RTL

El chat establece su propia Directionality según el idioma de la interfaz: he y ar van de derecha a izquierda; los demás, de izquierda a derecha. No cambia la dirección de tu app. Cada burbuja también elige su dirección según la primera letra con direccionalidad fuerte, para que el contenido mixto se lea correctamente. El idioma de la interfaz y el del guion son independientes: un bot en inglés puede mostrar botones en árabe, como "Volver", cuando se configura locale: 'ar'.

Colores e identidad de marca

Los colores se toman del ajuste primaryColor del bot. El texto sobre ese color es negro o blanco según el contraste WCAG, y los colores demasiado claros se sustituyen por uno oscuro cuando se usan para texto. Así puedes cambiar los colores:

Dart
KavchatLauncher(controller: kavchat, theme: KavchatThemeData.fromColor(const Color(0xFF0F766E)));
KavchatChat(controller: kavchat, theme: KavchatThemeData.fromHex('#0f766e').copyWith(background: Colors.white));

La etiqueta "Con tecnología de Kavchat by maxsave" aparece en todos los planes, con un enlace a kavchat.com. En Premium, puedes ocultarla en los ajustes del bot.

Chat con un agente

No necesitas configurar nada para empezar. Cuando activas el chat con agentes en el panel de administración, la opción aparece en el chat de la app igual que en el widget para sitios web.

  • La opción: "Hablar con un agente" aparece antes de "Contactar al equipo", solo después de que el servidor confirme que hay un agente disponible. La disponibilidad se comprueba de nuevo al tocar el botón. Fuera del horario de atención, o cuando todos los agentes están ocupados, solo se muestra el formulario de contacto con una breve explicación y, si se sabe, cuándo volverá el equipo. Cuando se agota la cuota mensual, el formulario se muestra sin explicación.
  • La solicitud: los visitantes dejan un correo electrónico o un teléfono por si ningún agente queda libre a tiempo. También pueden añadir un nombre y un primer mensaje. Si hay más de un departamento abierto, eligen uno. Los usuarios identificados (userToken) no necesitan introducir datos de contacto. Si ningún agente puede atender la conversación, se abre el formulario de contacto con los datos ya introducidos.
  • La espera: se muestra "Esperando a un agente" con una cuenta regresiva hasta el final del tiempo de espera que hayas configurado, junto con los botones "Dejar un mensaje en su lugar" y "Finalizar chat". El usuario puede escribir mientras espera. Cuando se acaba el tiempo, la conversación se convierte automáticamente en un ticket. Se muestran el número del ticket y una explicación de que ningún agente quedó libre a tiempo.
  • La conversación: nombre del agente, mensajes, indicador de escritura, transferencia a otro agente y finalización del chat. Todo lo que llega del servidor se muestra como texto sin formato, sin enlaces.
  • En tiempo real: una conexión Server-Sent Events mediante una respuesta http en streaming. Si la conexión se interrumpe, el chat vuelve a conectarse con intervalos cada vez mayores y recupera todo lo que se haya perdido. Así, ningún mensaje se pierde ni aparece dos veces. En redes que bloquean esta conexión y en Flutter Web, el chat funciona con consultas repetidas (long-poll). Cada mensaje tiene un identificador, por lo que un reintento no crea duplicados. Los mensajes que no se han enviado quedan marcados y se pueden reenviar con un toque.
  • Segundo plano y reinicio: cuando la app pasa a segundo plano (paused, hidden), la conexión se cierra. Cuando vuelve al primer plano (resumed), el chat recupera lo que se haya perdido. La credencial de acceso a la conversación es válida hasta 12 horas o hasta que termine la conversación, y se guarda en el almacenamiento que elijas. Así, tras reiniciar la app, una conversación abierta vuelve a aparecer en pantalla. La credencial no se guarda en la URL ni se escribe en los registros.
  • Sin notificaciones push en esta versión: los mensajes del agente solo llegan mientras la app está abierta.

Se recomienda guardar la credencial de acceso en almacenamiento seguro (Keychain en iOS, Keystore en Android), por ejemplo con flutter_secure_storage. Si no configuras secureStorage, la credencial se guarda en el mismo almacenamiento que la conversación. Un botón propio para hablar con un agente abre el formulario de solicitud cuando hay un agente disponible. Si no lo hay, se abre el formulario de contacto con la explicación:

Dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureKavchatStorage implements KavchatStorage {
  final _s = const FlutterSecureStorage();
  @override
  Future<String?> read(String key) => _s.read(key: key);
  @override
  Future<void> write(String key, String value) => _s.write(key: key, value: value);
  @override
  Future<void> remove(String key) => _s.delete(key: key);
}

final kavchat = KavchatController(
  publicKey: 'pk_live_...',
  secureStorage: SecureKavchatStorage(),
  livePage: 'checkout',
  onLivePhase: (phase) => debugPrint('live chat: $phase'),
);

// your own button
FilledButton(onPressed: kavchat.openLive, child: const Text('Chat with us'));

La misma lógica, sin Flutter, está en kavchat_engine (KavchatClient, LiveSession y la máquina de estados compartida liveApplyEvent, liveLines y liveStatus, que supera los mismos vectores de prueba que el widget para sitios web).

Preguntas, condiciones y leads

No necesitas configurar nada en la app. Un guion puede pedir datos (nombre, teléfono, correo electrónico, número o texto), decidir cómo continuar según condiciones (Premium) y enviar un lead. Todo se ejecuta en el dispositivo.

  • Teclado adecuado: cuando el bot espera una respuesta, el campo de texto muestra un teclado de teléfono, correo electrónico o números, con autocompletado. Si la respuesta no es válida, el bot explica qué falta y el campo queda resaltado. Las preguntas opcionales muestran un botón "Omitir".
  • Lead: cuando hay un texto de consentimiento, aparecen los botones "Enviar" y "No, gracias". Si no hay conexión, el lead se guarda en el dispositivo y se envía cuando vuelve la conexión, sin duplicados.
  • Datos precargados: las respuestas que el usuario ya ha dado rellenan el formulario de contacto y el de solicitud de chat con un agente, y se adjuntan al ticket y a la conversación con el agente. El nombre, el correo electrónico y el teléfono del token de identidad se consideran respuestas conocidas, por lo que el bot no vuelve a pedirlos.
  • Pantalla actual: livePage también indica la pantalla que comprueban las condiciones y que se envía con los leads. Cuando el usuario cambia de pantalla, llama a setPage('checkout').

Respuestas de IA (Premium)

No necesitas configurar nada en la app. Cuando activas las respuestas de IA en el panel de administración (con tu propia clave de un proveedor de IA), las preguntas que ningún guion responde se envían a Kavchat. Durante la espera, de hasta 20 segundos, el chat indica que el bot está escribiendo. La respuesta aparece como texto sin formato con una pequeña etiqueta "Respuesta de IA" en el idioma de la interfaz. Debajo se muestran un botón de un tema relacionado, si lo hay, y el botón "Contactar al equipo".

Si no hay respuesta, o si la solicitud falla, tarda demasiado o supera el límite (10 respuestas por conversación cada 15 minutos), el chat muestra el mensaje habitual "No encontré una respuesta". La pregunta sigue apareciendo en el informe de preguntas sin respuesta, y las respuestas de IA se guardan en la conversación como cualquier otro mensaje.

Uso sin conexión y caché

La versión se guarda en caché con su ETag (If-None-Match, 304). Cuando no hay conexión o se produce un error del servidor, se usa la copia guardada y se muestra un aviso. La conversación se guarda por bot y se restaura durante 24 horas, siempre que la versión no haya cambiado. El recorrido de la conversación se envía al servidor 2 segundos después de la última acción y también cuando la app pasa a segundo plano.