feat: facturación primer pago, fixes SAT/MP, autocompletado RFCs/conceptos
Backend: - Notificación email al admin cuando llega primer pago aprobado (sin factura auto) - Endpoints GET /pagos-sin-factura y POST /emitir-factura-pago para admin global - Fix vinculación org Facturapi Horux 360 (69f23a5a242e0af47a41fa0d) - Fix webhook MP: validación defensiva de x-signature header - Fix autocompleto RFCs: eliminado filtro por contribuyenteId - Fix autocompleto conceptos: eliminado filtro por contribuyenteId - SAT fixes: anti-bot CSF scraper, request reuse, date range fix, stale job thresholds - SAT sync request reuse across jobs para evitar agotar cuota diaria - Typo fix MP_ACCESS_TOKEN en .env - Trial invitations system backend Frontend: - Nueva página /admin/facturas-pendientes con tabla y emisión manual - Métrica 'Facturas pendientes' en /clientes (clickable) - Navegación onboarding FIEL/CSD corregida - Sidebar themes sincronizados - Fix SAT portal migration scraper (NetIQ) - Trial invitation acceptance pages
This commit is contained in:
70
apps/web/lib/api/trial-invitations.ts
Normal file
70
apps/web/lib/api/trial-invitations.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { apiClient } from './client';
|
||||
|
||||
export interface TrialInvitation {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
plan: string;
|
||||
durationDays: number;
|
||||
status: string;
|
||||
token: string;
|
||||
sentAt: string;
|
||||
expiresAt: string;
|
||||
acceptedAt: string | null;
|
||||
tenant: {
|
||||
nombre: string;
|
||||
rfc: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export async function getPendingInvitation(): Promise<TrialInvitation | null> {
|
||||
try {
|
||||
const response = await apiClient.get<TrialInvitation | null>('/invitations/trial/pending');
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
if (error?.response?.status === 404) return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getInvitationByToken(token: string): Promise<TrialInvitation | null> {
|
||||
try {
|
||||
const response = await apiClient.get<TrialInvitation | null>(`/invitations/trial/token/${token}`);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
if (error?.response?.status === 404) return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function acceptInvitation(token: string): Promise<{
|
||||
success: boolean;
|
||||
trialEndsAt: string;
|
||||
plan: string;
|
||||
durationDays: number;
|
||||
}> {
|
||||
const response = await apiClient.post(`/invitations/trial/${token}/accept`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// Admin endpoints
|
||||
export async function getAllInvitations(filters?: { tenantId?: string; status?: string }): Promise<TrialInvitation[]> {
|
||||
const params = new URLSearchParams();
|
||||
if (filters?.tenantId) params.append('tenantId', filters.tenantId);
|
||||
if (filters?.status) params.append('status', filters.status);
|
||||
const response = await apiClient.get<TrialInvitation[]>(`/invitations/trial?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createInvitation(data: {
|
||||
tenantId: string;
|
||||
plan?: string;
|
||||
durationDays: number;
|
||||
}): Promise<TrialInvitation> {
|
||||
const response = await apiClient.post<TrialInvitation>('/invitations/trial', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function cancelInvitation(id: string): Promise<TrialInvitation> {
|
||||
const response = await apiClient.post<TrialInvitation>(`/invitations/trial/${id}/cancel`);
|
||||
return response.data;
|
||||
}
|
||||
Reference in New Issue
Block a user