# MacroBase Freshchat API

API en Python/FastAPI para que Freshchat/Freshworks Automations consuma respuestas conversacionales desde API Library o Trigger API.

La API devuelve siempre estas claves principales:

```json
{
  "error": "false",
  "mensaje": "texto principal",
  "response_type": "text",
  "data": {}
}
```

`error` se devuelve como string `"false"` o `"true"` para facilitar el mapeo en Freshchat.

## Requisitos

- Python 3.11+
- FastAPI
- Pydantic
- Uvicorn
- pydantic-settings / python-dotenv

## Instalación Local

```bash
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env
```

Edita `.env` y define un token propio:

```env
APP_NAME=MacroBase Freshchat API
APP_ENV=development
FRESHCHAT_API_TOKEN=tu-token-seguro
DEFAULT_RESPONSE_MODE=freshchat_simple
```

Ejecutar:

```bash
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```

Healthcheck:

```bash
curl http://localhost:8000/health
```

## Docker

```bash
docker build -t macrobase-freshchat-api .
docker run --env-file .env -p 8000:8000 macrobase-freshchat-api
```

## Freshchat Webhooks

La integracion principal recomendada es Freshchat Webhooks. Freshchat envia eventos reales a esta API y la API responde al cliente usando Freshchat Conversations API.

No uses Advanced Automations API Library como motor principal para esto, porque en esta cuenta los placeholders de conversacion llegan vacios.

### Variables Necesarias

```env
FRESHCHAT_API_BASE_URL=https://api.freshchat.com/v2
FRESHCHAT_API_TOKEN=token-oficial-de-freshchat
FRESHCHAT_AUTO_REPLY_ENABLED=true
FRESHCHAT_SEND_AS_ACTOR_TYPE=bot
FRESHCHAT_WEBHOOK_VERIFY_SIGNATURE=false
CUSTOMER_STATE_BACKEND=mysql
CUSTOMER_STATE_STORE_PATH=data/customer_state.db
CUSTOMER_STATE_MYSQL_HOST=132.226.40.48
CUSTOMER_STATE_MYSQL_PORT=3309
CUSTOMER_STATE_MYSQL_DATABASE=mbinvfw
CUSTOMER_STATE_MYSQL_USER=usuario-aislado
CUSTOMER_STATE_MYSQL_PASSWORD=clave-segura
CUSTOMER_STATE_MYSQL_TABLE=customer_states
```

`FRESHCHAT_API_TOKEN` es el token oficial de Freshchat para llamar Conversations API. No lo pegues en capturas, README ni logs.

### Levantar Local

```bash
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```

Healthcheck:

```bash
curl http://localhost:8000/health
```

### Usar Ngrok

En otra terminal:

```bash
ngrok http 8000
```

Ngrok dara una URL publica similar a:

```text
https://abc123.ngrok-free.app
```

### URL Para Freshchat Webhooks

Configura el webhook de Freshchat con:

```text
https://TU-DOMINIO/freshchat/webhook
```

Ejemplo de produccion:

```text
https://apifresh.sistemasmb.com/freshchat/webhook
```

Evento inicial a activar:

```text
message_create
```

### Que Hace El Webhook

- Lee headers y payload completo.
- Loguea headers con datos sensibles redactados.
- Extrae `action`, `actor.actor_type`, `data.message.conversation_id`, `data.message.id`, `data.message.channel_id` y `message_parts[0].text.content`.
- Ignora eventos de `agent`, `bot`, `system`, `botsPrivateNote`, `isBotsInput` y `restrictResponse`.
- Procesa solo mensajes de usuario con `action=message_create`.
- Si `FRESHCHAT_AUTO_REPLY_ENABLED=true`, envia respuestas a `/conversations/{conversation_id}/messages`.
- Si el cliente elige un area de soporte, solicita los datos faltantes uno por uno y los guarda en `CUSTOMER_STATE_STORE_PATH`.
- Si Freshchat envia un evento de conversacion resuelta, manda una encuesta breve de cierre con botones.
- Siempre responde HTTP 200 al webhook para evitar reintentos innecesarios mientras probamos.

### Captura De Datos Del Cliente

