feat: add custom message functionality for diploma emails and WhatsApp notifications

This commit is contained in:
SirRobert-1 2025-06-03 22:33:10 -06:00
parent cddb2291c3
commit c747b4615c
3 changed files with 138 additions and 26 deletions

View File

@ -8,11 +8,16 @@ import {
import Diploma from "@/components/Diploma";
import { PDFDownloadLink, PDFViewer, pdf } from "@react-pdf/renderer";
import { supabaseClient } from "@/utils/supabase";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { mensajesSchema } from "@/schemas/mensajesSchema";
import { Textarea } from "../ui/textarea";
function VistaPreviaDiplomaDialog({
open,
onOpenChange,
alumno,
curso,
competencias: competenciasProp,
fecha,
competenciasAcreditadas,
@ -20,8 +25,77 @@ function VistaPreviaDiplomaDialog({
const [mostrarVistaPrevia, setMostrarVistaPrevia] = useState(false);
const [enviando, setEnviando] = useState(false);
const [mensaje, setMensaje] = useState("");
const [loadingMensajes, setLoadingMensajes] = useState(false);
const [competencias, setCompetencias] = useState([]);
const form = useForm({
resolver: zodResolver(mensajesSchema),
defaultValues: {
correo: "",
whatsapp: "",
},
});
const {
register,
handleSubmit,
setValue,
getValues,
formState: { errors },
} = form;
// 🔄 Cargar mensajes al abrir el modal
useEffect(() => {
if (open) {
setLoadingMensajes(true);
Promise.all([
supabaseClient.from("mensaje_correo").select("id, mensaje").single(),
supabaseClient.from("mensaje_whatsapp").select("id, mensaje").single(),
])
.then(([correoRes, whatsappRes]) => {
if (correoRes.data) setValue("correo", correoRes.data.mensaje);
if (whatsappRes.data) setValue("whatsapp", whatsappRes.data.mensaje);
})
.finally(() => setLoadingMensajes(false));
}
}, [open, setValue]);
// 📝 Guardar mensajes personalizados
const handleGuardarMensajes = async () => {
const { correo, whatsapp } = getValues();
const [{ data: correoExistente }] = await Promise.all([
supabaseClient.from("mensaje_correo").select("id").maybeSingle(),
]);
if (correoExistente) {
await supabaseClient
.from("mensaje_correo")
.update({ mensaje: correo })
.eq("id", correoExistente.id);
} else {
await supabaseClient.from("mensaje_correo").insert({ mensaje: correo });
}
const { data: whatsappExistente } = await supabaseClient
.from("mensaje_whatsapp")
.select("id")
.maybeSingle();
if (whatsappExistente) {
await supabaseClient
.from("mensaje_whatsapp")
.update({ mensaje: whatsapp })
.eq("id", whatsappExistente.id);
} else {
await supabaseClient
.from("mensaje_whatsapp")
.insert({ mensaje: whatsapp });
}
setMensaje("Mensajes guardados correctamente.");
};
useEffect(() => {
if (alumno && alumno.curso?.id) {
supabaseClient
@ -41,28 +115,25 @@ function VistaPreviaDiplomaDialog({
? competencias.filter((comp) => competenciasAcreditadas.includes(comp.id))
: competencias;
// Simulación de envío de PDF por correo y WhatsApp
const handleEnviar = async () => {
setEnviando(true);
setMensaje("");
// Genera el PDF como blob
const blob = await pdf(
<Diploma
alumno={alumno}
curso={alumno.curso}
curso={curso}
competencias={competenciasMostradas}
fecha={fecha || new Date().toLocaleDateString()}
/>
).toBlob();
// Convierte el blob a base64
const pdfBase64 = await new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result.split(",")[1]);
reader.readAsDataURL(blob);
});
// Llama a tu API de Next.js
const resp = await fetch("/api/send-diploma", {
method: "POST",
headers: { "Content-Type": "application/json" },
@ -71,32 +142,30 @@ function VistaPreviaDiplomaDialog({
nombre: alumno.nombre,
curso: alumno.curso?.nombre || "Sin curso",
pdfBase64,
mensajeCorreo: getValues("correo"),
}),
});
if (resp.ok) {
// WhatsApp real (abre ventana)
const telefono = alumno.telefono.replace(/\D/g, "");
const mensajeWhatsapp = encodeURIComponent(
`Hola ${alumno.nombre}, tu diploma ha sido generado y enviado a tu correo (${alumno.correo}). ¡Felicidades!`
);
const mensajeWhatsapp = encodeURIComponent(getValues("whatsapp"));
window.open(
`https://wa.me/${telefono}?text=${mensajeWhatsapp}`,
"_blank"
);
setMensaje(
`Diploma enviado por correo a ${alumno.correo} y mensaje enviado por WhatsApp al ${alumno.telefono}.`
);
setMensaje(`Diploma enviado por correo a ${alumno.correo}.`);
} else {
setMensaje("Error enviando el diploma por correo.");
setMensaje("Error enviando el diploma.");
}
setEnviando(false);
};
if (!alumno) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg text-black">
<DialogContent className="max-w-lg h-screen text-black overflow-y-auto">
<DialogHeader>
<DialogTitle>Diploma</DialogTitle>
</DialogHeader>
@ -117,15 +186,35 @@ function VistaPreviaDiplomaDialog({
<div className="text-lg mb-2">
<b>Fecha:</b> {fecha || new Date().toLocaleDateString()}
</div>
<div className="mt-auto text-gray-400 text-xs text-right">
Vista previa
</div>
<div className="mt-4 flex gap-2 justify-center flex-wrap">
{/* Campos de mensaje */}
<form>
<div className="mb-4">
<Textarea
label="Mensaje para correo"
placeholder="Escribe un mensaje personalizado para el correo"
{...register("correo")}
error={errors.correo?.message}
disabled={loadingMensajes}
/>
</div>
<div className="mb-4">
<Textarea
label="Mensaje para WhatsApp"
placeholder="Escribe un mensaje personalizado para WhatsApp"
{...register("whatsapp")}
error={errors.whatsapp?.message}
disabled={loadingMensajes}
/>
</div>
</form>
{/* Acciones */}
<div className="mt-4 flex flex-wrap gap-2 justify-center">
<PDFDownloadLink
document={
<Diploma
alumno={alumno}
curso={alumno.curso}
curso={curso}
competencias={competenciasMostradas}
fecha={fecha || new Date().toLocaleDateString()}
/>
@ -144,12 +233,14 @@ function VistaPreviaDiplomaDialog({
)
}
</PDFDownloadLink>
<button
className="bg-blue-500 hover:bg-blue-700 text-white px-4 py-2 rounded"
onClick={() => setMostrarVistaPrevia(true)}
>
Ver vista previa PDF
</button>
<button
className="bg-purple-600 hover:bg-purple-800 text-white px-4 py-2 rounded"
onClick={handleEnviar}
@ -157,20 +248,31 @@ function VistaPreviaDiplomaDialog({
>
{enviando ? "Enviando..." : "Enviar por correo y WhatsApp"}
</button>
<button
className="bg-yellow-500 hover:bg-yellow-600 text-white px-4 py-2 rounded"
onClick={handleGuardarMensajes}
type="button"
>
Guardar mensajes
</button>
</div>
{mensaje && (
<div className="mt-4 text-green-700 font-semibold text-center">
{mensaje}
</div>
)}
{/* Vista previa PDF */}
{mostrarVistaPrevia && (
<div className="fixed inset-0 bg-black bg-opacity-60 flex flex-col items-center justify-center z-50">
<div className="bg-white rounded shadow-lg p-4 flex flex-col items-center">
<div className="w-[80vw] h-[80vh] lg:h-[90vh] mb-4 border">
<div className="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50">
<div className="bg-white rounded shadow-lg p-4">
<div className="w-[80vw] h-[90vh] mb-4 border">
<PDFViewer width="100%" height="100%">
<Diploma
alumno={alumno}
curso={alumno.curso}
curso={curso}
competencias={competenciasMostradas}
fecha={fecha || new Date().toLocaleDateString()}
/>

View File

@ -5,14 +5,18 @@ sgMail.setApiKey(process.env.SENDGRID_API_KEY);
export default async function handler(req, res) {
if (req.method !== "POST") return res.status(405).end();
const { email, nombre, curso, pdfBase64 } = req.body;
// Recibe mensajeCorreo desde el body
const { email, nombre, curso, pdfBase64, mensajeCorreo } = req.body;
try {
await sgMail.send({
to: email,
from: "rviverosgonzalez@outlook.com", // Cambia esto por tu correo verificado en SendGrid
subject: "Tu diploma",
text: `Hola ${nombre}, has concluido tu curso ${curso} por lo que adjuntamos tu diploma.`,
// Usa el mensajeCorreo personalizado, si no viene usa el texto por defecto
text:
mensajeCorreo ||
`Hola ${nombre}, has concluido tu curso ${curso} por lo que adjuntamos tu diploma.`,
attachments: [
{
content: pdfBase64,

View File

@ -0,0 +1,6 @@
import { z } from "zod";
export const mensajesSchema = z.object({
correo: z.string().min(1, "El mensaje de correo es obligatorio"),
whatsapp: z.string().min(1, "El mensaje de WhatsApp es obligatorio"),
});