// 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)
