import { API_URL, conAuth, manejar401 } from './auth' /** Departamentos a los que se puede dirigir una solicitud de soporte */ export const DEPARTAMENTOS_SOPORTE = [ 'Sistemas', 'Administración', 'Ventas', 'Cobranza', 'Escrituración', 'Dirección', ] as const /** Prioridades de una solicitud */ export const PRIORIDADES_SOPORTE = ['Baja', 'Media', 'Alta', 'Urgente'] as const /** Estados por los que pasa una solicitud (los mueve admin/manager) */ export const ESTADOS_SOPORTE = ['ABIERTA', 'EN_PROCESO', 'CERRADA'] as const export type DepartamentoSoporte = (typeof DEPARTAMENTOS_SOPORTE)[number] export type PrioridadSoporte = (typeof PRIORIDADES_SOPORTE)[number] export type EstadoSoporte = (typeof ESTADOS_SOPORTE)[number] /** Tipos y tamaños de adjunto aceptados (igual que el API) */ export const MIMES_ADJUNTO_SOPORTE = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png'] export const MAX_BYTES_ADJUNTO_SOPORTE = 10 * 1024 * 1024 export const MAX_ADJUNTOS_SOPORTE = 10 export interface SolicitudSoporte { id: string titulo: string descripcion: string departamento: string prioridad: string responsable: string | null fechaSolicitud: string fechaLimite: string | null notas: string | null /** Rutas relativas de los adjuntos dentro de uploads/ */ archivos: string[] solicitante: string solicitanteEmail: string | null estado: string createdAt: string updatedAt: string } export interface FiltrosSoporte { estado?: string departamento?: string } /** * Valida un adjunto antes de subirlo: devuelve el motivo del rechazo o null si * es válido (el API valida lo mismo, aquí es para no esperar la subida). */ export function validarAdjuntoSoporte(archivo: File): string | null { if (!MIMES_ADJUNTO_SOPORTE.includes(archivo.type)) { return `${archivo.name}: solo se aceptan PDF, JPG o PNG.` } if (archivo.size > MAX_BYTES_ADJUNTO_SOPORTE) { return `${archivo.name}: excede 10 MB.` } return null } /** Nombre legible de un adjunto: sin carpeta ni prefijo uuid */ export function nombreArchivoSoporte(ruta: string): string { const base = ruta.split(/[\\/]/).pop() ?? ruta return base.replace(/^[0-9a-f]{8}-/i, '') } /** Mensaje real del servidor: el error handler responde { error, issues? } */ async function mensajeDeError(resp: Response): Promise { const body = (await resp.json().catch(() => null)) as | { error?: string; issues?: Array<{ path?: Array; message?: string }> } | null const detalles = (body?.issues ?? []) .map((i) => [i.path?.join('.'), i.message].filter(Boolean).join(': ')) .filter((d) => d.length > 0) if (detalles.length > 0) return `${body?.error ?? 'Validación fallida'}: ${detalles.join('; ')}` return body?.error ?? `HTTP ${resp.status}` } /** POST /api/soporte — crear solicitud de soporte (multipart si hay adjuntos) */ export async function crearSolicitudSoporte(payload: { titulo: string descripcion: string departamento: DepartamentoSoporte prioridad: PrioridadSoporte responsable?: string fechaSolicitud: string fechaLimite?: string notas?: string solicitante?: string email?: string /** Adjuntos reales: viajan en el multipart, el API los guarda en disco */ archivos?: File[] }): Promise { const form = new FormData() form.set('titulo', payload.titulo) form.set('descripcion', payload.descripcion) form.set('departamento', payload.departamento) form.set('prioridad', payload.prioridad) form.set('fechaSolicitud', payload.fechaSolicitud) if (payload.responsable) form.set('responsable', payload.responsable) if (payload.fechaLimite) form.set('fechaLimite', payload.fechaLimite) if (payload.notas) form.set('notas', payload.notas) if (payload.solicitante) form.set('solicitante', payload.solicitante) if (payload.email) form.set('email', payload.email) for (const archivo of payload.archivos ?? []) form.append('archivos', archivo) // Sin Content-Type explícito: el navegador pone el boundary del multipart const resp = await fetch(`${API_URL}/soporte`, conAuth({ method: 'POST', body: form })) manejar401(resp) if (!resp.ok) throw new Error(await mensajeDeError(resp)) return (await resp.json()) as SolicitudSoporte } /** GET /api/soporte — solicitudes propias (admin/manager: todas) con filtros */ export async function listarSolicitudesSoporte(filtros: FiltrosSoporte = {}): Promise { const params = new URLSearchParams() if (filtros.estado) params.set('estado', filtros.estado) if (filtros.departamento) params.set('departamento', filtros.departamento) const qs = params.toString() const resp = await fetch(`${API_URL}/soporte${qs ? `?${qs}` : ''}`, conAuth()) manejar401(resp) if (!resp.ok) throw new Error(await mensajeDeError(resp)) return (await resp.json()) as SolicitudSoporte[] } /** PATCH /api/soporte/:id — cambiar el estado (admin/manager) */ export async function cambiarEstadoSolicitud(id: string, estado: EstadoSoporte): Promise { const resp = await fetch( `${API_URL}/soporte/${encodeURIComponent(id)}`, conAuth({ method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ estado }), }), ) manejar401(resp) if (!resp.ok) throw new Error(await mensajeDeError(resp)) return (await resp.json()) as SolicitudSoporte } /** GET /api/soporte/:id/archivos/:indice — descarga un adjunto (blob) */ export async function descargarArchivoSolicitud(id: string, indice: number): Promise { const resp = await fetch( `${API_URL}/soporte/${encodeURIComponent(id)}/archivos/${indice}`, conAuth(), ) manejar401(resp) if (!resp.ok) throw new Error(await mensajeDeError(resp)) return await resp.blob() }