erp-core-frontend-web/src/services/api/crm.api.ts
rckrdmrd f1a9ea3d1f [TASK-2026-01-20-004] feat: Add API clients for sales, purchases, financial, CRM, projects
EPIC-P1-004: Frontend API clients
- Add sales.api.ts (orders, quotations, returns)
- Add purchases.api.ts (purchase orders, suppliers, receipts)
- Add financial.api.ts (accounts, journals, payments)
- Add crm.api.ts (leads, opportunities, campaigns)
- Add projects.api.ts (projects, tasks, timesheets)
- Update api/index.ts with new exports

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 04:06:32 -06:00

403 lines
11 KiB
TypeScript

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<PaginatedResponse<Lead>> => {
const response = await api.get<PaginatedResponse<Lead>>(
API_ENDPOINTS.CRM.LEADS,
{ params: filters }
);
return response.data;
},
getLeadById: async (id: string): Promise<Lead> => {
const response = await api.get<ApiResponse<Lead>>(
`${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<Lead> => {
const response = await api.post<ApiResponse<Lead>>(
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<Lead> => {
const response = await api.patch<ApiResponse<Lead>>(
`${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<ApiResponse<{ partner?: { id: string }; opportunity?: Opportunity }>>(
`${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<Lead> => {
const response = await api.post<ApiResponse<Lead>>(
`${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<Lead> => {
const response = await api.post<ApiResponse<Lead>>(
`${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<void> => {
const response = await api.delete<ApiResponse>(
`${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<PaginatedResponse<Opportunity>> => {
const response = await api.get<PaginatedResponse<Opportunity>>(
API_ENDPOINTS.CRM.OPPORTUNITIES,
{ params: filters }
);
return response.data;
},
getOpportunityById: async (id: string): Promise<Opportunity> => {
const response = await api.get<ApiResponse<Opportunity>>(
`${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<Opportunity> => {
const response = await api.post<ApiResponse<Opportunity>>(
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<Opportunity> => {
const response = await api.patch<ApiResponse<Opportunity>>(
`${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<Opportunity> => {
const response = await api.post<ApiResponse<Opportunity>>(
`${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<Opportunity> => {
const response = await api.post<ApiResponse<Opportunity>>(
`${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<void> => {
const response = await api.delete<ApiResponse>(
`${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<PipelineSummary> => {
const response = await api.get<ApiResponse<PipelineSummary>>(
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<CrmStage[]> => {
const response = await api.get<ApiResponse<CrmStage[]>>(
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;
},
};