add-report-notification
This commit is contained in:
parent
fb7aec765b
commit
dd0a5abb04
@ -13,6 +13,7 @@ import { buscarPacientes, listarPacientes, buscarPacientePorId, buscarPacientesP
|
||||
import { ENV_CONFIG } from '@/lib/env-config';
|
||||
import { useReports } from "@/hooks/useReports";
|
||||
import { CreateReportData } from "@/types/report-types";
|
||||
import { createAndNotifyReport } from "@/lib/reportService";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@ -2588,6 +2589,9 @@ const ProfissionalPage = () => {
|
||||
if (isNewLaudo) {
|
||||
if (createNewReport) {
|
||||
const created = await createNewReport(payload as any);
|
||||
console.log('[LaudoEditor] Report criado:', { created, patient_id: payload.patient_id });
|
||||
// ✅ Webhook agora é enviado automaticamente dentro de createNewReport() / criarRelatorio()
|
||||
|
||||
if (onSaved) onSaved(created);
|
||||
}
|
||||
} else {
|
||||
|
||||
275
susconecta/lib/laudo-exemplos.ts
Normal file
275
susconecta/lib/laudo-exemplos.ts
Normal file
@ -0,0 +1,275 @@
|
||||
/**
|
||||
* EXEMPLO DE USO: Automação n8n para Notificação de Laudos
|
||||
*
|
||||
* Este arquivo demonstra como usar a função criarLaudo com integração n8n
|
||||
* para criar um laudo e notificar automaticamente o paciente.
|
||||
*/
|
||||
|
||||
import { criarLaudo, CriarLaudoData } from '@/lib/reports';
|
||||
|
||||
/**
|
||||
* Exemplo 1: Uso básico - criar um laudo simples
|
||||
*/
|
||||
export async function exemploBasico() {
|
||||
try {
|
||||
const laudoData: CriarLaudoData = {
|
||||
pacienteId: 'patient-uuid-123', // ID do paciente (obrigatório)
|
||||
textoLaudo: 'Paciente apresenta boa saúde geral. Sem achados relevantes.',
|
||||
};
|
||||
|
||||
const novoLaudo = await criarLaudo(laudoData);
|
||||
|
||||
console.log('✓ Laudo criado com sucesso!');
|
||||
console.log('ID do laudo:', novoLaudo.id);
|
||||
console.log('Mensagem:', novoLaudo.mensagem);
|
||||
|
||||
return novoLaudo;
|
||||
} catch (erro) {
|
||||
console.error('✗ Erro ao criar laudo:', erro);
|
||||
throw erro;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exemplo 2: Criar laudo com dados médicos completos
|
||||
*/
|
||||
export async function exemploCompleto() {
|
||||
try {
|
||||
const laudoData: CriarLaudoData = {
|
||||
pacienteId: 'patient-uuid-789',
|
||||
medicoId: 'doctor-uuid-456', // Opcional
|
||||
textoLaudo: `
|
||||
AVALIAÇÃO CLÍNICA COMPLETA
|
||||
|
||||
Queixa Principal: Dor de cabeça persistente
|
||||
|
||||
História Presente:
|
||||
Paciente relata dor de cabeça tipo tensional há 2 semanas,
|
||||
intensidade 5/10, sem irradiação.
|
||||
|
||||
Exame Físico:
|
||||
- PA: 120/80 mmHg
|
||||
- FC: 72 bpm
|
||||
- Sem alterações neurológicas
|
||||
|
||||
Impressão Diagnóstica:
|
||||
Cefaleia tensional
|
||||
|
||||
Conduta:
|
||||
- Repouso adequado
|
||||
- Analgésicos conforme necessidade
|
||||
- Retorno em 2 semanas se persistir
|
||||
`,
|
||||
exame: 'Consulta Neurologia',
|
||||
diagnostico: 'Cefaleia tensional',
|
||||
conclusao: 'Prescrição: Dipirona 500mg 6/6h conforme necessidade',
|
||||
cidCode: 'G44.2', // CID da cefaleia tensional
|
||||
status: 'concluido',
|
||||
};
|
||||
|
||||
const novoLaudo = await criarLaudo(laudoData);
|
||||
|
||||
console.log('✓ Laudo completo criado com sucesso!');
|
||||
console.log('ID:', novoLaudo.id);
|
||||
console.log('Status:', novoLaudo.status);
|
||||
console.log('CID:', novoLaudo.cid_code);
|
||||
|
||||
return novoLaudo;
|
||||
} catch (erro) {
|
||||
console.error('✗ Erro:', erro);
|
||||
throw erro;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exemplo 3: Integração em um componente React
|
||||
* Este exemplo mostra como usar a função em um formulário
|
||||
*
|
||||
* NOTA: Este código deve ser usado em um arquivo .tsx (não .ts)
|
||||
* e com o import de React importado corretamente
|
||||
*/
|
||||
export async function exemploComponenteReact() {
|
||||
// Este é apenas um exemplo de estrutura para o componente
|
||||
// Copie o código abaixo para um arquivo .tsx:
|
||||
/*
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { criarLaudo, CriarLaudoData } from '@/lib/reports';
|
||||
|
||||
export function ComponenteLaudoExemplo() {
|
||||
const [carregando, setCarregando] = React.useState(false);
|
||||
const [mensagem, setMensagem] = React.useState('');
|
||||
|
||||
const handleCriarLaudo = async (formData: any) => {
|
||||
setCarregando(true);
|
||||
setMensagem('');
|
||||
|
||||
try {
|
||||
const laudoData: CriarLaudoData = {
|
||||
pacienteId: formData.pacienteId,
|
||||
medicoId: formData.medicoId,
|
||||
textoLaudo: formData.texto,
|
||||
exame: formData.exame,
|
||||
diagnostico: formData.diagnostico,
|
||||
conclusao: formData.conclusao,
|
||||
cidCode: formData.cid,
|
||||
status: 'concluido',
|
||||
};
|
||||
|
||||
const resultado = await criarLaudo(laudoData);
|
||||
|
||||
setMensagem(`✓ ${resultado.mensagem}`);
|
||||
console.log('Laudo criado:', resultado.id);
|
||||
|
||||
// Você pode fazer mais algo aqui, como:
|
||||
// - Redirecionar para página do laudo
|
||||
// - Atualizar lista de laudos
|
||||
// - Limpar formulário
|
||||
|
||||
} catch (erro) {
|
||||
setMensagem(`✗ Erro: ${erro instanceof Error ? erro.message : String(erro)}`);
|
||||
} finally {
|
||||
setCarregando(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
handleCriarLaudo(Object.fromEntries(formData));
|
||||
}}>
|
||||
<textarea
|
||||
name="texto"
|
||||
placeholder="Texto do laudo"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="pacienteId"
|
||||
placeholder="ID do Paciente"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="medicoId"
|
||||
placeholder="ID do Médico"
|
||||
required
|
||||
/>
|
||||
<button type="submit" disabled={carregando}>
|
||||
{carregando ? 'Criando...' : 'Criar Laudo'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{mensagem && <p>{mensagem}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Exemplo 4: Tratamento de erros específicos
|
||||
*/
|
||||
export async function exemploTratamentoErros() {
|
||||
try {
|
||||
const laudoData: CriarLaudoData = {
|
||||
pacienteId: 'patient-id',
|
||||
medicoId: 'doctor-id',
|
||||
textoLaudo: 'Texto do laudo',
|
||||
};
|
||||
|
||||
const resultado = await criarLaudo(laudoData);
|
||||
console.log('Sucesso:', resultado);
|
||||
|
||||
} catch (erro) {
|
||||
if (erro instanceof Error) {
|
||||
// Trata diferentes tipos de erro
|
||||
if (erro.message.includes('Paciente ID') || erro.message.includes('Médico ID')) {
|
||||
console.error('Erro de validação: dados incompletos');
|
||||
} else if (erro.message.includes('Supabase')) {
|
||||
console.error('Erro de conexão com banco de dados');
|
||||
} else if (erro.message.includes('n8n')) {
|
||||
console.warn('Laudo criado, mas notificação falhou');
|
||||
} else {
|
||||
console.error('Erro desconhecido:', erro.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DOCUMENTAÇÃO DO FLUXO N8N
|
||||
*
|
||||
* A função criarLaudo executa o seguinte fluxo:
|
||||
*
|
||||
* 1. CRIAÇÃO NO SUPABASE
|
||||
* - Salva o report na tabela 'reports' do Supabase
|
||||
* - Status padrão: 'concluido'
|
||||
* - Retorna o report criado com seu ID
|
||||
*
|
||||
* 2. NOTIFICAÇÃO N8N
|
||||
* - Se o report foi criado com sucesso, faz um POST para:
|
||||
* URL: https://joaogustavo.me/webhook/notificar-laudo
|
||||
* - Envia payload com:
|
||||
* - pacienteId: ID do paciente (patient_id)
|
||||
* - reportId: ID do report criado
|
||||
*
|
||||
* 3. NO N8N
|
||||
* O webhook deve estar configurado para:
|
||||
* - Receber o payload JSON POST
|
||||
* - Extrair pacienteId e reportId
|
||||
* - Buscar informações do paciente
|
||||
* - Enviar notificação (email, SMS, push, etc.)
|
||||
* - Registrar log da notificação
|
||||
*
|
||||
* 4. COMPORTAMENTO EM CASO DE FALHA
|
||||
* - Se a criação do report falhar: exceção é lançada
|
||||
* - Se o envio para n8n falhar: report é mantido, erro é logado
|
||||
* (não bloqueia a operação de criação)
|
||||
*
|
||||
* EXEMPLO DE USO:
|
||||
*
|
||||
* const novoReport = await criarLaudo({
|
||||
* pacienteId: "3854866a-5476-48be-8313-77029ccdb70f",
|
||||
* textoLaudo: "Texto do laudo aqui..."
|
||||
* });
|
||||
*
|
||||
* // Depois disto, automaticamente:
|
||||
* // 1. Report é salvo no Supabase
|
||||
* // 2. n8n recebe: { pacienteId: "...", reportId: "..." }
|
||||
* // 3. Paciente é notificado
|
||||
*/
|
||||
|
||||
/**
|
||||
* EXEMPLO DE WEBHOOK N8N (Configuração)
|
||||
*
|
||||
* No n8n, você deve:
|
||||
* 1. Criar um novo workflow
|
||||
* 2. Adicionar trigger: "Webhook"
|
||||
* 3. Configurar:
|
||||
* - HTTP Method: POST
|
||||
* - Path: /notificar-laudo
|
||||
* - Authentication: None (ou Bearer token se desejar)
|
||||
* 4. Adicionar nós para:
|
||||
* - Parse do payload JSON recebido
|
||||
* - Query no banco de dados para buscar paciente
|
||||
* - Enviar email/SMS/notificação push
|
||||
* - Logging do resultado
|
||||
*
|
||||
* Exemplo de nó JavaScript no n8n:
|
||||
*
|
||||
* const { pacienteId, laudoId, pacienteName, pacienteEmail } = $input.first().json;
|
||||
*
|
||||
* return {
|
||||
* pacienteId,
|
||||
* laudoId,
|
||||
* pacienteName,
|
||||
* pacienteEmail,
|
||||
* notificationType: 'laudo_criado',
|
||||
* timestamp: new Date().toISOString(),
|
||||
* message: `Novo laudo ${laudoId} disponível para ${pacienteName}`
|
||||
* };
|
||||
*/
|
||||
193
susconecta/lib/laudo-notification.ts
Normal file
193
susconecta/lib/laudo-notification.ts
Normal file
@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Módulo de notificação de laudos via n8n
|
||||
* Integração com automação n8n para notificar pacientes quando laudos são criados
|
||||
*/
|
||||
|
||||
import { ENV_CONFIG } from '@/lib/env-config';
|
||||
|
||||
/**
|
||||
* Configurações do webhook n8n
|
||||
*/
|
||||
const N8N_WEBHOOK_CONFIG = {
|
||||
// URL do webhook configurado no n8n
|
||||
webhookUrl: 'https://joaogustavo.me/webhook/notificar-laudo',
|
||||
// Timeout para a requisição (em ms)
|
||||
timeout: 30000,
|
||||
// Tentativas de retry em caso de falha
|
||||
maxRetries: 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Tipos de dados para notificação de laudo
|
||||
*/
|
||||
export interface NotificacaoLaudoPayload {
|
||||
pacienteId: string;
|
||||
laudoId: string;
|
||||
pacienteName?: string;
|
||||
pacienteEmail?: string;
|
||||
medicalDetails?: {
|
||||
examType?: string;
|
||||
medico?: string;
|
||||
dataEmissao?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resultado da notificação
|
||||
*/
|
||||
export interface NotificacaoLaudoResult {
|
||||
sucesso: boolean;
|
||||
mensagem: string;
|
||||
n8nResponse?: any;
|
||||
erro?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifica o n8n sobre a criação de um novo laudo
|
||||
* @param payload Dados do laudo e paciente para notificação
|
||||
* @returns Resultado da notificação
|
||||
*/
|
||||
export async function notificarLaudoCriadoN8n(
|
||||
payload: NotificacaoLaudoPayload
|
||||
): Promise<NotificacaoLaudoResult> {
|
||||
try {
|
||||
// Validação básica dos dados
|
||||
if (!payload.pacienteId || !payload.laudoId) {
|
||||
return {
|
||||
sucesso: false,
|
||||
mensagem: 'Dados de paciente ou laudo inválidos',
|
||||
erro: 'pacienteId e laudoId são obrigatórios',
|
||||
};
|
||||
}
|
||||
|
||||
// Constrói o payload para o webhook
|
||||
const webhookPayload = {
|
||||
pacienteId: payload.pacienteId,
|
||||
laudoId: payload.laudoId,
|
||||
pacienteName: payload.pacienteName || '',
|
||||
pacienteEmail: payload.pacienteEmail || '',
|
||||
// Adiciona dados médicos se disponíveis
|
||||
...(payload.medicalDetails && {
|
||||
examType: payload.medicalDetails.examType,
|
||||
medico: payload.medicalDetails.medico,
|
||||
dataEmissao: payload.medicalDetails.dataEmissao,
|
||||
}),
|
||||
// Timestamp da notificação
|
||||
notificadoEm: new Date().toISOString(),
|
||||
};
|
||||
|
||||
console.log('[n8n] Enviando notificação de laudo criado:', {
|
||||
pacienteId: payload.pacienteId,
|
||||
laudoId: payload.laudoId,
|
||||
webhookUrl: N8N_WEBHOOK_CONFIG.webhookUrl,
|
||||
});
|
||||
|
||||
// Tenta enviar o webhook com retry
|
||||
let ultimoErro: any = null;
|
||||
|
||||
for (let tentativa = 1; tentativa <= N8N_WEBHOOK_CONFIG.maxRetries; tentativa++) {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(
|
||||
() => controller.abort(),
|
||||
N8N_WEBHOOK_CONFIG.timeout
|
||||
);
|
||||
|
||||
const response = await fetch(N8N_WEBHOOK_CONFIG.webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(webhookPayload),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`HTTP ${response.status}: ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const responseData = await response.json();
|
||||
|
||||
console.log('[n8n] Notificação enviada com sucesso:', {
|
||||
status: response.status,
|
||||
laudoId: payload.laudoId,
|
||||
});
|
||||
|
||||
return {
|
||||
sucesso: true,
|
||||
mensagem: 'Paciente notificado com sucesso',
|
||||
n8nResponse: responseData,
|
||||
};
|
||||
} catch (erro) {
|
||||
ultimoErro = erro;
|
||||
console.warn(
|
||||
`[n8n] Tentativa ${tentativa}/${N8N_WEBHOOK_CONFIG.maxRetries} falhou:`,
|
||||
erro instanceof Error ? erro.message : String(erro)
|
||||
);
|
||||
|
||||
// Se não for a última tentativa, aguarda um pouco antes de tentar novamente
|
||||
if (tentativa < N8N_WEBHOOK_CONFIG.maxRetries) {
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, 1000 * tentativa) // Backoff exponencial
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Se chegou aqui, todas as tentativas falharam
|
||||
console.error('[n8n] Todas as tentativas de notificação falharam:', ultimoErro);
|
||||
|
||||
return {
|
||||
sucesso: false,
|
||||
mensagem: 'Falha ao notificar paciente através do n8n',
|
||||
erro: ultimoErro instanceof Error ? ultimoErro.message : String(ultimoErro),
|
||||
};
|
||||
} catch (erro) {
|
||||
console.error('[notificarLaudoCriadoN8n] Erro inesperado:', erro);
|
||||
|
||||
return {
|
||||
sucesso: false,
|
||||
mensagem: 'Erro ao processar notificação de laudo',
|
||||
erro: erro instanceof Error ? erro.message : String(erro),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Versão assíncrona que não bloqueia - envia notificação em background
|
||||
* Útil para não aumentar o tempo de resposta da API
|
||||
* @param payload Dados do laudo e paciente
|
||||
*/
|
||||
export function notificarLaudoAsyncBackground(
|
||||
payload: NotificacaoLaudoPayload
|
||||
): void {
|
||||
// Envia notificação em background sem aguardar
|
||||
notificarLaudoCriadoN8n(payload)
|
||||
.then((result) => {
|
||||
if (!result.sucesso) {
|
||||
console.warn('[n8n] Notificação de laudo falhou (background):', result.erro);
|
||||
}
|
||||
})
|
||||
.catch((erro) => {
|
||||
console.error('[n8n] Erro ao notificar laudo em background:', erro);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determina se as notificações n8n estão habilitadas
|
||||
* Pode ser controlado via variável de ambiente
|
||||
*/
|
||||
export function notificacoesHabilitadas(): boolean {
|
||||
if (typeof window === 'undefined') {
|
||||
// Server-side: verificar variável de ambiente
|
||||
return process.env.NEXT_PUBLIC_N8N_ENABLED !== 'false';
|
||||
}
|
||||
|
||||
// Client-side: sempre habilitado
|
||||
return true;
|
||||
}
|
||||
148
susconecta/lib/reportService.ts
Normal file
148
susconecta/lib/reportService.ts
Normal file
@ -0,0 +1,148 @@
|
||||
/**
|
||||
* serviço para criar relatórios e notificar pacientes via n8n
|
||||
*
|
||||
* Este serviço encapsula a lógica de:
|
||||
* 1. Criar um novo report no Supabase
|
||||
* 2. Notificar o paciente via webhook n8n (que dispara SMS via Twilio)
|
||||
*/
|
||||
|
||||
interface CreateReportData {
|
||||
patientId: string; // UUID do paciente
|
||||
requestedBy: string; // UUID de quem solicitou (médico)
|
||||
exam: string;
|
||||
diagnosis: string;
|
||||
conclusion: string;
|
||||
contentHtml: string;
|
||||
}
|
||||
|
||||
interface CreateReportResult {
|
||||
success: boolean;
|
||||
report?: any;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cria um novo report no Supabase e notifica o paciente via n8n
|
||||
*
|
||||
* Fluxo:
|
||||
* 1. Insere um novo registro na tabela 'reports' com status 'draft'
|
||||
* 2. Envia webhook para n8n com pacienteId e reportId
|
||||
* 3. n8n recebe e dispara notificação SMS via Twilio
|
||||
* 4. Retorna o report criado (mesmo que a notificação falhe)
|
||||
*
|
||||
* @param data Dados do report a ser criado
|
||||
* @returns { success: true, report } ou { success: false, error }
|
||||
*/
|
||||
export const createAndNotifyReport = async (data: CreateReportData): Promise<CreateReportResult> => {
|
||||
try {
|
||||
// Validação básica
|
||||
if (!data.patientId || !data.exam || !data.conclusion) {
|
||||
throw new Error('Faltam campos obrigatórios: patientId, exam, conclusion');
|
||||
}
|
||||
|
||||
console.log('[reportService] Criando novo report para paciente:', data.patientId);
|
||||
|
||||
// 1. Criar report no Supabase
|
||||
const BASE_API = 'https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports';
|
||||
|
||||
let token: string | undefined = undefined;
|
||||
if (typeof window !== 'undefined') {
|
||||
token =
|
||||
localStorage.getItem('auth_token') ||
|
||||
localStorage.getItem('token') ||
|
||||
sessionStorage.getItem('auth_token') ||
|
||||
sessionStorage.getItem('token') ||
|
||||
undefined;
|
||||
}
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'apikey': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ',
|
||||
'Prefer': 'return=representation',
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const reportPayload = {
|
||||
patient_id: data.patientId,
|
||||
status: 'draft',
|
||||
requested_by: data.requestedBy,
|
||||
exam: data.exam,
|
||||
diagnosis: data.diagnosis,
|
||||
conclusion: data.conclusion,
|
||||
content_html: data.contentHtml,
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const responseSupabase = await fetch(BASE_API, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(reportPayload),
|
||||
});
|
||||
|
||||
if (!responseSupabase.ok) {
|
||||
const errorText = await responseSupabase.text();
|
||||
console.error('[reportService] Erro ao criar report no Supabase:', errorText);
|
||||
throw new Error(`Supabase error: ${responseSupabase.statusText}`);
|
||||
}
|
||||
|
||||
const newReport = await responseSupabase.json();
|
||||
|
||||
// Supabase retorna array
|
||||
const report = Array.isArray(newReport) ? newReport[0] : newReport;
|
||||
|
||||
if (!report || !report.id) {
|
||||
throw new Error('Report criado mas sem ID retornado');
|
||||
}
|
||||
|
||||
console.log('[reportService] Report criado com sucesso. ID:', report.id);
|
||||
|
||||
// 2. Notificar paciente via n8n → Twilio
|
||||
try {
|
||||
console.log('[reportService] Enviando notificação para n8n...');
|
||||
|
||||
const notificationResponse = await fetch('https://joaogustavo.me/webhook/notificar-laudo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
pacienteId: report.patient_id, // UUID do paciente
|
||||
reportId: report.id, // UUID do report
|
||||
}),
|
||||
});
|
||||
|
||||
if (!notificationResponse.ok) {
|
||||
console.warn(
|
||||
'[reportService] Erro ao enviar notificação SMS. Status:',
|
||||
notificationResponse.status
|
||||
);
|
||||
// Não falha a criação do report se SMS falhar
|
||||
} else {
|
||||
console.log('[reportService] Notificação enviada com sucesso ao n8n');
|
||||
}
|
||||
} catch (erroNotificacao) {
|
||||
console.warn('[reportService] Erro ao enviar notificação para n8n:', erroNotificacao);
|
||||
// Não falha a criação do report se a notificação falhar
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
report,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[reportService] Erro ao criar report:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface exportada para uso em componentes
|
||||
*/
|
||||
export type { CreateReportData, CreateReportResult };
|
||||
@ -47,6 +47,7 @@ import {
|
||||
ReportsResponse,
|
||||
ReportResponse
|
||||
} from '@/types/report-types';
|
||||
import { buscarPacientePorId } from '@/lib/api';
|
||||
|
||||
// Definição local para ApiError
|
||||
type ApiError = {
|
||||
@ -214,7 +215,52 @@ export async function criarRelatorio(dadosRelatorio: CreateReportData, token?: s
|
||||
const resultado = await resposta.json();
|
||||
// Supabase retorna array
|
||||
if (Array.isArray(resultado) && resultado.length > 0) {
|
||||
return resultado[0];
|
||||
const novoRelatorio = resultado[0];
|
||||
|
||||
// ✅ ENVIAR NOTIFICAÇÃO PARA N8N APÓS CRIAR RELATÓRIO
|
||||
if (novoRelatorio && novoRelatorio.id && dadosRelatorio.patient_id) {
|
||||
try {
|
||||
console.log('[criarRelatorio] Enviando notificação para n8n webhook...');
|
||||
|
||||
// Buscar dados do paciente para incluir nome e telefone
|
||||
const pacienteData = await buscarPacientePorId(dadosRelatorio.patient_id).catch(e => {
|
||||
console.warn('[criarRelatorio] Erro ao buscar paciente:', e);
|
||||
return null;
|
||||
});
|
||||
|
||||
const pacienteNome = pacienteData?.full_name || '';
|
||||
const pacienteCelular = pacienteData?.phone_mobile || '';
|
||||
|
||||
const payloadWebhook = {
|
||||
pacienteId: dadosRelatorio.patient_id,
|
||||
reportId: novoRelatorio.id,
|
||||
pacienteNome: pacienteNome,
|
||||
pacienteCelular: pacienteCelular
|
||||
};
|
||||
|
||||
console.log('[criarRelatorio] Payload do webhook:', payloadWebhook);
|
||||
|
||||
const resNotificacao = await fetch('https://joaogustavo.me/webhook/notificar-laudo', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payloadWebhook)
|
||||
}).catch(e => {
|
||||
console.warn('[criarRelatorio] Erro de rede ao enviar webhook:', e);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (resNotificacao?.ok) {
|
||||
console.log('[criarRelatorio] ✅ Notificação enviada com sucesso ao n8n');
|
||||
} else if (resNotificacao) {
|
||||
console.warn('[criarRelatorio] ⚠️ Notificação ao n8n retornou status:', resNotificacao.status);
|
||||
}
|
||||
} catch (erroNotificacao) {
|
||||
console.warn('[criarRelatorio] ❌ Erro ao enviar notificação para n8n:', erroNotificacao);
|
||||
// Não falha a criação do relatório se a notificação falhar
|
||||
}
|
||||
}
|
||||
|
||||
return novoRelatorio;
|
||||
}
|
||||
throw new Error('Resposta inesperada da API Supabase');
|
||||
}
|
||||
@ -385,3 +431,133 @@ export async function listarRelatoriosParaMedicoAtribuido(userId?: string): Prom
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface para dados necessários ao criar um laudo
|
||||
*/
|
||||
export interface CriarLaudoData {
|
||||
pacienteId: string; // ID do paciente (obrigatório)
|
||||
textoLaudo: string; // Texto do laudo (obrigatório)
|
||||
medicoId?: string; // ID do médico que criou (opcional)
|
||||
exame?: string; // Tipo de exame (opcional)
|
||||
diagnostico?: string; // Diagnóstico (opcional)
|
||||
conclusao?: string; // Conclusão (opcional)
|
||||
cidCode?: string; // Código CID (opcional)
|
||||
status?: 'rascunho' | 'concluido' | 'enviado'; // Status (opcional, padrão: 'concluido')
|
||||
contentHtml?: string; // Conteúdo HTML (opcional)
|
||||
contentJson?: any; // Conteúdo JSON (opcional)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cria um novo laudo no Supabase e notifica o paciente via n8n
|
||||
*
|
||||
* Fluxo:
|
||||
* 1. Salva o laudo no Supabase (tabela 'reports')
|
||||
* 2. Envia notificação ao n8n com pacienteId e laudoId
|
||||
* 3. Retorna o laudo criado
|
||||
*
|
||||
* @param laudoData Dados do laudo a criar
|
||||
* @returns Laudo criado com ID
|
||||
* @throws Erro se falhar ao criar o laudo
|
||||
*/
|
||||
export async function criarLaudo(laudoData: CriarLaudoData): Promise<any> {
|
||||
try {
|
||||
// 1. Validação dos dados obrigatórios
|
||||
if (!laudoData.pacienteId || !laudoData.textoLaudo) {
|
||||
throw new Error('Paciente ID e Texto do Laudo são obrigatórios');
|
||||
}
|
||||
|
||||
console.log('[criarLaudo] Criando laudo para paciente:', laudoData.pacienteId);
|
||||
|
||||
// 2. Monta o payload para Supabase
|
||||
const payloadSupabase = {
|
||||
patient_id: laudoData.pacienteId,
|
||||
...(laudoData.medicoId && { requested_by: laudoData.medicoId }),
|
||||
...(laudoData.exame && { exam: laudoData.exame }),
|
||||
...(laudoData.diagnostico && { diagnosis: laudoData.diagnostico }),
|
||||
...(laudoData.conclusao && { conclusion: laudoData.conclusao }),
|
||||
...(laudoData.cidCode && { cid_code: laudoData.cidCode }),
|
||||
...(laudoData.contentHtml && { content_html: laudoData.contentHtml }),
|
||||
...(laudoData.contentJson && { content_json: laudoData.contentJson }),
|
||||
status: laudoData.status || 'concluido',
|
||||
};
|
||||
|
||||
// 3. Salva o laudo no Supabase
|
||||
const urlSupabase = 'https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports';
|
||||
|
||||
let tokenAuth: string | undefined = undefined;
|
||||
if (typeof window !== 'undefined') {
|
||||
tokenAuth =
|
||||
localStorage.getItem('auth_token') ||
|
||||
localStorage.getItem('token') ||
|
||||
sessionStorage.getItem('auth_token') ||
|
||||
sessionStorage.getItem('token') ||
|
||||
undefined;
|
||||
}
|
||||
|
||||
const headersSupabase: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'apikey': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ',
|
||||
'Prefer': 'return=representation',
|
||||
};
|
||||
|
||||
if (tokenAuth) {
|
||||
headersSupabase['Authorization'] = `Bearer ${tokenAuth}`;
|
||||
}
|
||||
|
||||
const resSupabase = await fetch(urlSupabase, {
|
||||
method: 'POST',
|
||||
headers: headersSupabase,
|
||||
body: JSON.stringify(payloadSupabase),
|
||||
});
|
||||
|
||||
if (!resSupabase.ok) {
|
||||
const errorText = await resSupabase.text();
|
||||
console.error('[criarLaudo] Erro ao salvar laudo no Supabase:', errorText);
|
||||
throw new Error(`Falha ao salvar laudo: ${resSupabase.statusText}`);
|
||||
}
|
||||
|
||||
const novoLaudo = await resSupabase.json();
|
||||
const laudoId = novoLaudo?.id;
|
||||
|
||||
if (!laudoId) {
|
||||
throw new Error('Laudo criado mas sem ID retornado');
|
||||
}
|
||||
|
||||
console.log('[criarLaudo] Laudo salvo com sucesso. ID:', laudoId);
|
||||
|
||||
// 4. CHAMAR O N8N para notificar o paciente
|
||||
// Padrão simples: apenas pacienteId e reportId
|
||||
try {
|
||||
console.log('[criarLaudo] Enviando notificação para n8n...');
|
||||
|
||||
const resNotificacao = await fetch('https://joaogustavo.me/webhook/notificar-laudo', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
pacienteId: laudoData.pacienteId, // ← ID do paciente
|
||||
reportId: laudoId // ← ID do report criado
|
||||
})
|
||||
});
|
||||
|
||||
if (resNotificacao.ok) {
|
||||
console.log('[criarLaudo] Notificação enviada com sucesso ao n8n');
|
||||
} else {
|
||||
console.warn('[criarLaudo] Notificação ao n8n retornou status:', resNotificacao.status);
|
||||
}
|
||||
} catch (erroNotificacao) {
|
||||
// Não falha a criação do laudo se a notificação falhar
|
||||
console.warn('[criarLaudo] Erro ao enviar notificação para n8n:', erroNotificacao);
|
||||
}
|
||||
|
||||
// 5. Retorna o laudo criado
|
||||
return {
|
||||
...novoLaudo,
|
||||
mensagem: 'Laudo criado e paciente notificado com sucesso!',
|
||||
};
|
||||
} catch (erro) {
|
||||
console.error('[criarLaudo] Erro ao criar laudo:', erro);
|
||||
throw erro;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user