# Plan: envio de resume Mercantil a NiCE por DFO message

## Objetivo

Hacer que el agente de NiCE reciba un resume operativo del reclamo aunque el transcript/custom attributes nativo de Cognigy no se muestre como se espera.

La estrategia principal sera enviar un mensaje DFO desde el backend Mercantil hacia NiCE, usando el mismo telefono del usuario como hilo externo.

No se debe romper el handover actual, que ya transfiere.

## Datos fijos del endpoint NiCE

Usar este canal DFO para publicar el mensaje:

```text
POST https://api-na1.niceincontact.com/dfo/3.0/channels/6a4e831b5954c4530f118c57/messages
```

Configurar este valor por variable de entorno:

```text
NICE_DFO_CHANNEL_ID=6a4e831b5954c4530f118c57
```

Nota de validacion: si NiCE responde que el canal no existe o no acepta mensajes, validar si `6a4e831b5954c4530f118c57` corresponde al channel DFO esperado o al Handover Provider de Cognigy. No cambiar ese valor sin confirmacion.

## Payload requerido para crear el mensaje

El backend debe construir el body asi:

```json
{
  "idOnExternalPlatform": "<telefono>_<utc-iso-con-milisegundos>",
  "createdAtWithMilliseconds": "<utc-iso-con-milisegundos>",
  "direction": "inbound",
  "thread": {
    "idOnExternalPlatform": "<telefono>"
  },
  "messageContent": {
    "type": "TEXT",
    "payload": {
      "text": "<resume>"
    }
  },
  "authorEndUserIdentity": {
    "idOnExternalPlatform": "<telefono>"
  }
}
```

Reglas:

- `telefono` debe venir del numero de WhatsApp que escribe.
- `idOnExternalPlatform` debe ser `telefono + "_" + time UTC`.
- `createdAtWithMilliseconds` debe ser el mismo time UTC usado para el id.
- `thread.idOnExternalPlatform` debe ser el telefono.
- `authorEndUserIdentity.idOnExternalPlatform` debe ser el telefono.
- El time UTC debe generarse con `new Date().toISOString()`, por ejemplo `2026-07-11T19:32:44.123Z`.

## Alcance

Incluido:

- Crear servicio de autenticacion NiCE en el backend.
- Crear endpoint backend para `auth/token`.
- Crear endpoint backend para `refreshToken`.
- Crear endpoint backend para enviar el resume como mensaje DFO.
- Capturar el telefono desde WhatsApp y pasarlo al backend.
- Insertar una llamada en Cognigy antes del handover para publicar el resume.
- Probar sin modificar Bancamiga.

Excluido:

- No cambiar ni borrar nada en Bancamiga.
- No reemplazar el handover funcional.
- No volver a agregar campos nuevos al nodo NiCE handover como custom attributes hasta validar.
- No guardar client secret, password, access token o refresh token en Markdown.
- No imprimir tokens en logs.

## Variables de entorno nuevas

Agregar al backend Mercantil:

```text
NICE_AUTH_URL=https://cxone.niceincontact.com/auth/token
NICE_API_BASE_URL=https://api-na1.niceincontact.com
NICE_DFO_CHANNEL_ID=6a4e831b5954c4530f118c57
NICE_CLIENT_ID=<secreto>
NICE_CLIENT_SECRET=<secreto>
NICE_USERNAME=<secreto>
NICE_PASSWORD=<secreto>
NICE_REFRESH_TOKEN=<opcional>
NICE_TOKEN_REFRESH_MARGIN_MS=60000
NICE_RESUME_ENABLED=true
```

Notas:

- `NICE_REFRESH_TOKEN` es opcional porque el backend puede recibirlo desde el primer `auth/token`.
- `NICE_RESUME_ENABLED=false` debe permitir apagar el envio sin tocar Cognigy ni NiCE.

## Endpoints backend a crear

Todos deben estar protegidos con el mismo `x-api-key` actual del backend Mercantil.

### 1. POST /bank/mercantil/demo/nice/auth/token

Funcion:

- Pedir token a NiCE con `grant_type=password`.
- Guardar en cache de memoria:
  - `access_token`
  - `refresh_token`
  - `expires_in`
  - `expires_at`
- No loguear secretos.

Request interno hacia NiCE:

```http
POST https://cxone.niceincontact.com/auth/token
Content-Type: application/x-www-form-urlencoded
```

Body:

```text
grant_type=password
client_id=<NICE_CLIENT_ID>
client_secret=<NICE_CLIENT_SECRET>
username=<NICE_USERNAME>
password=<NICE_PASSWORD>
```

Respuesta del backend:

```json
{
  "ok": true,
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "expiresAt": "2026-07-11T20:32:44.123Z",
  "hasRefreshToken": true
}
```

Por seguridad, no devolver `access_token` ni `refresh_token` en este endpoint salvo que luego se decida habilitar un modo admin explicito.

### 2. POST /bank/mercantil/demo/nice/auth/refresh

Funcion:

- Usar el refresh token en cache o `NICE_REFRESH_TOKEN`.
- Pedir token a NiCE con `grant_type=refresh_token`.
- Actualizar cache.
- Si no hay refresh token disponible, hacer fallback controlado a `auth/token`.