Cuando el cliente selecciona una opcion como `Soporte`, `Emergencia de Cobro`, `Implementacion`, `Ventas` o `Administracion`, el bot ya no manda solo una lista larga de requisitos. Primero revisa si ya tiene los datos guardados para ese cliente:

- Si falta informacion, pregunta un dato a la vez.
- Si el cliente responde vacio, vuelve a pedir el mismo dato.
- Si ya tiene datos estables, no los vuelve a pedir.
- Al completar la captura, envia un resumen en la misma conversacion para que soporte no tenga que volver a preguntar.
- `AnyDesk` se pide una vez y se reutiliza cuando otro flujo tambien lo necesita.
- `Empresa` se guarda como dato estable del cliente.
- `Sistema`, `tienda`, `caja` y `descripcion` se piden en cada caso nuevo, porque pueden cambiar.
- Si el cliente elige `POSTouch`, se pide `tienda` y `caja`.
- Si el cliente elige `BackOffice`, no se pide tienda ni caja.
- La pregunta de sistema se envia como quick replies con `POSTouch` y `BackOffice`.
- Si el cliente escribe en vez de tocar el boton, tambien se aceptan variantes:
  - POSTouch: `pos`, `facturacion`, `facturas`, `postouch`, `ticket`, `caja`.
  - BackOffice: `backoffice`, `backofice`, `inventario`, `mbinv`, `stock`, `bodega`.
- Si el texto es ambiguo, por ejemplo mezcla facturacion con inventario, el bot vuelve a pedir que seleccione el sistema.
- La evidencia ya no se pregunta como paso separado. Dentro de la descripcion se invita al cliente, de forma opcional, a enviar imagen o video si lo tiene.

Ejemplo del resumen que vera soporte:

```text
Datos recibidos para Soporte:
- Empresa: MacroBase
- Sistema: POS Touch
- Tienda: Zona 10
- Caja: 2
- Descripcion de la solicitud: No imprime ticket
- AnyDesk: 123 456 789

En breve un asesor continuara con el apoyo.
```

### Encuesta Al Resolver

Cuando Freshchat notifique que una conversacion fue resuelta, la API intenta enviar una encuesta:

1. Pregunta si el cliente desea responder la encuesta: `Si` / `No`.
2. Si responde `Si`, pregunta 3 calificaciones del 1 al 5 usando selector de estrellas:
   - Rapidez de respuesta.
   - Amabilidad y profesionalismo.
   - Resolucion de la conversacion.
3. Las opciones de calificacion se intentan enviar como dropdown horizontal/lista con:
   `⭐`, `⭐⭐`, `⭐⭐⭐`, `⭐⭐⭐⭐`, `⭐⭐⭐⭐⭐`.
4. Si Freshchat rechaza el selector en algun canal, la API envia un fallback en texto y acepta numeros del `1` al `5` o estrellas.
5. Al terminar la encuesta o si el cliente responde `No`, la API intenta cerrar nuevamente la conversacion con Freshchat Conversations API.

Para que esto funcione, en Freshchat Webhooks debes activar tambien el evento de conversacion resuelta o actualizacion de conversacion, ademas de `message_create`.

Nota importante: si la encuesta se envia despues de que Freshchat ya resolvio la conversacion, cualquier respuesta del cliente puede reabrir o crear una conversacion nueva. Eso es comportamiento de Freshchat/WhatsApp. La API guarda la encuesta por cliente para continuar el flujo aunque cambie el conversation id, y al finalizar intenta cerrar la conversacion automaticamente.

Por defecto, en desarrollo el estado puede guardarse en SQLite:

```text
data/customer_state.db
```

En produccion usa MySQL:

```env
CUSTOMER_STATE_BACKEND=mysql
CUSTOMER_STATE_MYSQL_HOST=132.226.40.48
CUSTOMER_STATE_MYSQL_PORT=3309
CUSTOMER_STATE_MYSQL_DATABASE=mbinvfw
CUSTOMER_STATE_MYSQL_USER=usuario-aislado
CUSTOMER_STATE_MYSQL_PASSWORD=clave-segura
CUSTOMER_STATE_MYSQL_TABLE=customer_states
```

