/** * Cliente API — Proceso comercial (Solicitudes/RFQ, tarifas, Cotizaciones). */ import { api } from '$lib/api'; // ---------- Tipos ---------- export type ServiceRequestStatus = 'nueva' | 'contacto' | 'en_analisis' | 'cotizada' | 'aceptada' | 'rechazada' | 'liberada'; export type QuoteStatus = 'borrador' | 'enviada' | 'aceptada' | 'rechazada'; export interface ServiceRequest { id: number; reference: string | null; account_id: number | null; opportunity_id: number | null; operation_type: string; transport_mode: string | null; service_type: string | null; incoterm: string | null; origin: string | null; destination: string | null; cargo_type: string | null; weight: number | null; volume: number | null; load_type: string | null; container_equipment: string | null; commodity: string | null; required_date: string | null; destination_agent_id: number | null; requirements: string | null; first_contact_at: string | null; first_contact_notes: string | null; status: ServiceRequestStatus; notes: string | null; owner_user_id: string | null; created_by: string | null; updated_by: string | null; tenant_id: number; company_id: number; created_at: string; updated_at: string; } export type ServiceRequestInput = Partial> & { operation_type: string; }; export interface RateRequest { id: number; service_request_id: number; supplier_id: number | null; concept: string; description: string | null; status: string; rate_amount: number | null; currency: string | null; valid_until: string | null; notes: string | null; tenant_id: number; company_id: number; created_at: string; updated_at: string; } export type RateRequestInput = Partial> & { service_request_id: number; concept: string; }; export interface Quote { id: number; reference: string | null; service_request_id: number | null; account_id: number | null; currency: string; status: QuoteStatus; issue_date: string | null; valid_until: string | null; total_cost: number; total_sale: number; margin: number; sent_at: string | null; accepted_at: string | null; rejected_at: string | null; notes: string | null; terms: string | null; owner_user_id: string | null; created_by: string | null; updated_by: string | null; tenant_id: number; company_id: number; created_at: string; updated_at: string; } export type QuoteInput = Partial>; export interface QuoteItem { id: number; quote_id: number; concept: string; description: string | null; supplier_id: number | null; quantity: number; unit_cost: number; unit_sale: number; currency: string | null; line_cost: number; line_sale: number; tenant_id: number; company_id: number; } export type QuoteItemInput = Partial> & { quote_id: number; concept: string; }; // ---------- Clientes ---------- function qp(companyId: number, extra?: Record) { const qs = new URLSearchParams({ company_id: String(companyId) }); for (const [k, v] of Object.entries(extra ?? {})) if (v !== undefined && v !== '') qs.set(k, String(v)); return qs.toString(); } async function unwrap(p: Promise<{ data?: T; error?: string }>): Promise { const res = await p; if (res.error) throw new Error(res.error); return res.data as T; } export const serviceRequestsAPI = { list: (companyId: number, params?: { search?: string; status?: string; operation_type?: string; account_id?: number }) => unwrap(api.get(`/v1/crm/service-requests?${qp(companyId, params)}`)), get: (id: number, companyId: number) => unwrap(api.get(`/v1/crm/service-requests/${id}?${qp(companyId)}`)), create: (data: ServiceRequestInput, companyId: number) => unwrap(api.post(`/v1/crm/service-requests?${qp(companyId)}`, data)), update: (id: number, data: Partial, companyId: number) => unwrap(api.patch(`/v1/crm/service-requests/${id}?${qp(companyId)}`, data)), registerContact: (id: number, companyId: number, notes?: string | null) => unwrap(api.post(`/v1/crm/service-requests/${id}/contact?${qp(companyId)}`, { notes })), requote: (id: number, companyId: number) => unwrap(api.post(`/v1/crm/service-requests/${id}/requote?${qp(companyId)}`, {})), fromOpportunity: (opportunityId: number, data: { operation_type: string; transport_mode?: string; service_type?: string; incoterm?: string; origin?: string; destination?: string; notes?: string | null }, companyId: number) => unwrap(api.post(`/v1/crm/service-requests/from-opportunity?${qp(companyId, { opportunity_id: opportunityId })}`, data)), remove: (id: number, companyId: number) => unwrap(api.delete(`/v1/crm/service-requests/${id}?${qp(companyId)}`)) }; export const rateRequestsAPI = { list: (companyId: number, serviceRequestId?: number) => unwrap(api.get(`/v1/crm/rate-requests?${qp(companyId, { service_request_id: serviceRequestId })}`)), create: (data: RateRequestInput, companyId: number) => unwrap(api.post(`/v1/crm/rate-requests?${qp(companyId)}`, data)), update: (id: number, data: Partial, companyId: number) => unwrap(api.patch(`/v1/crm/rate-requests/${id}?${qp(companyId)}`, data)), remove: (id: number, companyId: number) => unwrap(api.delete(`/v1/crm/rate-requests/${id}?${qp(companyId)}`)) }; export const quotesAPI = { list: (companyId: number, params?: { search?: string; status?: string; account_id?: number }) => unwrap(api.get(`/v1/crm/quotes?${qp(companyId, params)}`)), get: (id: number, companyId: number) => unwrap(api.get(`/v1/crm/quotes/${id}?${qp(companyId)}`)), create: (data: QuoteInput, companyId: number) => unwrap(api.post(`/v1/crm/quotes?${qp(companyId)}`, data)), update: (id: number, data: Partial, companyId: number) => unwrap(api.patch(`/v1/crm/quotes/${id}?${qp(companyId)}`, data)), send: (id: number, companyId: number) => unwrap(api.patch(`/v1/crm/quotes/${id}/send?${qp(companyId)}`, {})), accept: (id: number, companyId: number) => unwrap(api.patch(`/v1/crm/quotes/${id}/accept?${qp(companyId)}`, {})), reject: (id: number, companyId: number) => unwrap(api.patch(`/v1/crm/quotes/${id}/reject?${qp(companyId)}`, {})), clone: (id: number, companyId: number) => unwrap(api.post(`/v1/crm/quotes/${id}/clone?${qp(companyId)}`, {})), remove: (id: number, companyId: number) => unwrap(api.delete(`/v1/crm/quotes/${id}?${qp(companyId)}`)), items: (quoteId: number, companyId: number) => unwrap(api.get(`/v1/crm/quotes/${quoteId}/items?${qp(companyId)}`)), pdfBlob: (id: number, companyId: number) => (api as any).getBlob(`/v1/crm/quotes/${id}/pdf?${qp(companyId)}`) as Promise, sendEmail: (id: number, companyId: number, body: { to?: string | null; subject?: string | null; message?: string | null }) => unwrap<{ sent_to: string; reference: string }>(api.post(`/v1/crm/quotes/${id}/send-email?${qp(companyId)}`, body)) }; // ---------- Configuración de marca del formato de cotización ---------- export interface QuoteSettings { id?: number | null; emitter_name?: string | null; emitter_rfc?: string | null; emitter_address?: string | null; emitter_phone?: string | null; emitter_email?: string | null; emitter_website?: string | null; logo_file_key?: string | null; accent_color?: string | null; quote_prefix?: string | null; default_terms?: string | null; footer_note?: string | null; } export const quoteSettingsAPI = { get: (companyId: number) => unwrap(api.get(`/v1/crm/quote-settings?${qp(companyId)}`)), save: (companyId: number, data: QuoteSettings) => unwrap(api.put(`/v1/crm/quote-settings?${qp(companyId)}`, data)), logoUrl: (companyId: number) => unwrap<{ url: string | null }>(api.get(`/v1/crm/quote-settings/logo-url?${qp(companyId)}`)), async uploadLogo(companyId: number, file: File): Promise { const fd = new FormData(); fd.append('file', file); const res = await (api as any).request(`/v1/crm/quote-settings/logo?${qp(companyId)}`, { method: 'POST', body: fd }); if (res.error) throw new Error(res.error); return res.data as QuoteSettings; } }; // ---------- Catálogos de referencia (Incoterms, participantes) ---------- export interface Incoterm { code: string; name: string; } export interface ParticipantRole { code: string; label: string; source: string; } export interface Participant { id: number; source: string; name: string; role: string; roles: string[]; } export const catalogsAPI = { incoterms: (companyId: number) => unwrap(api.get(`/v1/crm/catalogs/incoterms?${qp(companyId)}`)), participantRoles: (companyId: number) => unwrap(api.get(`/v1/crm/catalogs/participant-roles?${qp(companyId)}`)), participants: (companyId: number, role?: string) => unwrap(api.get(`/v1/crm/participants?${qp(companyId, { role })}`)) }; export const quoteItemsAPI = { create: (data: QuoteItemInput, companyId: number) => unwrap(api.post(`/v1/crm/quote-items?${qp(companyId)}`, data)), update: (id: number, data: Partial, companyId: number) => unwrap(api.patch(`/v1/crm/quote-items/${id}?${qp(companyId)}`, data)), remove: (id: number, companyId: number) => unwrap(api.delete(`/v1/crm/quote-items/${id}?${qp(companyId)}`)) };