[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>
This commit is contained in:
rckrdmrd 2026-01-20 04:06:32 -06:00
parent eb144dc3ea
commit f1a9ea3d1f
6 changed files with 2012 additions and 0 deletions

402
src/services/api/crm.api.ts Normal file
View File

@ -0,0 +1,402 @@
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;
},
};

View File

@ -0,0 +1,559 @@
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 Account extends BaseEntity {
code: string;
name: string;
type: 'asset' | 'liability' | 'equity' | 'income' | 'expense';
accountType: string;
parentId?: string;
currencyId?: string;
reconcile: boolean;
isActive: boolean;
balance?: number;
tenantId: string;
}
export interface Journal extends BaseEntity {
code: string;
name: string;
type: 'sale' | 'purchase' | 'cash' | 'bank' | 'general';
defaultDebitAccountId?: string;
defaultCreditAccountId?: string;
currencyId?: string;
isActive: boolean;
tenantId: string;
}
export interface JournalEntryLine {
id?: string;
accountId: string;
accountName?: string;
partnerId?: string;
partnerName?: string;
name?: string;
debit: number;
credit: number;
currencyId?: string;
amountCurrency?: number;
}
export interface JournalEntry extends BaseEntity {
name: string;
journalId: string;
journalName?: string;
date: string;
ref?: string;
status: 'draft' | 'posted' | 'cancelled';
lines: JournalEntryLine[];
tenantId: string;
}
export interface InvoiceLine {
id?: string;
productId?: string;
productName?: string;
accountId: string;
name: string;
quantity: number;
unitPrice: number;
discount?: number;
taxIds?: string[];
subtotal?: number;
}
export interface Invoice extends BaseEntity {
name: string;
type: 'out_invoice' | 'out_refund' | 'in_invoice' | 'in_refund';
partnerId: string;
partnerName?: string;
dateInvoice: string;
dateDue?: string;
journalId: string;
status: 'draft' | 'open' | 'paid' | 'cancelled';
paymentState?: 'not_paid' | 'partial' | 'paid';
lines: InvoiceLine[];
amountUntaxed: number;
amountTax: number;
amountTotal: number;
amountResidual: number;
currencyId?: string;
notes?: string;
tenantId: string;
}
export interface Payment extends BaseEntity {
name: string;
partnerId: string;
partnerName?: string;
paymentType: 'inbound' | 'outbound' | 'transfer';
paymentMethod: 'manual' | 'check' | 'bank_transfer' | 'electronic';
journalId: string;
journalName?: string;
date: string;
amount: number;
currencyId?: string;
memo?: string;
status: 'draft' | 'posted' | 'reconciled' | 'cancelled';
invoiceIds?: string[];
tenantId: string;
}
// ============================================================================
// DTOs
// ============================================================================
export interface CreateAccountDto {
code: string;
name: string;
type: Account['type'];
accountType: string;
parentId?: string;
currencyId?: string;
reconcile?: boolean;
isActive?: boolean;
}
export interface UpdateAccountDto {
code?: string;
name?: string;
type?: Account['type'];
accountType?: string;
parentId?: string;
currencyId?: string;
reconcile?: boolean;
isActive?: boolean;
}
export interface CreateJournalDto {
code: string;
name: string;
type: Journal['type'];
defaultDebitAccountId?: string;
defaultCreditAccountId?: string;
currencyId?: string;
isActive?: boolean;
}
export interface UpdateJournalDto {
code?: string;
name?: string;
type?: Journal['type'];
defaultDebitAccountId?: string;
defaultCreditAccountId?: string;
currencyId?: string;
isActive?: boolean;
}
export interface CreateJournalEntryDto {
journalId: string;
date: string;
ref?: string;
lines: Omit<JournalEntryLine, 'id' | 'accountName' | 'partnerName'>[];
}
export interface CreateInvoiceDto {
type: Invoice['type'];
partnerId: string;
dateInvoice?: string;
dateDue?: string;
journalId: string;
lines: Omit<InvoiceLine, 'id' | 'productName' | 'subtotal'>[];
currencyId?: string;
notes?: string;
}
export interface UpdateInvoiceDto {
partnerId?: string;
dateInvoice?: string;
dateDue?: string;
journalId?: string;
lines?: Omit<InvoiceLine, 'productName' | 'subtotal'>[];
currencyId?: string;
notes?: string;
}
export interface CreatePaymentDto {
partnerId: string;
paymentType: Payment['paymentType'];
paymentMethod: Payment['paymentMethod'];
journalId: string;
date?: string;
amount: number;
currencyId?: string;
memo?: string;
invoiceIds?: string[];
}
export interface UpdatePaymentDto {
partnerId?: string;
paymentMethod?: Payment['paymentMethod'];
journalId?: string;
date?: string;
amount?: number;
currencyId?: string;
memo?: string;
invoiceIds?: string[];
}
// ============================================================================
// Filters
// ============================================================================
export interface AccountFilters extends PaginationParams {
type?: Account['type'];
isActive?: boolean;
code?: string;
}
export interface JournalFilters extends PaginationParams {
type?: Journal['type'];
isActive?: boolean;
}
export interface JournalEntryFilters extends PaginationParams {
journalId?: string;
status?: JournalEntry['status'];
dateFrom?: string;
dateTo?: string;
}
export interface InvoiceFilters extends PaginationParams {
type?: Invoice['type'];
partnerId?: string;
status?: Invoice['status'];
paymentState?: Invoice['paymentState'];
dateFrom?: string;
dateTo?: string;
}
export interface PaymentFilters extends PaginationParams {
partnerId?: string;
paymentType?: Payment['paymentType'];
status?: Payment['status'];
journalId?: string;
dateFrom?: string;
dateTo?: string;
}
// ============================================================================
// API Client
// ============================================================================
export const financialApi = {
// Accounts
getAccounts: async (filters?: AccountFilters): Promise<PaginatedResponse<Account>> => {
const response = await api.get<PaginatedResponse<Account>>(
API_ENDPOINTS.FINANCIAL.ACCOUNTS,
{ params: filters }
);
return response.data;
},
getAccountById: async (id: string): Promise<Account> => {
const response = await api.get<ApiResponse<Account>>(
`${API_ENDPOINTS.FINANCIAL.ACCOUNTS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Cuenta contable no encontrada');
}
return response.data.data;
},
createAccount: async (data: CreateAccountDto): Promise<Account> => {
const response = await api.post<ApiResponse<Account>>(
API_ENDPOINTS.FINANCIAL.ACCOUNTS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear cuenta contable');
}
return response.data.data;
},
updateAccount: async (id: string, data: UpdateAccountDto): Promise<Account> => {
const response = await api.patch<ApiResponse<Account>>(
`${API_ENDPOINTS.FINANCIAL.ACCOUNTS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar cuenta contable');
}
return response.data.data;
},
deleteAccount: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.FINANCIAL.ACCOUNTS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar cuenta contable');
}
},
// Journals
getJournals: async (filters?: JournalFilters): Promise<PaginatedResponse<Journal>> => {
const response = await api.get<PaginatedResponse<Journal>>(
API_ENDPOINTS.FINANCIAL.JOURNALS,
{ params: filters }
);
return response.data;
},
getJournalById: async (id: string): Promise<Journal> => {
const response = await api.get<ApiResponse<Journal>>(
`${API_ENDPOINTS.FINANCIAL.JOURNALS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Diario contable no encontrado');
}
return response.data.data;
},
createJournal: async (data: CreateJournalDto): Promise<Journal> => {
const response = await api.post<ApiResponse<Journal>>(
API_ENDPOINTS.FINANCIAL.JOURNALS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear diario contable');
}
return response.data.data;
},
updateJournal: async (id: string, data: UpdateJournalDto): Promise<Journal> => {
const response = await api.patch<ApiResponse<Journal>>(
`${API_ENDPOINTS.FINANCIAL.JOURNALS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar diario contable');
}
return response.data.data;
},
deleteJournal: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.FINANCIAL.JOURNALS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar diario contable');
}
},
// Journal Entries (Moves)
getJournalEntries: async (filters?: JournalEntryFilters): Promise<PaginatedResponse<JournalEntry>> => {
const response = await api.get<PaginatedResponse<JournalEntry>>(
API_ENDPOINTS.FINANCIAL.MOVES,
{ params: filters }
);
return response.data;
},
getJournalEntryById: async (id: string): Promise<JournalEntry> => {
const response = await api.get<ApiResponse<JournalEntry>>(
`${API_ENDPOINTS.FINANCIAL.MOVES}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Asiento contable no encontrado');
}
return response.data.data;
},
createJournalEntry: async (data: CreateJournalEntryDto): Promise<JournalEntry> => {
const response = await api.post<ApiResponse<JournalEntry>>(
API_ENDPOINTS.FINANCIAL.MOVES,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear asiento contable');
}
return response.data.data;
},
postJournalEntry: async (id: string): Promise<JournalEntry> => {
const response = await api.post<ApiResponse<JournalEntry>>(
`${API_ENDPOINTS.FINANCIAL.MOVES}/${id}/post`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al publicar asiento contable');
}
return response.data.data;
},
cancelJournalEntry: async (id: string): Promise<JournalEntry> => {
const response = await api.post<ApiResponse<JournalEntry>>(
`${API_ENDPOINTS.FINANCIAL.MOVES}/${id}/cancel`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al cancelar asiento contable');
}
return response.data.data;
},
deleteJournalEntry: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.FINANCIAL.MOVES}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar asiento contable');
}
},
// Invoices
getInvoices: async (filters?: InvoiceFilters): Promise<PaginatedResponse<Invoice>> => {
const response = await api.get<PaginatedResponse<Invoice>>(
API_ENDPOINTS.FINANCIAL.INVOICES,
{ params: filters }
);
return response.data;
},
getInvoiceById: async (id: string): Promise<Invoice> => {
const response = await api.get<ApiResponse<Invoice>>(
`${API_ENDPOINTS.FINANCIAL.INVOICES}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Factura no encontrada');
}
return response.data.data;
},
createInvoice: async (data: CreateInvoiceDto): Promise<Invoice> => {
const response = await api.post<ApiResponse<Invoice>>(
API_ENDPOINTS.FINANCIAL.INVOICES,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear factura');
}
return response.data.data;
},
updateInvoice: async (id: string, data: UpdateInvoiceDto): Promise<Invoice> => {
const response = await api.patch<ApiResponse<Invoice>>(
`${API_ENDPOINTS.FINANCIAL.INVOICES}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar factura');
}
return response.data.data;
},
validateInvoice: async (id: string): Promise<Invoice> => {
const response = await api.post<ApiResponse<Invoice>>(
`${API_ENDPOINTS.FINANCIAL.INVOICES}/${id}/validate`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al validar factura');
}
return response.data.data;
},
cancelInvoice: async (id: string): Promise<Invoice> => {
const response = await api.post<ApiResponse<Invoice>>(
`${API_ENDPOINTS.FINANCIAL.INVOICES}/${id}/cancel`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al cancelar factura');
}
return response.data.data;
},
deleteInvoice: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.FINANCIAL.INVOICES}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar factura');
}
},
// Payments
getPayments: async (filters?: PaymentFilters): Promise<PaginatedResponse<Payment>> => {
const response = await api.get<PaginatedResponse<Payment>>(
API_ENDPOINTS.FINANCIAL.PAYMENTS,
{ params: filters }
);
return response.data;
},
getPaymentById: async (id: string): Promise<Payment> => {
const response = await api.get<ApiResponse<Payment>>(
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Pago no encontrado');
}
return response.data.data;
},
createPayment: async (data: CreatePaymentDto): Promise<Payment> => {
const response = await api.post<ApiResponse<Payment>>(
API_ENDPOINTS.FINANCIAL.PAYMENTS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear pago');
}
return response.data.data;
},
updatePayment: async (id: string, data: UpdatePaymentDto): Promise<Payment> => {
const response = await api.patch<ApiResponse<Payment>>(
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar pago');
}
return response.data.data;
},
postPayment: async (id: string): Promise<Payment> => {
const response = await api.post<ApiResponse<Payment>>(
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}/post`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al publicar pago');
}
return response.data.data;
},
reconcilePayment: async (id: string, invoiceIds: string[]): Promise<Payment> => {
const response = await api.post<ApiResponse<Payment>>(
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}/reconcile`,
{ invoiceIds }
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al reconciliar pago');
}
return response.data.data;
},
cancelPayment: async (id: string): Promise<Payment> => {
const response = await api.post<ApiResponse<Payment>>(
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}/cancel`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al cancelar pago');
}
return response.data.data;
},
deletePayment: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.FINANCIAL.PAYMENTS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar pago');
}
},
};

