Atender a quien oculta su teléfono en WhatsApp (BSUID)
Meta desplegó los nombres de usuario: quien oculta su teléfono llega al webhook sin `from` ni `wa_id`, identificado solo por su BSUID con la forma "CO.1761088155094242". El webhook exigía teléfono y descartaba el mensaje en silencio, sin guardarlo ni dejar rastro: 221 mensajes de 65 personas ignorados por completo, ya el 3-6% de lo que entra cada día y subiendo. - webhook.php lee `from_user_id` cuando no llega el teléfono, y rescata el nombre del contacto indexando también por `user_id`. - WhatsAppService responde por BSUID: Meta exige el campo `recipient` en lugar de `to`. Se traduce en sendMessage(), el punto por donde pasan todos los envíos, en vez de en los diez métodos que arman payloads. - formatPhoneNumber() devuelve el BSUID intacto; antes le quitaba el punto y las letras y lo dejaba en un destinatario inexistente. - Las cuatro columnas del flujo pasan a varchar(32): los BSUID de Meta llegan a 23 caracteres y se truncaban en silencio en varchar(20). De estas personas no tendremos el teléfono; si un flujo lo necesita, hay que identificarlas por documento. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
381c2ca04e
commit
92179af56a
+11
-6
@@ -113,21 +113,26 @@ class WhatsAppWebhook {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extraer nombres de contactos del payload (contacts[].profile.name)
|
// Extraer nombres de contactos del payload (contacts[].profile.name).
|
||||||
|
// Se indexa por wa_id y también por user_id (BSUID): desde que Meta desplegó
|
||||||
|
// los nombres de usuario, quien oculta su teléfono llega sin wa_id.
|
||||||
$contactNames = [];
|
$contactNames = [];
|
||||||
if (isset($value['contacts']) && is_array($value['contacts'])) {
|
if (isset($value['contacts']) && is_array($value['contacts'])) {
|
||||||
foreach ($value['contacts'] as $contact) {
|
foreach ($value['contacts'] as $contact) {
|
||||||
$waId = $contact['wa_id'] ?? null;
|
|
||||||
$name = $contact['profile']['name'] ?? null;
|
$name = $contact['profile']['name'] ?? null;
|
||||||
if ($waId && $name) {
|
if (!$name) continue;
|
||||||
$contactNames[$waId] = $name;
|
foreach ([$contact['wa_id'] ?? null, $contact['user_id'] ?? null] as $clave) {
|
||||||
|
if ($clave) $contactNames[$clave] = $name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($items as $message) {
|
foreach ($items as $message) {
|
||||||
// En algunos payloads la estructura key es 'from' y 'id' (mensajes), en otros puede venir distinta; normalizamos
|
// En algunos payloads la estructura key es 'from' y 'id' (mensajes), en otros puede venir distinta; normalizamos.
|
||||||
$phoneNumber = $message['from'] ?? ($message['wa_id'] ?? null);
|
// `from`/`wa_id` dejaron de ser obligatorios: si la persona oculta su teléfono
|
||||||
|
// solo llega `from_user_id` (BSUID, ej. "CO.1761088155094242"). Ese identificador
|
||||||
|
// ocupa el lugar del teléfono en todo el flujo y sirve para responderle.
|
||||||
|
$phoneNumber = $message['from'] ?? ($message['wa_id'] ?? ($message['from_user_id'] ?? null));
|
||||||
$messageId = $message['id'] ?? ($message['message_id'] ?? null);
|
$messageId = $message['id'] ?? ($message['message_id'] ?? null);
|
||||||
$timestamp = $message['timestamp'] ?? null;
|
$timestamp = $message['timestamp'] ?? null;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-- 20260811_bsuid_identidad_whatsapp.sql
|
||||||
|
--
|
||||||
|
-- Meta desplegó los nombres de usuario de WhatsApp: quien oculta su teléfono
|
||||||
|
-- llega al webhook sin `from` ni `wa_id`, identificado solo por su BSUID
|
||||||
|
-- (identificador de usuario por empresa), con la forma "CO.1761088155094242".
|
||||||
|
--
|
||||||
|
-- Ese identificador ocupa el lugar del teléfono en el flujo del bot, pero no
|
||||||
|
-- cabe en varchar(20): los BSUID de Meta llegan a 23 caracteres, así que se
|
||||||
|
-- truncarían en silencio y la persona quedaría imposible de responder.
|
||||||
|
--
|
||||||
|
-- Se amplían a varchar(32) las cuatro tablas por donde circula.
|
||||||
|
-- Las vistas (scheduled_messages_view, v_paciente_resumen) heredan el tipo.
|
||||||
|
-- lab_pacientes.telefono se deja igual: ahí va el teléfono real del paciente,
|
||||||
|
-- y de estas personas justamente no lo tenemos.
|
||||||
|
|
||||||
|
ALTER TABLE users MODIFY phone_number VARCHAR(32) NOT NULL;
|
||||||
|
ALTER TABLE user_states MODIFY phone_number VARCHAR(32) NOT NULL;
|
||||||
|
ALTER TABLE terms_acceptance MODIFY phone_number VARCHAR(32) NOT NULL;
|
||||||
|
ALTER TABLE file_requests MODIFY phone_number VARCHAR(32) NOT NULL;
|
||||||
@@ -767,6 +767,14 @@ class WhatsAppService
|
|||||||
unset($payload['__app_meta']);
|
unset($payload['__app_meta']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A quien oculta su teléfono se le responde por BSUID, y Meta exige que vaya
|
||||||
|
// en `recipient` en lugar de `to`. Se corrige aquí, que es por donde pasan
|
||||||
|
// todos los envíos, en vez de en cada método que arma un payload.
|
||||||
|
if (isset($payload['to']) && self::esBsuid($payload['to'])) {
|
||||||
|
$payload['recipient'] = $payload['to'];
|
||||||
|
unset($payload['to']);
|
||||||
|
}
|
||||||
|
|
||||||
// Debug log (payload sent to WhatsApp)
|
// Debug log (payload sent to WhatsApp)
|
||||||
error_log("WhatsApp API URL: " . $url);
|
error_log("WhatsApp API URL: " . $url);
|
||||||
error_log("WhatsApp Token length: " . strlen($this->token));
|
error_log("WhatsApp Token length: " . strlen($this->token));
|
||||||
@@ -881,11 +889,26 @@ class WhatsAppService
|
|||||||
return $decoded;
|
return $decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ¿El destinatario es un BSUID (identificador de usuario por empresa) y no un teléfono?
|
||||||
|
* Formato de Meta: código de país, punto y dígitos. Ej.: "CO.1761088155094242".
|
||||||
|
*/
|
||||||
|
public static function esBsuid($destinatario)
|
||||||
|
{
|
||||||
|
return (bool) preg_match('/^[A-Z]{2}\.\d+$/', (string) $destinatario);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formatear número de teléfono
|
* Formatear número de teléfono
|
||||||
*/
|
*/
|
||||||
private function formatPhoneNumber($phone)
|
private function formatPhoneNumber($phone)
|
||||||
{
|
{
|
||||||
|
// Un BSUID no es un teléfono: se devuelve intacto, porque quitarle
|
||||||
|
// el punto y las letras lo convertiría en un destinatario inexistente.
|
||||||
|
if (self::esBsuid($phone)) {
|
||||||
|
return $phone;
|
||||||
|
}
|
||||||
|
|
||||||
// Remover caracteres especiales
|
// Remover caracteres especiales
|
||||||
$phone = preg_replace('/[^0-9]/', '', $phone);
|
$phone = preg_replace('/[^0-9]/', '', $phone);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user