Request interno hacia NiCE:

```http
POST https://cxone.niceincontact.com/auth/token
Content-Type: application/x-www-form-urlencoded
```

Body:

```text
grant_type=refresh_token
client_id=<NICE_CLIENT_ID>
refresh_token=<refresh token cache/env>
```

Respuesta backend igual al endpoint anterior, sin exponer tokens.

### 3. POST /bank/mercantil/demo/nice/resume-message

Funcion:

- Recibir telefono y resume.
- Obtener token con cache/refresh.
- Construir payload DFO.
- Enviar `POST /dfo/3.0/channels/{channelId}/messages`.
- Devolver resultado resumido.

Request esperado:

```json
{
  "phone": "584121722759",
  "operatorSummary": "Reclamo Mercantil ...",
  "customAttributes": {
    "mc_claimId": "MC-...",
    "mc_issueType": "consumo_no_reconocido",
    "mc_ticketSummary": "..."
  },
  "channel": "whatsapp"
}
```

Reglas:

- Si `NICE_RESUME_ENABLED=false`, devolver `skipped`.
- Si no hay `phone`, devolver `skipped` para no romper Webchat ni REST.
- Normalizar telefono dejando solo digitos.
- Rechazar telefonos vacios o demasiado cortos.
- Formatear el texto del resume con datos seguros.
- No incluir PAN, CVV, PIN, OTP, claves ni tokens.

Respuesta esperada:

```json
{
  "ok": true,
  "sent": true,
  "channelId": "6a4e831b5954c4530f118c57",
  "threadIdOnExternalPlatform": "584121722759",
  "messageIdOnExternalPlatform": "584121722759_2026-07-11T19:32:44.123Z"
}
```

## Archivos backend esperados

Crear:

- `src/services/niceAuth.service.js`
- `src/services/niceDfo.service.js`
- `src/schemas/nice.schema.js`
- `src/controllers/nice.controller.js`
- `src/routes/nice.routes.js`

Modificar:

- `src/config/env.js`
- `src/app.js`
- `test/server.test.js`
- `test/mercantilDemo.test.js` o nuevo `test/nice.test.js`

## Captura del telefono en Cognigy/WhatsApp

La fuente principal debe ser el payload raw de WhatsApp:

- `request.entry[].changes[].value.messages[0].from`
- fallback: `request.entry[].changes[].value.contacts[0].wa_id`

El transformer de WhatsApp debe conservar ese valor como:

```js
data.mercantilPhone = phone;
input.mercantilPhone = phone;
```

Y el pre-process de `tool_build_handover_summary` debe mandarlo al backend:

```json
{
  "phone": "{{input.mercantilPhone || context.mercantilPhone || ''}}",
  "channel": "whatsapp"
}
```

Si Cognigy no permite `input.mercantilPhone` de esa forma en el transformer, guardar el telefono en `data.phone` o `data.mercantilPhone` y leerlo desde el pre-process.

## Punto exacto en el flujo Cognigy

No tocar el handover funcional directamente.

Agregar una llamada HTTP nueva antes del nodo de handover:

```text
tool_build_handover_summary - Pre-Process
tool_build_handover_summary - HTTP Request
tool_build_handover_summary - Post-Process
tool_build_handover_summary - Prepare NiCE Handover
tool_build_handover_summary - Send NiCE Resume Message   <-- nuevo
tool_build_handover_summary - Execute NiCE Handover
tool_build_handover_summary - Resolve
```

Razon:

- Despues del `Handover to Human Agent`, Cognigy pausa el flujo.
- Por eso el resume debe enviarse antes del handover.
- El mensaje DFO debe caer en el mismo thread porque usa el telefono como `thread.idOnExternalPlatform`.

Para WhatsApp, el router actual fuera del AI Agent debe seguir funcionando y no debe volver a activar `sendResolveEvent`.

## Script reusable para Cognigy

Crear un script idempotente:

```text
scripts/patch_mercantil_nice_resume_message.js
```

Responsabilidades:

- Leer nodos actuales.
- Crear snapshot antes de tocar Cognigy.
- Insertar o actualizar el HTTP node de resume.
- Actualizar transformer de WhatsApp solo para exponer `mercantilPhone`.
- No borrar nodos existentes.
- No duplicar nodos si ya existen.
- Mantener intactos:
  - `tool_build_handover_summary - Execute NiCE Handover`
  - `Mercantil WhatsApp Handover Router - Execute NiCE Handover`
  - `sendResolveEvent=false` en el router WhatsApp.

Usar MCP Cognigy primero. Si el MCP no permite insertar en la posicion exacta, usar API directa solo despues de confirmar el nodo y guardar script reusable.

## Pruebas backend

### Unitarias

Validar:

- Normalizacion de telefono.
- Construccion de `idOnExternalPlatform`.
- `createdAtWithMilliseconds` en UTC con milisegundos.
- `thread.idOnExternalPlatform` igual al telefono.
- `authorEndUserIdentity.idOnExternalPlatform` igual al telefono.
- Redaccion de datos sensibles.
- `NICE_RESUME_ENABLED=false` devuelve skipped.
- Si no hay telefono, devuelve skipped.
- Token cache usa access token vigente.
- Refresh se ejecuta cuando expira el token.

