diff --git a/api/webhook.php b/api/webhook.php index 1feb060..fc974b6 100644 --- a/api/webhook.php +++ b/api/webhook.php @@ -113,21 +113,26 @@ class WhatsAppWebhook { 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 = []; if (isset($value['contacts']) && is_array($value['contacts'])) { foreach ($value['contacts'] as $contact) { - $waId = $contact['wa_id'] ?? null; $name = $contact['profile']['name'] ?? null; - if ($waId && $name) { - $contactNames[$waId] = $name; + if (!$name) continue; + foreach ([$contact['wa_id'] ?? null, $contact['user_id'] ?? null] as $clave) { + if ($clave) $contactNames[$clave] = $name; } } } foreach ($items as $message) { - // En algunos payloads la estructura key es 'from' y 'id' (mensajes), en otros puede venir distinta; normalizamos - $phoneNumber = $message['from'] ?? ($message['wa_id'] ?? null); + // En algunos payloads la estructura key es 'from' y 'id' (mensajes), en otros puede venir distinta; normalizamos. + // `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); $timestamp = $message['timestamp'] ?? null; diff --git a/migrations/20260811_bsuid_identidad_whatsapp.sql b/migrations/20260811_bsuid_identidad_whatsapp.sql new file mode 100644 index 0000000..f5b241e --- /dev/null +++ b/migrations/20260811_bsuid_identidad_whatsapp.sql @@ -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; diff --git a/services/WhatsAppService.php b/services/WhatsAppService.php index ba9befe..39b6d37 100644 --- a/services/WhatsAppService.php +++ b/services/WhatsAppService.php @@ -767,6 +767,14 @@ class WhatsAppService 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) error_log("WhatsApp API URL: " . $url); error_log("WhatsApp Token length: " . strlen($this->token)); @@ -881,11 +889,26 @@ class WhatsAppService 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 */ 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 $phone = preg_replace('/[^0-9]/', '', $phone);