import api from './axios-instance'; import { API_ENDPOINTS } from '@shared/constants/api-endpoints'; import type { BaseEntity } from '@shared/types/entities.types'; import type { ApiResponse, PaginatedResponse, PaginationParams } from '@shared/types/api.types'; // ============================================================================ // Interfaces // ============================================================================ export interface Lead extends BaseEntity { name: string; contactName?: string; email?: string; phone?: string; mobile?: string; companyName?: string; title?: string; street?: string; city?: string; stateId?: string; countryId?: string; website?: string; description?: string; source?: string; medium?: string; campaign?: string; priority: 'low' | 'medium' | 'high' | 'very_high'; status: 'new' | 'contacted' | 'qualified' | 'proposition' | 'won' | 'lost'; stageId?: string; stageName?: string; userId?: string; userName?: string; teamId?: string; expectedRevenue?: number; probability?: number; lostReason?: string; tags?: string[]; tenantId: string; } export interface Opportunity extends BaseEntity { name: string; partnerId: string; partnerName?: string; contactName?: string; email?: string; phone?: string; description?: string; priority: 'low' | 'medium' | 'high' | 'very_high'; status: 'new' | 'qualified' | 'proposition' | 'negotiation' | 'won' | 'lost'; stageId: string; stageName?: string; userId?: string; userName?: string; teamId?: string; expectedRevenue: number; probability: number; expectedCloseDate?: string; lostReason?: string; tags?: string[]; leadId?: string; tenantId: string; } export interface CrmStage extends BaseEntity { name: string; sequence: number; probability: number; isFolded: boolean; requirements?: string; teamId?: string; tenantId: string; } export interface PipelineSummary { stages: { id: string; name: string; sequence: number; count: number; totalRevenue: number; weightedRevenue: number; }[]; totals: { leads: number; opportunities: number; totalRevenue: number; weightedRevenue: number; }; } // ============================================================================ // DTOs // ============================================================================ export interface CreateLeadDto { name: string; contactName?: string; email?: string; phone?: string; mobile?: string; companyName?: string; title?: string; street?: string; city?: string; stateId?: string; countryId?: string; website?: string; description?: string; source?: string; medium?: string; campaign?: string; priority?: Lead['priority']; stageId?: string; userId?: string; teamId?: string; expectedRevenue?: number; probability?: number; tags?: string[]; } export interface UpdateLeadDto { name?: string; contactName?: string; email?: string; phone?: string; mobile?: string; companyName?: string; title?: string; street?: string; city?: string; stateId?: string; countryId?: string; website?: string; description?: string; source?: string; medium?: string; campaign?: string; priority?: Lead['priority']; status?: Lead['status']; stageId?: string; userId?: string; teamId?: string; expectedRevenue?: number; probability?: number; lostReason?: string; tags?: string[]; } export interface CreateOpportunityDto { name: string; partnerId: string; contactName?: string; email?: string; phone?: string; description?: string; priority?: Opportunity['priority']; stageId: string; userId?: string; teamId?: string; expectedRevenue: number; probability?: number; expectedCloseDate?: string; tags?: string[]; leadId?: string; } export interface UpdateOpportunityDto { name?: string; partnerId?: string; contactName?: string; email?: string; phone?: string; description?: string; priority?: Opportunity['priority']; status?: Opportunity['status']; stageId?: string; userId?: string; teamId?: string; expectedRevenue?: number; probability?: number; expectedCloseDate?: string; lostReason?: string; tags?: string[]; } export interface ConvertLeadDto { createPartner: boolean; partnerId?: string; createOpportunity: boolean; opportunityName?: string; } // ============================================================================ // Filters // ============================================================================ export interface LeadFilters extends PaginationParams { status?: Lead['status']; priority?: Lead['priority']; stageId?: string; userId?: string; teamId?: string; source?: string; dateFrom?: string; dateTo?: string; } export interface OpportunityFilters extends PaginationParams { status?: Opportunity['status']; priority?: Opportunity['priority']; stageId?: string; partnerId?: string; userId?: string; teamId?: string; dateFrom?: string; dateTo?: string; minRevenue?: number; maxRevenue?: number; } // ============================================================================ // API Client // ============================================================================ export const crmApi = { // Leads getLeads: async (filters?: LeadFilters): Promise> => { const response = await api.get>( API_ENDPOINTS.CRM.LEADS, { params: filters } ); return response.data; }, getLeadById: async (id: string): Promise => { const response = await api.get>( `${API_ENDPOINTS.CRM.LEADS}/${id}` ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Lead no encontrado'); } return response.data.data; }, createLead: async (data: CreateLeadDto): Promise => { const response = await api.post>( API_ENDPOINTS.CRM.LEADS, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al crear lead'); } return response.data.data; }, updateLead: async (id: string, data: UpdateLeadDto): Promise => { const response = await api.patch>( `${API_ENDPOINTS.CRM.LEADS}/${id}`, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al actualizar lead'); } return response.data.data; }, convertLead: async (id: string, data: ConvertLeadDto): Promise<{ partner?: { id: string }; opportunity?: Opportunity }> => { const response = await api.post>( `${API_ENDPOINTS.CRM.LEADS}/${id}/convert`, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al convertir lead'); } return response.data.data; }, markLeadAsWon: async (id: string): Promise => { const response = await api.post>( `${API_ENDPOINTS.CRM.LEADS}/${id}/won` ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al marcar lead como ganado'); } return response.data.data; }, markLeadAsLost: async (id: string, reason?: string): Promise => { const response = await api.post>( `${API_ENDPOINTS.CRM.LEADS}/${id}/lost`, { reason } ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al marcar lead como perdido'); } return response.data.data; }, deleteLead: async (id: string): Promise => { const response = await api.delete( `${API_ENDPOINTS.CRM.LEADS}/${id}` ); if (!response.data.success) { throw new Error(response.data.error || 'Error al eliminar lead'); } }, // Opportunities getOpportunities: async (filters?: OpportunityFilters): Promise> => { const response = await api.get>( API_ENDPOINTS.CRM.OPPORTUNITIES, { params: filters } ); return response.data; }, getOpportunityById: async (id: string): Promise => { const response = await api.get>( `${API_ENDPOINTS.CRM.OPPORTUNITIES}/${id}` ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Oportunidad no encontrada'); } return response.data.data; }, createOpportunity: async (data: CreateOpportunityDto): Promise => { const response = await api.post>( API_ENDPOINTS.CRM.OPPORTUNITIES, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al crear oportunidad'); } return response.data.data; }, updateOpportunity: async (id: string, data: UpdateOpportunityDto): Promise => { const response = await api.patch>( `${API_ENDPOINTS.CRM.OPPORTUNITIES}/${id}`, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al actualizar oportunidad'); } return response.data.data; }, markOpportunityAsWon: async (id: string): Promise => { const response = await api.post>( `${API_ENDPOINTS.CRM.OPPORTUNITIES}/${id}/won` ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al marcar oportunidad como ganada'); } return response.data.data; }, markOpportunityAsLost: async (id: string, reason?: string): Promise => { const response = await api.post>( `${API_ENDPOINTS.CRM.OPPORTUNITIES}/${id}/lost`, { reason } ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al marcar oportunidad como perdida'); } return response.data.data; }, deleteOpportunity: async (id: string): Promise => { const response = await api.delete( `${API_ENDPOINTS.CRM.OPPORTUNITIES}/${id}` ); if (!response.data.success) { throw new Error(response.data.error || 'Error al eliminar oportunidad'); } }, // Pipeline getPipeline: async (teamId?: string): Promise => { const response = await api.get>( API_ENDPOINTS.CRM.PIPELINE, { params: teamId ? { teamId } : undefined } ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al obtener pipeline'); } return response.data.data; }, // Stages getStages: async (): Promise => { const response = await api.get>( API_ENDPOINTS.CRM.STAGES ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al obtener etapas'); } return response.data.data; }, };