const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000"; async function fetchApi( path: string, options: RequestInit = {} ): Promise { const token = typeof window !== "undefined" ? localStorage.getItem("helpdesk_token") : null; const headers: Record = { "Content-Type": "application/json", ...((options.headers as Record) || {}), }; if (token) { headers["Authorization"] = `Bearer ${token}`; } const res = await fetch(`${API_BASE}${path}`, { ...options, headers }); if (!res.ok) { const errorData = await res.json().catch(() => ({})); throw new Error(errorData.message || `API Error: ${res.status}`); } if (res.status === 204) return undefined as T; return res.json(); } // ==================== Auth ==================== export async function login(idUsuario: string, password: string) { return fetchApi<{ success: boolean; message?: string; token?: string; user?: HelpDeskUser }>( "/api/helpdesk/login", { method: "POST", body: JSON.stringify({ idUsuario, password }) } ); } export async function getMe() { return fetchApi("/api/helpdesk/me"); } // ==================== Tickets ==================== export async function getTickets(params?: { status?: string | string[]; priority?: string; assigneeId?: number; requesterId?: number; groupId?: number; search?: string; page?: number; pageSize?: number; }) { const query = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value === undefined || value === "" || value === null) return; // El filtro de estados acepta uno o varios: se envía como lista // separada por comas (ej: status=Open,Resolved). Vacío = Todos. if (Array.isArray(value)) { if (value.length === 0) return; query.set(key, value.join(",")); return; } query.set(key, String(value)); }); } return fetchApi(`/api/helpdesk/tickets?${query.toString()}`); } export async function getTicket(id: number) { return fetchApi(`/api/helpdesk/ticket/${id}`); } export async function createTicket(data: { title: string; description: string; priority?: string; assigneeId?: number; groupId?: number; slaPolicyId?: number; }) { return fetchApi<{ id: number }>("/api/helpdesk/tickets", { method: "POST", body: JSON.stringify(data), }); } export async function updateTicket(id: number, data: { title?: string; description?: string; priority?: string; assigneeId?: number; groupId?: number; }) { return fetchApi(`/api/helpdesk/ticket/${id}`, { method: "PUT", body: JSON.stringify(data), }); } export async function deleteTicket(id: number) { return fetchApi(`/api/helpdesk/ticket/${id}`, { method: "DELETE" }); } export async function updateTicketStatus(id: number, status: string) { return fetchApi(`/api/helpdesk/ticket/${id}/status`, { method: "PUT", body: JSON.stringify({ status }), }); } export async function assignTicket(id: number, assigneeId: number, groupId?: number) { return fetchApi(`/api/helpdesk/ticket/${id}/assign`, { method: "PUT", body: JSON.stringify({ assigneeId, groupId }), }); } export async function getTicketStats() { return fetchApi("/api/helpdesk/stats"); } // ==================== Comments ==================== export async function getComments(ticketId: number) { return fetchApi(`/api/helpdesk/comments/${ticketId}`); } export async function addComment( ticketId: number, content: string, isInternal: boolean = false ) { return fetchApi<{ id: number }>(`/api/helpdesk/comments/${ticketId}`, { method: "POST", body: JSON.stringify({ content, isInternal }), }); } // ==================== 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; body?: string; createdAt: string; } export interface TicketReceivedEmail { authorId?: string; authorEmail: string; subject: string; status: string; error?: string; body?: string; commentId?: number; 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("/api/helpdesk/kb/published"); } export async function searchKB(query: string) { return fetchApi(`/api/helpdesk/kb/search?q=${encodeURIComponent(query)}`); } export async function getKBCategories() { return fetchApi("/api/helpdesk/kb/categories"); } // ==================== Groups & SLA ==================== export async function getGroups() { return fetchApi("/api/helpdesk/groups"); } export async function getSLAPolicies() { return fetchApi("/api/helpdesk/sla"); } // ==================== Types ==================== export interface HelpDeskUser { idUsuario: string; nombres: string; apPaterno: string; apMaterno?: string; email: string; tipo?: string; activo: number; fono?: string; idSede?: number; role?: string; departmentId?: number; departmentName?: string; } export interface Ticket { id: number; title: string; description: string; status: string; priority: string; ticketNumber: string; requesterId: number; requesterName?: string; requesterEmail?: string; assigneeId?: number; assigneeName?: string; groupId?: number; groupName?: string; slaPolicyId?: number; createdAt: string; updatedAt: string; firstResponseAt?: string; resolvedAt?: string; closedAt?: string; } export interface TicketListResult { items: Ticket[]; total: number; page: number; pageSize: number; totalPages: number; } export interface Comment { id: number; ticketId: number; userId: string; userName?: string; content: string; isInternal: boolean; createdAt: string; } export interface Group { id: number; name: string; description?: string; isActive: boolean; notificationEmail?: string | null; } export interface KBArticle { id: number; title: string; content: string; categoryId: number; categoryName?: string; authorId: string; authorName?: string; isPublished: boolean; viewCount: number; createdAt: string; updatedAt: string; } export interface KBCategory { id: number; name: string; description?: string; sortOrder: number; } export interface SLAPolicy { id: number; name: string; description?: string; responseTimeHours: number; resolutionTimeHours: number; isActive: boolean; } export interface DashboardStats { totalTickets: number; openTickets: number; inProgressTickets: number; resolvedTickets: number; closedTickets: number; unassignedTickets: number; criticalTickets: number; dailyCounts: { date: string; count: number }[]; } // ==================== Departamentos ==================== export async function createGroup(data: { name: string; description?: string; notificationEmail?: string | null; }) { return fetchApi("/api/helpdesk/groups", { method: "POST", body: JSON.stringify(data), }); } export async function updateGroup( id: number, data: { name?: string; description?: string; isActive?: boolean; notificationEmail?: string | null; } ) { return fetchApi(`/api/helpdesk/groups/${id}`, { method: "PUT", body: JSON.stringify(data), }); } export async function deleteGroup(id: number) { return fetchApi(`/api/helpdesk/groups/${id}`, { method: "DELETE" }); } // ==================== Equipo ==================== export interface TeamMember extends HelpDeskUser { role: string; departmentId?: number; departmentName?: string; isActive?: boolean; } export async function getTeam() { return fetchApi("/api/helpdesk/users"); } export async function addMember(data: { idUsuario: string; role?: string; departmentId?: number; }) { return fetchApi("/api/helpdesk/users", { method: "POST", body: JSON.stringify(data), }); } export async function updateMember( idUsuario: string, data: { role?: string; departmentId?: number; isActive?: boolean } ) { return fetchApi(`/api/helpdesk/users/${encodeURIComponent(idUsuario)}`, { method: "PUT", body: JSON.stringify(data), }); } // ==================== SLA ==================== export async function createSla(data: { name: string; description?: string; responseTimeHours?: number; resolutionTimeHours?: number; }) { return fetchApi("/api/helpdesk/sla", { method: "POST", body: JSON.stringify(data), }); } export async function updateSla( id: number, data: { name?: string; description?: string; responseTimeHours?: number; resolutionTimeHours?: number; isActive?: boolean; } ) { return fetchApi(`/api/helpdesk/sla/${id}`, { method: "PUT", body: JSON.stringify(data), }); } // ==================== Correo / plantillas (banco único mail.* vía /api/email/*) ==================== export interface EmailTemplate { key: string; name: string; subject: string; bodyHtml: string; module?: string; isActive: boolean; } export interface EmailLogEntry { id: number; template: string; module?: string; ticketId?: number; recipient: string; subject: string; status: string; error?: string; createdAt: string; } export async function getEmailTemplates() { return fetchApi("/api/email/templates"); } export async function createEmailTemplate(data: { key: string; name: string; subject: string; bodyHtml: string; module?: string; }) { return fetchApi<{ key: string }>("/api/email/templates", { method: "POST", body: JSON.stringify(data), }); } export async function updateEmailTemplate( key: string, data: { subject?: string; bodyHtml?: string; isActive?: boolean } ) { return fetchApi(`/api/email/templates/${encodeURIComponent(key)}`, { method: "PUT", body: JSON.stringify(data), }); } export async function getEmailLog(limit = 50, module = "helpdesk") { const query = new URLSearchParams({ limit: String(limit) }); if (module) query.set("module", module); return fetchApi(`/api/email/log?${query.toString()}`); } // Envío vía route handler propio (/api/correos) → POST /api/email/send con // X-Module + X-Api-Key en el servidor (la clave nunca va en el bundle). // Reenvía el Bearer de admin para que el route verifique el rol. export async function sendModuleEmail(data: { to: string | string[]; templateKey?: string; subject?: string; htmlBody?: string; vars?: Record; }) { const token = typeof window !== "undefined" ? localStorage.getItem("helpdesk_token") : null; const res = await fetch("/api/correos", { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify(data), }); const payload = await res.json().catch(() => ({})); if (!res.ok) { throw new Error( (payload as { message?: string }).message || `Error de envío: ${res.status}` ); } return payload as { success: boolean; sent?: number; failed?: number; message?: string }; } export async function sendTestEmail(to: string) { return sendModuleEmail({ to, subject: "[HelpDesk] Prueba de correo", htmlBody: `

