envio de sms ao agendar consulta

This commit is contained in:
Lucas Deiró Rodrigues 2025-11-11 00:53:04 -03:00
parent 0fcc7ae97b
commit 866e15df9e
4 changed files with 113 additions and 27 deletions

View File

@ -14,8 +14,10 @@ import { Textarea } from "@/components/ui/textarea";
import { Calendar as CalendarShadcn } from "@/components/ui/calendar"; import { Calendar as CalendarShadcn } from "@/components/ui/calendar";
import { format, addDays } from "date-fns"; import { format, addDays } from "date-fns";
import { User, StickyNote, Calendar } from "lucide-react"; import { User, StickyNote, Calendar } from "lucide-react";
import {smsService } from "@/services/Sms.mjs"
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/use-toast";
export default function ScheduleForm() { export default function ScheduleForm() {
// Estado do usuário e role // Estado do usuário e role
const [role, setRole] = useState<string>("paciente"); const [role, setRole] = useState<string>("paciente");
@ -244,41 +246,56 @@ const handleSubmit = async (e: React.FormEvent) => {
}.`, }.`,
}); });
// 📞 busca o telefone corretamente let phoneNumber = "+5511999999999"; // fallback
let phoneNumber = "+5511999999999"; // fallback
try { try {
if (isSecretaryLike) { if (isSecretaryLike) {
// se for secretária/admin → usa paciente selecionado // Secretária/admin → telefone do paciente selecionado
const patient = patients.find((p: any) => p.id === patientId); const patient = patients.find((p: any) => p.id === patientId);
if (patient?.phone_number) phoneNumber = patient.phone_number;
// Pacientes criados no sistema podem ter phone ou phone_mobile
const rawPhone = patient?.phone || patient?.phone_mobile || null;
if (rawPhone) phoneNumber = rawPhone;
} else { } else {
// se for paciente → usa o service do próprio user // Paciente → telefone vem do perfil do próprio usuário logado
const me = await usersService.getMe(); const me = await usersService.getMe();
if (me?.profile?.phone) phoneNumber = me.profile.phone;
const rawPhone =
me?.profile?.phone ||
(typeof me?.profile === "object" && "phone_mobile" in me.profile ? (me.profile as any).phone_mobile : null) ||
(typeof me === "object" && "user_metadata" in me ? (me as any).user_metadata?.phone : null) ||
null;
if (rawPhone) phoneNumber = rawPhone;
} }
// padroniza número para formato internacional (+55) // 🔹 Normaliza para formato internacional (+55)
if (phoneNumber) { if (phoneNumber) {
phoneNumber = phoneNumber.replace(/\D/g, ""); // remove caracteres não numéricos phoneNumber = phoneNumber.replace(/\D/g, "");
if (!phoneNumber.startsWith("55")) phoneNumber = `55${phoneNumber}`; if (!phoneNumber.startsWith("55")) phoneNumber = `55${phoneNumber}`;
phoneNumber = `+${phoneNumber}`; phoneNumber = `+${phoneNumber}`;
} }
} catch (err) {
console.warn("Não foi possível obter telefone do paciente:", err); console.log("📞 Telefone usado:", phoneNumber);
} } catch (err) {
console.warn("⚠️ Não foi possível obter telefone do paciente:", err);
}
// 💬 envia o SMS de confirmação // 💬 envia o SMS de confirmação
// 💬 Envia o SMS de lembrete (sem mostrar nada ao paciente) // 💬 Envia o SMS de lembrete (sem mostrar nada ao paciente)
// 💬 Envia o SMS de lembrete (somente loga no console, não mostra no sistema)
try { try {
const smsRes = await appointmentsService.send_sms({ const smsRes = await smsService.sendSms({
phone_number: phoneNumber, phone_number: phoneNumber,
message: `Lembrete: sua consulta é em ${dateFormatted} às ${selectedTime} na Clínica MediConnect.`, message: `Lembrete: sua consulta é em ${dateFormatted} às ${selectedTime} na Clínica MediConnect.`,
patient_id: patientId, patient_id: patientId,
}); });
if (smsRes?.success) { if (smsRes?.success) {
console.log("✅ SMS enviado com sucesso:", smsRes.message_sid || smsRes.sid || "(sem SID retornado)"); console.log("✅ SMS enviado com sucesso:", smsRes.message_sid);
} else { } else {
console.warn("⚠️ Falha no envio do SMS:", smsRes); console.warn("⚠️ Falha no envio do SMS:", smsRes);
} }
@ -287,6 +304,8 @@ try {
} }
// 🧹 limpa os campos // 🧹 limpa os campos
setSelectedDoctor(""); setSelectedDoctor("");
setSelectedDate(""); setSelectedDate("");

58
services/Sms.mjs Normal file
View File

@ -0,0 +1,58 @@
/**
* Serviço de SMS via Supabase Edge Function (sem backend)
* Usa o token JWT salvo no localStorage (chave: "token")
*/
const SUPABASE_FUNCTION_URL =
"https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/send-sms";
export const smsService = {
/**
* Envia um SMS de lembrete via Twilio
* @param {Object} params
* @param {string} params.phone_number - Ex: +5511999999999
* @param {string} params.message - Mensagem de texto
* @param {string} [params.patient_id] - ID opcional do paciente
*/
async sendSms({ phone_number, message, patient_id }) {
try {
// 🔹 Busca o token salvo pelo login
const token = localStorage.getItem("token");
if (!token) {
console.error("❌ Nenhum token JWT encontrado no localStorage (chave: 'token').");
return { success: false, error: "Token JWT não encontrado." };
}
const body = JSON.stringify({
phone_number,
message,
patient_id,
});
console.log("[smsService] Enviando SMS para:", phone_number);
const response = await fetch(SUPABASE_FUNCTION_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`, // 🔑 autenticação Supabase
},
body,
});
const result = await response.json();
if (!response.ok) {
console.error("❌ Falha no envio do SMS:", result);
return { success: false, error: result };
}
console.log("✅ SMS enviado com sucesso:", result);
return result;
} catch (err) {
console.error("❌ Erro inesperado ao enviar SMS:", err);
return { success: false, error: err.message };
}
},
};

View File

@ -89,11 +89,20 @@ async function request(endpoint, options = {}) {
// --- CORREÇÃO 1: PARA O SUBMIT DO AGENDAMENTO --- // --- CORREÇÃO 1: PARA O SUBMIT DO AGENDAMENTO ---
// Se a resposta for um sucesso de criação (201) ou sem conteúdo (204), não quebra. // Se a resposta for um sucesso de criação (201) ou sem conteúdo (204), não quebra.
if (response.status === 201 || response.status === 204) { // --- CORREÇÃO: funções do Supabase retornam 200 ou 201, nunca queremos perder o body ---
if (response.status === 204) {
return null; return null;
} }
return response.json(); const text = await response.text();
try {
return JSON.parse(text);
} catch {
return text || null;
}
} }
// Exportamos o objeto 'api' com os métodos que os componentes vão usar. // Exportamos o objeto 'api' com os métodos que os componentes vão usar.

View File

@ -46,6 +46,6 @@ export const appointmentsService = {
*/ */
delete: (id) => api.delete(`/rest/v1/appointments?id=eq.${id}`), delete: (id) => api.delete(`/rest/v1/appointments?id=eq.${id}`),
send_sms: (data) => api.post("/functions/v1/send-sms", data)
}; };