La API crea la tabla automaticamente si el usuario MySQL tiene permisos `CREATE`, `SELECT`, `INSERT` y `UPDATE`.

Consulta rapida:

```sql
SELECT customer_key, active_flow, current_field, data_json, updated_at
FROM customer_states
ORDER BY updated_at DESC
LIMIT 20;
```

Si necesitas limpiar datos guardados en SQLite durante pruebas, detén la API y elimina `data/customer_state.db`.

### Probar Configuracion

```bash
curl https://apifresh.sistemasmb.com/freshchat/test/config
```

Debe mostrar:

```json
{
  "status": "ok",
  "freshchat_api_base_url_configured": true,
  "freshchat_api_token_configured": true,
  "freshchat_auto_reply_enabled": true,
  "freshchat_send_as_actor_type": "bot"
}
```

### Probar Envio Manual

Usa un `conversation_id` real tomado del log del webhook:

```bash
curl -X POST https://apifresh.sistemasmb.com/freshchat/test/send-message \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "PEGAR_CONVERSATION_ID_REAL",
    "message": "Prueba de respuesta desde Python"
  }'
```

Si falla, revisa en los logs de Uvicorn `status_code` y `response_body`.

### Probar Logica Del Bot

Este endpoint no envia nada a Freshchat. Solo muestra que respuesta construiria el webhook automatico.

```bash
curl -X POST https://apifresh.sistemasmb.com/freshchat/test/build-reply \
  -H "Content-Type: application/json" \
  -d '{"message": "hola"}'
```

Debe devolver `type=quick_replies` con botones:

```text
Soporte
Emergencia de Cobro
Mas opciones
```

Segundo menu:

```bash
curl -X POST https://apifresh.sistemasmb.com/freshchat/test/build-reply \
  -H "Content-Type: application/json" \
  -d '{"message": "más opciones"}'
```

Tambien prueba:

```bash
curl -X POST https://apifresh.sistemasmb.com/freshchat/test/build-reply \
  -H "Content-Type: application/json" \
  -d '{"message": "Prueba"}'

curl -X POST https://apifresh.sistemasmb.com/freshchat/test/build-reply \
  -H "Content-Type: application/json" \
  -d '{"message": "soporte"}'

curl -X POST https://apifresh.sistemasmb.com/freshchat/test/build-reply \
  -H "Content-Type: application/json" \
  -d '{"message": "ventas"}'

curl -X POST https://apifresh.sistemasmb.com/freshchat/test/build-reply \
  -H "Content-Type: application/json" \
  -d '{"message": "administracion"}'

curl -X POST https://apifresh.sistemasmb.com/freshchat/test/build-reply \
  -H "Content-Type: application/json" \
  -d '{"message": "implementación"}'
```

La prueba real del webhook automatico es enviar `hola` desde WhatsApp. Si `FRESHCHAT_AUTO_REPLY_ENABLED=true`, el webhook intentara responder con quick replies; si Freshchat rechaza los botones, enviara el fallback en texto.

Si el usuario escribe algo no reconocido, por ejemplo `Prueba`, el bot vuelve a mostrar el menu principal con quick replies en lugar de responder un eco del mensaje.

Si el usuario elige `Soporte`, el webhook empezara a pedir los datos faltantes:

```text
Indicanos el nombre de tu empresa.
```

Despues de cada respuesta, pedira el siguiente dato hasta completar el resumen para el agente.

El bot usa quick replies de maximo 3 botones porque ese es el formato que ya quedo validado para WhatsApp/Freshchat. Por eso el menu principal queda:

```text
Soporte
Emergencia de Cobro
Mas opciones
```

Para mostrar mas de 3 opciones en una sola pantalla habria que implementar y validar otro payload, como lista/dropdown.

### Probar Quick Reply Buttons

Este endpoint manual sirve para validar `reply_parts` con quick reply buttons sin esperar un evento de WhatsApp.

Prueba primero con `display_order`:

```bash
curl -X POST https://apifresh.sistemasmb.com/freshchat/test/send-quick-replies \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "896a29e1-b2ae-40f9-a5ab-60ec0f4262cf",
    "message": "Por favor selecciona una opcion:",
    "help_text": "Selecciona una opción",
    "display_order_style": "snake",
    "buttons": ["Soporte", "Ventas", "Administracion"]
  }'
```