Prueba de envío desde la Mesa de Ayuda ICHN (${new Date().toISOString().slice(0, 16).replace("T", " ")} UTC).

Vía banco mail.templates + POST /api/email/send (módulo helpdesk).

`, }); } export async function syncInboundEmail() { return fetchApi<{ success: boolean; checkedCount: number; inserted: number; skipped: number; error?: string; }>("/api/helpdesk/email/inbound/sync", { method: "POST" }); } // ==================== Notificaciones in-app (estándar multi-módulo) ==================== export interface AppNotification { id: number; module: string; ticketId?: number; notifRef?: string; type: string; title: string; body?: string; isRead: boolean; createdAt: string; } export async function getNotifications(limit = 50, module = "", unreadOnly = false) { const query = new URLSearchParams({ limit: String(limit) }); if (module) query.set("module", module); if (unreadOnly) query.set("unreadOnly", "true"); return fetchApi(`/api/notifications?${query.toString()}`); } export async function getUnreadCount(module = "") { const query = new URLSearchParams(); if (module) query.set("module", module); const qs = query.toString(); return fetchApi<{ count: number }>(`/api/notifications/unread-count${qs ? `?${qs}` : ""}`); } export async function markNotificationRead(id: number) { return fetchApi<{ id: number }>(`/api/notifications/${id}/read`, { method: "PUT", }); } export async function markAllNotificationsRead(module = "") { const query = new URLSearchParams(); if (module) query.set("module", module); const qs = query.toString(); return fetchApi<{ updated: number }>(`/api/notifications/read-all${qs ? `?${qs}` : ""}`, { method: "PUT", }); }