# WhatsApp Handover Change Log

## 2026-07-11

Goal:

Fix Mercantil WhatsApp transfers that send the customer-facing transfer text but never enter Cognigy/NiCE handover state.

Baseline evidence:

- Latest WhatsApp test session: `c6287c65-49f3-4f10-8ca2-97366d385b5d`.
- Input timestamp: `2026-07-11T14:13:17.145Z`.
- Bot sent: `Te conecto con un especialista Mercantil. Ya envie el resumen seguro de tu reclamo para continuidad de atencion.`
- Conversation flags stayed `inHandoverRequest=false` and `inHandoverConversation=false`.
- Logs for `2026-07-11T14:12:30Z` to `2026-07-11T14:14:30Z` showed no `service-handover` entry.
- Existing handover node is enabled and points to NiCE provider:
  `tool_build_handover_summary - Execute NiCE Handover` / `6a50edecd9543729fbcd68d6`.

Snapshot before structural change:

- Name: `pre-whatsapp-handover-structural-20260711`
- ID: `6a52509d5523ba360979e0fa`
- Hash: `bee7fb01e24501f09438299d5662a2807887f797`

Snapshot after structural change:

- Name: `post-whatsapp-handover-router-20260711`
- ID: `6a525314fac4430baed2d6d1`
- Hash: `e20d2015b0ea6477baf941e9e6a3cc07874e0914`

Planned structural change:

- Keep the existing Webchat handover path unchanged.
- Add a WhatsApp-only router after the AI Agent node.
- The router checks whether `context.mercantilHandoverReady` is true and the inbound payload/channel is WhatsApp.
- If true, execute a normal Flow-level NiCE handover node outside the AI Agent tool branch.
- Do not delete the existing handover node.

Reusable script:

```bash
node scripts/patch_mercantil_whatsapp_handover_router.js --dry-run
node scripts/patch_mercantil_whatsapp_handover_router.js
```

Applied changes:

- Updated `tool_build_handover_summary - Pre-Process` / `6a4e854ffac4430baeb3a582`.
  - It now detects WhatsApp from `input.channel` or raw WhatsApp payload and sends that channel to `/handover-summary`.
- Added `Mercantil WhatsApp Handover Router - Detect` / `6a5251a85523ba360979e4cc`.
  - Position: after `AI Agent` / `6a4e832cbad16ab4ee453271`.
  - Purpose: set `context.mercantilHandoverRouteWhatsApp` only when handover is ready and the turn is WhatsApp.
- Added `Mercantil WhatsApp Handover Router - Should Transfer` / `6a5251ddfac4430baed2d322`.
  - Type in raw chart: `if`.
  - Condition: `context.mercantilHandoverRouteWhatsApp === true`.
  - Then branch: `6a5251ddfac4430baed2d32c`.
  - Else branch: `6a5251ddfac4430baed2d339`.
- Added `Mercantil WhatsApp Handover Router - Mark Consumed` / `6a525201fac4430baed2d3ab`.
  - Position: inside Then branch.
  - Purpose: clear router flags before handover execution.
- Added `Mercantil WhatsApp Handover Router - Execute NiCE Handover` / `6a525202d9543729fbd4fd11`.
  - Position: after Mark Consumed.
  - Purpose: execute normal Flow-level NiCE handover for WhatsApp.
- Kept existing handover node enabled:
  `tool_build_handover_summary - Execute NiCE Handover` / `6a50edecd9543729fbcd68d6`.

Validation:

- `node --check scripts/patch_mercantil_whatsapp_handover_router.js`: passed.
- `node scripts/patch_mercantil_whatsapp_handover_router.js --dry-run`: idempotent; reports updates only after initial creation.
- REST smoke session `mercantil-whatsapp-router-smoke-20260711-002` responded with transfer text, but did not create `service-handover` because it still enters through channel `rest`.
- Real WhatsApp validation is still required. Expected success signals:
  - logs include `service-handover` for the WhatsApp trace, or
  - conversation events show `inHandoverRequest=true`, and ideally `inHandoverConversation=true`, or
  - the conversation appears in the NiCE agent desktop.

UI render follow-up:

- After the router was created, the Cognigy Flow UI showed `Unable to render the main content`.
- The handover itself was reported as working, so the router was not removed.
- Non-destructive fix applied: normalized `Mercantil WhatsApp Handover Router - Should Transfer` / `6a5251ddfac4430baed2d322` back to the Cognigy UI-compatible condition object:
  `config.condition.condition = context.mercantilHandoverRouteWhatsApp === true`.
