channel('integracao-safecare'); } public function integracaoPaciente(Agendamento $agendamento): void { $cliente = Cliente::where('link', config('database.default'))->firstOrFail(); $token = $this->autenticarSafecare($cliente); $payload = $this->pacientePayload($agendamento, $cliente); $this->log()->debug('SafeCare payload (final)', $payload); $response = $this->postPaciente($payload, $agendamento, $token); if ($response->failed()) { $this->log()->error('Safecare: falha ao registrar paciente', [ 'agendamento_id' => $agendamento->id, 'customer' => $cliente->link, 'status' => $response->status(), 'response_body' => $response->json() ?? $response->body(), 'payload' => $payload, ]); throw new RuntimeException(sprintf( 'Erro API Safecare (status %s): %s', $response->status(), json_encode($response->json() ?? $response->body(), JSON_UNESCAPED_UNICODE) )); } $this->log()->info('Safecare: paciente registrado com sucesso', [ 'agendamento_id' => $agendamento->id, 'customer' => $cliente->link, 'result' => $response->json('result'), 'patient_id' => $response->json('patientId'), ]); } protected function autenticarSafecare(Cliente $cliente): string { if (!$cliente->safecare_usuario || !$cliente->safecare_senha) { $this->log()->error('Safecare: credenciais não configuradas', [ 'customer' => $cliente->link, ]); throw new RuntimeException( 'Credenciais Safecare não configuradas para o cliente: ' . $cliente->link ); } $cacheKey = 'safecare_token_' . $cliente->getKey(); $tokenCached = Cache::get($cacheKey); if ($tokenCached && $this->tokenEstaAtivo($tokenCached)) { return $tokenCached; } $this->log()->info('Safecare: solicitando novo token', ['customer' => $cliente->link]); $token = $this->solicitarTokenSafecare($cliente); Cache::put($cacheKey, $token, now()->addHours(23)); return $token; } private function solicitarTokenSafecare(Cliente $cliente): string { $response = Http::asJson() ->timeout(15) ->post( rtrim(config('services.safecare.base_url'), '/') . '/api/partner/auth/login', [ 'identity' => $cliente->safecare_usuario, 'password' => $cliente->safecare_senha, ] ); if ($response->failed()) { $this->log()->error('Safecare: falha na autenticação', [ 'customer' => $cliente->link, 'status' => $response->status(), 'response_body' => $response->json() ?? $response->body(), ]); throw new RuntimeException(sprintf( 'Erro autenticação Safecare (status %s): %s', $response->status(), json_encode($response->json() ?? $response->body(), JSON_UNESCAPED_UNICODE) )); } $token = $response->json('token'); if (!$token) { $this->log()->error('Safecare: token ausente na resposta de autenticação', [ 'customer' => $cliente->link, 'response_body' => $response->json(), ]); throw new RuntimeException('Token ausente na resposta de autenticação Safecare'); } return $token; } private function tokenEstaAtivo(string $token): bool { $partes = explode('.', $token); if (count($partes) !== 3) { return false; } $payload = json_decode(base64_decode(strtr($partes[1], '-_', '+/')), true); if (!isset($payload['exp'])) { return false; } return now()->timestamp < ($payload['exp'] - 300); } protected function postPaciente(array $payload, Agendamento $agendamento, string $token): Response { $chaveIdempotencia = 'agendamento:' . $agendamento->id; return Http::asJson() ->withHeaders([ 'Authorization' => 'Bearer ' . $token, 'X-Idempotency-Key' => $chaveIdempotencia, ]) ->timeout(15) ->retry(3, 200, fn($_, $response) => !$response || $response->status() !== HttpResponse::HTTP_UNAUTHORIZED) ->post( rtrim(config('services.safecare.base_url'), '/') . '/api/partner/patients', $payload ); } protected function pacientePayload(Agendamento $agendamento, Cliente $cliente): array { $dadosPaciente = DB::table('ambulatorio_agendamento') ->join('cadastro_paciente', 'cadastro_paciente.id', 'ambulatorio_agendamento.paciente_id') ->join('cadastro_cidadao', 'cadastro_cidadao.id', 'cadastro_paciente.cidadao_id') ->leftJoin('cadastro_cidadao_telefone', function ($join) { $join->on('cadastro_cidadao_telefone.cidadao_id', '=', 'cadastro_cidadao.id') ->where('cadastro_cidadao_telefone.principal', 1); }) ->leftJoin('cadastro_sexo', 'cadastro_sexo.id', 'cadastro_cidadao.sexo_id') ->leftJoin('cadastro_nacionalidade', 'cadastro_nacionalidade.id', 'cadastro_cidadao.nacionalidade_id') ->leftJoin('cadastro_raca_cor', 'cadastro_raca_cor.id', 'cadastro_cidadao.raca_cor_id') ->leftJoin('cadastro_domicilio', 'cadastro_domicilio.id', 'cadastro_cidadao.domicilio_id') ->leftJoin('cadastro_endereco', 'cadastro_endereco.id', 'cadastro_domicilio.endereco_id') ->leftJoin('cadastro_logradouro', 'cadastro_logradouro.id', 'cadastro_endereco.logradouro_id') ->leftJoin('cadastro_logradouro_tipo', 'cadastro_logradouro_tipo.id', 'cadastro_logradouro.tipo_id') ->leftJoin('cadastro_bairro', 'cadastro_bairro.id', 'cadastro_logradouro.bairro_id') ->where('ambulatorio_agendamento.id', $agendamento->id) ->select( [ 'cadastro_cidadao.nome AS name', 'cadastro_cidadao.cns', 'cadastro_cidadao.cpf', 'cadastro_cidadao.data_nascimento AS birthdate', DB::raw("COALESCE(cadastro_cidadao_telefone.numero, '') AS phone"), 'cadastro_sexo.descricao AS sex', DB::raw("COALESCE(cadastro_cidadao.nome_mae, '') AS motherName"), DB::raw("COALESCE(cadastro_nacionalidade.descricao, '') AS nationality"), DB::raw("COALESCE(cadastro_raca_cor.descricao, '') AS race"), 'cadastro_logradouro_tipo.descricao as type', 'cadastro_logradouro.nome as street', 'cadastro_endereco.numero as number', 'cadastro_bairro.nome as neighborhood', 'cadastro_logradouro.cep as zipCode' ] ) ->distinct() ->orderBy('cadastro_cidadao.nome') ->first(); if (!$dadosPaciente) { throw new RuntimeException( 'Dados do paciente não encontrados para o agendamento: ' . $agendamento->id ); } $race = $this->mapearRaca($dadosPaciente->race ?? null); $sex = $this->mapearSexo($dadosPaciente->sex ?? null); return [ 'address' => [ 'city' => '', 'complement' => '', 'neighborhood' => $dadosPaciente->neighborhood ?? '', 'number' => isset($dadosPaciente->number) ? (string) $dadosPaciente->number : '', 'reference' => '', 'state' => '', 'street' => $dadosPaciente->street ?? '', 'zipCode' => $dadosPaciente->zipCode ?? '', ], 'birthdate' => $dadosPaciente->birthdate ? Carbon::parse($dadosPaciente->birthdate)->format('Y-m-d') : '', 'cns' => $dadosPaciente->cns ?? '', 'cpf' => $dadosPaciente->cpf ?? '', 'ethnicity' => '', 'guardianName' => '', 'isHomeCare' => false, 'metaData' => $this->metaDadosSafeCare($agendamento, $cliente), 'motherName' => $dadosPaciente->motherName ?? '', 'name' => $dadosPaciente->name ?? '', 'phone' => $dadosPaciente->phone ?? '', 'race' => $race, 'sex' => $sex, ]; } protected function metaDadosSafeCare(Agendamento $agendamento, Cliente $cliente): array { return [ 'agendamento_id' => (string) $agendamento->id, 'jornada_id' => (string) $agendamento->jornada_id, 'paciente_id' => (string) $agendamento->paciente_id, 'customer' => $cliente->link ?? '', ]; } private function normalizarEnum(?string $valor): string { if ($valor === null) { return ''; } return strtoupper(trim(Str::ascii($valor))); } private function mapearRaca(?string $raca): string { $normalizada = $this->normalizarEnum($raca); $mapa = [ 'BRANCA' => 'white', 'PRETA' => 'black', 'PARDA' => 'brown', 'AMARELA' => 'yellow', 'INDIGENA' => 'indigenous', ]; return $mapa[$normalizada] ?? ''; } private function mapearSexo(?string $sexo): string { $normalizada = $this->normalizarEnum($sexo); $mapa = [ 'MASCULINO' => 'male', 'FEMININO' => 'female', ]; return $mapa[$normalizada] ?? ''; } }