Por qué la gobernanza de IA en bots financieros es un problema de arquitectura, no de políticas
Según Sinch, invertir en gobernanza de IA no está siendo suficiente para mantener operativos a los agentes de servicios financieros. El problema no es que las empresas no quieran cumplir: es que diseñan primero el bot y luego intentan pegarle gobernanza encima como parche. Eso no funciona. En servicios financieros —donde cada mensaje de confirmación de pago, cada alerta de fraude y cada respuesta automatizada toca datos sensibles del cliente— la gobernanza tiene que estar en los cimientos, no en el techo.
Este tutorial te muestra cómo construir un bot de WhatsApp para servicios financieros con gobernanza desde el día uno: trazabilidad de mensajes, control de consentimiento, manejo de datos sensibles con enmascaramiento, logs de auditoría y handoff a humano cuando el agente de IA no tiene autorización para seguir. Todo esto usando la WhatsApp Cloud API de Meta, un backend en Node.js y una capa de gobernanza que puedes adaptar a cualquier stack.
Requisitos
- Cuenta de Meta for Developers con una app de WhatsApp Business configurada
- Número de teléfono verificado en WhatsApp Business Platform
- Node.js 20+ y npm
- Una base de datos PostgreSQL (para logs de auditoría inmutables)
- Variables de entorno configuradas:
WHATSAPP_TOKEN,VERIFY_TOKEN,PHONE_NUMBER_ID,DATABASE_URL - Opcional pero recomendado: Redis para manejo de estado de conversación
Paso 1: Configurar el webhook de WhatsApp Cloud API
Primero, levanta tu endpoint de webhook. Meta requiere que respondas con un 200 en menos de 20 segundos o reintenta el mensaje, lo que en producción con servicios financieros puede generar duplicados peligrosos.
// webhook.js
import express from 'express';
const app = express();
app.use(express.json());
app.get('/webhook', (req, res) => {
const mode = req.query['hub.mode'];
const token = req.query['hub.verify_token'];
const challenge = req.query['hub.challenge'];
if (mode === 'subscribe' && token === process.env.VERIFY_TOKEN) {
console.log('Webhook verificado');
res.status(200).send(challenge);
} else {
res.sendStatus(403);
}
});
app.post('/webhook', async (req, res) => {
// Responde inmediatamente para evitar reintentos
res.sendStatus(200);
await processIncoming(req.body);
});
app.listen(3000);
Advertencia de producción: Nunca proceses el mensaje antes de responder el 200. Ponlo en una cola (BullMQ, SQS, lo que uses) y procesa de forma asíncrona.
Paso 2: Implementar el log de auditoría inmutable
En servicios financieros, cada mensaje que entra o sale debe quedar registrado. No en un archivo de texto: en una tabla de base de datos con timestamp de servidor, no del cliente.
-- migrations/001_audit_log.sql
CREATE TABLE message_audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
direction VARCHAR(10) NOT NULL CHECK (direction IN ('inbound', 'outbound')),
wa_message_id TEXT NOT NULL,
phone_number_hash TEXT NOT NULL, -- NUNCA guardes el número en claro
message_type TEXT NOT NULL,
payload JSONB NOT NULL,
agent_id TEXT, -- qué modelo o humano respondió
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
session_id UUID NOT NULL
);
-- Índice para consultas de auditoría por sesión
CREATE INDEX idx_audit_session ON message_audit_log(session_id, created_at);
// audit.js
import { pool } from './db.js';
import { createHash } from 'crypto';
export async function logMessage({ direction, waMessageId, phoneNumber, messageType, payload, agentId, sessionId }) {
const phoneHash = createHash('sha256')
.update(phoneNumber + process.env.PHONE_SALT)
.digest('hex');
await pool.query(
`INSERT INTO message_audit_log
(direction, wa_message_id, phone_number_hash, message_type, payload, agent_id, session_id)
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
[direction, waMessageId, phoneHash, messageType, payload, agentId, sessionId]
);
}
Advertencia de producción: Hashea el número de teléfono con un salt secreto. En México, el número de teléfono es dato personal bajo la LFPDPPP. En la UE, bajo GDPR. Guardarlo en claro en logs es un pasivo legal.
Paso 3: Control de consentimiento antes de cualquier procesamiento de IA
Antes de que tu modelo de IA procese un solo mensaje, verifica que el usuario haya dado consentimiento explícito. Esto es especialmente crítico en servicios financieros donde el procesamiento de datos tiene implicaciones legales.
// consent.js
import { pool } from './db.js';
export async function hasConsent(phoneHash) {
const result = await pool.query(
`SELECT consented_at FROM user_consents
WHERE phone_hash = $1 AND revoked_at IS NULL
LIMIT 1`,
[phoneHash]
);
return result.rows.length > 0;
}
export async function requestConsent(phoneNumber) {
// Envía mensaje de consentimiento via WhatsApp Cloud API
await sendWhatsAppMessage(phoneNumber, {
type: 'interactive',
interactive: {
type: 'button',
body: {
text: 'Para continuar, necesito tu autorización para procesar tus datos conforme a nuestra política de privacidad. ¿Aceptas?'
},
action: {
buttons: [
{ type: 'reply', reply: { id: 'consent_yes', title: 'Sí, acepto' } },
{ type: 'reply', reply: { id: 'consent_no', title: 'No acepto' } }
]
}
}
});
}
Paso 4: Enmascaramiento de datos sensibles en respuestas del modelo
Si usas un LLM para generar respuestas, necesitas un filtro de salida que detecte y enmascare datos sensibles antes de enviar al usuario. Los modelos alucinan y a veces repiten información del contexto que no debería salir.
// sanitizer.js
const SENSITIVE_PATTERNS = [
{ pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, replacement: '**** **** **** ****' }, // tarjetas
{ pattern: /\b\d{18}\b/g, replacement: '[CLABE enmascarada]' }, // CLABE mexicana
{ pattern: /\b[A-Z]{4}\d{6}[A-Z0-9]{3}\b/g, replacement: '[RFC enmascarado]' }, // RFC
{ pattern: /\b\d{10,11}\b/g, replacement: '[número enmascarado]' }, // teléfonos
];
export function sanitizeOutput(text) {
let sanitized = text;
for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
sanitized = sanitized.replace(pattern, replacement);
}
return sanitized;
}
Paso 5: Handoff obligatorio a humano en casos críticos
La gobernanza de IA exige que el bot reconozca sus límites. Define explícitamente los casos donde el agente de IA no puede continuar y debe transferir a un humano.
// handoff.js
const HANDOFF_TRIGGERS = [
'fraude',
'robo',
'cancelar cuenta',
'demanda',
'queja formal',
'no reconozco',
'me cobraron de más',
];
export function requiresHumanHandoff(message) {
const lower = message.toLowerCase();
return HANDOFF_TRIGGERS.some(trigger => lower.includes(trigger));
}
export async function initiateHandoff(sessionId, phoneNumber, reason) {
// Log del handoff
await logHandoffEvent({ sessionId, reason, timestamp: new Date() });
// Notifica al equipo interno (Slack, PagerDuty, lo que uses)
await notifyHumanAgent({ sessionId, phoneNumber: hashPhone(phoneNumber), reason });
// Informa al usuario
await sendWhatsAppMessage(phoneNumber, {
type: 'text',
text: {
body: 'Tu caso requiere atención personalizada. Un especialista te contactará en los próximos 15 minutos en este mismo chat.'
}
});
}
Paso 6: Pruebas de gobernanza antes de ir a producción
No basta con probar que el bot responde. Prueba explícitamente los controles de gobernanza:
- Prueba de consentimiento: Envía un mensaje sin consentimiento previo. El bot debe solicitar autorización y no procesar nada más.
- Prueba de enmascaramiento: Inyecta una CLABE en el contexto del LLM. Verifica que no aparezca en la respuesta al usuario.
- Prueba de handoff: Envía "no reconozco este cargo". Verifica que el log de handoff se genere y el humano sea notificado.
- Prueba de auditoría: Revisa que cada mensaje inbound y outbound aparezca en
message_audit_logcon el hash correcto. - Prueba de duplicados: Simula un timeout del webhook. Verifica que el mensaje no se procese dos veces (usa
wa_message_idcomo clave de idempotencia).
Advertencias finales de producción
- No uses WhatsApp Personal API ni Baileys en producción financiera. Meta puede banear el número y perderías el canal. Usa solo WhatsApp Cloud API oficial o un BSP certificado.
- Retención de logs: En México, la CNBV exige retención de registros de comunicación por al menos 5 años en algunos sectores. Diseña tu esquema de base de datos con esto en mente desde el inicio.
- Rate limits: WhatsApp Cloud API tiene límites por tier. En finanzas, los picos de alertas de fraude pueden saturar el rate limit. Implementa backpressure con una cola antes de que eso te explote en producción.
- Nunca confíes solo en el LLM para decisiones financieras. El modelo puede generar texto convincente pero incorrecto. Toda acción con consecuencia real (transferencia, cancelación, bloqueo) debe pasar por una capa de validación determinista antes de ejecutarse.
La gobernanza no es un checkbox de compliance: es lo que determina si tu bot sobrevive una auditoría o te mete en un problema legal que ningún prompt puede resolver.