- Updated `scripts/patch_mercantil_whatsapp_handover_router.js` so future runs preserve that condition object instead of replacing it with a plain string.

NiCE close re-transfer follow-up:

- Reported symptom: when the agent closed the transfer in NiCE, the customer received the same transfer text again and the conversation was transferred again.
- Evidence conversation: `713cf18c-7e17-43e8-9d48-49be3571dc46`.
- Evidence timestamp: after the NiCE close event at `2026-07-11T14:37:40Z`, the bot sent the transfer text again at `2026-07-11T14:37:43Z`.
- Cause: the WhatsApp router handover node had `sendResolveEvent=true`, so closing the NiCE interaction sent a resolve event back into the Flow. That event reentered after the AI Agent and the router still had enough context to execute handover again.
- Snapshot before fix:
  - Name: `pre-whatsapp-handover-no-resolve-event-20260711`
  - ID: `6a5257ad5523ba360979f32e`
  - Hash: `7ca55ac07bf6cfe74ffa97706b2ee370d7663bd1`
- Snapshot after fix:
  - Name: `post-whatsapp-handover-no-resolve-event-20260711`
  - ID: `6a52586c5523ba360979f64a`
  - Hash: `b8b03eeacb55f4dffcd07f05834c476128784031`
- Fix applied only to the router handover node:
  `Mercantil WhatsApp Handover Router - Execute NiCE Handover` / `6a525202d9543729fbd4fd11`.
- Current key config:
  - `repeatHandoverMessage=false`
  - `sendResolveEvent=false`
  - `handoverProvider=6a4e831b5954c4530f118c57`
  - `handoverProviderConfig.sendTranscriptAsFirstMessage=true`
- `scripts/patch_mercantil_whatsapp_handover_router.js` now keeps `sendResolveEvent=false` for future runs.
- Expected validation: start a WhatsApp transfer, accept it in NiCE, close/resolve it in NiCE, and confirm WhatsApp does not receive the transfer text again.

NiCE completeHandover event guard follow-up:

- Reported symptom: if the customer sent the last message during handover and the NiCE agent closed without replying, WhatsApp opened another interaction. If the agent sent the last message and then closed, the conversation stayed closed.
- Evidence conversation: `0579511f-fb3f-4761-bb0a-88472eb00f22`.
- Evidence sequence:
  - `2026-07-11T14:57:28Z`: agent output `123`.
  - `2026-07-11T14:57:33Z`: customer input `Ffff`.
  - `2026-07-11T14:57:39Z`: NiCE injected `_cognigy.controlCommands[0].type = completeHandover` with `messageContent.text = #__AGENT__ENDED__HANDOVER__#` and empty `text`.
  - `2026-07-11T14:57:42Z`: router sent the transfer text again.
- Cause: even with `sendResolveEvent=false`, NiCE can inject a provider close event through `endpoint-handleProviderEvent`. The router detected WhatsApp/context and re-triggered handover because it did not explicitly filter `completeHandover`/`#__AGENT__ENDED__HANDOVER__#`.
- Snapshot before fix:
  - Name: `pre-whatsapp-handover-complete-event-guard-20260711`
  - ID: `6a525c2cd9543729fbd54918`
  - Hash: `b8b03eeacb55f4dffcd07f05834c476128784031`
- Snapshot after fix:
  - Name: `post-whatsapp-handover-complete-event-guard-20260711`
  - ID: `6a525c97fac4430baed3222e`
  - Hash: `3f6445526e9377b4251c0e7c6395bc10865cbdde`
- Fix applied only to `Mercantil WhatsApp Handover Router - Detect` / `6a5251a85523ba360979e4cc`.
- Current guard behavior: if the payload includes `completeHandover` or `#__AGENT__ENDED__HANDOVER__#`, the detector clears `context.mercantilHandoverReady`, sets `context.mercantilHandoverRouteWhatsApp=false`, and marks `input.mercantilHandoverCompleteEventIgnored=true`.
- Verified `hasError=false` on the Cognigy code node after patching.
- Expected validation:
  - customer sends last message, agent closes in NiCE: no new transfer/interaccion.
  - agent sends last message, agent closes in NiCE: remains closed.
  - normal WhatsApp transfer request: still transfers to NiCE.

Resume/default handover data follow-up:

