"use client"; import { useEffect, useState, useCallback } from "react"; import { getEmailTemplates, createEmailTemplate, updateEmailTemplate, getEmailLog, sendTestEmail, syncInboundEmail, type EmailTemplate, type EmailLogEntry, } from "@/lib/api"; import { useAuth } from "@/lib/auth"; import { formatDateTime } from "@/lib/format"; import Modal from "@/components/Modal"; const PLACEHOLDERS = [ "TicketNumber", "Title", "Description", "Status", "Priority", "RequesterName", "AssigneeName", "Department", "Link", "Comment", "CommentAuthor", ]; export default function EmailPage() { const { user, isLoading: authLoading } = useAuth(); const [templates, setTemplates] = useState([]); const [log, setLog] = useState([]); const [logModule, setLogModule] = useState("helpdesk"); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [showModal, setShowModal] = useState(false); const [editing, setEditing] = useState(null); const [subject, setSubject] = useState(""); const [bodyHtml, setBodyHtml] = useState(""); const [isActive, setIsActive] = useState(true); const [saving, setSaving] = useState(false); const [modalError, setModalError] = useState(""); // Crear plantilla (POST /api/email/templates) const [showCreate, setShowCreate] = useState(false); const [newKey, setNewKey] = useState(""); const [newName, setNewName] = useState(""); const [newSubject, setNewSubject] = useState(""); const [newBody, setNewBody] = useState(""); const [newModule, setNewModule] = useState("helpdesk"); const [creating, setCreating] = useState(false); const [createError, setCreateError] = useState(""); const [testTo, setTestTo] = useState(""); const [testResult, setTestResult] = useState<{ ok: boolean; text: string } | null>(null); const [testing, setTesting] = useState(false); const [syncing, setSyncing] = useState(false); const [syncResult, setSyncResult] = useState<{ ok: boolean; text: string } | null>(null); const fetchData = useCallback(async () => { setLoading(true); setError(""); try { const [t, l] = await Promise.all([ getEmailTemplates(), getEmailLog(50, logModule), ]); setTemplates(t); setLog(l); } catch (err) { setError(err instanceof Error ? err.message : "Error al cargar"); setTemplates([]); setLog([]); } finally { setLoading(false); } }, [logModule]); useEffect(() => { if (authLoading || user?.role !== "admin") return; Promise.all([getEmailTemplates(), getEmailLog(50, logModule)]) .then(([t, l]) => { setTemplates(t); setLog(l); setError(""); }) .catch((err) => { setError(err instanceof Error ? err.message : "Error al cargar"); setTemplates([]); setLog([]); }) .finally(() => setLoading(false)); }, [authLoading, user?.role, logModule]); const openEdit = (t: EmailTemplate) => { setEditing(t); setSubject(t.subject); setBodyHtml(t.bodyHtml); setIsActive(t.isActive); setModalError(""); setShowModal(true); }; const handleSubmit = async () => { if (!editing) return; if (!subject.trim() || !bodyHtml.trim()) { setModalError("Asunto y cuerpo son requeridos"); return; } setSaving(true); setModalError(""); try { await updateEmailTemplate(editing.key, { subject: subject.trim(), bodyHtml: bodyHtml.trim(), isActive, }); setShowModal(false); fetchData(); } catch (err) { setModalError(err instanceof Error ? err.message : "Error al guardar"); } finally { setSaving(false); } }; const openCreate = () => { setNewKey(""); setNewName(""); setNewSubject(""); setNewBody(""); setNewModule("helpdesk"); setCreateError(""); setShowCreate(true); }; const handleCreate = async () => { const key = newKey.trim().toLowerCase(); const modName = (newModule.trim().toLowerCase() || "helpdesk"); if (!/^[a-z0-9_]{2,64}$/.test(key)) { setCreateError("Key inválida ([a-z0-9_], 2-64)"); return; } if (!newName.trim() || !newSubject.trim() || !newBody.trim()) { setCreateError("Nombre, asunto y cuerpo son requeridos"); return; } if (!/^[a-z0-9-]{2,32}$/.test(modName)) { setCreateError("Módulo inválido ([a-z0-9-], 2-32)"); return; } setCreating(true); setCreateError(""); try { await createEmailTemplate({ key, name: newName.trim(), subject: newSubject.trim(), bodyHtml: newBody.trim(), module: modName, }); setShowCreate(false); fetchData(); } catch (err) { setCreateError(err instanceof Error ? err.message : "Error al crear"); } finally { setCreating(false); } }; const handleTest = async () => { if (!testTo.trim() || !testTo.includes("@")) { setTestResult({ ok: false, text: "Destino inválido" }); return; } setTesting(true); setTestResult(null); try { const r = await sendTestEmail(testTo.trim()); const sent = typeof r.sent === "number" ? ` (${r.sent} enviado(s))` : ""; setTestResult({ ok: true, text: `Correo de prueba enviado a ${testTo.trim()}${sent} vía módulo helpdesk.` }); fetchData(); } catch (err) { setTestResult({ ok: false, text: err instanceof Error ? err.message : "Error al enviar" }); } finally { setTesting(false); } }; const handleSync = async () => { setSyncing(true); setSyncResult(null); try { const r = await syncInboundEmail(); setSyncResult({ ok: r.success, text: r.success ? `Revisados: ${r.checkedCount}, agregados al ticket: ${r.inserted}, omitidos: ${r.skipped}${r.error ? ` (${r.error})` : ""}` : r.error || "Error en la sincronización", }); } catch (err) { setSyncResult({ ok: false, text: err instanceof Error ? err.message : "Error en la sincronización" }); } finally { setSyncing(false); } }; if (!authLoading && user?.role !== "admin") { return (

Correo

Solo los administradores pueden gestionar las plantillas de correo.
); } return (

Plantillas de Correo

{error && (
{error}
)}
{loading ? ( ) : templates.length === 0 ? ( ) : ( templates.map((t) => ( )) )}
Plantilla Módulo Asunto Estado Acciones
Sin plantillas
{t.name}
{t.key}
{t.module ?? "helpdesk"} {t.subject} {t.isActive ? ( Activa ) : ( Inactiva )}
Probar envío
{testResult && (
{testResult.text}
)}
setTestTo(e.target.value)} style={{ maxWidth: 320 }} />
Va por el route propio /api/correos → POST /api/email/send con módulo helpdesk. Requiere MAIL_API_KEY en el servidor. Si falla, revisa la bitácora y la configuración SMTP del backend.
Respuestas por correo
{syncResult && (
{syncResult.text}
)}
Lee la casilla Gmail y agrega como comentarios las respuestas con etiqueta [HelpDesk HD-000123]. El worker lo hace solo cada 5 minutos. Requiere scope gmail.readonly autorizado en admin.google.com.
Últimos envíos
{log.length === 0 ? ( ) : ( log.map((e) => ( )) )}
Fecha Plantilla Módulo Ticket Destinatario Estado
Sin envíos registrados
{formatDateTime(e.createdAt)} {e.template} {e.module ?? "helpdesk"} {e.ticketId ? `#${e.ticketId}` : "—"} {e.recipient} {e.status === "sent" ? ( Enviado ) : e.status === "skipped" ? ( Omitido ) : ( Error )}
setShowModal(false)} onSubmit={handleSubmit} submitting={saving} > {modalError && (
{modalError}
)}
setSubject(e.target.value)} autoFocus />