View File

@ -5,3 +5,8 @@ export * from './products.api';
export * from './inventory.api';
export * from './warehouses.api';
export * from './billing.api';
export * from './sales.api';
export * from './purchases.api';
export * from './financial.api';
export * from './crm.api';
export * from './projects.api';

View File

@ -0,0 +1,493 @@
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 Project extends BaseEntity {
name: string;
code?: string;
description?: string;
partnerId?: string;
partnerName?: string;
userId?: string;
userName?: string;
teamId?: string;
status: 'draft' | 'active' | 'on_hold' | 'completed' | 'cancelled';
priority: 'low' | 'medium' | 'high' | 'critical';
dateStart?: string;
dateEnd?: string;
plannedHours?: number;
effectiveHours?: number;
progress?: number;
color?: string;
isTemplate?: boolean;
allowTimesheets: boolean;
privacyVisibility: 'employees' | 'followers' | 'portal';
tags?: string[];
tenantId: string;
}
export interface Task extends BaseEntity {
name: string;
projectId: string;
projectName?: string;
description?: string;
parentId?: string;
userId?: string;
userName?: string;
assigneeIds?: string[];
stageId?: string;
stageName?: string;
status: 'new' | 'in_progress' | 'done' | 'cancelled';
priority: 'low' | 'medium' | 'high' | 'critical';
dateStart?: string;
dateDeadline?: string;
dateEnd?: string;
plannedHours?: number;
effectiveHours?: number;
remainingHours?: number;
progress?: number;
sequence?: number;
color?: string;
tags?: string[];
milestoneId?: string;
milestoneName?: string;
tenantId: string;
}
export interface Timesheet extends BaseEntity {
projectId: string;
projectName?: string;
taskId?: string;
taskName?: string;
userId: string;
userName?: string;
date: string;
hours: number;
description?: string;
isBillable: boolean;
unitAmount?: number;
tenantId: string;
}
export interface ProjectStage extends BaseEntity {
name: string;
sequence: number;
isFolded: boolean;
projectIds?: string[];
tenantId: string;
}
export interface Milestone extends BaseEntity {
name: string;
projectId: string;
projectName?: string;
dateDeadline?: string;
isReached: boolean;
reachedDate?: string;
description?: string;
tenantId: string;
}
// ============================================================================
// DTOs
// ============================================================================
export interface CreateProjectDto {
name: string;
code?: string;
description?: string;
partnerId?: string;
userId?: string;
teamId?: string;
priority?: Project['priority'];
dateStart?: string;
dateEnd?: string;
plannedHours?: number;
color?: string;
isTemplate?: boolean;
allowTimesheets?: boolean;
privacyVisibility?: Project['privacyVisibility'];
tags?: string[];
}
export interface UpdateProjectDto {
name?: string;
code?: string;
description?: string;
partnerId?: string;
userId?: string;
teamId?: string;
status?: Project['status'];
priority?: Project['priority'];
dateStart?: string;
dateEnd?: string;
plannedHours?: number;
color?: string;
allowTimesheets?: boolean;
privacyVisibility?: Project['privacyVisibility'];
tags?: string[];
}
export interface CreateTaskDto {
name: string;
projectId: string;
description?: string;
parentId?: string;
userId?: string;
assigneeIds?: string[];
stageId?: string;
priority?: Task['priority'];
dateStart?: string;
dateDeadline?: string;
plannedHours?: number;
sequence?: number;
color?: string;
tags?: string[];
milestoneId?: string;
}
export interface UpdateTaskDto {
name?: string;
description?: string;
parentId?: string;
userId?: string;
assigneeIds?: string[];
stageId?: string;
status?: Task['status'];
priority?: Task['priority'];
dateStart?: string;
dateDeadline?: string;
dateEnd?: string;
plannedHours?: number;
sequence?: number;
color?: string;
tags?: string[];
milestoneId?: string;
}
export interface CreateTimesheetDto {
projectId: string;
taskId?: string;
date: string;
hours: number;
description?: string;
isBillable?: boolean;
unitAmount?: number;
}
export interface UpdateTimesheetDto {
projectId?: string;
taskId?: string;
date?: string;
hours?: number;
description?: string;
isBillable?: boolean;
unitAmount?: number;
}
export interface CreateMilestoneDto {
name: string;
projectId: string;
dateDeadline?: string;
description?: string;
}
export interface UpdateMilestoneDto {
name?: string;
dateDeadline?: string;
description?: string;
isReached?: boolean;
}
// ============================================================================
// Filters
// ============================================================================
export interface ProjectFilters extends PaginationParams {
status?: Project['status'];
priority?: Project['priority'];
partnerId?: string;
userId?: string;
teamId?: string;
isTemplate?: boolean;
dateFrom?: string;
dateTo?: string;
}
export interface TaskFilters extends PaginationParams {
projectId?: string;
status?: Task['status'];
priority?: Task['priority'];
stageId?: string;
userId?: string;
assigneeId?: string;
milestoneId?: string;
dateFrom?: string;
dateTo?: string;
}
export interface TimesheetFilters extends PaginationParams {
projectId?: string;
taskId?: string;
userId?: string;
isBillable?: boolean;
dateFrom?: string;
dateTo?: string;
}
// ============================================================================
// API Client
// ============================================================================
export const projectsApi = {
// Projects
getProjects: async (filters?: ProjectFilters): Promise<PaginatedResponse<Project>> => {
const response = await api.get<PaginatedResponse<Project>>(
API_ENDPOINTS.PROJECTS.PROJECTS,
{ params: filters }
);
return response.data;
},
getProjectById: async (id: string): Promise<Project> => {
const response = await api.get<ApiResponse<Project>>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Proyecto no encontrado');
}
return response.data.data;
},
createProject: async (data: CreateProjectDto): Promise<Project> => {
const response = await api.post<ApiResponse<Project>>(
API_ENDPOINTS.PROJECTS.PROJECTS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear proyecto');
}
return response.data.data;
},
updateProject: async (id: string, data: UpdateProjectDto): Promise<Project> => {
const response = await api.patch<ApiResponse<Project>>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar proyecto');
}
return response.data.data;
},
archiveProject: async (id: string): Promise<Project> => {
const response = await api.post<ApiResponse<Project>>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}/archive`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al archivar proyecto');
}
return response.data.data;
},
unarchiveProject: async (id: string): Promise<Project> => {
const response = await api.post<ApiResponse<Project>>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}/unarchive`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al desarchivar proyecto');
}
return response.data.data;
},
duplicateProject: async (id: string, name?: string): Promise<Project> => {
const response = await api.post<ApiResponse<Project>>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}/duplicate`,
{ name }
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al duplicar proyecto');
}
return response.data.data;
},
deleteProject: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar proyecto');
}
},
// Tasks
getTasks: async (filters?: TaskFilters): Promise<PaginatedResponse<Task>> => {
const response = await api.get<PaginatedResponse<Task>>(
API_ENDPOINTS.PROJECTS.TASKS,
{ params: filters }
);
return response.data;
},
getTaskById: async (id: string): Promise<Task> => {
const response = await api.get<ApiResponse<Task>>(
`${API_ENDPOINTS.PROJECTS.TASKS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Tarea no encontrada');
}
return response.data.data;
},
createTask: async (data: CreateTaskDto): Promise<Task> => {
const response = await api.post<ApiResponse<Task>>(
API_ENDPOINTS.PROJECTS.TASKS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear tarea');
}
return response.data.data;
},
updateTask: async (id: string, data: UpdateTaskDto): Promise<Task> => {
const response = await api.patch<ApiResponse<Task>>(
`${API_ENDPOINTS.PROJECTS.TASKS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar tarea');
}
return response.data.data;
},
moveTask: async (id: string, stageId: string, sequence?: number): Promise<Task> => {
const response = await api.post<ApiResponse<Task>>(
`${API_ENDPOINTS.PROJECTS.TASKS}/${id}/move`,
{ stageId, sequence }
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al mover tarea');
}
return response.data.data;
},
deleteTask: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.PROJECTS.TASKS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar tarea');
}
},
// Timesheets
getTimesheets: async (filters?: TimesheetFilters): Promise<PaginatedResponse<Timesheet>> => {
const response = await api.get<PaginatedResponse<Timesheet>>(
API_ENDPOINTS.PROJECTS.TIMESHEETS,
{ params: filters }
);
return response.data;
},
getTimesheetById: async (id: string): Promise<Timesheet> => {
const response = await api.get<ApiResponse<Timesheet>>(
`${API_ENDPOINTS.PROJECTS.TIMESHEETS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Registro de tiempo no encontrado');
}
return response.data.data;
},
createTimesheet: async (data: CreateTimesheetDto): Promise<Timesheet> => {
const response = await api.post<ApiResponse<Timesheet>>(
API_ENDPOINTS.PROJECTS.TIMESHEETS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear registro de tiempo');
}
return response.data.data;
},
updateTimesheet: async (id: string, data: UpdateTimesheetDto): Promise<Timesheet> => {
const response = await api.patch<ApiResponse<Timesheet>>(
`${API_ENDPOINTS.PROJECTS.TIMESHEETS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar registro de tiempo');
}
return response.data.data;
},
deleteTimesheet: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.PROJECTS.TIMESHEETS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar registro de tiempo');
}
},
// Stages
getStages: async (): Promise<ProjectStage[]> => {
const response = await api.get<ApiResponse<ProjectStage[]>>(
API_ENDPOINTS.PROJECTS.STAGES
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al obtener etapas');
}
return response.data.data;
},
// Milestones
getMilestones: async (projectId: string): Promise<Milestone[]> => {
const response = await api.get<ApiResponse<Milestone[]>>(
API_ENDPOINTS.PROJECTS.MILESTONES,
{ params: { projectId } }
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al obtener hitos');
}
return response.data.data;
},
createMilestone: async (data: CreateMilestoneDto): Promise<Milestone> => {
const response = await api.post<ApiResponse<Milestone>>(
API_ENDPOINTS.PROJECTS.MILESTONES,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear hito');
}
return response.data.data;
},
updateMilestone: async (id: string, data: UpdateMilestoneDto): Promise<Milestone> => {
const response = await api.patch<ApiResponse<Milestone>>(
`${API_ENDPOINTS.PROJECTS.MILESTONES}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar hito');
}
return response.data.data;
},
deleteMilestone: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.PROJECTS.MILESTONES}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar hito');
}
},
};