- Reported symptom: the transfer reaches NiCE, but the resume/default customer data is not visible. OCR is not currently used for transfer tests, so at minimum the agent should receive default customer/name/context data.
- Cause: `/bank/mercantil/demo/handover-summary` can return two shapes:
  - claim/OCR path: root object with `operatorSummary` and `customAttributes`.
  - default/case path: `{ handoverSummary: { nombre, customerId, motivo, ... } }`.
- The `tool_build_handover_summary - Prepare NiCE Handover` node only read root-level `operatorSummary` and `customAttributes`, so default handover data stayed nested and did not populate `context.mercantilHandover`.
- Snapshot before fix:
  - Name: `pre-handover-resume-defaults-20260711`
  - ID: `6a52600c5523ba36097a4019`
  - Hash: `3f6445526e9377b4251c0e7c6395bc10865cbdde`
- Snapshot after fix:
  - Name: `post-handover-resume-defaults-20260711`
  - ID: `6a5265c15523ba36097a4f7b`
  - Hash: `8d8ea206525927592cf0375558159e2891fcc5c3`
- Fix applied with `scripts/update_mercantil_handover_resume_defaults.js`.
- The Prepare node now accepts both root and nested `handoverSummary` formats and creates `operatorSummary` plus `customAttributes`.
- Both NiCE handover nodes now include non-empty resume/default custom attributes:
  `mc_customerName`, `mc_customerId`, `mc_caseId`, `mc_resume`, `mc_actionRecommended`, and `mc_urgency`, in addition to the existing `mc_*` attributes.
- Verified `tool_build_handover_summary - Prepare NiCE Handover` has `hasError=false`.
- Expected validation: start a transfer without OCR/claim and confirm NiCE receives at least `mc_customerName`, `mc_customerId`, and `mc_resume` in the interaction details/custom attributes.

Rollback:

- Reported symptom after the resume/default fix: handover stopped transferring.
- Reverted on `2026-07-11` with `scripts/revert_mercantil_handover_resume_defaults.js`.
- Restored `tool_build_handover_summary - Prepare NiCE Handover` / `6a5173085523ba3609761beb` to the previous simple preparation code.
- Restored both NiCE handover nodes to the original 11 `mc_*` custom attributes:
  `mc_claimId`, `mc_issueType`, `mc_ruleDecision`, `mc_identityVerified`, `mc_ticketSummary`, `mc_amount`, `mc_currency`, `mc_merchant`, `mc_cardLast4`, `mc_channel`, and `mc_ocrConfidence`.
- Confirmed `Mercantil WhatsApp Handover Router - Execute NiCE Handover` / `6a525202d9543729fbd4fd11` still has `sendResolveEvent=false`.
- Removed the failed `scripts/update_mercantil_handover_resume_defaults.js` script to avoid accidental reuse.
- Future resume work should not add new NiCE custom attribute keys until NiCE field limits/allowed attributes are validated in a disposable snapshot.

NiCE resume message via backend follow-up:

- Goal: send the resume as a DFO inbound message before the handover executes, instead of adding new handover custom attributes.
- Backend endpoints added:
  - `POST /bank/mercantil/demo/nice/auth/token`
  - `POST /bank/mercantil/demo/nice/auth/refresh`
  - `POST /bank/mercantil/demo/nice/resume-message`
- Cognigy node added:
  - `tool_build_handover_summary - Send NiCE Resume Message` / `6a527b46fac4430baed3747d`.
- Verified chain:
  - `tool_build_handover_summary - Prepare NiCE Handover` -> `tool_build_handover_summary - Send NiCE Resume Message` -> `tool_build_handover_summary - Execute NiCE Handover`.
- The message uses the WhatsApp phone as `thread.idOnExternalPlatform` and `authorEndUserIdentity.idOnExternalPlatform`.
- Current tunnel URL used in Cognigy:
  - `https://fares-perhaps-participating-constraints.trycloudflare.com`
- Snapshot before:
  - Name: `Mercantil_Demo_snapshot_20260711T164506Z`
  - ID: `6a5273135523ba36097a79e7`
  - Hash: `0016ea556522e8a9c37bb3d82414338a31b7ee07`
- Snapshot after initial Cognigy patch:
  - Name: `Mercantil_Demo_snapshot_20260711T172045Z`
  - ID: `6a527b6d5523ba36097a8e15`
  - Hash: `5807bd7602e3c70cee58fb11b4be27e461aaa88d`
