Resumen del proyecto
Arcana es un acompañante de tarot con IA que guía a los usuarios a través de lecturas de cartas mediante una interfaz de chat conversacional. El backend de IA —responsable del conocimiento sobre tarot y de la lógica de interpretación— lo desarrolló el equipo del cliente. Nuestra responsabilidad era el cliente móvil en Flutter: una experiencia de chat cuidada y con buen rendimiento tanto en iOS como en Android.
La naturaleza del producto planteaba un reto técnico inmediato. Las sesiones de chat se alargan. Los usuarios vuelven a diario, acumulan lecturas y el historial de mensajes puede alcanzar miles de entradas. Al mismo tiempo, la IA transmite sus respuestas en tiempo real, generando texto con formato que incluye interpretaciones en negrita, nombres de cartas en cursiva, encabezados y desgloses en viñetas. Construir una pantalla de chat que gestionara ambas cosas con soltura, a 60 fps en hardware económico, fue el núcleo de nuestro trabajo.

Tirada de la carta diaria

Respuesta de chat en streaming

Lectura de tirada de cartas
Streaming en tiempo real con SSE
Las respuestas de la IA llegan como un flujo de tokens y no como una única carga. Implementamos un cliente de Server-Sent Events (SSE) en Flutter que abre una conexión HTTP persistente con el backend y procesa el flujo de tokens a medida que llega. Cada fragmento entrante se añade al mensaje activo del chat, dando a los usuarios el familiar efecto de «escribiendo»: ver aparecer la interpretación palabra a palabra.
Manejar SSE en Flutter exigió una gestión cuidadosa del ciclo de vida de la conexión: reconectar ante interrupciones de red sin perder contenido parcial, almacenar en búfer las secuencias UTF-8 incompletas en los límites de los fragmentos y garantizar que la UI reconstruyera solo el widget del mensaje afectado y no la lista entera con cada token que llegaba.
Renderizado de Markdown sobre la marcha
Como la IA da formato a sus respuestas en Markdown —negrita para los nombres de las cartas, cursiva para las palabras clave, encabezados para las secciones de la tirada y listas para los puntos principales—, el renderizador del chat tenía que parsear y mostrar Markdown de forma incremental, actualizándose en tiempo real conforme llegaban nuevos tokens.
Construimos un renderizador de Markdown incremental a medida que procesa la cadena creciente en cada actualización sin volver a parsear el mensaje entero desde cero. Los tokens parciales al final del búfer actual (un ** sin cerrar o un encabezado a medio escribir) se mantienen en un estado pendiente y se renderizan como texto plano hasta que el delimitador se resuelve. Esto evita el parpadeo y los saltos de maquetación manteniendo el formato correcto conforme la respuesta se completa.
Benchmarks de rendimiento en dispositivos de gama baja
Los historiales de chat largos son un hecho en una app de uso diario. Diseñamos el sistema para soportar más de 5.000 mensajes sin degradarse, y lo validamos con benchmarks estructurados en dispositivos Android de gama baja: teléfonos representativos del segmento económico, donde la presión sobre el rendimiento de Flutter es mayor.
Nuestro conjunto de benchmarks recorría listas de chat de distintos tamaños (500, 2.000 y 5.000 mensajes) mientras transmitía simultáneamente una respuesta SSE activa, midiendo los tiempos de fotograma durante todo el proceso. Las primeras versiones mostraban caídas de fotogramas durante el scroll rápido a partir de los 2.000 mensajes, a medida que las pasadas de maquetación de las burbujas con Markdown se volvían caras.
Lo abordamos con varias optimizaciones dirigidas:
Caché de maquetación perezosa: las maquetaciones de Markdown ya renderizadas se cachean por ID de mensaje. Una vez maquetada una burbuja de mensaje, sus dimensiones y su árbol de widgets se reutilizan en las siguientes pasadas de scroll en lugar de recalcularse.
Virtualización basada en slivers: la lista de chat usa un SliverList con un delegate a medida que evita construir widgets fuera de pantalla. Solo se instancian los mensajes dentro o cerca del viewport visible, manteniendo el árbol de widgets poco profundo independientemente del número total de mensajes.
Reconstrucciones aisladas del stream: el mensaje en streaming al final de la lista se gestiona de forma independiente de la lista histórica. Las actualizaciones de tokens por SSE disparan una reconstrucción dirigida solo del widget de la burbuja activa, dejando la lista virtualizada completamente intacta.
Tras estas optimizaciones, el tiempo de fotograma se mantuvo de forma consistente por debajo de 16 ms en todos los escenarios de benchmark —incluidos historiales de 5.000 mensajes con un stream activo— en los dispositivos de gama baja objetivo.
Resultados
El cliente final ofrece una experiencia de chat que se siente fluida y con respuesta a cualquier profundidad de historial y en cualquier dispositivo del rango objetivo. Las respuestas en streaming se renderizan con suavidad en tiempo real con el formato Markdown correcto de principio a fin, y los usuarios de largo recorrido que acumulan cientos de sesiones nunca se encuentran con ralentizaciones. La base de rendimiento que establecimos da al producto margen para crecer sin revisar la arquitectura de renderizado.
El cliente de Arcana se construyó como parte de nuestro trabajo de desarrollo de apps con Flutter, donde el rendimiento de renderizado como este es un foco central.
