diff --git a/api/lab/crear_desde_whatsapp.php b/api/lab/crear_desde_whatsapp.php index f0e5243..6b33cda 100644 --- a/api/lab/crear_desde_whatsapp.php +++ b/api/lab/crear_desde_whatsapp.php @@ -21,6 +21,53 @@ requireAuthentication(); $adminId = (int)($_SESSION['admin_user']['id'] ?? 0); $db = Database::getInstance(); +/** + * Le pide el teléfono a quien lo tiene oculto en WhatsApp, una sola vez. + * + * Solo aplica a quien se identifica con un BSUID: de esa persona no tenemos + * número, y sin él el laboratorio no puede llamarla. Se le manda el botón que + * Meta dispone para esto y ella decide si lo comparte; si acepta, el webhook + * recibe el teléfono y lo vincula solo. + * + * No se insiste: pedir los datos una vez es razonable, repetirlo en cada + * trámite es acoso. Tampoco se interrumpe la creación de la ficha si el envío + * falla, porque la ficha es lo importante. + * + * @return string qué pasó, para que la interfaz lo pueda mostrar + */ +function pedirContactoSiHaceFalta(Database $db, int $userId): string { + $u = $db->fetch( + 'SELECT phone_number, contacto_pedido_at FROM users WHERE id = ?', + [$userId] + ); + if (!$u) return 'usuario_no_encontrado'; + if (!esBsuid($u['phone_number'])) return 'no_hace_falta'; // ya tenemos su número + if (!empty($u['contacto_pedido_at'])) return 'ya_se_pidio'; + + try { + require_once __DIR__ . '/../../services/WhatsAppService.php'; + $wa = new WhatsAppService(); + $texto = getConfigFromDB( + 'whatsapp_texto_pedir_contacto', + 'Para poder registrar su atención necesitamos un número de contacto. ¿Nos comparte el suyo?' + ); + + $r = $wa->pedirContacto($u['phone_number'], $texto); + if (!$r) { + error_log('[crear_desde_whatsapp] WhatsApp rechazó la solicitud de contacto del usuario ' . $userId); + return 'fallo_envio'; + } + + // Se marca solo si Meta aceptó: si falló, hay que poder reintentarlo + $db->update('users', ['contacto_pedido_at' => date('Y-m-d H:i:s')], 'id = ?', [$userId]); + return 'pedido'; + + } catch (Exception $e) { + error_log('[crear_desde_whatsapp] Error pidiendo el contacto: ' . $e->getMessage()); + return 'fallo_envio'; + } +} + // ── GET: solo_paciente ───────────────────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['solo_paciente'])) { $convId = (int)($_GET['conversation_id'] ?? 0); @@ -57,7 +104,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['solo_paciente'])) { $pacienteRepo = new Paciente(); $pacienteId = $pacienteRepo->obtenerOCrearDesdeWhatsapp($conv['user_id']); $paciente = $pacienteRepo->obtener($pacienteId); - echo json_encode(['success' => true, 'paciente' => $paciente]); + + // Si la persona oculta su teléfono, la ficha queda sin número. Es el momento + // de pedírselo: se le manda el botón de WhatsApp una sola vez. + $contactoPedido = pedirContactoSiHaceFalta($db, (int) $conv['user_id']); + + echo json_encode([ + 'success' => true, + 'paciente' => $paciente, + 'contacto_pedido' => $contactoPedido, + ]); exit; } diff --git a/classes/lab/Paciente.php b/classes/lab/Paciente.php index 413bc59..8b03426 100644 --- a/classes/lab/Paciente.php +++ b/classes/lab/Paciente.php @@ -7,6 +7,15 @@ require_once __DIR__ . '/../../classes/Database.php'; require_once __DIR__ . '/ActividadAdmin.php'; +// esBsuid() vive en config.php; se garantiza aquí por si esta clase se incluye +// directamente, sin pasar por el arranque del ERP. +if (!function_exists('esBsuid')) { + $configPaciente = __DIR__ . '/../../config/config.php'; + if (file_exists($configPaciente)) { + require_once $configPaciente; + } +} + class Paciente { private Database $db; @@ -258,10 +267,19 @@ class Paciente { [$userId] ); + // Quien oculta su teléfono en WhatsApp se identifica con un BSUID, que ocupa + // el lugar del número en `users`. No es un teléfono: guardarlo aquí dejaría + // en la historia clínica un dato falso con apariencia de número real, porque + // normalizarTelefono() le quita el punto y las letras y lo deja en 16 dígitos. + // Mejor la ficha sin teléfono, que es la verdad: no lo tenemos. + $identificador = $user['phone_number'] ?? null; + $esIdentificadorSinTelefono = esBsuid($identificador); + return $this->crear([ 'user_id' => $userId, - 'nombre_completo'=> $user['name'] ?? ('Paciente ' . $user['phone_number']), - 'telefono' => $user['phone_number'] ?? null, + 'nombre_completo'=> $user['name'] + ?? ($esIdentificadorSinTelefono ? 'Paciente sin identificar' : 'Paciente ' . $identificador), + 'telefono' => $esIdentificadorSinTelefono ? null : $identificador, ]); } diff --git a/config/config.php b/config/config.php index bd7ff17..49b1d73 100644 --- a/config/config.php +++ b/config/config.php @@ -339,6 +339,24 @@ function deleteConfigFromDB($key) { } } +if (!function_exists('esBsuid')) { +/** + * ¿Este identificador es un BSUID de Meta y no un número de teléfono? + * + * Desde que WhatsApp permite ocultar el número, quien lo oculta llega + * identificado solo por su BSUID, con la forma "CO.1761088155094242". + * Ese valor ocupa el lugar del teléfono dentro del bot, así que hay que + * distinguirlo antes de tratarlo como si fuera un número real: guardarlo + * en un campo de teléfono deja un dato falso con toda la pinta de verdadero. + * + * @param mixed $valor + * @return bool + */ +function esBsuid($valor) { + return (bool) preg_match('/^[A-Z]{2}\.\d+$/', (string) $valor); +} +} + /** * Limpia el cache estático de configuraciones */ diff --git a/lab_pacientes.php b/lab_pacientes.php index 72549b1..0bda550 100644 --- a/lab_pacientes.php +++ b/lab_pacientes.php @@ -472,6 +472,28 @@ function fmtFecha(str) { return str.slice(0, 10).split('-').reverse().join('/'); } +// Quien oculta su número en WhatsApp llega identificado con un BSUID +// ("CO.1761088155094242"), que se guarda en phone_number pero no es un teléfono. +const esBsuid = v => /^[A-Z]{2}\.\d+$/.test(String(v || '')); + +/** + * Celda de contacto. Si de esta persona no tenemos teléfono porque lo tiene + * oculto, se dice así en vez de mostrar el identificador crudo: recepción + * necesita entender por qué no puede llamarla, no ver un código. + */ +function celdaTelefono(p) { + if (esBsuid(p.phone_number)) { + return (p.telefono ? esc(p.telefono) : 'Sin teléfono') + + '
' + + 'Solo por WhatsApp'; + } + return esc(p.telefono || '—') + + (p.phone_number + ? `
${esc(p.phone_number)}` + : ''); +} + async function cargarLista(pag = 1) { paginaActual = pag; const busq = document.getElementById('buscador').value.trim(); @@ -495,7 +517,7 @@ async function cargarLista(pag = 1) { ${p.genero ? `${p.genero==='M'?'Masculino':p.genero==='F'?'Femenino':'Otro'}` : ''} ${tipoDocLabel(p.tipo_documento)}
${esc(p.numero_documento||'—')} - ${esc(p.telefono||'—')}${p.phone_number ? `
${esc(p.phone_number)}` : ''} + ${celdaTelefono(p)} ${esc(p.email||'—')} ${esc(p.ciudad||'—')} ${esc(p.eps||'—')} @@ -546,8 +568,18 @@ async function verDetalle(id) { document.getElementById('detail-body').innerHTML = `
Documento
${tipoDocLabel(p.tipo_documento)} ${esc(p.numero_documento||'—')}
-
Teléfono
${esc(p.telefono||'—')}
-
WhatsApp
${p.phone_number ? ` ${esc(p.phone_number)}` : '—'}
+
Teléfono
${ + p.telefono ? esc(p.telefono) + : (esBsuid(p.phone_number) + ? 'Sin teléfono — lo tiene oculto en WhatsApp' + : '—') + }
+
WhatsApp
${ + !p.phone_number ? '—' + : (esBsuid(p.phone_number) + ? ' Se le puede escribir por el chat, pero no llamar' + : ` ${esc(p.phone_number)}`) + }
Origen
${origenBadge(p.origen)}
Registro
${fmtFecha(p.created_at)}
${p.email ? `
Email
${esc(p.email)}
` : ''} diff --git a/migrations/20260811_bsuid_pedir_contacto.sql b/migrations/20260811_bsuid_pedir_contacto.sql new file mode 100644 index 0000000..a5e71ba --- /dev/null +++ b/migrations/20260811_bsuid_pedir_contacto.sql @@ -0,0 +1,24 @@ +-- 20260811_bsuid_pedir_contacto.sql +-- +-- Tercera parte del cambio de identidad de WhatsApp. +-- +-- A quien oculta su teléfono se le puede pedir con el botón request_contact_info. +-- Se registra cuándo se le pidió para no volver a insistirle: pedirle los datos +-- una vez es razonable, repetírselo en cada trámite es acoso. +-- +-- Queda NULL para todo el mundo; solo se llena cuando efectivamente se pide. + +ALTER TABLE users + ADD COLUMN contacto_pedido_at DATETIME NULL DEFAULT NULL + COMMENT 'Cuándo se le pidió el teléfono por el botón de WhatsApp. NULL = nunca.' + AFTER bsuid; + +-- Texto editable desde configuración, para que el laboratorio ajuste el mensaje +-- sin tocar código. Si la fila ya existe, se respeta lo que haya. +INSERT INTO system_config (config_key, config_value, description) +VALUES ( + 'whatsapp_texto_pedir_contacto', + 'Para poder registrar su atención necesitamos un número de contacto. ¿Nos comparte el suyo?', + 'Mensaje del botón que pide el teléfono a quien lo tiene oculto en WhatsApp' +) +ON DUPLICATE KEY UPDATE config_key = config_key; diff --git a/services/WhatsAppService.php b/services/WhatsAppService.php index dafd150..538831a 100644 --- a/services/WhatsAppService.php +++ b/services/WhatsAppService.php @@ -917,11 +917,12 @@ class WhatsAppService /** * ¿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". + * La definición vive en config.php, que es donde la ven también las clases del + * laboratorio; aquí solo se expone para quien ya tiene el servicio a la mano. */ public static function esBsuid($destinatario) { - return (bool) preg_match('/^[A-Z]{2}\.\d+$/', (string) $destinatario); + return esBsuid($destinatario); } /**