diff --git a/frontend/src/app/(app)/settings/email/page.tsx b/frontend/src/app/(app)/settings/email/page.tsx
index d41a1c3..584e079 100644
--- a/frontend/src/app/(app)/settings/email/page.tsx
+++ b/frontend/src/app/(app)/settings/email/page.tsx
@@ -12,6 +12,7 @@ import {
type EmailLogEntry,
} from "@/lib/api";
import { useAuth } from "@/lib/auth";
+import { formatDateTime } from "@/lib/format";
import Modal from "@/components/Modal";
const PLACEHOLDERS = [
@@ -415,7 +416,7 @@ export default function EmailPage() {
log.map((e) => (
|
- {new Date(e.createdAt).toLocaleString("es-CL")}
+ {formatDateTime(e.createdAt)}
|
{e.template}
diff --git a/frontend/src/app/(app)/tickets/[id]/page.tsx b/frontend/src/app/(app)/tickets/[id]/page.tsx
index fdc381c..6073429 100644
--- a/frontend/src/app/(app)/tickets/[id]/page.tsx
+++ b/frontend/src/app/(app)/tickets/[id]/page.tsx
@@ -6,6 +6,7 @@ import { useAuth } from "@/lib/auth";
import {
getTicket,
getComments,
+ getTicketEmails,
addComment,
updateTicketStatus,
assignTicket,
@@ -15,9 +16,12 @@ import {
type Comment,
type Group,
type TeamMember,
+ type TicketSentEmail,
+ type TicketReceivedEmail,
} from "@/lib/api";
import BadgeStatus from "@/components/BadgeStatus";
import BadgePriority from "@/components/BadgePriority";
+import { formatDateTime } from "@/lib/format";
import Link from "next/link";
const STATUS_OPTIONS = [
@@ -37,6 +41,8 @@ export default function TicketDetailPage() {
const [ticket, setTicket] = useState(null);
const [comments, setComments] = useState([]);
+ const [sentEmails, setSentEmails] = useState([]);
+ const [receivedEmails, setReceivedEmails] = useState([]);
const [groups, setGroups] = useState([]);
const [team, setTeam] = useState([]);
const [loading, setLoading] = useState(true);
@@ -52,12 +58,15 @@ export default function TicketDetailPage() {
Promise.all([
getTicket(id).catch(() => null),
getComments(id).catch(() => []),
+ getTicketEmails(id).catch(() => ({ sent: [], received: [] })),
getGroups().catch(() => []),
getTeam().catch(() => []),
])
- .then(([ticketData, commentsData, groupsData, teamData]) => {
+ .then(([ticketData, commentsData, emailsData, groupsData, teamData]) => {
setTicket(ticketData);
setComments(commentsData);
+ setSentEmails(emailsData.sent ?? []);
+ setReceivedEmails(emailsData.received ?? []);
setGroups(groupsData);
setTeam(teamData);
})
@@ -303,7 +312,7 @@ export default function TicketDetailPage() {
{c.content}
- {new Date(c.createdAt).toLocaleString("es-CL")}
+ {formatDateTime(c.createdAt)}
@@ -317,6 +326,74 @@ export default function TicketDetailPage() {
+ {/* Historial de correos: enviados desde soporte + respuestas
+ recibidas, integrados al ticket (hilo email <-> ticket) */}
+
+
+
+ Historial de correos ({sentEmails.length + receivedEmails.length})
+
+
+ {sentEmails.length === 0 && receivedEmails.length === 0 ? (
+
+
+ Sin correos asociados a este ticket
+
+ ) : (
+ [...sentEmails.map((e) => ({
+ key: `sent-${e.id}`,
+ dir: "sent" as const,
+ who: e.recipient,
+ subject: e.subject,
+ extra: e.template,
+ status: e.status,
+ error: e.error,
+ createdAt: e.createdAt,
+ })), ...receivedEmails.map((e, i) => ({
+ key: `recv-${i}-${e.createdAt}`,
+ dir: "received" as const,
+ who: e.authorEmail,
+ subject: e.subject,
+ extra: e.status === "inserted" ? "Agregado como comentario" : (e.error || e.status),
+ status: e.status,
+ error: e.error,
+ createdAt: e.createdAt,
+ }))]
+ .sort((a, b) => +new Date(b.createdAt) - +new Date(a.createdAt))
+ .map((m) => (
+
+
+
+
+
+
+ {m.dir === "sent" ? "Para: " : "De: "}{m.who}
+
+ {m.subject}
+
+ {formatDateTime(m.createdAt)}
+ ·
+ {m.extra}
+ {m.status !== "sent" && m.status !== "inserted" && (
+
+ {m.status}
+
+ )}
+
+
+
+ ))
+ )}
+
+ Las respuestas con etiqueta HD-{String(id).padStart(6, "0")} en el asunto
+ se agregan solas como comentarios (ingesta cada 5 min).
+
+
+
+
{/* Comment Form */}
Creado
- {new Date(ticket.createdAt).toLocaleString("es-CL")}
+ {formatDateTime(ticket.createdAt)}
Actualizado
- {new Date(ticket.updatedAt).toLocaleString("es-CL")}
+ {formatDateTime(ticket.updatedAt)}
diff --git a/frontend/src/app/(app)/tickets/page.tsx b/frontend/src/app/(app)/tickets/page.tsx
index d1be2da..f1df11d 100644
--- a/frontend/src/app/(app)/tickets/page.tsx
+++ b/frontend/src/app/(app)/tickets/page.tsx
@@ -7,6 +7,7 @@ import { useAuth } from "@/lib/auth";
import BadgeStatus from "@/components/BadgeStatus";
import BadgePriority from "@/components/BadgePriority";
import PaginationComponent from "@/components/Pagination";
+import { formatDate } from "@/lib/format";
import Link from "next/link";
export default function TicketsPage() {
@@ -204,7 +205,7 @@ export default function TicketsPage() {
|
{ticket.assigneeName || Sin asignar} |
- {new Date(ticket.updatedAt).toLocaleDateString("es-CL")}
+ {formatDate(ticket.updatedAt)}
|
|
))
diff --git a/frontend/src/components/NotificationBell.tsx b/frontend/src/components/NotificationBell.tsx
index 812fe09..faac699 100644
--- a/frontend/src/components/NotificationBell.tsx
+++ b/frontend/src/components/NotificationBell.tsx
@@ -9,6 +9,7 @@ import {
markAllNotificationsRead,
type AppNotification,
} from "@/lib/api";
+import { formatDate } from "@/lib/format";
const POLL_MS = 60000;
@@ -38,7 +39,7 @@ function timeAgo(iso: string): string {
if (hours < 24) return `hace ${hours} h`;
const days = Math.floor(hours / 24);
if (days < 7) return `hace ${days} d`;
- return new Date(iso).toLocaleDateString("es-CL");
+ return formatDate(iso);
}
export default function NotificationBell() {
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index 385ba79..2327d02 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -129,6 +129,35 @@ export async function addComment(
});
}
+// ==================== Historial de correos del ticket ====================
+// Integración email <-> ticket: enviados desde soporte (mail.log) +
+// respuestas recibidas (helpdesk.email_inbound). Visible para quien
+// puede ver el ticket (equipo + solicitante).
+export interface TicketSentEmail {
+ id: number;
+ template: string;
+ recipient: string;
+ subject: string;
+ status: string;
+ error?: string;
+ createdAt: string;
+}
+
+export interface TicketReceivedEmail {
+ authorId?: string;
+ authorEmail: string;
+ subject: string;
+ status: string;
+ error?: string;
+ createdAt: string;
+}
+
+export async function getTicketEmails(ticketId: number) {
+ return fetchApi<{ sent: TicketSentEmail[]; received: TicketReceivedEmail[] }>(
+ `/api/helpdesk/ticket/${ticketId}/emails`
+ );
+}
+
// ==================== Knowledge Base ====================
export async function getKBArticles() {
return fetchApi