diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 449dc92..5772958 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -19,7 +19,9 @@ model Tenant { createdAt DateTime @default(now()) @map("created_at") expiresAt DateTime? @map("expires_at") - users User[] + users User[] + fielCredential FielCredential? + satSyncJobs SatSyncJob[] @@map("tenants") } @@ -62,3 +64,75 @@ enum Role { contador visor } + +// ============================================ +// SAT Sync Models +// ============================================ + +model FielCredential { + id String @id @default(uuid()) + tenantId String @unique @map("tenant_id") + rfc String @db.VarChar(13) + cerData Bytes @map("cer_data") + keyData Bytes @map("key_data") + keyPasswordEncrypted Bytes @map("key_password_encrypted") + encryptionIv Bytes @map("encryption_iv") + encryptionTag Bytes @map("encryption_tag") + serialNumber String? @map("serial_number") @db.VarChar(50) + validFrom DateTime @map("valid_from") + validUntil DateTime @map("valid_until") + isActive Boolean @default(true) @map("is_active") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + + @@map("fiel_credentials") +} + +model SatSyncJob { + id String @id @default(uuid()) + tenantId String @map("tenant_id") + type SatSyncType + status SatSyncStatus @default(pending) + dateFrom DateTime @map("date_from") @db.Date + dateTo DateTime @map("date_to") @db.Date + cfdiType CfdiSyncType? @map("cfdi_type") + satRequestId String? @map("sat_request_id") @db.VarChar(50) + satPackageIds String[] @map("sat_package_ids") + cfdisFound Int @default(0) @map("cfdis_found") + cfdisDownloaded Int @default(0) @map("cfdis_downloaded") + cfdisInserted Int @default(0) @map("cfdis_inserted") + cfdisUpdated Int @default(0) @map("cfdis_updated") + progressPercent Int @default(0) @map("progress_percent") + errorMessage String? @map("error_message") + startedAt DateTime? @map("started_at") + completedAt DateTime? @map("completed_at") + createdAt DateTime @default(now()) @map("created_at") + retryCount Int @default(0) @map("retry_count") + nextRetryAt DateTime? @map("next_retry_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + + @@index([tenantId]) + @@index([status]) + @@index([status, nextRetryAt]) + @@map("sat_sync_jobs") +} + +enum SatSyncType { + initial + daily +} + +enum SatSyncStatus { + pending + running + completed + failed +} + +enum CfdiSyncType { + emitidos + recibidos +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index d9adc8f..614f976 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -8,6 +8,7 @@ export * from './types/impuestos'; export * from './types/alertas'; export * from './types/reportes'; export * from './types/calendario'; +export * from './types/sat'; // Constants export * from './constants/plans'; diff --git a/packages/shared/src/types/sat.ts b/packages/shared/src/types/sat.ts new file mode 100644 index 0000000..fdee3e7 --- /dev/null +++ b/packages/shared/src/types/sat.ts @@ -0,0 +1,132 @@ +// ============================================ +// FIEL (e.firma) Types +// ============================================ + +export interface FielUploadRequest { + cerFile: string; // Base64 + keyFile: string; // Base64 + password: string; +} + +export interface FielStatus { + configured: boolean; + rfc?: string; + serialNumber?: string; + validFrom?: string; + validUntil?: string; + isExpired?: boolean; + daysUntilExpiration?: number; +} + +// ============================================ +// SAT Sync Types +// ============================================ + +export type SatSyncType = 'initial' | 'daily'; +export type SatSyncStatus = 'pending' | 'running' | 'completed' | 'failed'; +export type CfdiSyncType = 'emitidos' | 'recibidos'; + +export interface SatSyncJob { + id: string; + tenantId: string; + type: SatSyncType; + status: SatSyncStatus; + dateFrom: string; + dateTo: string; + cfdiType?: CfdiSyncType; + satRequestId?: string; + satPackageIds: string[]; + cfdisFound: number; + cfdisDownloaded: number; + cfdisInserted: number; + cfdisUpdated: number; + progressPercent: number; + errorMessage?: string; + startedAt?: string; + completedAt?: string; + createdAt: string; + retryCount: number; +} + +export interface SatSyncStatusResponse { + hasActiveSync: boolean; + currentJob?: SatSyncJob; + lastCompletedJob?: SatSyncJob; + totalCfdisSynced: number; +} + +export interface SatSyncHistoryResponse { + jobs: SatSyncJob[]; + total: number; + page: number; + limit: number; +} + +export interface StartSyncRequest { + type?: SatSyncType; + dateFrom?: string; + dateTo?: string; +} + +export interface StartSyncResponse { + jobId: string; + message: string; +} + +// ============================================ +// SAT Web Service Types +// ============================================ + +export interface SatAuthResponse { + token: string; + expiresAt: Date; +} + +export interface SatDownloadRequest { + rfcSolicitante: string; + fechaInicio: Date; + fechaFin: Date; + tipoSolicitud: 'CFDI' | 'Metadata'; + tipoComprobante?: 'I' | 'E' | 'T' | 'N' | 'P'; + rfcEmisor?: string; + rfcReceptor?: string; +} + +export interface SatDownloadRequestResponse { + idSolicitud: string; + codEstatus: string; + mensaje: string; +} + +export interface SatVerifyResponse { + codEstatus: string; + estadoSolicitud: number; // 1=Aceptada, 2=EnProceso, 3=Terminada, 4=Error, 5=Rechazada, 6=Vencida + codigoEstadoSolicitud: string; + numeroCfdis: number; + mensaje: string; + paquetes: string[]; +} + +export interface SatPackageResponse { + paquete: string; // Base64 ZIP +} + +// ============================================ +// SAT Error Codes +// ============================================ + +export const SAT_STATUS_CODES: Record = { + '5000': 'Solicitud recibida con éxito', + '5002': 'Se agotó el límite de solicitudes', + '5004': 'No se encontraron CFDIs', + '5005': 'Solicitud duplicada', +}; + +export const SAT_REQUEST_STATUS: Record = { + 1: 'Aceptada', + 2: 'En proceso', + 3: 'Terminada', + 4: 'Error', + 5: 'Rechazada', + 6: 'Vencida', +};