Si Freshchat sigue pidiendo `displayOrder`, prueba con camelCase:

```bash
curl -X POST https://apifresh.sistemasmb.com/freshchat/test/send-quick-replies \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "896a29e1-b2ae-40f9-a5ab-60ec0f4262cf",
    "message": "Por favor selecciona una opcion:",
    "help_text": "Selecciona una opción",
    "display_order_style": "camel",
    "buttons": ["Soporte", "Ventas", "Administracion"]
  }'
```

Reglas de esta prueba:

- Maximo 3 botones.
- Si mandas mas de 3, la API corta la lista a los primeros 3.
- `help_text` es opcional; si llega vacio, se usa `Selecciona una opción`.
- `display_order_style` puede ser `snake` o `camel`; si llega otro valor, se usa `snake`.
- Si Freshchat rechaza el payload, la respuesta incluye `status_code` y `response_body`.
- El token nunca se imprime en logs; solo se loguea el payload enviado.

### Datos A Buscar En Logs

Cuando Freshchat llame el webhook, revisa la consola de Uvicorn y busca:

- `conversation_id`
- `freshchat_conversation_id`
- `conversationAlias`
- `messageAlias`
- `message_id`
- `channel_id`
- `actor`
- `actor.type`
- `actor.actor_type`
- `message_parts`
- `content`

La variable `FRESHCHAT_WEBHOOK_VERIFY_SIGNATURE=false` deja desactivada la validacion de firma por ahora. La funcion de validacion ya existe para activar `X-Freshchat-Signature` cuando confirmemos el formato real.

## Despliegue En Servidor

Para un servidor Linux/Apache con proyectos bajo `/var/www/html`, revisa [docs/deploy_linux_server.md](</c:/Users/cvela/OneDrive/Desktop/Api/docs/deploy_linux_server.md>).

## Botones Reales En WhatsApp

Para enviar botones/listas reales no uses solamente Advanced Automations API Library. Usa la Custom App incluida en [freshworks_custom_app](</c:/Users/cvela/OneDrive/Desktop/Api/freshworks_custom_app>) y sigue [docs/freshworks_custom_app_installation.md](</c:/Users/cvela/OneDrive/Desktop/Api/docs/freshworks_custom_app_installation.md>).

Para la version final de produccion con HTTPS, reverse proxy y eventos de Freshworks, usa [docs/production_buttons_setup.md](</c:/Users/cvela/OneDrive/Desktop/Api/docs/production_buttons_setup.md>).

## Autenticación

Todos los endpoints de Freshchat requieren:

```http
Authorization: Bearer <FRESHCHAT_API_TOKEN>
```

No expongas el token en Freshchat como texto visible ni en logs. Configúralo como header en API Library.

## Modos de Respuesta

`DEFAULT_RESPONSE_MODE` controla cómo responden los endpoints cuando el canal soporta interactividad:

- `internal`: estructura rica con `data.messages`.
- `freshchat_simple`: estructura rica más variables planas como `option_1`, `option_1_id`, `fallback_text`.
- `fallback`: solo texto para canales donde no se puedan usar botones/listas/rating.

Si el request trae `supports_interactive: false`, la API responde en fallback aunque el modo default sea otro.

## Endpoints

### GET /health

Respuesta:

```json
{
  "status": "ok"
}
```

### POST /freshchat/workflow/start

Inicia el menú principal.

```bash
curl -X POST http://localhost:8000/freshchat/workflow/start \
  -H "Authorization: Bearer $FRESHCHAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cliente": "123",
    "nombreCompleto": "Cliente Demo",
    "nombre": "Cliente",
    "telefono": "+50255555555",
    "email": "cliente@example.com",
    "canal": "whatsapp",
    "supports_interactive": true
  }'
```

Opciones del menú:

- Emergencia de Cobro
- Soporte
- Implementación
- Ventas
- Administración
- MBIA

### POST /freshchat/workflow/start-interactive

Envía el menú principal como mensaje interactivo usando Freshchat Conversation API. Este endpoint es para casos donde quieres que el backend inserte botones/lista en la conversación, sin usar `Send a message` en Advanced Automations.