View File

@ -0,0 +1,279 @@
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 PurchaseOrderLine {
id?: string;
productId: string;
productName?: string;
quantity: number;
unitPrice: number;
discount?: number;
taxIds?: string[];
subtotal?: number;
dateExpected?: string;
}
export interface PurchaseOrder extends BaseEntity {
name: string;
partnerId: string;
partnerName?: string;
dateOrder: string;
dateExpected?: string;
status: 'draft' | 'confirmed' | 'received' | 'done' | 'cancelled';
paymentTermId?: string;
warehouseId?: string;
lines: PurchaseOrderLine[];
amountUntaxed: number;
amountTax: number;
amountTotal: number;
notes?: string;
tenantId: string;
}
export interface PurchaseRfq extends BaseEntity {
name: string;
partnerId: string;
partnerName?: string;
dateRfq: string;
dateExpiry?: string;
status: 'draft' | 'sent' | 'confirmed' | 'cancelled' | 'expired';
paymentTermId?: string;
lines: PurchaseOrderLine[];
amountUntaxed: number;
amountTax: number;
amountTotal: number;
notes?: string;
tenantId: string;
}
// ============================================================================
// DTOs
// ============================================================================
export interface CreatePurchaseOrderDto {
partnerId: string;
dateOrder?: string;
dateExpected?: string;
paymentTermId?: string;
warehouseId?: string;
lines: Omit<PurchaseOrderLine, 'id' | 'productName' | 'subtotal'>[];
notes?: string;
}
export interface UpdatePurchaseOrderDto {
partnerId?: string;
dateOrder?: string;
dateExpected?: string;
paymentTermId?: string;
warehouseId?: string;
lines?: Omit<PurchaseOrderLine, 'productName' | 'subtotal'>[];
notes?: string;
}
export interface CreatePurchaseRfqDto {
partnerId: string;
dateRfq?: string;
dateExpiry?: string;
paymentTermId?: string;
lines: Omit<PurchaseOrderLine, 'id' | 'productName' | 'subtotal'>[];
notes?: string;
}
export interface UpdatePurchaseRfqDto {
partnerId?: string;
dateRfq?: string;
dateExpiry?: string;
paymentTermId?: string;
lines?: Omit<PurchaseOrderLine, 'productName' | 'subtotal'>[];
notes?: string;
}
// ============================================================================
// Filters
// ============================================================================
export interface PurchaseOrderFilters extends PaginationParams {
partnerId?: string;
status?: PurchaseOrder['status'];
dateFrom?: string;
dateTo?: string;
}
export interface PurchaseRfqFilters extends PaginationParams {
partnerId?: string;
status?: PurchaseRfq['status'];
dateFrom?: string;
dateTo?: string;
}
// ============================================================================
// API Client
// ============================================================================
export const purchasesApi = {
// Orders
getOrders: async (filters?: PurchaseOrderFilters): Promise<PaginatedResponse<PurchaseOrder>> => {
const response = await api.get<PaginatedResponse<PurchaseOrder>>(
API_ENDPOINTS.PURCHASES.ORDERS,
{ params: filters }
);
return response.data;
},
getOrderById: async (id: string): Promise<PurchaseOrder> => {
const response = await api.get<ApiResponse<PurchaseOrder>>(
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Orden de compra no encontrada');
}
return response.data.data;
},
createOrder: async (data: CreatePurchaseOrderDto): Promise<PurchaseOrder> => {
const response = await api.post<ApiResponse<PurchaseOrder>>(
API_ENDPOINTS.PURCHASES.ORDERS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear orden de compra');
}
return response.data.data;
},
updateOrder: async (id: string, data: UpdatePurchaseOrderDto): Promise<PurchaseOrder> => {
const response = await api.patch<ApiResponse<PurchaseOrder>>(
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar orden de compra');
}
return response.data.data;
},
confirmOrder: async (id: string): Promise<PurchaseOrder> => {
const response = await api.post<ApiResponse<PurchaseOrder>>(
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}/confirm`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al confirmar orden de compra');
}
return response.data.data;
},
receiveOrder: async (id: string): Promise<PurchaseOrder> => {
const response = await api.post<ApiResponse<PurchaseOrder>>(
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}/receive`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al recibir orden de compra');
}
return response.data.data;
},
cancelOrder: async (id: string): Promise<PurchaseOrder> => {
const response = await api.post<ApiResponse<PurchaseOrder>>(
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}/cancel`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al cancelar orden de compra');
}
return response.data.data;
},
deleteOrder: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.PURCHASES.ORDERS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar orden de compra');
}
},
// RFQs (Request for Quotation)
getRfqs: async (filters?: PurchaseRfqFilters): Promise<PaginatedResponse<PurchaseRfq>> => {
const response = await api.get<PaginatedResponse<PurchaseRfq>>(
API_ENDPOINTS.PURCHASES.RFQS,
{ params: filters }
);
return response.data;
},
getRfqById: async (id: string): Promise<PurchaseRfq> => {
const response = await api.get<ApiResponse<PurchaseRfq>>(
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Solicitud de cotizacion no encontrada');
}
return response.data.data;
},
createRfq: async (data: CreatePurchaseRfqDto): Promise<PurchaseRfq> => {
const response = await api.post<ApiResponse<PurchaseRfq>>(
API_ENDPOINTS.PURCHASES.RFQS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear solicitud de cotizacion');
}
return response.data.data;
},
updateRfq: async (id: string, data: UpdatePurchaseRfqDto): Promise<PurchaseRfq> => {
const response = await api.patch<ApiResponse<PurchaseRfq>>(
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar solicitud de cotizacion');
}
return response.data.data;
},
sendRfq: async (id: string): Promise<PurchaseRfq> => {
const response = await api.post<ApiResponse<PurchaseRfq>>(
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}/send`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al enviar solicitud de cotizacion');
}
return response.data.data;
},
confirmRfq: async (id: string): Promise<PurchaseOrder> => {
const response = await api.post<ApiResponse<PurchaseOrder>>(
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}/confirm`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al confirmar solicitud de cotizacion');
}
return response.data.data;
},
cancelRfq: async (id: string): Promise<PurchaseRfq> => {
const response = await api.post<ApiResponse<PurchaseRfq>>(
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}/cancel`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al cancelar solicitud de cotizacion');
}
return response.data.data;
},
deleteRfq: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.PURCHASES.RFQS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar solicitud de cotizacion');
}
},
};

