feat(sat): add database models for SAT sync

Phase 1 - Database models:
- Add FielCredential model for encrypted FIEL storage
- Add SatSyncJob model for sync job tracking
- Add SAT-related enums (SatSyncType, SatSyncStatus, CfdiSyncType)
- Add TypeScript types in shared package
- Relations: Tenant -> FielCredential (1:1), Tenant -> SatSyncJobs (1:N)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Consultoria AS
2026-01-25 00:38:51 +00:00
parent 3763014eca
commit 787aac9a4c
3 changed files with 208 additions and 1 deletions

View File

@@ -19,7 +19,9 @@ model Tenant {
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
expiresAt DateTime? @map("expires_at") expiresAt DateTime? @map("expires_at")
users User[] users User[]
fielCredential FielCredential?
satSyncJobs SatSyncJob[]
@@map("tenants") @@map("tenants")
} }
@@ -62,3 +64,75 @@ enum Role {
contador contador
visor 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
}

View File

@@ -8,6 +8,7 @@ export * from './types/impuestos';
export * from './types/alertas'; export * from './types/alertas';
export * from './types/reportes'; export * from './types/reportes';
export * from './types/calendario'; export * from './types/calendario';
export * from './types/sat';
// Constants // Constants
export * from './constants/plans'; export * from './constants/plans';

View File

@@ -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<string, string> = {
'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<number, string> = {
1: 'Aceptada',
2: 'En proceso',
3: 'Terminada',
4: 'Error',
5: 'Rechazada',
6: 'Vencida',
};