SDD — Bot CDS · Mesa de ayuda a estaciones¶
Ficha técnica
| Campo | Valor |
|---|---|
| Repositorio / carpeta | salesiq/bot-cds/ |
| Plataforma | Zoho SalesIQ · Zobot con código Deluge |
| Disparo | Mensaje entrante por WhatsApp |
| Código | flujos/message_handler.dg · flujos/context_handler.dg · flujos/error_handler.dg |
| Integraciones | Kira2, Convenia, Zoho Sheet |
| Proceso de negocio | PDD |
Cómo usar este documento
Aquí se describe la estructura del bot y cómo aplicar los cambios que se piden con frecuencia: agregar estaciones, cambiar seguidores o encargados, y agregar opciones. El inventario actual (qué estaciones, qué opciones, qué seguidores) está en el código, que es la fuente de verdad. Este documento solo se actualiza si cambia la estructura: un área nueva, una integración nueva o un tipo de salida nuevo.
1. Arquitectura general¶
flowchart LR
WA[WhatsApp] --> SIQ[Zoho SalesIQ<br/>Zobot Deluge]
SIQ -->|service-login · crear-ticket<br/>send-file · editar-ticket<br/>cumplimientos| K[Kira2 API]
SIQ -->|login · depositos · cierres-pos<br/>imagen-depositos-cierres| C[Convenia API]
SIQ -->|createRecords| S[Zoho Sheet<br/>etiquetascds]
SIQ -->|forward| AG[Agentes SalesIQ]
El bot no tiene servidor propio: toda la lógica vive en los tres handlers de SalesIQ y se
ejecuta en la nube de Zoho en cada mensaje. No guarda estado fuera de la conversación;
el avance de cada flujo se reconstruye en cada turno a partir de context_id y del mapa
answers (ver sección 6).
2. Componentes¶
| Handler | Responsabilidad |
|---|---|
message_handler |
Punto de entrada. Siempre responde con el menú principal y abre el contexto menu_principal |
context_handler |
Toda la lógica: enrutamiento por área, selección de región y estación, preguntas, llamadas a APIs y bitácora |
error_handler |
Ante cualquier excepción no controlada responde "inconveniente técnico" y hace forward a un agente |
El context_handler arranca con un bloque global que intercepta "Ir al menú principal",
y después tiene un bloque if / else if(context_id.equals("…")) por área:
context_id |
Área | ¿Pide estación? |
|---|---|---|
menu_principal |
Menú de áreas y "Ver más opciones" | No |
soporte_it |
Soporte IT | Sí |
grh |
Recursos Humanos | Sí |
cr |
Cierres | Sí |
mv |
Manejo de Valores | Sí |
cc |
Control de Calidad | Sí |
lg |
Legal | Sí |
cdc |
Cartera de clientes | Sí |
compras |
Compras | Sí |
cds |
Contactar con CDS | Sí |
sso |
SSO | Sí |
conta |
Contabilidad | Sí |
th |
Tiendas: imágenes a Convenia | No |
rino |
Plan Rino Referidos | No |
Los códigos de contexto no coinciden con el nombre del área
cr es Cierres (no Cartera) y cdc es Cartera de clientes (no Control de
Calidad, que es cc). Para ubicarte en el archivo, busca context_id.equals("<id>").
3. Flujo detallado¶
3.1 Esqueleto de un contexto de área¶
Todos los contextos que piden estación tienen exactamente el mismo esqueleto, copiado en cada uno:
- Lee
regionyestaciondeanswers. - Declara los catálogos (sección 6.1):
regionIdMap,estMap,stIdMapyuserIdMap. - Si la estación ya viene elegida, deduce la región recorriendo
estMap. - Calcula
region_id,estacion_idyusuario_kira = ifnull(userIdMap.get(estacion),"740"). - Si falta la región, muestra un
selectcon las regiones en dos páginas. - Si falta la estación, muestra un
selectcon las estaciones de la región. Con más de 9, pagina sola: primeras 8 +➡️ Ver más (<IdRegion>), y la segunda página se reconoce por ese prefijo. Siempre incluye⬅️ Volver a regiones. - Con región y estación resueltas, lee
opcionnumerica2y entra a la rama de la solicitud. Si un área tiene submenú (por ejemplo POS dentro de Soporte IT), la rama lee una segunda variable (opcion_pos,opcion_traslados…) con la misma lógica.
3.2 Recolección de datos¶
Cada solicitud pregunta un campo por turno, en orden, y pregunta el primero que falte. Hay dos estilos que conviven y funcionan igual:
- Leer cada campo a una variable (
x = ""; if(map != null) x = map.get("text")) y preguntar el primero vacío. - Encadenar
if(!answers.containsKey("campo"))en el orden de las preguntas.
Para validar un dato, se elimina la respuesta con answers.remove("campo") y se vuelve a
preguntar con un mensaje de error. Las respuestas siempre se leen del text, no del value.
3.3 Creación del ticket en Kira2¶
POST /v1/auth/service-logincon el usuario de servicio →token.- Arma
descripcioncomo una tabla HTML con los datos recogidos y al final agregaEmpleadoID: <id>por cada seguidor: los fijos del tipo de ticket más el encargado de la estación (usuario_kira). POST /v1/tareas/crear-ticketconTicket(título),Descripcion,IdUsuario(creador),FechaInicioyFechaEntrega(hoy),IdPrioridad,IdCategoria,IdSubcategoria(si aplica),IdArea,IdRegioneIdEstacion.- Lee
IdTareade la respuesta. - Registra la fila en la bitácora (sección 8).
- Responde con el número de ticket y hace
forwarda un agente.
Algunos tipos deciden IdCategoria o IdSubcategoria según una respuesta (por ejemplo,
la forma de pago o la red del POS). En esos casos la variable se asigna con if justo
antes del ticketParams.
3.4 Solicitudes con adjuntos¶
Las solicitudes que llevan varios archivos piden primero la cantidad y luego los reciben
de uno en uno. Como Deluge no guarda variables entre turnos, el número de ticket se guarda
en el nombre de la pregunta:
- El primer archivo se pide con el nombre
img_1_tk0(tk0= aún no hay ticket). - Al recibirlo, se busca en
answersla claveimg_N_tk…con el índice más alto. - Si el sufijo es
0, se crea el ticket. Si no, el sufijo es elIdTarea. - Se sube el archivo con
POST /v1/files/send-file(multipart). - Se leen los adjuntos actuales con
GET /v1/tareas/<id>?Detalle=true. - Se reenvía la lista completa (anteriores + nuevo) con
PUT /v1/tareas/editar-ticket/<id>, porque la API reemplaza la lista en lugar de agregar. - Si faltan archivos, se pide
img_<N+1>_tk<IdTarea>; si no, se registra en la bitácora y se cierra conforward.
Formatos: imágenes (jpg, jpeg, png, gif, bmp) y pdf; según el flujo también docx o xlsx. Límite de 24.41 MB por archivo.
3.5 Cumplimiento en ticket existente¶
Para agregar información a un ticket que ya existe (por ejemplo, el número de TR en
Manejo de Valores): pide el número de ticket, valida que sea numérico y hace
PATCH /v1/cumplimientos-tareas/crear-multiples. Si CumplimientosCreados viene vacío,
informa que el ID es incorrecto.
3.6 Imágenes a Convenia (th)¶
- Pide el número de corte (
numero_corte_1,numero_corte_2… en cada reintento). POST /v1/zoho/login→ token. La respuesta puede venir como objeto o como lista; el código acepta ambas.- Consulta
GET /v1/zoho/depositos/<corte>oGET /v1/zoho/cierres-pos/<corte>. - Si es 404 o vacío, vuelve a pedir el corte. Si hay resultados, muestra hasta 10 en un
selectcon monto, boleta y usuario. - Pide una imagen y la envía a
POST /v1/zoho/imagen-depositos-cierrescomo multipart condata = {"tipo": 1|2, "id": <DepositoID>}. Tipo 1 = depósito o transferencia, tipo 2 = cierre POS. - Termina con
end(sin agente).
4. Sistemas y dependencias¶
| Sistema | Uso | Acceso | Notas |
|---|---|---|---|
| Kira2 | Crear tickets, adjuntar archivos, agregar cumplimientos | API REST kira2-api.somosdonarturo.com/v1 |
Login en cada flujo; no se reutiliza token |
| Convenia | Consultar depósitos y cierres por corte, subir imagen | API REST convenia-api.somosdonarturo.com/v1/zoho |
Solo contexto th |
| Zoho Sheet | Bitácora de atenciones | zoho.sheet.createRecords, conexión etiquetascds |
Una hoja por mes (yyyy-MM) |
| Archivos en Kira2 | PDFs de consulta enviados como enlace (por ejemplo, Cartera Sinergia) | URL fija en el texto de la pregunta | Si se publica una versión nueva, hay que cambiar la URL en el código |
5. Credenciales y configuración¶
| Nombre | Tipo | Dónde se guarda |
|---|---|---|
Usuario de servicio zoho en Kira2 |
Usuario y contraseña | En texto plano dentro de context_handler, en cada bloque de login |
Usuario de servicio zoho en Convenia |
Usuario y contraseña | En texto plano dentro de context_handler |
Conexión etiquetascds |
Conexión de Zoho | Configurada en SalesIQ |
| Libro de bitácora | ID de Zoho Sheet | Fijo en el código, en cada registro de bitácora |
Contraseña en el código y en el repositorio
La contraseña del usuario de servicio está escrita en el Deluge. Al copiar el código a
flujos/ queda también en el historial de Git. En la copia del repositorio reemplázala
por un marcador (<<KIRA2_PASSWORD>>) antes de cada commit. A mediano plazo conviene
moverla a una conexión de Zoho o a una función con la credencial centralizada, y
rotarla. Si ya se hizo commit con el valor real, rotarla es obligatorio.
6. Estados y datos persistentes¶
El bot no persiste nada propio. El estado vive en la sesión de SalesIQ:
| Elemento | Uso |
|---|---|
context_id |
Área actual de la conversación |
answers |
Todas las respuestas acumuladas, indexadas por el name de cada pregunta |
img_<N>_tk<IdTarea> |
Índice del archivo y número de ticket en los flujos con adjuntos |
numero_corte_<N> |
Intentos de número de corte en Convenia |
6.1 Catálogos dentro del código¶
| Mapa | Contenido | De dónde sale cada valor |
|---|---|---|
regionIdMap |
Nombre de región → IdRegion |
Kira2 |
estMap |
Región → lista de estaciones, en el orden en que se muestran | Nombre visible para el usuario |
stIdMap |
Estación → IdEstacion |
Kira2 |
userIdMap |
Estación → id del encargado | Kira2 (usuario del encargado) |
El nombre de la estación es la llave que une los tres mapas: tiene que escribirse igual,
con tildes y mayúsculas, en estMap, stIdMap y userIdMap. Estos mapas están
copiados en cada contexto que pide estación.
7. Manejo de errores y reintentos¶
| Tipo | Ejemplo | Comportamiento |
|---|---|---|
| Negocio | Opción no reconocida en el menú | Repite el menú principal |
| Negocio | Dato con formato inválido | Elimina la respuesta y vuelve a preguntar |
| Negocio | Corte inexistente en Convenia | Pide el corte otra vez con un nuevo name |
| Aplicación | Kira2 no devuelve IdTarea |
Mensaje de error y forward |
| Aplicación | Falla al escribir en Zoho Sheet | Se captura con try/catch y solo se registra con info; no afecta al usuario |
| Aplicación | Error no controlado (nulo, toLong() sobre vacío, etc.) |
Lo atrapa error_handler: mensaje genérico y forward |
| Infraestructura | Login fallido en Kira2 o Convenia | Mensaje "no se pudo autenticar" y forward (Kira2) o end (Convenia) |
No hay reintentos automáticos contra las APIs: un fallo se resuelve pasando la conversación a un agente.
8. Monitoreo y notificaciones¶
Cada atención agrega una fila al libro de Zoho Sheet, en la hoja del mes (yyyy-MM), con
las columnas Fecha, Bot ("CDS"), Area, Opcion, Visitante, Estación elegida,
Región y Ticket. Es la fuente para las métricas del catálogo (atenciones por área y
opción) y también el inventario real de qué opciones se usan.
Los mensajes info (LOG_ERROR, CAMBIO_PC …) solo se ven en los registros de
ejecución de SalesIQ.
9. Cambios frecuentes¶
Estas son las solicitudes que llegan por ticket. Al terminar cualquiera de ellas, aplica siempre la sección 9.6.
9.1 Agregar una estación¶
Datos que necesitas: nombre visible, región, IdEstacion y id del encargado en Kira2.
- En cada contexto que pide estación, agrega el nombre a la lista de su región en
estMap, en la posición en que debe aparecer. - En los mismos contextos, agrega
stIdMap.put("<nombre>","<IdEstacion>")yuserIdMap.put("<nombre>","<id encargado>"). - La paginación se ajusta sola. No hay que tocar nada más.
Si falta en un mapa
Sin stIdMap, estacion_id.toLong() falla y la conversación cae al error_handler
sin crear ticket. Sin userIdMap, el ticket se crea pero el seguidor queda como el
usuario por defecto 740. Al terminar, busca el nombre en el archivo: debe aparecer
tres veces por contexto.
Renombrar o dar de baja una estación es lo mismo a la inversa, en los tres mapas y en todos los contextos.
9.2 Cambiar el encargado de una estación¶
Cambia el valor en userIdMap en todos los contextos. Busca userIdMap.put("<estación>"
para ubicarlos.
9.3 Agregar o quitar un seguidor de un tipo de ticket¶
- Busca el título del ticket (
ticketParams.put("Ticket","<título>")). - Unas líneas arriba está la asignación de
descripcion. Al final de esa línea están losEmpleadoID: <id>: agrega o quita el que corresponda, separado por un espacio. - No quites
EmpleadoID: " + usuario_kira: es el encargado de la estación.
En los flujos con adjuntos, la descripción se arma dentro del bloque if(ticket_tag.equals("0")).
9.4 Agregar una opción a un área existente¶
Datos que necesitas: texto del botón, preguntas en orden, título del ticket, IdArea,
IdCategoria, IdSubcategoria (si aplica), seguidores y si lleva adjuntos.
- Botón. La lista de sugerencias del área aparece en dos lugares: en la rama del área
dentro de
menu_principaly en la primera entrada del contexto (if(opcionnumerica2.equals(""))). Agrega el botón en ambas, antes de "Ir al menú principal". Si la opción va dentro de un submenú, el botón va en la lista de ese submenú. -
Rama. Agrega un
else if(opcionnumerica2.equalsIgnoreCase("<texto del botón>"))copiando la rama de una opción del mismo tipo:Tipo de salida Copia una rama como Ticket sin adjuntos "Fallo en impresora" (Soporte IT) Ticket con varios adjuntos "Requerimientos MEM" (Legal) Traspaso a agente sin ticket "Error en mangueras" (Cierres) Cumplimiento en ticket existente "Registro de TR" (Manejo de Valores) -
Ajusta las preguntas (
nameúnico y en el orden deseado), la tabla dedescripcion, los seguidores, el título y los ids de Kira2, y los camposAreayOpcionde la bitácora. - Si la rama copiada tiene
response.put("context_id","<área>"), cámbialo al contexto donde la estás pegando. Si no, la conversación salta al área de origen.
El texto del botón y el de la condición deben coincidir
El enrutamiento compara el texto visible, no el value. Un emoji o una palabra
distinta hacen que la opción no entre a su rama y el usuario vuelva al menú sin error.
9.5 Agregar un área nueva¶
- Agrega el botón al menú principal o a "Ver más opciones" en
menu_principal. El menú principal se repite enmessage_handler, en el bloque global de retorno y en las ramas demenu_principalque lo reconstruyen: agrégalo en todas. - Agrega su rama en
menu_principalque abre el nuevocontext_idcon sus opciones. - Crea el bloque
else if(context_id.equals("<nuevo>"))copiando completo uno que pida estación (por ejemploconta, que tiene una sola opción) y cambia elcontext_iden todas sus apariciones dentro del bloque. - Este es un cambio de estructura: agrega el área a la tabla de la sección 2 y al catálogo del PDD.
9.6 Después de cualquier cambio¶
- Prueba el flujo en SalesIQ hasta ver el ticket en Kira2 con sus seguidores.
- Copia los handlers a
flujos/, reemplaza la contraseña por el marcador y actualizasincronizado_elenmeta.yml. - Haz commit con el número de ticket del requerimiento en el mensaje, por ejemplo
bot-cds: agrega estación X (KIRA 12345). El historial de Git es la bitácora de cambios del bot; este documento no se toca.
10. Restricciones conocidas y trampas¶
Catálogos y bloques copiados
Los mapas de estaciones, el login, la subida de archivos y la bitácora están copiados en cada contexto. Un cambio en cualquiera de ellos (una estación, la URL de Kira2, una columna de la bitácora) hay que replicarlo en todas las copias. Las copias ya tienen pequeñas diferencias entre sí (por ejemplo, el orden de algunas estaciones), así que al copiar un bloque conviene tomarlo del contexto más reciente. Si algún día se refactoriza, el primer candidato es mover catálogos y login a funciones de Deluge.
Después del ticket, la conversación también pasa a un agente
Los flujos que crean ticket terminan con forward. Esas conversaciones entran a la
cola de SalesIQ aunque la solicitud ya esté en Kira2, y las que nadie toma terminan
como chats perdidos. [confirmar si es intencional o si debería ser end]
Bitácora en opciones sin ticket
Las ramas de traspaso a agente escriben idTarea en la bitácora aunque en esa rama no
se crea ticket. Está dentro del try/catch y no afecta al usuario, pero la columna
Ticket de esas filas no es confiable. Al copiar una rama de traspaso, puedes quitar
esa línea.
Depósito y transferencia son el mismo flujo en Convenia
En th, "Imagen depósitos" e "Imagen transferencia" consultan el mismo endpoint y
envían el mismo tipo: 1; solo cambian los textos. [confirmar con Convenia]
Nombre de la opción en el menú
La opción de imágenes de Convenia se muestra como "Tiendas/Hoteles 🏨" y el código compara contra ese texto exacto.
11. Operación (runbook)¶
El bot responde con 'Tuvimos un inconveniente técnico'
- Es el
error_handler. Revisa en SalesIQ los registros de ejecución del Zobot para ver la línea que falló. - Si ocurre solo con una estación, revisa que esté en
stIdMapde ese contexto con el mismo nombre exacto que enestMap. - Si ocurre en todas las opciones, revisa si cambió la contraseña del usuario de servicio en Kira2 o si la API está caída.
Los tickets se crean pero no le llega al encargado
- Busca la estación en
userIdMapdel contexto. Si no está, se usó el 740. - Verifica que el id siga siendo válido en Kira2.
Un botón dejó de funcionar y el bot regresa al menú
Compara el texto de la sugerencia con el de la condición equalsIgnoreCase. Un emoji
o una palabra distinta es suficiente para que no coincida.
Falla el registro de imágenes en Convenia
- Verifica el login (
/v1/zoho/login) y que el corte exista en Convenia. - Si el corte tiene más de 10 depósitos, solo se muestran los primeros 10.
12. Historial de cambios¶
Solo cambios de estructura. Los cambios de contenido (estaciones, opciones, seguidores)
quedan en el historial de Git de flujos/.
| Versión | Fecha | Cambio | Autor |
|---|---|---|---|
| 1.0 | 2026-10-07 | Documento inicial a partir del código | Angel |