Problema
En muchos procesos de aprobación o verificación se necesita enviar una Adaptive Card a Teams y esperar una respuesta del usuario. La acción Post adaptive card and wait for a response permite definir un timeout (por ejemplo, PT2M). Cuando el usuario pulsa el botón, la salida contiene el submitActionId; cuando el timeout ocurre, la salida es nula y el estado de la acción pasa a TimedOut.
El desafío aparece al intentar encadenar una condición que evalúe ambos escenarios dentro de un bucle Until. Si la condición intenta leer body('Post_adaptive_card_and_wait_for_a_response')['submitActionId'] cuando la tarjeta expiró, el motor lanza InvalidTemplate porque la propiedad no existe. El resultado es un flujo que se detiene antes de poder enviar recordatorios o marcar la tarea como completada.
Causa
- Acceso directo a la carga del webhook: La expresión
body('action')asume que siempre hay un objeto JSON. En caso de timeout, la acción devuelve{}y la evaluación falla. - Orden de ejecución: La condición se ejecuta después de la acción, pero el motor solo considera los resultados de estados Succeeded y Failed por defecto. Cuando la acción termina en TimedOut, la condición necesita estar configurada para ejecutarse también en ese caso.
- Uso de
actions('action')['status']sin manejo de nulidad: Aunqueactions('action')['status']devuelve la cadena"TimedOut", la expresión suele combinarse con otras verificaciones que siguen intentando leer el cuerpo, provocando el mismo error.
Solución
La forma más robusta es separar claramente los dos caminos:
- Obtener el estado de la acción mediante
actions('Post_adaptive_card_and_wait_for_a_response')['status']. - Usar
coalescepara extraersubmitActionIdsolo cuando exista, evitandoInvalidTemplate. - Configurar
runAfterde la condición para que acepteTimedOutcomo ruta válida.
Un patrón reutilizable queda así:
{
"type": "If",
"expression": {
"or": [
{
"equals": [
"@actions('Post_adaptive_card_and_wait_for_a_response')['status']",
"Succeeded"
]
},
{
"equals": [
"@actions('Post_adaptive_card_and_wait_for_a_response')['status']",
"TimedOut"
]
}
]
},
"runAfter": {
"Post_adaptive_card_and_wait_for_a_response": [
"Succeeded",
"TimedOut"
]
},
"actions": {
"CheckResponse": {
"type": "If",
"expression": {
"equals": [
"@coalesce(body('Post_adaptive_card_and_wait_for_a_response')?['submitActionId'], '')",
"completed"
]
},
"actions": {
"SetTaskComplete": {
"type": "SetVariable",
"inputs": {
"name": "TaskAComplete",
"value": true
}
}
},
"else": {
"SendReminder": {
"type": "ApiConnection",
"inputs": {
"method": "post",
"path": "/v2/Microsoft.Outlook/sendMail",
"body": {
"message": {
"subject": "Recordatorio: completa la tarea",
"toRecipients": [
{
"emailAddress": {
"address": "responsable@contoso.com"
}
}
]
}
}
}
}
}
}
}
}
Puntos clave del fragmento:
- La condición externa (
If) solo verifica que la acción haya terminado, seaSucceededoTimedOut. - Dentro,
coalescedevuelve una cadena vacía cuando el cuerpo es nulo, evitando la excepción. - La comparación con
"completed"(o el valor que corresponda alsubmitActionIdesperado) determina si la tarea está concluida. - El bucle
Untilsigue evaluando@equals(variables('TaskAComplete'), true)y se detendrá cuando la variable se ponga atrue.
Cuándo aplicar esta solución
- Flujos que requieren interacción humana mediante Adaptive Cards y deben repetir recordatorios hasta obtener una respuesta.
- Escenarios con timeout definido (cualquier valor de
PTnMoPTnS). - Cuando el flujo se ejecuta en entornos de producción y no se puede permitir que un
InvalidTemplaterompa la cadena de acciones.
No es necesario usar esta lógica si la tarjeta nunca expira (por ejemplo, se espera indefinidamente) o si la respuesta se gestiona mediante otro trigger (como When a HTTP request is received). En esos casos, la condición basada en submitActionId basta sin comprobar el estado.
Código
# Ejemplo de expresión para la condición interna
# Se coloca directamente en el campo "Expression" del bloque If
@equals(coalesce(body('Post_adaptive_card_and_wait_for_a_response')?['submitActionId'], ''), 'completed')
Verificación
- Desplegar el flujo con el bucle
Untily la condición mostrada. - Ejecutar una prueba enviando la Adaptive Card a Teams.
- No pulsar el botón y esperar al timeout (2 min).
- Verificar en el historial que la acción
Post_adaptive_card_and_wait_for_a_responsetienestatus = TimedOut. - Confirmar que la rama else (recordatorio) se ejecuta sin error.
- Verificar en el historial que la acción
- Volver a ejecutar y pulsar el botón antes del timeout.
- El historial debe mostrar
status = Succeededy la rama then (SetTaskComplete) debe activarse.
- El historial debe mostrar
- Comprobar la variable
TaskACompleteal final del ciclo; debe sertruesolo cuando se recibió la respuesta.
Si alguna de las verificaciones falla, revisar que el runAfter incluye TimedOut y que la expresión coalesce está escrita exactamente como se muestra.
Notas adicionales
- En entornos con alta concurrencia, es buena práctica generar un
correlationIdy pasarlo dentro delvaluedel botón. Así la condición puede validar que la respuesta pertenece al mismo ciclo del bucle. - La acción Post adaptive card and wait for a response no permite cambiar dinámicamente el timeout dentro del mismo ciclo; si se necesita un tiempo variable, recrear la acción con una variable que contenga la cadena
PT${minutes}M. - Cuando se usan varios botones, el
submitActionIdserá distinto para cada uno; la lógica decoalescesigue funcionando, solo hay que comparar contra el identificador esperado. - Si el flujo se ejecuta bajo una suscripción con limite de acciones bajo, limitar el número de iteraciones del
Until(propiedadlimit.count) evita bloqueos inesperados.
Con este patrón, los flujos que dependen de Adaptive Cards pueden recuperarse de timeouts sin romper la cadena de ejecución, garantizando que los recordatorios se envíen y que la tarea se marque como completada tan pronto como el usuario interactúe.