diff --git a/scripts/test_bsuid.php b/scripts/test_bsuid.php new file mode 100644 index 0000000..7e98034 --- /dev/null +++ b/scripts/test_bsuid.php @@ -0,0 +1,162 @@ +newInstanceWithoutConstructor(); +$fmt = new ReflectionMethod('WhatsAppService', 'formatPhoneNumber'); +$fmt->setAccessible(true); + +chequear('el BSUID sale intacto', + $fmt->invoke($svc, 'CO.1761088155094242') === 'CO.1761088155094242'); +chequear('el celular sin indicativo sigue recibiendo el 57', + $fmt->invoke($svc, '3214533764') === '573214533764'); +chequear('el celular con indicativo se conserva', + $fmt->invoke($svc, '573214533764') === '573214533764'); + +// --- 3. El payload debe usar `recipient` para BSUID y `to` para teléfonos --- +// Se replica la transformación de sendMessage(), que es privada y hace red. +$transformar = function (array $payload) { + if (isset($payload['to']) && WhatsAppService::esBsuid($payload['to'])) { + $payload['recipient'] = $payload['to']; + unset($payload['to']); + } + return $payload; +}; + +$conBsuid = $transformar(['to' => 'CO.1761088155094242', 'type' => 'text']); +chequear('con BSUID se envía en `recipient`', ($conBsuid['recipient'] ?? null) === 'CO.1761088155094242'); +chequear('con BSUID desaparece `to`', !isset($conBsuid['to'])); + +$conTel = $transformar(['to' => '573214533764', 'type' => 'text']); +chequear('con teléfono se mantiene `to`', ($conTel['to'] ?? null) === '573214533764'); +chequear('con teléfono no aparece `recipient`', !isset($conTel['recipient'])); + +// --- 4. El webhook debe extraer el remitente del payload real que llegó hoy --- +$payloadReal = json_decode('{"messaging_product":"whatsapp", + "contacts":[{"profile":{"name":"con la fe puesta en Dios","username":"Luzmarytosfer"}, + "user_id":"CO.1761088155094242"}], + "messages":[{"from_user_id":"CO.1761088155094242","id":"wamid.XXX", + "timestamp":"1786481656","text":{"body":"Buenos dias"},"type":"text"}]}', true); + +$m = $payloadReal['messages'][0]; +$remitente = $m['from'] ?? ($m['wa_id'] ?? ($m['from_user_id'] ?? null)); +chequear('el mensaje que se perdía ahora sí tiene remitente', $remitente === 'CO.1761088155094242'); + +$nombres = []; +foreach ($payloadReal['contacts'] as $c) { + $n = $c['profile']['name'] ?? null; + if (!$n) continue; + foreach ([$c['wa_id'] ?? null, $c['user_id'] ?? null] as $k) { + if ($k) $nombres[$k] = $n; + } +} +chequear('se rescata el nombre aunque no haya wa_id', + ($nombres['CO.1761088155094242'] ?? null) === 'con la fe puesta en Dios'); + +// --- 5. El identificador debe caber en la columna --- +chequear('el BSUID más largo cabe en varchar(32)', strlen('US.13491208655302741918') <= 32); + +// --- 6. Extraer el teléfono del contacto compartido (webhook `contacts` de Meta) --- +$extraerTelefono = function (array $mensaje) { + foreach ($mensaje['contacts'] ?? [] as $c) { + foreach ($c['phones'] ?? [] as $t) { + $cand = $t['wa_id'] ?? ($t['phone'] ?? null); + if ($cand) return preg_replace('/[^0-9]/', '', $cand); + } + } + return null; +}; + +$porBoton = ['type' => 'contacts', 'from_user_id' => 'CO.1761088155094242', + 'contacts' => [['origin' => 'contact_request', + 'phones' => [['phone' => '+57 321 4533764', 'wa_id' => '573214533764', 'type' => 'CELL']]]]]; +chequear('del botón se extrae el teléfono', $extraerTelefono($porBoton) === '573214533764'); + +// Compartido a mano: puede venir con vcard y sin wa_id +$aMano = ['type' => 'contacts', 'from_user_id' => 'CO.1761088155094242', + 'contacts' => [['origin' => 'other', 'vcard' => 'BEGIN:VCARD...', + 'phones' => [['phone' => '+57 321 453 3764', 'type' => 'CELL']]]]]; +chequear('compartido a mano también se extrae', $extraerTelefono($aMano) === '573214533764'); +chequear('sin teléfono devuelve nulo', $extraerTelefono(['type' => 'contacts', 'contacts' => [[]]]) === null); + +// --- 7. El botón para pedir el contacto debe armarse como exige Meta --- +$pedir = new ReflectionMethod('WhatsAppService', 'pedirContacto'); +chequear('pedirContacto existe y es pública', $pedir->isPublic()); + +$payloadBoton = [ + 'messaging_product' => 'whatsapp', + 'recipient_type' => 'individual', + 'to' => $fmt->invoke($svc, 'CO.1761088155094242'), + 'type' => 'interactive', + 'interactive' => ['type' => 'request_contact_info', + 'body' => ['text' => 'texto'], + 'action' => ['name' => 'request_contact_info']], +]; +$enviado = $transformar($payloadBoton); +chequear('el botón viaja con `recipient` cuando es BSUID', + ($enviado['recipient'] ?? null) === 'CO.1761088155094242' && !isset($enviado['to'])); +chequear('el tipo interactivo es el que Meta espera', + $enviado['interactive']['type'] === 'request_contact_info' + && $enviado['interactive']['action']['name'] === 'request_contact_info'); + +// --- 8. La ficha del paciente no debe recibir el BSUID como teléfono --- +// Réplica de la decisión de Paciente::obtenerOCrearDesdeWhatsapp() +$fichaDesde = function ($phoneNumber, $nombre = null) { + $sinTelefono = esBsuid($phoneNumber); + return [ + 'nombre_completo' => $nombre ?? ($sinTelefono ? 'Paciente sin identificar' : 'Paciente ' . $phoneNumber), + 'telefono' => $sinTelefono ? null : $phoneNumber, + ]; +}; + +$fichaOculta = $fichaDesde('CO.1761088155094242'); +chequear('la ficha queda SIN teléfono si es BSUID', $fichaOculta['telefono'] === null); +chequear('y sin el identificador metido en el nombre', + strpos($fichaOculta['nombre_completo'], 'CO.') === false); + +$fichaNormal = $fichaDesde('573214533764'); +chequear('con teléfono real la ficha lo conserva', $fichaNormal['telefono'] === '573214533764'); +chequear('y el nombre por defecto no cambia', $fichaNormal['nombre_completo'] === 'Paciente 573214533764'); + +// El daño concreto que se evita: normalizarTelefono() dejaba 16 dígitos con +// toda la pinta de un número real dentro de la historia clínica. +$comoQuedaba = preg_replace('/[^0-9+]/', '', 'CO.1761088155094242'); +chequear('antes se guardaba un número falso creíble', $comoQuedaba === '1761088155094242'); +chequear('y ahora eso ya no llega al campo teléfono', $fichaOculta['telefono'] !== $comoQuedaba); + +// --- 9. Solo se pide el contacto a quien hace falta, y una sola vez --- +$decidir = function ($phoneNumber, $pedidoAt) { + if (!esBsuid($phoneNumber)) return 'no_hace_falta'; + if (!empty($pedidoAt)) return 'ya_se_pidio'; + return 'pedido'; +}; +chequear('a quien tiene teléfono no se le pide', $decidir('573214533764', null) === 'no_hace_falta'); +chequear('a quien lo oculta sí se le pide', $decidir('CO.1761088155094242', null) === 'pedido'); +chequear('y no se le insiste una segunda vez', + $decidir('CO.1761088155094242', '2026-08-11 16:00:00') === 'ya_se_pidio'); + +// --- 10. La comprobación vive en un solo sitio --- +chequear('esBsuid() global y el del servicio coinciden', + esBsuid('CO.1761088155094242') === WhatsAppService::esBsuid('CO.1761088155094242') + && esBsuid('573214533764') === WhatsAppService::esBsuid('573214533764')); + +echo $fallos === 0 ? "\nTodo correcto.\n" : "\n$fallos verificaciones fallaron.\n"; +exit($fallos === 0 ? 0 : 1);