- En los providers con
capabilities.forms === 'native'(hoy la Cloud API de WhatsApp,@botmesh/meta) el formulario se envía como un formulario nativo, un WhatsApp Flow, y la respuesta llega en un solo mensaje. - En el resto (Baileys, Telegram, Discord, Messenger, Instagram, el simulador) el bot pregunta campo por campo con mensajes de texto.
c.state.
Campos
Todos los campos aceptan además
name, label, required (por defecto true;
false en optin), helper, prompt (texto de la pregunta campo por campo) y
error (mensaje de error propio). La definición se comprueba al construir el
flujo: un id inválido, un nombre de campo repetido u opciones vacías lanzan un
TypeError. Además de id, title, intro y fields, una definición acepta
cta (el botón que abre un formulario nativo) y submitLabel. Si se omiten,
salen de las entradas core.form.cta / core.form.submit del catálogo en el
ctx.lang del usuario ('Abrir formulario' / 'Enviar' en español, 'Open form' / 'Submit' en inglés); una definición sin intro ni title usa
core.form.body como cuerpo del mensaje. La etiqueta de envío forma parte del
Flow de WhatsApp publicado, así que se fija al publicarlo:
toFlowJson(def, { lang: 'es' }) o, para publishForm(), la opción lang del
provider (new CloudAPIProvider({ lang: 'es' }), ponle el BotConfig.lang de tu
bot) o publishForm(def, { lang: 'es' }) (por defecto 'en', el locale por defecto
del core), o define submitLabel.
Campo por campo, select/multiselect aceptan el número del menú, la clave o la
etiqueta (varias elecciones separadas por comas, ;, y/and o espacios entre
números); las fechas aceptan AAAA-MM-DD, DD/MM/AAAA, DD-MM-AAAA y DD.MM.AAAA; los números aceptan
punto o coma decimal (1.5, 1,5). Los números agrupados como 1.500 o 1,500
se rechazan (el separador puede ser de miles o decimal) y se pide al usuario
que los escriba sin separador de miles.
Las respuestas nativas se validan otra vez en el servidor: nunca se confía en un
formulario del lado del cliente.
Opciones
skipWord, pendingMessage, las pistas del modo campo por campo (1) Sí / 2) No, el formato de fecha, “escribe … para saltar”) y todos los errores de
validación salen por defecto de los mensajes internos core.form.* en el
idioma del usuario (ctx.lang, con es y en incluidos). Cámbialos por
idioma con bot.locale() (ver i18n); el error
propio de un campo sigue ganando.
En modo 'auto' el paso solo se muestra de forma nativa si el provider declara
forms: 'native' y puede mostrar ese formulario en concreto. El provider lo
indica con el método opcional IProvider.canRenderForm(form, { nativeId })
(disponible como ctx.canRenderForm()). Si devuelve false, el paso pregunta
campo por campo directamente, sin enviar antes un mensaje de formulario que
fallaría. Si aun así el envío nativo se rechaza con UnsupportedContentError, el
modo auto también pasa a campo por campo. 'native' nunca hace ese cambio.
Cloud API de WhatsApp
@botmesh/meta compila el formulario a un WhatsApp Flow estático de una
pantalla (Flow JSON 6.2, acción navigate, sin endpoint ni cifrado) y lo
envía como mensaje interactivo flow. El webhook nfm_reply se convierte en un
mensaje 'form' cuyo flow_token corresponde al paso pendiente.
El Flow tiene que existir en Meta antes de enviarse. Asocia tus formularios a Flows:
config.flows, sin publishForm() y sin
nativeId) se pregunta campo por campo, como en cualquier otro canal.
flowJsonWarnings(def) avisa de las etiquetas que WhatsApp recortará (FLOW_LIMITS).
toFlowJson(def, { lang }) elige el idioma de la etiqueta de envío por defecto.
Los Flows con endpoint (data_exchange, pantallas dinámicas cifradas) aún no están soportados.
Tests e introspección
MockProvider no declara formularios nativos, así que createTestBot ejecuta los
formularios campo por campo. Para probar el camino nativo, pon
provider.capabilities.forms = 'native' y responde con
chat.send(formReplyBody({ token, values }), { type: 'form' })
(formReplyBody sale de @botmesh/core/spi; token es el del mensaje de
formulario que envió el bot, en provider.outbox).
bot.describe() dibuja nodos form con sus campos y bot.lint() avisa con
form-state-collision cuando un formulario escribe una clave de c.state que
otro ask/choose/form del mismo flujo también escribe. Las reglas de
timeouts se aplican también a los formularios. Más en
Inspeccionar y validar flujos.