Requiere estas variables en `.env`:

```env
FRESHWORKS_API_BASE_URL=https://macrobase-chat.myfreshworks.com
FRESHWORKS_API_TOKEN=token-oficial-freshchat
FRESHWORKS_DEFAULT_ACTOR_TYPE=agent
FRESHWORKS_DEFAULT_ACTOR_ID=id-del-agente
```

Ejemplo:

```bash
curl -X POST http://localhost:8000/freshchat/workflow/start-interactive \
  -H "Authorization: Bearer $FRESHCHAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cliente": "123",
    "conversation_id": "conversation-id",
    "user_id": "contact-id",
    "nombreCompleto": "Cliente Demo",
    "nombre": "Cliente",
    "telefono": "+50255555555",
    "email": "cliente@example.com",
    "canal": "whatsapp",
    "supports_interactive": true
  }'
```

En Freshworks API Library, usa este endpoint como una acción `Trigger an API`. No agregues `Send a message`, porque el backend ya envía el mensaje interactivo.

### POST /freshchat/workflow/handle-option

Procesa una opción seleccionada. Acepta ID o texto.

```bash
curl -X POST http://localhost:8000/freshchat/workflow/handle-option \
  -H "Authorization: Bearer $FRESHCHAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cliente": "123",
    "selected_option": "Soporte",
    "canal": "whatsapp",
    "supports_interactive": true
  }'
```

### POST /freshchat/mbia/login

Valida acceso MBIA de forma simulada.

```bash
curl -X POST http://localhost:8000/freshchat/mbia/login \
  -H "Authorization: Bearer $FRESHCHAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cliente": "123",
    "nombreCompleto": "Cliente Demo",
    "nombre": "Cliente",
    "telefono": "+50255555555",
    "email": "cliente@example.com"
  }'
```

### POST /freshchat/mbia/bot

Responde una petición MBIA de forma simulada.

```bash
curl -X POST http://localhost:8000/freshchat/mbia/bot \
  -H "Authorization: Bearer $FRESHCHAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cliente": "123",
    "nombreCompleto": "Cliente Demo",
    "nombre": "Cliente",
    "telefono": "+50255555555",
    "email": "cliente@example.com",
    "peticion": "Necesito consultar mi información"
  }'
```

### POST /freshchat/encuesta/start

Inicia encuesta con botones Sí/No.

```bash
curl -X POST http://localhost:8000/freshchat/encuesta/start \
  -H "Authorization: Bearer $FRESHCHAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cliente": "123",
    "nombre": "Cliente",
    "empresa": "Empresa Demo",
    "canal": "whatsapp",
    "supports_interactive": true
  }'
```

### POST /freshchat/encuesta/question

Devuelve pregunta de rating 1 a 5.

```bash
curl -X POST http://localhost:8000/freshchat/encuesta/question \
  -H "Authorization: Bearer $FRESHCHAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cliente": "123",
    "step": "rapidez",
    "canal": "whatsapp",
    "supports_interactive": true
  }'
```

Valores permitidos para `step`: `rapidez`, `amabilidad`, `resolucion`.

### POST /freshchat/encuesta/save

Guarda encuesta de forma simulada y calcula promedio.

```bash
curl -X POST http://localhost:8000/freshchat/encuesta/save \
  -H "Authorization: Bearer $FRESHCHAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "nombre": "Cliente Demo",
    "empresa": "Empresa Demo",
    "agente_id": "agent-1",
    "agente": "Agente Demo agente@example.com",
    "rapidez": 5,
    "amabilidad": 4,
    "resolucion": 5,
    "fecha_hora_conversacion_abierta": "2026-05-14T10:00:00Z"
  }'
```

### POST /freshchat/render

Transforma una respuesta interna al modo deseado.

```bash
curl -X POST http://localhost:8000/freshchat/render \
  -H "Authorization: Bearer $FRESHCHAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "fallback",
    "payload": {
      "error": "false",
      "mensaje": "Selecciona una opción",
      "response_type": "buttons",
      "data": {
        "messages": [
          {
            "type": "buttons",
            "text": "Selecciona:",
            "buttons": [
              { "id": "soporte", "title": "Soporte" },
              { "id": "ventas", "title": "Ventas" }
            ],
            "fallback_text": "Responde Soporte o Ventas."
          }
        ]
      }
    }
  }'
```