View File

@ -0,0 +1,274 @@
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 SalesOrderLine {
id?: string;
productId: string;
productName?: string;
quantity: number;
unitPrice: number;
discount?: number;
taxIds?: string[];
subtotal?: number;
}
export interface SalesOrder extends BaseEntity {
name: string;
partnerId: string;
partnerName?: string;
dateOrder: string;
dateDelivery?: string;
status: 'draft' | 'confirmed' | 'done' | 'cancelled';
pricelistId?: string;
paymentTermId?: string;
warehouseId?: string;
lines: SalesOrderLine[];
amountUntaxed: number;
amountTax: number;
amountTotal: number;
notes?: string;
tenantId: string;
}
export interface Quotation extends BaseEntity {
name: string;
partnerId: string;
partnerName?: string;
dateQuotation: string;
dateExpiry?: string;
status: 'draft' | 'sent' | 'confirmed' | 'cancelled' | 'expired';
pricelistId?: string;
paymentTermId?: string;
lines: SalesOrderLine[];
amountUntaxed: number;
amountTax: number;
amountTotal: number;
notes?: string;
tenantId: string;
}
// ============================================================================
// DTOs
// ============================================================================
export interface CreateSalesOrderDto {
partnerId: string;
dateOrder?: string;
dateDelivery?: string;
pricelistId?: string;
paymentTermId?: string;
warehouseId?: string;
lines: Omit<SalesOrderLine, 'id' | 'productName' | 'subtotal'>[];
notes?: string;
}
export interface UpdateSalesOrderDto {
partnerId?: string;
dateOrder?: string;
dateDelivery?: string;
pricelistId?: string;
paymentTermId?: string;
warehouseId?: string;
lines?: Omit<SalesOrderLine, 'productName' | 'subtotal'>[];
notes?: string;
}
export interface CreateQuotationDto {
partnerId: string;
dateQuotation?: string;
dateExpiry?: string;
pricelistId?: string;
paymentTermId?: string;
lines: Omit<SalesOrderLine, 'id' | 'productName' | 'subtotal'>[];
notes?: string;
}
export interface UpdateQuotationDto {
partnerId?: string;
dateQuotation?: string;
dateExpiry?: string;
pricelistId?: string;
paymentTermId?: string;
lines?: Omit<SalesOrderLine, 'productName' | 'subtotal'>[];
notes?: string;
}
// ============================================================================
// Filters
// ============================================================================
export interface SalesOrderFilters extends PaginationParams {
partnerId?: string;
status?: SalesOrder['status'];
dateFrom?: string;
dateTo?: string;
}
export interface QuotationFilters extends PaginationParams {
partnerId?: string;
status?: Quotation['status'];
dateFrom?: string;
dateTo?: string;
}
// ============================================================================
// API Client
// ============================================================================
export const salesApi = {
// Orders
getOrders: async (filters?: SalesOrderFilters): Promise<PaginatedResponse<SalesOrder>> => {
const response = await api.get<PaginatedResponse<SalesOrder>>(
API_ENDPOINTS.SALES.ORDERS,
{ params: filters }
);
return response.data;
},
getOrderById: async (id: string): Promise<SalesOrder> => {
const response = await api.get<ApiResponse<SalesOrder>>(
`${API_ENDPOINTS.SALES.ORDERS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Orden de venta no encontrada');
}
return response.data.data;
},
createOrder: async (data: CreateSalesOrderDto): Promise<SalesOrder> => {
const response = await api.post<ApiResponse<SalesOrder>>(
API_ENDPOINTS.SALES.ORDERS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear orden de venta');
}
return response.data.data;
},
updateOrder: async (id: string, data: UpdateSalesOrderDto): Promise<SalesOrder> => {
const response = await api.patch<ApiResponse<SalesOrder>>(
`${API_ENDPOINTS.SALES.ORDERS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar orden de venta');
}
return response.data.data;
},
confirmOrder: async (id: string): Promise<SalesOrder> => {
const response = await api.post<ApiResponse<SalesOrder>>(
`${API_ENDPOINTS.SALES.ORDERS}/${id}/confirm`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al confirmar orden de venta');
}
return response.data.data;
},
cancelOrder: async (id: string): Promise<SalesOrder> => {
const response = await api.post<ApiResponse<SalesOrder>>(
`${API_ENDPOINTS.SALES.ORDERS}/${id}/cancel`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al cancelar orden de venta');
}
return response.data.data;
},
deleteOrder: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.SALES.ORDERS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar orden de venta');
}
},
// Quotations
getQuotations: async (filters?: QuotationFilters): Promise<PaginatedResponse<Quotation>> => {
const response = await api.get<PaginatedResponse<Quotation>>(
API_ENDPOINTS.SALES.QUOTATIONS,
{ params: filters }
);
return response.data;
},
getQuotationById: async (id: string): Promise<Quotation> => {
const response = await api.get<ApiResponse<Quotation>>(
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Cotizacion no encontrada');
}
return response.data.data;
},
createQuotation: async (data: CreateQuotationDto): Promise<Quotation> => {
const response = await api.post<ApiResponse<Quotation>>(
API_ENDPOINTS.SALES.QUOTATIONS,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al crear cotizacion');
}
return response.data.data;
},
updateQuotation: async (id: string, data: UpdateQuotationDto): Promise<Quotation> => {
const response = await api.patch<ApiResponse<Quotation>>(
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}`,
data
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al actualizar cotizacion');
}
return response.data.data;
},
confirmQuotation: async (id: string): Promise<SalesOrder> => {
const response = await api.post<ApiResponse<SalesOrder>>(
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}/confirm`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al confirmar cotizacion');
}
return response.data.data;
},
sendQuotation: async (id: string): Promise<Quotation> => {
const response = await api.post<ApiResponse<Quotation>>(
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}/send`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al enviar cotizacion');
}
return response.data.data;
},
cancelQuotation: async (id: string): Promise<Quotation> => {
const response = await api.post<ApiResponse<Quotation>>(
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}/cancel`
);
if (!response.data.success || !response.data.data) {
throw new Error(response.data.error || 'Error al cancelar cotizacion');
}
return response.data.data;
},
deleteQuotation: async (id: string): Promise<void> => {
const response = await api.delete<ApiResponse>(
`${API_ENDPOINTS.SALES.QUOTATIONS}/${id}`
);
if (!response.data.success) {
throw new Error(response.data.error || 'Error al eliminar cotizacion');
}
},
};