- Snapshot after final public URL update:
  - Name: `Mercantil_Demo_snapshot_20260711T172555Z`
  - ID: `6a527ca4d9543729fbd5a060`
  - Hash: `8107d62ad82e5cf8ec6774998997d37baedfac26`
- Snapshot after backend diagnostics and `phoneSource` Cognigy payload:
  - Name: `Mercantil_Demo_snapshot_20260711T194843Z`
  - ID: `6a529e1bfac4430baed3fbf2`
  - Hash: `eae48485454761540918095c53d964e7e5c3a2b7`
- Validation completed:
  - `npm run check`: passed.
  - `npm test`: 30 passed.
  - Public `/health`: passed through cloudflared.
  - Public `/nice/resume-message` with phone and NiCE credentials loaded: `sent=true`.
  - Backend diagnostic logs are written to `.run/mercantil-demo-server.log` with scopes `mercantil_nice_resume` and `mercantil_nice_auth`; phone is masked and `phoneSource` is included.
- Pending for real NiCE delivery:
  - Configure `NICE_CLIENT_ID`, `NICE_CLIENT_SECRET`, `NICE_USERNAME`, `NICE_PASSWORD` in the backend process.
  - Optional: configure `NICE_REFRESH_TOKEN`.
  - Retest a real WhatsApp transfer and confirm the resume appears in the agent interaction without creating a second NiCE interaction.

WhatsApp input transformer/OCR follow-up:

- Reported symptom: when the input transformer was enabled, MIA could stop responding even to `Hola`; when the transformer was disabled, MIA responded again.
- Confirmed endpoint: `Mercantil DEMO WS` / `6a501c77fac4430baec2fc44`.
- Confirmed public backend URL in transformer:
  `https://fares-perhaps-participating-constraints.trycloudflare.com/bank/mercantil/demo/whatsapp-media/import`.
- Fix applied to `scripts/update_mercantil_whatsapp_transformer.js` and published to Cognigy:
  - wraps `getSessionStorage` in `try/catch`;
  - keeps text-only messages returning normal `text`;
  - sends both `url` and `mediaId` to the backend for WhatsApp media import;
  - avoids `inputAttachments` with an empty URL;
  - adds diagnostic fields `whatsappMediaImportStatus`, `whatsappMediaImportError`, `whatsappMediaId`, `whatsappMediaHasUrl`, and `whatsappSessionStorageError`;
  - keeps endpoint privacy flags unmasked for debugging.
- Backend fix applied in `src/app.js`:
  - `/bank/mercantil/demo/whatsapp-media/import` accepts `mediaId`;
  - when only `mediaId` is present, it resolves the media URL using the endpoint bearer token passed by the transformer;
  - it emits safe logs with `scope=mercantil_whatsapp_media_import`, masking IDs and never printing bearer tokens.
- Validation:
  - `node --check scripts/update_mercantil_whatsapp_transformer.js`: passed.
  - `node --check src/app.js`: passed.
  - `npm test`: 31 passed.
  - Cognigy transformer publish: succeeded.
  - Endpoint summary shows `inputTransformerEnabled=true`, URL current, `hasWhatsappMediaImport=true`, `hasInputAttachments=true`.
  - Public health check passed through the same Cloudflare URL.
  - Public mediaId/no-token probe returned `MISSING_WHATSAPP_BEARER_TOKEN`, confirming the backend code path is active.
- Runtime note:
  - The current backend is running via direct `HOST=127.0.0.1 PORT=3001 npm start` because the wrapper/redirection path hit `listen EPERM` in this sandbox.
  - The Cloudflare tunnel remained active and still points to `127.0.0.1:3001`.

NiCE resume `[object Object]` and OCR ticket follow-up:

- Reported symptom: the resume reached NiCE, but the first block showed:
  - `Motivo: [object Object]`
  - `Ticket: sin_ticket`
- Interpretation:
  - `Motivo` is not the OCR payload. It comes from `customAttributes.mc_issueType`.
  - OCR data should be visible in `Ticket` and in OCR-related custom attributes such as `mc_merchant`, `mc_amount`, `mc_date`, and `mc_cardLast4`.
- Cause:
  - Some tool arguments/custom attributes arrived as objects. The NiCE resume builder stringified them directly, so JavaScript produced `[object Object]`.
  - When `mc_ticketSummary` was empty, the resume builder did not reconstruct the ticket from individual OCR fields.