## Ejemplos Para Freshchat API Library

Configura el método como `POST`, agrega header `Authorization` con `Bearer <token>` y `Content-Type: application/json`.

También hay una guía de migración basada en los exports JSON existentes en [docs/freshchat_workflow_mapping.md](</c:/Users/cvela/OneDrive/Desktop/Api/docs/freshchat_workflow_mapping.md>).

Para configurar la pantalla de API Library paso a paso, revisa [docs/freshchat_api_library_setup.md](</c:/Users/cvela/OneDrive/Desktop/Api/docs/freshchat_api_library_setup.md>).

Body para iniciar workflow:

```json
{
  "cliente": "{{contact.id}}",
  "nombreCompleto": "{{contact.name}}",
  "nombre": "{{contact.firstname}}",
  "telefono": "{{contact.phone}}",
  "email": "{{contact.email}}",
  "canal": "whatsapp",
  "supports_interactive": true
}
```

Body para MBIA bot:

```json
{
  "cliente": "{{contact.id}}",
  "nombreCompleto": "{{contact.name}}",
  "nombre": "{{contact.firstname}}",
  "telefono": "{{contact.phone}}",
  "email": "{{contact.email}}",
  "peticion": "{{contact.last_incoming_message}}"
}
```

Body para guardar encuesta:

```json
{
  "nombre": "{{contact.name}}",
  "empresa": "{{contact.empresa_lista}}",
  "agente_id": "{{assignee.id}}",
  "agente": "{{assignee.firstname}} {{assignee.lastname}} {{assignee.email}}",
  "rapidez": "{{workflow.rapidez}}",
  "amabilidad": "{{workflow.amabilidad}}",
  "resolucion": "{{workflow.resolucion}}",
  "fecha_hora_conversacion_abierta": "{{conversation.opened_timestamp}}"
}
```

## Mapeo Sugerido En Freshchat

Variables principales:

- `mensaje`: texto principal para mostrar.
- `response_type`: `text`, `buttons`, `list`, `rating`, `mixed`.
- `data.messages[0].type`: tipo del primer componente.
- `data.messages[0].buttons`: arreglo de botones cuando aplique.
- `data.messages[0].sections`: secciones y filas cuando aplique lista.
- `data.messages[0].fallback_text`: texto alternativo si el canal no permite interacción.
- `option_1`, `option_2`, `option_3`: opciones planas en modo `freshchat_simple`.
- `option_1_id`, `option_2_id`, `option_3_id`: IDs planos para guardar selección.
- `fallback_text`: fallback plano en modo `freshchat_simple`.
- `send_message`: texto listo para usar en `Send a message`; combina pregunta y opciones.
- `question_text`: texto principal de la pregunta.
- `options_text`: opciones enumeradas.
- `options_csv` y `option_ids_csv`: opciones e IDs como texto separado por comas.
- `whatsapp_interactive`: payload estructurado tipo WhatsApp interactive para integraciones directas que acepten mensajes interactivos.

Freshchat puede tener limitaciones para renderizar botones directamente desde una API externa. Por eso esta API devuelve una estructura rica, una versión plana y un fallback de texto. Si el canal no permite botones/listas/rating, usa `fallback_text` o envía `supports_interactive: false`.

En Advanced Automations, `Trigger an API` solo guarda la respuesta del API; no renderiza botones por si mismo. Para enviar algo al usuario desde `Send a message`, usa:

```text
{{workflow_start.send_message}}
```

Si Freshchat permite usar una acción nativa de pregunta/opciones, puedes mapear:

```text
question_text
option_1
option_2
option_3
option_4
option_5
option_6
```

## Errores

Errores controlados devuelven HTTP 200 con:

```json
{
  "error": "true",
  "mensaje": "mensaje claro",
  "response_type": "text",
  "data": {}
}
```

Errores técnicos usan HTTP `401`, `422` o `500` según corresponda, manteniendo una forma JSON compatible.
