Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26025a6399 | ||
|
|
92179af56a |
+112
-6
@@ -113,24 +113,41 @@ 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);
|
$phoneNumber = $message['from'] ?? ($message['wa_id'] ?? null);
|
||||||
$messageId = $message['id'] ?? ($message['message_id'] ?? null);
|
$messageId = $message['id'] ?? ($message['message_id'] ?? null);
|
||||||
$timestamp = $message['timestamp'] ?? null;
|
$timestamp = $message['timestamp'] ?? null;
|
||||||
|
|
||||||
|
// Meta manda el BSUID en todos los mensajes, traigan teléfono o no.
|
||||||
|
$bsuid = $message['from_user_id'] ?? null;
|
||||||
|
|
||||||
|
// Si la persona oculta su teléfono, `from` y `wa_id` no llegan. En ese caso
|
||||||
|
// se busca por BSUID: si ya escribió antes mostrando su número, se le
|
||||||
|
// reconoce y se le sigue respondiendo a ese teléfono.
|
||||||
|
if (empty($phoneNumber) && $bsuid) {
|
||||||
|
$conocido = $this->db->fetch(
|
||||||
|
"SELECT phone_number FROM users WHERE bsuid = :b", ['b' => $bsuid]
|
||||||
|
);
|
||||||
|
// Si no se le conoce, el BSUID hace de identificador: sirve para responderle,
|
||||||
|
// aunque no permita cruzarlo con el paciente ni con el turnero.
|
||||||
|
$phoneNumber = $conocido['phone_number'] ?? $bsuid;
|
||||||
|
}
|
||||||
|
|
||||||
// Si falta lo crítico, saltar
|
// Si falta lo crítico, saltar
|
||||||
if (empty($phoneNumber) || empty($messageId)) {
|
if (empty($phoneNumber) || empty($messageId)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -148,7 +165,7 @@ class WhatsAppWebhook {
|
|||||||
|
|
||||||
// Obtener o crear usuario
|
// Obtener o crear usuario
|
||||||
$user = $this->getUserByPhone($phoneNumber);
|
$user = $this->getUserByPhone($phoneNumber);
|
||||||
$contactName = $contactNames[$phoneNumber] ?? null;
|
$contactName = $contactNames[$phoneNumber] ?? ($bsuid ? ($contactNames[$bsuid] ?? null) : null);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
$userId = $this->createUser($phoneNumber);
|
$userId = $this->createUser($phoneNumber);
|
||||||
$user = $this->getUserById($userId);
|
$user = $this->getUserById($userId);
|
||||||
@@ -173,6 +190,20 @@ class WhatsAppWebhook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Guardar la equivalencia BSUID↔usuario mientras la persona todavía muestra
|
||||||
|
// su teléfono. El día que lo oculte, ese registro es lo único que permitirá
|
||||||
|
// reconocerla, así que se anota en cada mensaje y no solo la primera vez.
|
||||||
|
if ($bsuid && !empty($user['id']) && ($user['bsuid'] ?? null) !== $bsuid) {
|
||||||
|
try {
|
||||||
|
$this->db->update('users', ['bsuid' => $bsuid], 'id = ?', [$user['id']]);
|
||||||
|
$user['bsuid'] = $bsuid;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Choca si ese BSUID ya está en otro usuario (la persona cambió de
|
||||||
|
// número). No es motivo para perder el mensaje: se sigue adelante.
|
||||||
|
error_log('[webhook] No se pudo guardar el BSUID ' . $bsuid . ': ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Procesar diferentes tipos de mensaje
|
// Procesar diferentes tipos de mensaje
|
||||||
$messageText = '';
|
$messageText = '';
|
||||||
$messageType = 'text';
|
$messageType = 'text';
|
||||||
@@ -191,6 +222,28 @@ class WhatsAppWebhook {
|
|||||||
'reaction_emoji' => $emoji
|
'reaction_emoji' => $emoji
|
||||||
];
|
];
|
||||||
|
|
||||||
|
} elseif (($message['type'] ?? '') === 'contacts') {
|
||||||
|
// La persona compartió su contacto, sea por el botón que se le pidió
|
||||||
|
// o a mano. Es la única forma de obtener el teléfono de quien lo oculta.
|
||||||
|
$messageType = 'contacts';
|
||||||
|
$messageText = json_encode($message['contacts'] ?? []);
|
||||||
|
|
||||||
|
$telefonoCompartido = null;
|
||||||
|
foreach ($message['contacts'] ?? [] as $c) {
|
||||||
|
foreach ($c['phones'] ?? [] as $t) {
|
||||||
|
// wa_id ya viene normalizado; `phone` puede traer espacios y signos
|
||||||
|
$candidato = $t['wa_id'] ?? ($t['phone'] ?? null);
|
||||||
|
if ($candidato) {
|
||||||
|
$telefonoCompartido = preg_replace('/[^0-9]/', '', $candidato);
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($telefonoCompartido && !empty($user['id'])) {
|
||||||
|
$this->vincularTelefonoCompartido($user, $telefonoCompartido, $bsuid);
|
||||||
|
}
|
||||||
|
|
||||||
} elseif (isset($message['interactive'])) {
|
} elseif (isset($message['interactive'])) {
|
||||||
// Interactive replies (list or button) - normalize to text so bot can process
|
// Interactive replies (list or button) - normalize to text so bot can process
|
||||||
$messageType = 'text';
|
$messageType = 'text';
|
||||||
@@ -450,6 +503,59 @@ class WhatsAppWebhook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vincula el teléfono que la persona acaba de compartir con el usuario que
|
||||||
|
* hasta ahora solo se conocía por su BSUID.
|
||||||
|
*
|
||||||
|
* Puede haber dos registros de la misma persona: el viejo, de cuando escribía
|
||||||
|
* mostrando el número, y el nuevo creado con el BSUID de identificador. No se
|
||||||
|
* fusionan aquí (implicaría mover conversaciones, estados y aceptación de
|
||||||
|
* términos, y una fusión mal hecha mezcla historias clínicas de dos personas):
|
||||||
|
* se deja el registro con el teléfono como el bueno y se marca el otro, para
|
||||||
|
* que alguien lo revise.
|
||||||
|
*
|
||||||
|
* @return bool si el teléfono quedó vinculado
|
||||||
|
*/
|
||||||
|
private function vincularTelefonoCompartido(&$user, $telefono, $bsuid) {
|
||||||
|
// Ya lo teníamos: nada que hacer
|
||||||
|
if (($user['phone_number'] ?? null) === $telefono) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$existente = $this->db->fetch(
|
||||||
|
"SELECT id FROM users WHERE phone_number = :t AND id <> :id",
|
||||||
|
['t' => $telefono, 'id' => $user['id']]
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($existente) {
|
||||||
|
// El registro bueno es el que tiene el teléfono. Se le pasa el BSUID
|
||||||
|
// para que a partir de ahora se le reconozca por ahí.
|
||||||
|
if ($bsuid) {
|
||||||
|
$this->db->update('users', ['bsuid' => null], 'id = ?', [$user['id']]);
|
||||||
|
$this->db->update('users', ['bsuid' => $bsuid], 'id = ?', [$existente['id']]);
|
||||||
|
}
|
||||||
|
error_log(sprintf(
|
||||||
|
'[webhook] BSUID %s compartió el teléfono %s, que ya era del usuario %d. ' .
|
||||||
|
'El usuario %d queda duplicado y hay que revisarlo a mano.',
|
||||||
|
$bsuid, $telefono, $existente['id'], $user['id']
|
||||||
|
));
|
||||||
|
$user = $this->getUserById($existente['id']) ?: $user;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No había otro registro: el placeholder pasa a tener el teléfono real
|
||||||
|
$this->db->update('users', ['phone_number' => $telefono], 'id = ?', [$user['id']]);
|
||||||
|
$user['phone_number'] = $telefono;
|
||||||
|
error_log(sprintf('[webhook] BSUID %s quedó vinculado al teléfono %s', $bsuid, $telefono));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('[webhook] Error vinculando el teléfono compartido: ' . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function getUserByPhone($phoneNumber) {
|
private function getUserByPhone($phoneNumber) {
|
||||||
return $this->db->fetch(
|
return $this->db->fetch(
|
||||||
"SELECT * FROM users WHERE phone_number = :phone",
|
"SELECT * FROM users WHERE phone_number = :phone",
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
-- 20260811_bsuid_mapa_identidad.sql
|
||||||
|
--
|
||||||
|
-- Segunda parte del cambio de identidad de WhatsApp (ver 20260811_bsuid_identidad_whatsapp.sql).
|
||||||
|
--
|
||||||
|
-- Meta manda el BSUID en TODOS los webhooks de mensaje, también en los que aún
|
||||||
|
-- traen teléfono. Eso permite guardar la equivalencia BSUID↔teléfono mientras
|
||||||
|
-- la persona todavía muestra su número, de modo que el día que lo oculte
|
||||||
|
-- siga siendo reconocible: ya sabemos quién es.
|
||||||
|
--
|
||||||
|
-- El histórico de webhook_logs tiene 6.423 equivalencias que se cargan con
|
||||||
|
-- scripts/backfill_bsuid.php.
|
||||||
|
--
|
||||||
|
-- Se guarda en una columna aparte y no en phone_number porque son dos cosas
|
||||||
|
-- distintas: el BSUID identifica, el teléfono además sirve para cruzar con el
|
||||||
|
-- paciente y el turnero. Mezclarlos rompería esos cruces.
|
||||||
|
|
||||||
|
ALTER TABLE users ADD COLUMN bsuid VARCHAR(32) NULL DEFAULT NULL COMMENT 'Identificador de usuario por empresa (Meta). Presente aunque la persona oculte su teléfono.' AFTER phone_number;
|
||||||
|
|
||||||
|
-- Único: un BSUID identifica a una sola persona dentro del portafolio.
|
||||||
|
-- Admite varios NULL, que es el caso de todos los usuarios ya existentes.
|
||||||
|
ALTER TABLE users ADD UNIQUE KEY uk_users_bsuid (bsuid);
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* scripts/backfill_bsuid.php
|
||||||
|
*
|
||||||
|
* Rellena users.bsuid a partir del histórico de webhook_logs.
|
||||||
|
*
|
||||||
|
* Meta manda el BSUID en todos los webhooks de mensaje, así que en el histórico
|
||||||
|
* están las equivalencias BSUID↔teléfono de la gente que escribió cuando aún
|
||||||
|
* mostraba su número. Cargarlas significa que, el día que oculten el teléfono,
|
||||||
|
* el bot siga sabiendo quiénes son en vez de tratarlos como desconocidos.
|
||||||
|
*
|
||||||
|
* Es idempotente: se puede correr las veces que haga falta.
|
||||||
|
*
|
||||||
|
* Uso:
|
||||||
|
* php scripts/backfill_bsuid.php --simular (no escribe, solo informa)
|
||||||
|
* php scripts/backfill_bsuid.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../config/config.php';
|
||||||
|
|
||||||
|
$simular = in_array('--simular', $argv, true);
|
||||||
|
echo $simular ? "Modo simulación: no se escribe nada.\n\n" : "Aplicando cambios.\n\n";
|
||||||
|
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
// --- 1. Recorrer el histórico y armar el mapa BSUID → teléfono ---
|
||||||
|
echo "Leyendo webhook_logs...\n";
|
||||||
|
$mapa = [];
|
||||||
|
$sinTelefono = [];
|
||||||
|
|
||||||
|
// Una sola pasada sin buffer: son más de 400.000 registros, así que ni caben
|
||||||
|
// en memoria de golpe ni conviene reconsultar la tabla por bloques (cada bloque
|
||||||
|
// volvía a recorrerla entera y tardaba una eternidad).
|
||||||
|
$conn = $db->getConnection();
|
||||||
|
$bufferPrevio = $conn->getAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY);
|
||||||
|
$conn->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
|
||||||
|
|
||||||
|
$leidos = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $conn->query(
|
||||||
|
"SELECT request_body FROM webhook_logs
|
||||||
|
WHERE request_body LIKE '%user_id%' AND request_body LIKE '%\"messages\"%'"
|
||||||
|
);
|
||||||
|
|
||||||
|
while ($fila = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$leidos++;
|
||||||
|
if ($leidos % 50000 === 0) printf(" ...%d registros\n", $leidos);
|
||||||
|
$d = json_decode($fila['request_body'], true);
|
||||||
|
if (!$d) continue;
|
||||||
|
|
||||||
|
foreach ($d['entry'] ?? [] as $entry) {
|
||||||
|
foreach ($entry['changes'] ?? [] as $cambio) {
|
||||||
|
$v = $cambio['value'] ?? [];
|
||||||
|
|
||||||
|
// El teléfono puede venir en contacts[].wa_id o en messages[].from
|
||||||
|
foreach ($v['contacts'] ?? [] as $c) {
|
||||||
|
if (!empty($c['user_id']) && !empty($c['wa_id'])) {
|
||||||
|
$mapa[$c['user_id']] = $c['wa_id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($v['messages'] ?? [] as $m) {
|
||||||
|
$uid = $m['from_user_id'] ?? null;
|
||||||
|
if (!$uid) continue;
|
||||||
|
if (!empty($m['from'])) $mapa[$uid] = $m['from'];
|
||||||
|
else $sinTelefono[$uid] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cuando alguien comparte su contacto, el teléfono llega aquí
|
||||||
|
foreach ($v['messages'] ?? [] as $m) {
|
||||||
|
if (($m['type'] ?? '') !== 'contacts') continue;
|
||||||
|
$uid = $m['from_user_id'] ?? null;
|
||||||
|
foreach ($m['contacts'] ?? [] as $c) {
|
||||||
|
foreach ($c['phones'] ?? [] as $t) {
|
||||||
|
$num = $t['wa_id'] ?? ($t['phone'] ?? null);
|
||||||
|
if ($uid && $num) $mapa[$uid] = preg_replace('/[^0-9]/', '', $num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$stmt->closeCursor();
|
||||||
|
} finally {
|
||||||
|
// Dejar la conexión como estaba: las escrituras siguientes la necesitan con buffer
|
||||||
|
$conn->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $bufferPrevio);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n registros revisados: %d\n", $leidos);
|
||||||
|
printf(" equivalencias encontradas: %d\n", count($mapa));
|
||||||
|
printf(" personas que llegaron sin teléfono: %d\n\n", count($sinTelefono));
|
||||||
|
|
||||||
|
// --- 2. Escribir el BSUID en el usuario que corresponde ---
|
||||||
|
$marcados = 0; $noExisten = 0; $yaEstaban = 0; $conflictos = 0;
|
||||||
|
|
||||||
|
foreach ($mapa as $bsuid => $telefono) {
|
||||||
|
$u = $db->fetch("SELECT id, bsuid FROM users WHERE phone_number = :t", ['t' => $telefono]);
|
||||||
|
if (!$u) { $noExisten++; continue; }
|
||||||
|
if ($u['bsuid'] === $bsuid) { $yaEstaban++; continue; }
|
||||||
|
if (!empty($u['bsuid'])) {
|
||||||
|
// El BSUID se regenera si la persona cambia de número: gana el más reciente
|
||||||
|
printf(" aviso: %s tenía %s y ahora %s\n", $telefono, $u['bsuid'], $bsuid);
|
||||||
|
$conflictos++;
|
||||||
|
}
|
||||||
|
if (!$simular) {
|
||||||
|
try {
|
||||||
|
$db->update('users', ['bsuid' => $bsuid], 'id = ?', [$u['id']]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
printf(" error en %s: %s\n", $telefono, $e->getMessage());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$marcados++;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n usuarios marcados con su BSUID: %d\n", $marcados);
|
||||||
|
printf(" ya lo tenían: %d\n", $yaEstaban);
|
||||||
|
printf(" sin usuario en la base: %d\n", $noExisten);
|
||||||
|
printf(" BSUID reemplazados (cambio de número): %d\n", $conflictos);
|
||||||
|
|
||||||
|
// --- 3. ¿A cuántos de los perdidos rescatamos? ---
|
||||||
|
$rescatables = array_intersect_key($mapa, $sinTelefono);
|
||||||
|
printf("\n de los que llegaron sin teléfono, quedan identificados: %d\n", count($rescatables));
|
||||||
|
foreach ($rescatables as $b => $t) {
|
||||||
|
printf(" %-24s → %s\n", $b, substr($t, 0, -4) . '****');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pendientes = array_diff_key($sinTelefono, $mapa);
|
||||||
|
printf("\n siguen sin identificar: %d (hay que pedirles el contacto)\n", count($pendientes));
|
||||||
|
|
||||||
|
echo $simular ? "\nSimulación terminada, no se escribió nada.\n" : "\nListo.\n";
|
||||||
@@ -238,6 +238,32 @@ class WhatsAppService
|
|||||||
/**
|
/**
|
||||||
* Enviar mensaje con botones interactivos
|
* Enviar mensaje con botones interactivos
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Pedirle a la persona que comparta su número de teléfono.
|
||||||
|
*
|
||||||
|
* Quien activó el nombre de usuario de WhatsApp llega sin teléfono, y sin él
|
||||||
|
* no se le puede cruzar con su historia ni con el turnero. Este botón es la
|
||||||
|
* vía que da Meta para pedírselo: la persona decide si lo comparte, y si
|
||||||
|
* acepta llega un mensaje de tipo `contacts` con el número.
|
||||||
|
*
|
||||||
|
* Solo tiene sentido dentro de la ventana de 24 horas; fuera de ella hay que
|
||||||
|
* usar una plantilla con el botón REQUEST_CONTACT_INFO.
|
||||||
|
*/
|
||||||
|
public function pedirContacto($to, $bodyText)
|
||||||
|
{
|
||||||
|
return $this->sendMessage([
|
||||||
|
'messaging_product' => 'whatsapp',
|
||||||
|
'recipient_type' => 'individual',
|
||||||
|
'to' => $this->formatPhoneNumber($to),
|
||||||
|
'type' => 'interactive',
|
||||||
|
'interactive' => [
|
||||||
|
'type' => 'request_contact_info',
|
||||||
|
'body' => ['text' => $bodyText],
|
||||||
|
'action' => ['name' => 'request_contact_info'],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function sendInteractiveMessage($to, $bodyText, $buttons, $header = null, $footer = null)
|
public function sendInteractiveMessage($to, $bodyText, $buttons, $header = null, $footer = null)
|
||||||
{
|
{
|
||||||
$interactive = [
|
$interactive = [
|
||||||
@@ -767,6 +793,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 +915,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