true] + $data); exit; } function jpubErr(string $msg, int $code = 400): void { ob_clean(); http_response_code($code); echo json_encode(['ok' => false, 'error' => $msg]); exit; } if ($_SERVER['REQUEST_METHOD'] !== 'POST') jpubErr('Método no permitido', 405); $raw = file_get_contents('php://input'); $datos = json_decode($raw, true); $cedula = trim($datos['cedula'] ?? ''); if (!$cedula || !preg_match('/^\d{4,12}$/', $cedula)) jpubErr('Número de documento inválido.'); try { $db = Database::getInstance(); $pac = $db->fetch( "SELECT id, nombre_completo, telefono FROM lab_pacientes WHERE numero_documento = ? AND is_active = 1 LIMIT 1", [$cedula] ); } catch (\Throwable $e) { jpubErr('Error de base de datos.', 500); } if (!$pac) jpubErr('Paciente no encontrado con ese documento.'); if (!$pac['telefono']) jpubErr('El paciente no tiene teléfono registrado. Acércate a recepción.'); // Leer config SMS try { $cfgs = $db->fetchAll("SELECT clave, valor FROM lab_config WHERE clave IN ('sms_url','sms_api_key','sms_activo')"); $cfg = array_column($cfgs, 'valor', 'clave'); } catch (\Throwable $e) { jpubErr('Error de configuración.', 500); } if (($cfg['sms_activo'] ?? '0') !== '1') jpubErr('El servicio de SMS no está activo.'); $smsUrl = rtrim($cfg['sms_url'] ?? '', '/'); $smsKey = $cfg['sms_api_key'] ?? ''; if (!$smsUrl || !$smsKey) jpubErr('SMS no configurado.'); // Invalidar OTPs anteriores del mismo paciente try { $db->query("UPDATE lab_otp_tokens SET usado = 1 WHERE paciente_id = ? AND usado = 0", [$pac['id']]); } catch (\Throwable $_) {} // Generar OTP $codigo = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT); $expires = date('Y-m-d H:i:s', time() + 300); // 5 min try { $db->query( "INSERT INTO lab_otp_tokens (paciente_id, codigo, expires_at) VALUES (?, ?, ?)", [$pac['id'], $codigo, $expires] ); } catch (\Throwable $e) { jpubErr('No se pudo generar el código.', 500); } // Enviar SMS $numero = preg_replace('/\D/', '', $pac['telefono']); $mensaje = "{$codigo} es tu codigo de verificacion, Laboratorio Ximena Caicedo"; $payload = json_encode(['numero' => $numero, 'mensaje' => $mensaje], JSON_UNESCAPED_UNICODE); $ch = curl_init($smsUrl . '/api/sms/send'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $smsKey], CURLOPT_TIMEOUT => 12, ]); $resp = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlErr = curl_error($ch); curl_close($ch); if ($curlErr || $httpCode >= 400) { $smsResp = json_decode($resp, true); jpubErr('No se pudo enviar el SMS: ' . ($smsResp['error'] ?? "HTTP {$httpCode}"), 502); } // Enmascarar teléfono para mostrar en UI: 3001****67 $tel = $pac['telefono']; $digits = preg_replace('/\D/', '', $tel); $masked = substr($digits, 0, 3) . str_repeat('*', max(0, strlen($digits) - 5)) . substr($digits, -2); jpubOk([ 'nombre' => explode(' ', $pac['nombre_completo'])[0], 'telefono' => $masked, 'expira' => 300, ]);