- Backend fix:
  - `src/services/niceDfo.service.js` now normalizes object-like values before rendering the resume.
  - `src/services/niceDfo.service.js` reconstructs `Ticket` from OCR fields when `mc_ticketSummary` is empty.
  - `src/mercantilDemo.js` normalizes object-like issue type/rule/ticket fields before producing `customAttributes`.
- Cognigy fix:
  - `scripts/patch_mercantil_nice_resume_message.js` updated `tool_build_handover_summary - Pre-Process` / `6a4e854ffac4430baeb3a582`.
  - It now passes `issueType` and ticket variants from `ticket`, `ticketExtract`, `ticketData`, `ocrTicket`, `ocr`, `receipt`, and `claim.ticket`.
- Snapshots:
  - Before: `pre-fix-nice-resume-object-object-20260711` / `6a52b1f3fac4430baed45396`.
  - After: `post-fix-nice-resume-object-object-20260711` / `6a52b249fac4430baed455fa`.
- Validation:
  - `npm test`: 33 passed.
  - Direct builder validation produced:
    `Motivo: consumo_no_reconocido`
    and
    `Ticket: Farmacia Mercantil | 245,70 VES | tarjeta ****4821`.
  - Direct `/bank/mercantil/demo/handover-summary` claim payload with object-like OCR fields returned string custom attributes and no `[object Object]`.

OCR ticket resume without claimId + automatic OTP handover follow-up:

- Reported symptom:
  - NiCE resume showed `Ticket: sin_ticket` / `Motivo: human_handover` even when the OCR flow had already read the receipt.
  - After OTP validation, the desired behavior is automatic transfer to a specialist without waiting for the user to ask for transfer.
- Root cause:
  - `/bank/mercantil/demo/handover-summary` only used the claim/OCR summary builder when `claimId` or `claim` existed. If Cognigy had an OCR `ticket` but no created claim yet, the backend returned the generic `{ handoverSummary: ... }` case shape, which does not include OCR `customAttributes`.
  - `tool_build_handover_summary - Pre-Process` could use empty LLM arguments like `ticket: {}` or `claim: {}` before checking `context.mercantilLastTicket`, so it could lose the last OCR result.
- Backend fix:
  - `src/controllers/cases.controller.js` now routes to the claim/OCR summary builder when the payload contains a real ticket variant, claim, claimId, issueType, rules, or ruleDecision.
  - `src/schemas/cases.schema.js` treats empty `claimId`, `caseId`, and `userId` as undefined so Cognigy can call the endpoint before a claim is created.
  - `test/server.test.js` covers the claimId-empty OCR payload and asserts that `customAttributes.mc_ticketSummary` is returned.
- Cognigy fix:
  - New reusable script: `scripts/patch_mercantil_ocr_otp_auto_handover.js`.
  - OCR, rules, claim, and OTP post-process nodes persist the latest state into context.
  - Handover pre-process now uses `firstContent(...)`, so empty objects do not block fallback to `context.mercantilLastTicket`.
  - AI Agent instructions now require: after valid OTP in an OCR claim, evaluate rules, create claim, and call `tool_build_handover_summary` immediately.
- Snapshot:
  - Name: `post-ocr-ticket-resume-auto-otp-handover-20260711`
  - ID: `6a52b742d9543729fbd68802`
  - Hash: `9129f09fcddb93a29fa3d2f253219debb1fa87b8`
  - Note: this snapshot was created before the final `evaluate_rules` / `create_claim` pre-process fallback patch.
  - Final snapshot attempt name: `post-ocr-ticket-resume-auto-otp-handover-final-20260711`.
  - Final snapshot result: failed because Cognigy reported the project snapshot limit was exceeded. No snapshots were deleted.
- Validation:
  - `npm test`: 33 passed.
  - Public `/health`: passed through the current Cloudflare URL.
  - Public `/bank/mercantil/demo/handover-summary` with `claimId: ""`, OCR ticket and `issueType: "cobro duplicado"` returned:
    `Ticket: CECOSESOLA LARA EL C | 87079.04 VES | 04/07/2026 | tarjeta ****9956`
    plus `mc_merchant`, `mc_amount`, `mc_date`, `mc_time`, `mc_cardLast4`, `mc_ocrConfidence`.
  - Verified `hasError=false` after patching:
    `tool_evaluate_claim_rules - Pre-Process`,
    `tool_create_claim - Pre-Process`,
    `tool_verify_identity - Post-Process`,
    `tool_build_handover_summary - Pre-Process`.
  - Backend restarted directly with `HOST=127.0.0.1 PORT=3001 npm start`; the existing Cloudflare tunnel remains active at `https://fares-perhaps-participating-constraints.trycloudflare.com`.

