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'}` : ''}