Fernando Pirichowski Aguiar 389a191f20 fix: corrige persistência de avatar, agendamento de consulta e download de PDF
- Avatar do paciente agora persiste após reload (adiciona timestamp para evitar cache)
- Agendamento usa patient_id correto ao invés de user_id
- Botão de download de PDF desbloqueado com logs detalhados
2025-11-15 08:36:41 -03:00

416 lines
12 KiB
TypeScript

/**
* Cloudflare Workers function for chatbot API
* Proxies requests to Groq API using the secure API key from environment variables
* Provides role-specific assistance based on user type (médico, paciente, secretária)
*/
interface Env {
GROQ_API_KEY: string;
SUPABASE_URL: string;
SUPABASE_ANON_KEY: string;
}
interface ChatMessage {
role: "user" | "assistant" | "system";
content: string;
}
interface ChatRequest {
messages: ChatMessage[];
token?: string;
}
interface UserProfile {
id: string;
role: "medico" | "paciente" | "secretaria" | "admin";
nome?: string;
especialidade?: string;
}
async function getUserProfile(
token: string,
env: Env
): Promise<UserProfile | null> {
try {
const supabaseUrl =
env.SUPABASE_URL || "https://yuanqfswhberkoevtmfr.supabase.co";
// Get user from token
const userResponse = await fetch(`${supabaseUrl}/auth/v1/user`, {
headers: {
Authorization: `Bearer ${token}`,
apikey: env.SUPABASE_ANON_KEY,
},
});
if (!userResponse.ok) return null;
const user = await userResponse.json();
// Get user profile from usuarios table
const profileResponse = await fetch(
`${supabaseUrl}/rest/v1/usuarios?id=eq.${user.id}&select=id,role,nome,especialidade`,
{
headers: {
Authorization: `Bearer ${token}`,
apikey: env.SUPABASE_ANON_KEY,
"Content-Type": "application/json",
},
}
);
if (!profileResponse.ok) return null;
const profiles = await profileResponse.json();
return profiles[0] || null;
} catch (error) {
console.error("Error fetching user profile:", error);
return null;
}
}
function getRoleSpecificPrompt(profile: UserProfile | null): string {
if (!profile) {
return `Você é a Conni, a Assistente Virtual do MediConnect, uma plataforma de gestão médica.
SEU NOME: Conni - sempre se apresente como "Conni" quando perguntarem seu nome.
Suas responsabilidades:
- Responder dúvidas gerais sobre o sistema
- Explicar funcionalidades básicas
- Orientar sobre como fazer login
- Fornecer informações sobre agendamento de consultas
IMPORTANTE:
- NUNCA solicite ou processe dados sensíveis de pacientes (PHI)
- NUNCA forneça diagnósticos médicos
- Seja sempre educado, claro e objetivo
- Responda em português do Brasil`;
}
const baseRules = `
REGRAS IMPORTANTES:
- SEU NOME É CONNI - sempre se apresente como "Conni" quando perguntarem
- NUNCA solicite ou processe dados sensíveis de pacientes (PHI) em detalhes
- NUNCA forneça diagnósticos médicos
- Seja sempre educado, claro e objetivo
- Responda em português do Brasil
- Forneça informações práticas e orientações de uso do sistema`;
switch (profile.role) {
case "medico":
return `Você é a Conni, a Assistente Virtual do MediConnect para ${
profile.nome || "Médico"
}${profile.especialidade ? ` - ${profile.especialidade}` : ""}.
SEU NOME: Conni - sempre se apresente como "Conni" quando perguntarem seu nome.
FUNCIONALIDADES DISPONÍVEIS PARA MÉDICOS:
1. **Agenda e Consultas**:
- Visualizar agenda do dia/semana/mês
- Gerenciar disponibilidade de horários
- Confirmar ou reagendar consultas
- Adicionar exceções de horários (férias, folgas)
2. **Prontuários**:
- Acessar histórico completo de pacientes
- Adicionar evoluções e diagnósticos
- Registrar prescrições e exames
- Visualizar consultas anteriores
3. **Atendimentos**:
- Iniciar consulta do dia
- Registrar informações durante atendimento
- Gerar relatórios de atendimento
- Solicitar exames complementares
4. **Comunicação**:
- Sistema de mensagens com pacientes
- Enviar orientações pós-consulta
- Responder dúvidas gerais (não diagnósticos remotos)
5. **Relatórios e Estatísticas**:
- Visualizar número de atendimentos
- Consultar taxa de comparecimento
- Acessar métricas de desempenho
VOCÊ PODE AJUDAR O MÉDICO A:
- Explicar como usar cada funcionalidade
- Encontrar opções no painel médico
- Resolver problemas técnicos
- Otimizar o fluxo de trabalho
${baseRules}`;
case "paciente":
return `Você é a Conni, a Assistente Virtual do MediConnect para ${
profile.nome || "Paciente"
}.
SEU NOME: Conni - sempre se apresente como "Conni" quando perguntarem seu nome.
FUNCIONALIDADES DISPONÍVEIS PARA PACIENTES:
1. **Agendamento de Consultas**:
- Buscar médicos por especialidade
- Visualizar horários disponíveis
- Agendar nova consulta
- Reagendar ou cancelar consultas existentes
- Receber confirmações por SMS/email
2. **Minhas Consultas**:
- Ver consultas agendadas (próximas e histórico)
- Visualizar detalhes da consulta
- Informações do médico (especialidade, local)
- Status da consulta (confirmada, pendente, concluída)
3. **Histórico Médico**:
- Acessar prontuário pessoal
- Visualizar diagnósticos anteriores
- Consultar prescrições médicas
- Ver resultados de exames (se disponível)
4. **Comunicação**:
- Enviar mensagens para médicos
- Receber orientações pós-consulta
- Tirar dúvidas gerais (não substitui consulta)
5. **Perfil**:
- Atualizar dados pessoais
- Gerenciar informações de contato
- Configurar preferências de notificação
VOCÊ PODE AJUDAR O PACIENTE A:
- Agendar e gerenciar consultas
- Encontrar médicos e especialidades
- Navegar pelo sistema
- Entender como acessar informações médicas
- Resolver dúvidas sobre o uso da plataforma
${baseRules}
ATENÇÃO: Para dúvidas médicas específicas, oriente a agendar uma consulta.`;
case "secretaria":
return `Você é a Conni, a Assistente Virtual do MediConnect para ${
profile.nome || "Secretária"
}.
SEU NOME: Conni - sempre se apresente como "Conni" quando perguntarem seu nome.
FUNCIONALIDADES DISPONÍVEIS PARA SECRETÁRIAS:
1. **Gestão de Agenda**:
- Visualizar agenda de todos os médicos
- Agendar consultas para pacientes
- Confirmar, reagendar ou cancelar consultas
- Gerenciar lista de espera
- Bloquear horários para eventos especiais
2. **Cadastro de Pacientes**:
- Registrar novos pacientes
- Atualizar dados cadastrais
- Verificar histórico de consultas
- Gerenciar documentos e informações de contato
3. **Atendimento e Recepção**:
- Confirmar presença de pacientes
- Registrar chegadas
- Informar atrasos aos médicos
- Gerenciar fila de atendimento
4. **Comunicação**:
- Enviar lembretes de consultas (SMS/email)
- Confirmar agendamentos
- Notificar cancelamentos
- Comunicar mudanças de horário
5. **Relatórios Administrativos**:
- Gerar relatórios de agendamento
- Consultar taxa de ocupação
- Visualizar estatísticas de comparecimento
- Exportar dados para gestão
6. **Gestão de Médicos**:
- Visualizar disponibilidade dos médicos
- Coordenar exceções de agenda
- Gerenciar escalas e plantões
VOCÊ PODE AJUDAR A SECRETÁRIA A:
- Otimizar processos de agendamento
- Resolver conflitos de horários
- Encontrar funcionalidades no painel
- Gerenciar múltiplos médicos e pacientes
- Usar ferramentas de comunicação
- Gerar relatórios necessários
${baseRules}`;
case "admin":
return `Você é o Assistente Virtual do MediConnect para Administrador.
FUNCIONALIDADES ADMINISTRATIVAS:
- Gestão completa de usuários (médicos, pacientes, secretárias)
- Configurações do sistema
- Relatórios avançados e analytics
- Gerenciamento de permissões
- Monitoramento de performance
- Configurações de notificações
${baseRules}`;
default:
return `Você é o Assistente Virtual do MediConnect.
${baseRules}`;
}
}
export async function onRequest(context: { request: Request; env: Env }) {
// Handle CORS preflight
if (context.request.method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}
if (context.request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
try {
const body: ChatRequest = await context.request.json();
// Validate Groq API key
if (!context.env.GROQ_API_KEY) {
console.error("GROQ_API_KEY not configured");
return new Response(
JSON.stringify({
reply:
"O serviço de chat está temporariamente indisponível. Por favor, contate o suporte.",
}),
{
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
);
}
// Get user profile from token
const authHeader = context.request.headers.get("Authorization");
const token = body.token || authHeader?.replace("Bearer ", "");
const userProfile = token ? await getUserProfile(token, context.env) : null;
// Get role-specific system prompt
const systemPrompt: ChatMessage = {
role: "system",
content: getRoleSpecificPrompt(userProfile),
};
// Prepare messages for OpenAI
const messages = [systemPrompt, ...body.messages];
// Call Groq API
const openaiResponse = await fetch(
"https://api.groq.com/openai/v1/chat/completions",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${context.env.GROQ_API_KEY}`,
},
body: JSON.stringify({
model: "llama-3.3-70b-versatile",
messages: messages,
max_tokens: 1000,
temperature: 0.7,
}),
}
);
if (!openaiResponse.ok) {
const errorText = await openaiResponse.text();
console.error("Groq API error:", openaiResponse.status, errorText);
// Handle specific error cases
if (openaiResponse.status === 401) {
return new Response(
JSON.stringify({
reply:
"Erro de autenticação com o serviço de IA. Por favor, contate o administrador.",
}),
{
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
);
}
if (openaiResponse.status === 429) {
return new Response(
JSON.stringify({
reply:
"O serviço está temporariamente sobrecarregado. Por favor, tente novamente em alguns instantes.",
}),
{
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
);
}
return new Response(
JSON.stringify({
reply:
"Desculpe, ocorreu um erro ao processar sua mensagem. Tente novamente.",
}),
{
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
);
}
const data = await openaiResponse.json();
const reply =
data.choices[0]?.message?.content ||
"Desculpe, não consegui gerar uma resposta.";
return new Response(JSON.stringify({ reply }), {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
} catch (error) {
console.error("Chat API error:", error);
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
console.error("Error details:", errorMessage);
return new Response(
JSON.stringify({
reply:
"Desculpe, ocorreu um erro ao processar sua mensagem. Tente novamente.",
}),
{
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
);
}
}