Meta no permite averiguar el teléfono a partir del BSUID: no hay endpoint de consulta inversa, cada empresa debe llevar su propia equivalencia. Pero el BSUID llega en TODOS los webhooks de mensaje, también en los que aún traen teléfono, así que la equivalencia se puede ir guardando sola mientras la persona todavía muestra su número. - users.bsuid guarda esa equivalencia, y el webhook la anota en cada mensaje. Cuando alguien oculte su número, se le seguirá reconociendo y respondiendo a su teléfono de siempre. - Va en columna aparte y no en phone_number porque el teléfono además cruza con el paciente y el turnero; mezclarlos rompería esos cruces. - Para quien nunca escribió mostrando el número queda pedirle el contacto: pedirContacto() manda el botón request_contact_info, y el webhook atiende el mensaje `contacts` que llega si acepta. - Al vincular puede aparecer un segundo registro de la misma persona. No se fusionan: una fusión mal hecha mezcla dos historias clínicas. Gana el que tiene el teléfono, el otro queda anotado en el log para revisarlo a mano. - scripts/backfill_bsuid.php carga las equivalencias del histórico (6.423), con --simular para verlas sin escribir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
132 lines
5.0 KiB
PHP
132 lines
5.0 KiB
PHP
<?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";
|