### Integracion local sin NiCE real

Levantar un servidor fake en test para simular:

- `POST /auth/token`
- `POST /dfo/3.0/channels/6a4e831b5954c4530f118c57/messages`

Validar que el backend envie el body exacto.

### Smoke real

Con backend y cloudflared:

1. Crear snapshot Cognigy.
2. Levantar backend local.
3. Levantar cloudflared.
4. Actualizar URL publica en el HTTP node de Cognigy.
5. Desde WhatsApp, enviar:

```text
Quiero hablar con un especialista Mercantil. Mi reclamo es por un consumo no reconocido y necesito que me transfieras ahora
```

6. Validar en NiCE:
   - La transferencia cae.
   - Antes o al inicio de la interaccion aparece un mensaje con el resume.
   - El hilo corresponde al telefono real.
   - No se crea una segunda interaccion separada.

## Rollback

Rollback sin perder handover:

1. Poner `NICE_RESUME_ENABLED=false` en backend.
2. Si aun hay efectos secundarios, deshabilitar solo el HTTP node nuevo `Send NiCE Resume Message`.
3. No borrar los nodos de handover.
4. Si Cognigy queda inconsistente, restaurar snapshot previo.

## Riesgos

### Riesgo 1: se crea una interaccion nueva en NiCE

Causa probable:

- `thread.idOnExternalPlatform` no coincide con el hilo que usa NiCE para la transferencia.

Mitigacion:

- Usar exactamente el telefono de WhatsApp como thread.
- Validar con una llamada real.
- Si crea otra interaccion, probar enviar el mensaje despues de que Cognigy registre el handover, desde backend usando un evento o webhook posterior.

### Riesgo 2: channelId incorrecto

Causa probable:

- El ID `6a4e831b5954c4530f118c57` puede ser interpretado por NiCE como canal invalido si no es el DFO channel ID.

Mitigacion:

- Mantenerlo como config.
- Registrar status y body de error de NiCE sin tokens.
- Si falla, validar contra NiCE/Postman el channel ID correcto.

### Riesgo 3: token expira durante el envio

Mitigacion:

- Cache con margen de 60 segundos.
- Si NiCE responde 401, refrescar token una vez y reintentar una vez.

### Riesgo 4: se rompe el handover funcional

Mitigacion:

- No modificar los handover nodes.
- Insertar solo un HTTP node anterior.
- Backend devuelve `skipped` o error controlado sin bloquear el handover.
- Crear snapshot antes.

## Criterios de exito

- `npm test` pasa.
- `npm run check` pasa.
- Cognigy sigue transfiriendo a NiCE.
- WhatsApp conserva el comportamiento actual de cierre sin retransferir.
- El agente ve el resume con al menos:
  - motivo,
  - claimId o `sin_reclamo`,
  - decision de reglas,
  - ticket summary u OCR unreadable,
  - canal,
  - telefono/hilo.
- No se expone token ni secreto en logs, Markdown o respuesta final.

## Ejecucion 2026-07-11

Estado:

- Backend implementado.
- Pruebas backend implementadas con NiCE fake.
- Cognigy actualizado con HTTP node de resume antes del handover.
- Tunel publico activo:

```text
https://fares-perhaps-participating-constraints.trycloudflare.com
```

Snapshots:

```text
Antes: Mercantil_Demo_snapshot_20260711T164506Z / 6a5273135523ba36097a79e7
Despues patch Cognigy: Mercantil_Demo_snapshot_20260711T172045Z / 6a527b6d5523ba36097a8e15
Final URL publica: Mercantil_Demo_snapshot_20260711T172555Z / 6a527ca4d9543729fbd5a060
Final logs phoneSource: Mercantil_Demo_snapshot_20260711T194843Z / 6a529e1bfac4430baed3fbf2
```

Nodo creado:

```text
tool_build_handover_summary - Send NiCE Resume Message / 6a527b46fac4430baed3747d
```

Cadena verificada:

```text
tool_build_handover_summary - Prepare NiCE Handover
-> tool_build_handover_summary - Send NiCE Resume Message
-> tool_build_handover_summary - Execute NiCE Handover
```

Validacion:

```text
npm run check: OK
npm test: 30 passed
GET /health por cloudflared: OK
POST /nice/resume-message con phone y credenciales NiCE cargadas: sent=true
Logs backend en .run/mercantil-demo-server.log con scope mercantil_nice_resume y mercantil_nice_auth
```

Pendiente:

```text
Configurar credenciales NiCE reales en el proceso backend:
NICE_CLIENT_ID
NICE_CLIENT_SECRET
NICE_USERNAME
NICE_PASSWORD
NICE_REFRESH_TOKEN opcional
```

Con esas variables cargadas, el nodo Cognigy llama al backend y el backend crea el mensaje DFO real en NiCE. Si faltan variables, el backend devuelve `skipped` y no interrumpe el handover porque responde controladamente y el HTTP node tiene `abortOnError=false`.
