Intercepción liviana de adjuntos en WhatsApp + Agentforce — PROTOTIPO
The Challenge
Cuando el usuario envía un archivo por WhatsApp mid-conversación, Agentforce responde error. Este approach intercepta en el punto sync disponible en el path estándar, sin reemplazar el canal Enhanced.
In short
- El path estándar Enhanced WhatsApp no expone un hook post-agent — descartado empíricamente después de probar 10 candidatos (ConversationEntry no es triggerable, Message vacío, Sensitive Data Rules son del stack Live Chat legado, no hay 'post-response flow', el Trust Layer no acepta filtros custom, action sequencing es no-determinístico).
- El único punto sync-observable en el path estándar es ContentDocumentLink.BEFORE_INSERT cuando LinkedEntityType=MessagingSession — dispara en el AutomatedProcess user context antes de que Agentforce procese el turno con el adjunto.
- Desde ese trigger podemos publicar un Platform Event y encolar un Queueable que llama a Prompt Builder (GPT-4o vision para imagen/PDF, Whisper para audio) — todo dentro de Salesforce, sin credenciales externas.
- El resultado del análisis se puede escribir en campos custom de MessagingSession (AI_Issue__c, AI_Resolution__c, AI_Summary__c ya existen en la org Laila) o inyectar como mensaje sintético al canal.
- Prototipo no verificado — falta construir y probar el pipeline completo. El write-back al canal (cómo entregar el mensaje sintético al usuario) es la parte más incierta.
Symptom
El usuario envía un archivo por WhatsApp mid-conversación. Agentforce responde 'no pude procesar el archivo' o similar. Las dos primeras 'soluciones' probadas (instruir al agente para omitir el error, o reemplazar todo el canal con webhook custom) tienen problemas: la primera es no-determinística, la segunda es cara.
Root cause
En Enhanced WhatsApp, ConversationEntry vive off-platform y el campo Message está vacío en 100% de los registros consultables. El texto del agente nunca es visible on-platform. Ver hallazgos empíricos.
Impact
Cualquier caso donde el cliente adjunta evidencia (comprobantes, fotos, notas de voz) durante una conversación activa queda bloqueado.
El 2026-08-01 se desplegaron 3 triggers de solo-debug en la org Laila (jgr@laila.demo) para mapear qué objetos disparan y bajo qué user context durante flujos reales de WhatsApp. La tabla siguiente resume la matriz observada.
| Objeto | Triggerable en describe | Dispara en test real | User context observado | Utilidad |
|---|---|---|---|---|
| ConversationEntry | false | N/A | N/A | Descartado — no permite trigger. Field 'Message' además está siempre vacío. |
| MessagingSession | true | Sí — insert + updates de Status/Owner | AutomatedProcess (05K...002DT8JYAW) | Útil para reaccionar a cambios de sesión, no a turnos individuales. |
| MessagingEndUser | true | No en este test (MEU pre-existente) | N/A | Dispararía solo en primer contacto de un teléfono nuevo. |
| ContentDocumentLink | true | Sí — BEFORE_INSERT sync con LinkedEntityType=MessagingSession | AutomatedProcess | PUNTO DE INTERCEPCIÓN CLAVE. |
Con el punto de detección (CDL BEFORE_INSERT) confirmado, la pregunta abierta era: ¿cómo hacemos write-back al canal para entregar un texto útil al usuario? Se probaron 4 rutas concretas en Apex ejecutado directamente contra la org.
| Ruta probada | Comportamiento observado | Veredicto |
|---|---|---|
| aiplatform.ModelsAPI.createChatGenerations con content multi-part (image_url embed base64) | El wrapper Apex rechaza content como JSON serializado — sólo acepta String plano. Sin API multimodal en la clase nativa. | Descartado — para imagen se requiere Flex Prompt Template invocado con ConnectApi.EinsteinLLM.generateMessagesForPromptTemplate. Texto sí funciona (respuesta real de GPT-4o obtenida). |
| Insert ConversationEntry con ActorType=Bot, Message poblado, MessageStatus=Sent | DML success. Registro persiste (Id 0ZyKY000009QuNB0A0). Pero MessageStatusCode=null, MessageSendTime=null, MessageDeliverTime=null. El usuario NO recibe el mensaje por WhatsApp. | Descartado — el registro es cosmético, no dispara pipeline outbound a Meta. |
| Insert ConversationEntry con ActorType=EndUser (imitando el approach Corona de 'turno sintético del usuario') | DML success (Id 0ZyKY000009QuNG0A0). Se probó en una sesión ACTIVA de WhatsApp. El bot NO reaccionó al insert. Los contadores EndUserMessageCount/AgentMessageCount no cambiaron. Cero notificación al usuario, cero turno de Agentforce. | Descartado — el pipeline inbound del agente vive off-platform. Insertar CE on-platform no lo activa. |
| ConvMessageSendRequest — probable candidato por nombre | createable=false, updateable=false. TODOS los fields con create=false. Es read-only para clientes. | Descartado — es un log del sistema, no un vector de write-back. |
Meta Cloud API ──► Enhanced WhatsApp Channel (path estándar, sin cambios)
│
▼
ContentDocument creado (por AutomatedProcess)
│
▼
ContentDocumentLink insert
(LinkedEntityType=MessagingSession)
│
▼ [TRIGGER ANTES DE QUE AGENTFORCE PROCESE EL TURNO]
WhatsApp_MediaIntercept_Event__e publica (Platform Event)
│
▼
WhatsApp_MediaProcess_Queueable
│
▼
ConnectApi.EinsteinLLM.generateMessages
(Prompt Template: WhatsApp_Image_Analysis o similar,
GPT-4o vision / Whisper — todo dentro de Salesforce)
│
▼
Update MessagingSession.AI_Summary__c
│
▼
[PENDIENTE: cómo hacer que Agentforce vea el resumen
antes de responder, o cómo enviar mensaje sintético
al usuario]
Legend
- AutomatedProcess — User system que Salesforce usa para persistir el flujo de Enhanced Messaging. userId 005KY000002DT8JYAW en la org Laila.
- Cambios al canalReemplaza Enhanced WhatsApp Channel con webhook + Meta directoNinguno — sigue usando Enhanced Channel
- Componentes construidos6 objetos custom + ~30 Apex classes + 4 Flows + 2 Platform Events1 trigger + 1 Platform Event + 1 Apex handler + 1 Queueable + 2 Prompt Templates
- Compatibilidad con Digital EngagementN/A (bypass)100% — no interfiere
- Manejo de firma HMAC con MetaCliente responsable la manejaSalesforce la maneja (inalterada)
- Escalación a humanoCustom Apex + custom LWC consoleOmni-Channel Flow estándar sigue funcionando
- RollbackComplejo — desactivar múltiples piezas coordinadasSimple — desactivar 1 trigger
- Riesgo de romper demos existentesAlto durante migraciónBajo — no toca el path del agente ni Meta
- EstadoVerificado en producción (Corona)Prototipo — NO verificado
- Casos que cubreTodos los tipos de adjunto + escalamiento humano avanzadoSolo el adjunto entrante — la respuesta del agente sigue siendo la que Atlas decide
- ¿El trigger CDL BEFORE_INSERT tiene tiempo suficiente para publicar el PE antes de que Agentforce procese el turno? La ventana observada es ~milisegundos. Necesita medición empírica en múltiples cargas.
- ¿Actualizar MessagingSession.AI_Summary__c hace que Agentforce lo vea antes de responder al turno? Depende de si Agentforce refresca contexto entre turnos.
- ¿Existe API pública para inyectar un mensaje sintético al canal Enhanced sin pasar por el agente? ConnectApi.EnhancedMessaging tiene métodos como sendMessage pero su comportamiento en sesiones con bot activo no está documentado.
- ¿El AutomatedProcess user tiene los permisos necesarios para publicar Platform Events y hacer callouts internos (ConnectApi.EinsteinLLM)? Probable que sí para PE, dudoso para EinsteinLLM.
- ¿Latencia total del pipeline (CDL insert → PE → Queueable → Prompt Template → write-back)? Meta espera respuesta rápida — si el pipeline toma >2 seg, el usuario ve el error del agente antes de que llegue el resumen.
- Construir el Trigger + Platform Event + Queueable + Prompt Template en sandbox (no en jgr@laila.demo directo).
- Medir latencia end-to-end con 20+ archivos de prueba mixtos (imagen/PDF/audio).
- Probar las 3 opciones de write-back y comparar cuál llega al usuario primero.
- Si el approach funciona: actualizar esta receta a status 'verified' y documentar la solución final. Si falla: documentar por qué y volver al bypass Corona para casos críticos.
