Update: nueva version Horux Despachos
This commit is contained in:
397
docs/architecture/database-schema.prisma
Normal file
397
docs/architecture/database-schema.prisma
Normal file
@@ -0,0 +1,397 @@
|
||||
// Horux360 - Database Schema Documentation
|
||||
// PostgreSQL Multi-tenant: database-per-tenant
|
||||
// Última actualización: 2026-04-11
|
||||
//
|
||||
// ARQUITECTURA:
|
||||
// horux360 (central) ← Prisma: tenants, users, subscriptions, catálogos
|
||||
// horux_<rfc> (por tenant) ← Raw SQL (pg Pool): cfdis, cfdi_conceptos, rfcs, alertas
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// BASE DE DATOS CENTRAL (horux360)
|
||||
// Gestionada por Prisma Client
|
||||
// ============================================
|
||||
|
||||
model Tenant {
|
||||
id String @id @default(uuid())
|
||||
nombre String
|
||||
rfc String @unique
|
||||
plan Plan @default(starter)
|
||||
databaseName String @unique @map("database_name")
|
||||
cfdiLimit Int @default(100) @map("cfdi_limit")
|
||||
usersLimit Int @default(1) @map("users_limit")
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
expiresAt DateTime? @map("expires_at")
|
||||
|
||||
users User[]
|
||||
fielCredential FielCredential?
|
||||
satSyncJobs SatSyncJob[]
|
||||
subscriptions Subscription[]
|
||||
payments Payment[]
|
||||
regimenesIgnorados TenantRegimenIgnorado[]
|
||||
regimenesActivos TenantRegimenActivo[]
|
||||
coeficientes CoeficienteUtilidad[]
|
||||
|
||||
@@map("tenants")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @map("tenant_id")
|
||||
email String @unique
|
||||
passwordHash String @map("password_hash")
|
||||
nombre String
|
||||
role Role @default(visor)
|
||||
active Boolean @default(true)
|
||||
lastLogin DateTime? @map("last_login")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model RefreshToken {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
token String @unique
|
||||
expiresAt DateTime @map("expires_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@map("refresh_tokens")
|
||||
}
|
||||
|
||||
enum Plan {
|
||||
starter
|
||||
business
|
||||
professional
|
||||
enterprise
|
||||
}
|
||||
|
||||
enum Role {
|
||||
admin
|
||||
contador
|
||||
visor
|
||||
}
|
||||
|
||||
// ─── Catálogo de Regímenes Fiscales SAT ───
|
||||
|
||||
model Regimen {
|
||||
id Int @id @default(autoincrement())
|
||||
clave String @unique @db.VarChar(3) // Clave SAT: 601, 603, 605, 606, etc.
|
||||
descripcion String
|
||||
tipoPersona String @map("tipo_persona") @db.VarChar(20) // fisica, moral, ambos
|
||||
activo Boolean @default(true)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenantIgnorados TenantRegimenIgnorado[]
|
||||
tenantActivos TenantRegimenActivo[]
|
||||
|
||||
@@map("regimenes")
|
||||
}
|
||||
|
||||
model TenantRegimenIgnorado {
|
||||
id Int @id @default(autoincrement())
|
||||
tenantId String @map("tenant_id")
|
||||
regimenId Int @map("regimen_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||
regimen Regimen @relation(fields: [regimenId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([tenantId, regimenId])
|
||||
@@map("tenant_regimenes_ignorados")
|
||||
}
|
||||
|
||||
model TenantRegimenActivo {
|
||||
id Int @id @default(autoincrement())
|
||||
tenantId String @map("tenant_id")
|
||||
regimenId Int @map("regimen_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||
regimen Regimen @relation(fields: [regimenId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([tenantId, regimenId])
|
||||
@@map("tenant_regimenes_activos")
|
||||
}
|
||||
|
||||
// ─── Catálogo de Eventos Fiscales ───
|
||||
|
||||
model EventoFiscalCatalogo {
|
||||
id Int @id @default(autoincrement())
|
||||
titulo String
|
||||
descripcion String?
|
||||
tipo String @db.VarChar(20) // declaracion, pago, obligacion, informativa
|
||||
diaBase Int @map("dia_base")
|
||||
mesRelativo Int @default(1) @map("mes_relativo")
|
||||
mesFijo Int? @map("mes_fijo")
|
||||
recurrencia String @default("mensual") @db.VarChar(20) // mensual, anual
|
||||
usaExtensionRfc Boolean @default(false) @map("usa_extension_rfc")
|
||||
regimenes String @default("todos") // 'todos' o CSV: '601,603,612'
|
||||
condicion String? @db.VarChar(50) // null, 'tiene_nomina', 'ingresos_4m'
|
||||
activo Boolean @default(true)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@map("eventos_fiscales_catalogo")
|
||||
}
|
||||
|
||||
/// Lista negra SAT (Art. 69-B CFF)
|
||||
model ListaNegra {
|
||||
id Int @id @default(autoincrement())
|
||||
rfc String @unique @db.VarChar(13)
|
||||
nombre String
|
||||
situacion String @db.VarChar(30) // Definitivo, Presunto, Desvirtuado, Sentencia Favorable
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([rfc])
|
||||
@@map("lista_negra")
|
||||
}
|
||||
|
||||
/// Días inhábiles fiscales (festivos oficiales de México)
|
||||
model DiaInhabil {
|
||||
id Int @id @default(autoincrement())
|
||||
fecha DateTime @unique @db.Date
|
||||
nombre String
|
||||
|
||||
@@map("dias_inhabiles")
|
||||
}
|
||||
|
||||
// ─── ISR ───
|
||||
|
||||
/// Tasas RESICO (Art. 113-E)
|
||||
model IsrResicoTasa {
|
||||
id Int @id @default(autoincrement())
|
||||
anio Int @map("anio")
|
||||
montoMaximo Decimal @map("monto_maximo") @db.Decimal(18, 2)
|
||||
porcentaje Decimal @db.Decimal(5, 2)
|
||||
|
||||
@@unique([anio, montoMaximo])
|
||||
@@map("isr_resico_tasas")
|
||||
}
|
||||
|
||||
/// Tarifa ISR progresiva (Art. 96) - mensual
|
||||
model IsrTarifa {
|
||||
id Int @id @default(autoincrement())
|
||||
anio Int @map("anio")
|
||||
limiteInferior Decimal @map("limite_inferior") @db.Decimal(18, 2)
|
||||
limiteSuperior Decimal? @map("limite_superior") @db.Decimal(18, 2)
|
||||
cuotaFija Decimal @map("cuota_fija") @db.Decimal(18, 2)
|
||||
porcentajeExcedente Decimal @map("porcentaje_excedente") @db.Decimal(5, 2)
|
||||
|
||||
@@unique([anio, limiteInferior])
|
||||
@@map("isr_tarifas")
|
||||
}
|
||||
|
||||
/// Coeficiente de utilidad por tenant/año
|
||||
model CoeficienteUtilidad {
|
||||
id Int @id @default(autoincrement())
|
||||
tenantId String @map("tenant_id")
|
||||
anio Int @map("anio")
|
||||
coeficiente Decimal @db.Decimal(10, 4)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([tenantId, anio])
|
||||
@@map("coeficiente_utilidad")
|
||||
}
|
||||
|
||||
// ─── SAT Sync ───
|
||||
|
||||
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")
|
||||
cerIv Bytes @map("cer_iv")
|
||||
cerTag Bytes @map("cer_tag")
|
||||
keyIv Bytes @map("key_iv")
|
||||
keyTag Bytes @map("key_tag")
|
||||
passwordIv Bytes @map("password_iv")
|
||||
passwordTag Bytes @map("password_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")
|
||||
}
|
||||
|
||||
// ─── Pagos (MercadoPago) ───
|
||||
|
||||
model Subscription {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @map("tenant_id")
|
||||
plan Plan
|
||||
mpPreapprovalId String? @map("mp_preapproval_id")
|
||||
status String @default("pending")
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
frequency String @default("monthly")
|
||||
currentPeriodStart DateTime? @map("current_period_start")
|
||||
currentPeriodEnd DateTime? @map("current_period_end")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||||
payments Payment[]
|
||||
|
||||
@@index([tenantId])
|
||||
@@index([status])
|
||||
@@map("subscriptions")
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @map("tenant_id")
|
||||
subscriptionId String? @map("subscription_id")
|
||||
mpPaymentId String? @map("mp_payment_id")
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
status String @default("pending")
|
||||
paymentMethod String? @map("payment_method")
|
||||
paidAt DateTime? @map("paid_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||||
subscription Subscription? @relation(fields: [subscriptionId], references: [id])
|
||||
|
||||
@@index([tenantId])
|
||||
@@index([subscriptionId])
|
||||
@@map("payments")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// BASES DE DATOS POR TENANT (horux_<rfc>)
|
||||
// Gestionadas por raw SQL via pg Pool
|
||||
// DDL en: apps/api/src/config/database.ts → createTables()
|
||||
// ============================================
|
||||
//
|
||||
// TABLA: rfcs
|
||||
// id SERIAL PRIMARY KEY
|
||||
// rfc VARCHAR(13) UNIQUE NOT NULL
|
||||
// nombre VARCHAR(255)
|
||||
// creado_en TIMESTAMP DEFAULT NOW()
|
||||
// actualizado_en TIMESTAMP DEFAULT NOW()
|
||||
//
|
||||
// TABLA: cfdis
|
||||
// id SERIAL PRIMARY KEY
|
||||
// year VARCHAR(4), month VARCHAR(2)
|
||||
// type VARCHAR(10) -- EMITIDO | RECIBIDO
|
||||
// uuid VARCHAR(36) UNIQUE
|
||||
// serie, folio VARCHAR(50)
|
||||
// status VARCHAR(20) -- Vigente, Cancelado, 0, 1
|
||||
// fecha_emision TIMESTAMP
|
||||
// rfc_emisor, rfc_receptor VARCHAR(13)
|
||||
// nombre_emisor, nombre_receptor VARCHAR(255)
|
||||
// subtotal, total, descuento NUMERIC(18,4) (+ _mxn variants)
|
||||
// moneda VARCHAR(3), tipo_cambio NUMERIC(18,6)
|
||||
// tipo_comprobante VARCHAR(1) -- I, E, T, P, N
|
||||
// metodo_pago VARCHAR(3) -- PUE, PPD
|
||||
// forma_pago VARCHAR(2), uso_cfdi VARCHAR(3)
|
||||
// pac VARCHAR(13), fecha_cert_sat, fecha_cancelacion
|
||||
// uuid_relacionado TEXT
|
||||
// -- Impuestos (cada uno con variante _mxn):
|
||||
// isr_retencion, iva_traslado, iva_retencion, ieps_traslado, ieps_retencion
|
||||
// impuestos_locales_trasladado, impuestos_locales_retenidos
|
||||
// -- Complemento de pagos:
|
||||
// monto_pago, fecha_pago_p, num_parcialidad, saldo_pendiente
|
||||
// isr_retencion_pago, iva_traslado_pago, iva_retencion_pago, ieps_traslado_pago, ieps_retencion_pago
|
||||
// -- Nómina:
|
||||
// num_seguro_social, puesto, salario_base_cot_apor, salario_diario_integrado
|
||||
// total_percepciones, total_deducciones, imp_retenidos_nomina, otras_deducciones_nomina, subsidio_causado
|
||||
// -- Metadata:
|
||||
// conciliado VARCHAR(50)
|
||||
// regimen_fiscal_emisor, regimen_fiscal_receptor VARCHAR(3)
|
||||
// rfc_emisor_id, rfc_receptor_id INTEGER REFERENCES rfcs(id)
|
||||
// xml_url, pdf_url, xml_original TEXT
|
||||
// last_sat_sync TIMESTAMP, sat_sync_job_id UUID
|
||||
// source VARCHAR(20) DEFAULT 'manual'
|
||||
// creado_en, actualizado_en TIMESTAMP
|
||||
//
|
||||
// TABLA: cfdi_conceptos
|
||||
// id SERIAL PRIMARY KEY
|
||||
// cfdi_id INTEGER REFERENCES cfdis(id) ON DELETE CASCADE
|
||||
// clave_prod_serv, no_identificacion, descripcion, cantidad, clave_unidad, unidad
|
||||
// valor_unitario, importe, descuento (+ _mxn)
|
||||
// isr_retencion, iva_traslado, iva_retencion, ieps_traslado, ieps_retencion (+ _mxn)
|
||||
// impuestos_locales_trasladado, impuestos_locales_retenidos (+ _mxn)
|
||||
// total_percepciones, total_deducciones, imp_retenidos_nomina, otras_deducciones_nomina, subsidio_causado (+ _mxn)
|
||||
// creado_en TIMESTAMP
|
||||
//
|
||||
// TABLA: alertas
|
||||
// id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
||||
// tipo VARCHAR(50), titulo VARCHAR(200), mensaje TEXT
|
||||
// prioridad VARCHAR(20) DEFAULT 'media'
|
||||
// fecha_vencimiento TIMESTAMP, leida BOOLEAN, resuelta BOOLEAN
|
||||
// created_at TIMESTAMP
|
||||
//
|
||||
// ÍNDICES:
|
||||
// idx_cfdis_fecha_emision (DESC), idx_cfdis_type, idx_cfdis_status
|
||||
// idx_cfdis_rfc_emisor, idx_cfdis_rfc_receptor
|
||||
// idx_cfdis_year_month, idx_cfdis_rfc_emisor_id, idx_cfdis_rfc_receptor_id
|
||||
// idx_cfdis_nombre_emisor_trgm (GIN trigram), idx_cfdis_nombre_receptor_trgm (GIN trigram)
|
||||
// idx_cfdi_conceptos_cfdi_id, idx_cfdi_conceptos_clave
|
||||
// EXTENSION: pg_trgm (para búsquedas fuzzy por nombre)
|
||||
Reference in New Issue
Block a user