Session rotation + single NiCE handover follow-up:

- Reported symptoms: (a) MIA stopped responding (even to `Hola`) on numbers that had gone through a NiCE handover while the input transformer was ON; turning the transformer OFF "fixed" it. (b) Every WhatsApp transfer created TWO NiCE interactions: one with the "Resumen para especialista Mercantil", one with the transcript.
- Root cause (a): the transformer pinned `sessionId = phone`, so sessions never rotated. Sessions with unresolved NiCE handovers stayed poisoned forever: the flow ran but the AI Agent failed silently (`errorHandling=continue`, empty `errorMessage`, `logErrorToSystem=false`) and produced zero output. Transformer OFF only worked because the endpoint rotated to a fresh UUID session (`sessionExpiration=15`). A Cognigy UI save at 2026-07-11T21:46:48Z had also republished a stale pre-fix transformer from an old editor buffer.
- Root cause (b): the NiCE CXone (integrated) provider creates the DFO thread with `idOnExternalPlatform = Cognigy sessionId` (confirmed in a provider inject payload; same DFO channel `6a4e831b5954c4530f118c57`), while the backend resume message used `thread = phone`. Different threads -> different contacts. They only merged before because `sessionId == phone`. The WhatsApp router also double-executed handover in the same turn as the tool-chain handover node.
- Fixes applied:
  1. `scripts/update_mercantil_whatsapp_transformer.js`: `sessionId = phone + "-" + seq`, rotating after 15 min of inactivity (markers in `getSessionStorage(userId, userId)`); `userId` stays the phone. Republished (7424 bytes, diagnostics fingerprint present). Never save the transformer from the Cognigy UI; always republish with the script.
  2. `scripts/patch_mercantil_single_whatsapp_handover.js` (new): puts the router to sleep — `Detect` (`6a5251a85523ba360979e4cc`) never routes, keeps the completeHandover guard, and consumes `mercantilHandoverReady`; also sets `logErrorToSystem=true` on the AI Agent (`6a4e832cbad16ab4ee453271`). `patch_mercantil_whatsapp_handover_router.js` now generates the dormant Detect code too. The only WhatsApp handover executor is `tool_build_handover_summary - Execute NiCE Handover` (`6a50edecd9543729fbcd68d6`, `sendResolveEvent=true`).
  3. Resume/handover single contact: `tool_build_handover_summary - Prepare NiCE Handover` (`6a5173085523ba3609761beb`) now sends `threadId = input.sessionId` in `niceResumeRequest`; backend `/bank/mercantil/demo/nice/resume-message` accepts `threadId` and uses it as the DFO `thread.idOnExternalPlatform` (fallback: phone). Backend log includes `threadIdSource`.
- CAUTION: `patch_mercantil_nice_resume_message.js` also rewrites the `Pre-Process` node with code OLDER than `patch_mercantil_ocr_otp_auto_handover.js`; the live patch was applied to the Prepare node only. Do not run that script end-to-end without reconciling the Pre-Process code first.
- Snapshot before: `pre-single-handover-session-rotation-20260711` / `6a52d2205523ba36097bc19c`.
- Validation: `npm test` 34 passed; previously-stuck numbers answer `Hola` again (rotated sessions `phone-suffix`); full OCR+OTP transfer produced ONE NiCE interaction with resume + transcript; backend logged `threadIdSource=cognigy_session` and `niceInteractionId`.
- Channel fix (same day): the resume showed `Canal: chat` because the LLM invented `channel: "chat"` as a tool argument and `args.channel` outranked real detection in `tool_build_handover_summary - Pre-Process`, polluting `mc_channel` and `context.mercantilDetectedChannel`. New priority: `isWhatsApp` (raw payload/channel) > `context.mercantilDetectedChannel` > `input.channel` > `args.channel` > `"webchat"`. Updated in `scripts/patch_mercantil_ocr_otp_auto_handover.js` (`handoverPreCode`, now exported) and applied live to node `6a4e854ffac4430baeb3a582` (hasError=false). Recovery cycle (agent closes in NiCE, user writes again, MIA responds without re-transfer) validated by the user the same day.
- Security note: NiCE credentials are hardcoded defaults in `src/config/env.js` (committed to git); accepted as-is for the demo (user decision 2026-07-11).
