- Corregido auth.service.ts para usar estructura multi-tenant correcta (user_tenants) - Dashboard rediseñado para CFO digital (ingresos/egresos, CFDIs, alertas) - Sidebar actualizado con rutas correctas (Métricas, Transacciones, CFDIs, Reportes, Asistente IA) - Agregadas páginas de Perfil (/profile) y Configuración (/settings) - Corregidos errores de TypeScript (strict mode, tipos duplicados) - Actualizado docker-compose.yml a PostgreSQL 16 - Corregidas migraciones SQL (índices IMMUTABLE, constraints) - Configuración ESM modules en packages - CORS configurado para acceso de red local Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
680 lines
16 KiB
TypeScript
680 lines
16 KiB
TypeScript
/**
|
|
* @horux/database
|
|
*
|
|
* Database package for Horux Strategy - CFO Digital para Empresas Mexicanas
|
|
*
|
|
* Provides:
|
|
* - PostgreSQL connection pool with multi-tenant support
|
|
* - Tenant schema management (create, delete, suspend)
|
|
* - Migration utilities
|
|
* - Type definitions for database entities
|
|
*/
|
|
|
|
// Import types used in this file
|
|
import type { TenantStatus, TenantSettings } from './tenant.js';
|
|
|
|
// Connection management
|
|
export {
|
|
DatabaseConnection,
|
|
TenantDatabase,
|
|
getDatabase,
|
|
createDatabase,
|
|
createTenantDatabase,
|
|
type DatabaseConfig,
|
|
type TenantContext,
|
|
type QueryOptions,
|
|
type Pool,
|
|
type PoolClient,
|
|
type QueryResult,
|
|
type QueryResultRow,
|
|
} from './connection.js';
|
|
|
|
// Tenant management
|
|
export {
|
|
createTenantSchema,
|
|
deleteTenantSchema,
|
|
suspendTenant,
|
|
reactivateTenant,
|
|
getTenant,
|
|
getTenantBySlug,
|
|
listTenants,
|
|
updateTenantSettings,
|
|
validateTenantAccess,
|
|
getSchemaName,
|
|
createTenantContext,
|
|
type CreateTenantOptions,
|
|
type TenantSettings,
|
|
type TenantInfo,
|
|
type TenantStatus,
|
|
} from './tenant.js';
|
|
|
|
// Migration utilities (for programmatic use)
|
|
export {
|
|
runMigrations,
|
|
printStatus as getMigrationStatus,
|
|
rollbackLast as rollbackMigration,
|
|
ensureDatabase,
|
|
loadMigrationFiles,
|
|
getExecutedMigrations,
|
|
ensureMigrationsTable,
|
|
type MigrationFile,
|
|
type MigrationRecord,
|
|
} from './migrate.js';
|
|
|
|
// Seed data exports
|
|
export {
|
|
PLANS,
|
|
SYSTEM_SETTINGS,
|
|
} from './seed.js';
|
|
|
|
// ============================================================================
|
|
// Type Definitions for Database Entities
|
|
// ============================================================================
|
|
|
|
// User roles
|
|
export type UserRole = 'super_admin' | 'owner' | 'admin' | 'manager' | 'analyst' | 'viewer';
|
|
|
|
// Subscription status
|
|
export type SubscriptionStatus = 'active' | 'trial' | 'past_due' | 'cancelled' | 'suspended' | 'expired';
|
|
|
|
// Job status
|
|
export type JobStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
|
|
// Transaction types
|
|
export type TransactionType = 'income' | 'expense' | 'transfer' | 'adjustment';
|
|
export type TransactionStatus = 'pending' | 'confirmed' | 'reconciled' | 'voided';
|
|
|
|
// CFDI types
|
|
export type CfdiStatus = 'active' | 'cancelled' | 'pending_cancellation';
|
|
export type CfdiType = 'I' | 'E' | 'T' | 'N' | 'P'; // Ingreso, Egreso, Traslado, Nomina, Pago
|
|
|
|
// Contact types
|
|
export type ContactType = 'customer' | 'supplier' | 'both' | 'employee';
|
|
|
|
// Category types
|
|
export type CategoryType = 'income' | 'expense' | 'cost' | 'other';
|
|
|
|
// Account types
|
|
export type AccountType = 'asset' | 'liability' | 'equity' | 'revenue' | 'expense';
|
|
|
|
// Alert severity
|
|
export type AlertSeverity = 'info' | 'warning' | 'critical';
|
|
|
|
// Report status
|
|
export type ReportStatus = 'draft' | 'generating' | 'completed' | 'failed' | 'archived';
|
|
|
|
// ============================================================================
|
|
// Entity Interfaces
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Plan entity
|
|
*/
|
|
export interface Plan {
|
|
id: string;
|
|
name: string;
|
|
description: string | null;
|
|
priceMonthly: number;
|
|
priceYearly: number;
|
|
maxUsers: number;
|
|
maxCfdisMonthly: number;
|
|
maxStorageMb: number;
|
|
maxApiCallsDaily: number;
|
|
maxReportsMonthly: number;
|
|
features: Record<string, boolean>;
|
|
hasSatSync: boolean;
|
|
hasBankSync: boolean;
|
|
hasAiInsights: boolean;
|
|
hasCustomReports: boolean;
|
|
hasApiAccess: boolean;
|
|
hasWhiteLabel: boolean;
|
|
hasPrioritySupport: boolean;
|
|
hasDedicatedAccountManager: boolean;
|
|
dataRetentionMonths: number;
|
|
displayOrder: number;
|
|
isActive: boolean;
|
|
isPopular: boolean;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* User entity
|
|
*/
|
|
export interface User {
|
|
id: string;
|
|
email: string;
|
|
firstName: string | null;
|
|
lastName: string | null;
|
|
phone: string | null;
|
|
avatarUrl: string | null;
|
|
defaultRole: UserRole;
|
|
isActive: boolean;
|
|
isEmailVerified: boolean;
|
|
emailVerifiedAt: Date | null;
|
|
twoFactorEnabled: boolean;
|
|
preferences: Record<string, unknown>;
|
|
timezone: string;
|
|
locale: string;
|
|
lastLoginAt: Date | null;
|
|
lastLoginIp: string | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Tenant entity
|
|
*/
|
|
export interface Tenant {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
schemaName: string;
|
|
rfc: string | null;
|
|
razonSocial: string | null;
|
|
email: string | null;
|
|
phone: string | null;
|
|
ownerId: string;
|
|
planId: string;
|
|
status: TenantStatus;
|
|
settings: TenantSettings;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Subscription entity
|
|
*/
|
|
export interface Subscription {
|
|
id: string;
|
|
tenantId: string;
|
|
planId: string;
|
|
status: SubscriptionStatus;
|
|
billingCycle: 'monthly' | 'yearly';
|
|
trialEndsAt: Date | null;
|
|
currentPeriodStart: Date;
|
|
currentPeriodEnd: Date;
|
|
cancelledAt: Date | null;
|
|
cancelAtPeriodEnd: boolean;
|
|
paymentProcessor: string | null;
|
|
externalSubscriptionId: string | null;
|
|
externalCustomerId: string | null;
|
|
priceCents: number;
|
|
currency: string;
|
|
usageCfdisCurrent: number;
|
|
usageStorageMbCurrent: number;
|
|
usageApiCallsCurrent: number;
|
|
usageResetAt: Date | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* User session entity
|
|
*/
|
|
export interface UserSession {
|
|
id: string;
|
|
userId: string;
|
|
tenantId: string | null;
|
|
userAgent: string | null;
|
|
ipAddress: string | null;
|
|
deviceType: string | null;
|
|
deviceName: string | null;
|
|
locationCity: string | null;
|
|
locationCountry: string | null;
|
|
isActive: boolean;
|
|
expiresAt: Date;
|
|
refreshExpiresAt: Date | null;
|
|
lastActivityAt: Date;
|
|
createdAt: Date;
|
|
revokedAt: Date | null;
|
|
}
|
|
|
|
/**
|
|
* Background job entity
|
|
*/
|
|
export interface BackgroundJob {
|
|
id: string;
|
|
tenantId: string | null;
|
|
userId: string | null;
|
|
jobType: string;
|
|
jobName: string | null;
|
|
queue: string;
|
|
priority: number;
|
|
payload: Record<string, unknown>;
|
|
status: JobStatus;
|
|
progress: number;
|
|
result: Record<string, unknown> | null;
|
|
errorMessage: string | null;
|
|
errorStack: string | null;
|
|
attempts: number;
|
|
maxAttempts: number;
|
|
scheduledAt: Date;
|
|
startedAt: Date | null;
|
|
completedAt: Date | null;
|
|
timeoutSeconds: number;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* API key entity
|
|
*/
|
|
export interface ApiKey {
|
|
id: string;
|
|
tenantId: string;
|
|
name: string;
|
|
description: string | null;
|
|
keyPrefix: string;
|
|
scopes: string[];
|
|
allowedIps: string[] | null;
|
|
allowedOrigins: string[] | null;
|
|
rateLimitPerMinute: number;
|
|
rateLimitPerDay: number;
|
|
lastUsedAt: Date | null;
|
|
usageCount: number;
|
|
isActive: boolean;
|
|
expiresAt: Date | null;
|
|
createdBy: string | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
revokedAt: Date | null;
|
|
revokedBy: string | null;
|
|
}
|
|
|
|
/**
|
|
* Audit log entry entity
|
|
*/
|
|
export interface AuditLogEntry {
|
|
id: string;
|
|
tenantId: string | null;
|
|
userId: string | null;
|
|
action: string;
|
|
entityType: string;
|
|
entityId: string | null;
|
|
oldValues: Record<string, unknown> | null;
|
|
newValues: Record<string, unknown> | null;
|
|
details: Record<string, unknown> | null;
|
|
ipAddress: string | null;
|
|
userAgent: string | null;
|
|
requestId: string | null;
|
|
createdAt: Date;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Tenant Schema Entity Interfaces
|
|
// ============================================================================
|
|
|
|
/**
|
|
* SAT credentials entity (tenant schema)
|
|
*/
|
|
export interface SatCredentials {
|
|
id: string;
|
|
rfc: string;
|
|
cerSerialNumber: string | null;
|
|
cerIssuedAt: Date | null;
|
|
cerExpiresAt: Date | null;
|
|
cerIssuer: string | null;
|
|
isActive: boolean;
|
|
isValid: boolean;
|
|
lastValidatedAt: Date | null;
|
|
validationError: string | null;
|
|
syncEnabled: boolean;
|
|
syncFrequencyHours: number;
|
|
lastSyncAt: Date | null;
|
|
lastSyncStatus: string | null;
|
|
lastSyncError: string | null;
|
|
createdBy: string;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* CFDI entity (tenant schema)
|
|
*/
|
|
export interface Cfdi {
|
|
id: string;
|
|
uuidFiscal: string;
|
|
serie: string | null;
|
|
folio: string | null;
|
|
tipoComprobante: CfdiType;
|
|
status: CfdiStatus;
|
|
fechaEmision: Date;
|
|
fechaTimbrado: Date | null;
|
|
fechaCancelacion: Date | null;
|
|
emisorRfc: string;
|
|
emisorNombre: string;
|
|
emisorRegimenFiscal: string;
|
|
receptorRfc: string;
|
|
receptorNombre: string;
|
|
receptorRegimenFiscal: string | null;
|
|
receptorDomicilioFiscal: string | null;
|
|
receptorUsoCfdi: string;
|
|
subtotal: number;
|
|
descuento: number;
|
|
total: number;
|
|
totalImpuestosTrasladados: number;
|
|
totalImpuestosRetenidos: number;
|
|
iva16: number;
|
|
iva8: number;
|
|
iva0: number;
|
|
ivaExento: number;
|
|
isrRetenido: number;
|
|
ivaRetenido: number;
|
|
moneda: string;
|
|
tipoCambio: number;
|
|
formaPago: string | null;
|
|
metodoPago: string | null;
|
|
condicionesPago: string | null;
|
|
cfdiRelacionados: Record<string, unknown> | null;
|
|
tipoRelacion: string | null;
|
|
conceptos: Record<string, unknown>[];
|
|
isEmitted: boolean;
|
|
categoryId: string | null;
|
|
contactId: string | null;
|
|
isReconciled: boolean;
|
|
reconciledAt: Date | null;
|
|
reconciledBy: string | null;
|
|
aiCategorySuggestion: string | null;
|
|
aiConfidenceScore: number | null;
|
|
source: string;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Transaction entity (tenant schema)
|
|
*/
|
|
export interface Transaction {
|
|
id: string;
|
|
type: TransactionType;
|
|
status: TransactionStatus;
|
|
amount: number;
|
|
currency: string;
|
|
exchangeRate: number;
|
|
amountMxn: number;
|
|
transactionDate: Date;
|
|
valueDate: Date | null;
|
|
recordedAt: Date;
|
|
description: string | null;
|
|
reference: string | null;
|
|
notes: string | null;
|
|
categoryId: string | null;
|
|
accountId: string | null;
|
|
contactId: string | null;
|
|
cfdiId: string | null;
|
|
bankTransactionId: string | null;
|
|
bankAccountId: string | null;
|
|
bankDescription: string | null;
|
|
isRecurring: boolean;
|
|
recurringPattern: Record<string, unknown> | null;
|
|
parentTransactionId: string | null;
|
|
attachments: Record<string, unknown>[] | null;
|
|
tags: string[] | null;
|
|
isReconciled: boolean;
|
|
reconciledAt: Date | null;
|
|
reconciledBy: string | null;
|
|
requiresApproval: boolean;
|
|
approvedAt: Date | null;
|
|
approvedBy: string | null;
|
|
aiCategoryId: string | null;
|
|
aiConfidence: number | null;
|
|
aiNotes: string | null;
|
|
createdBy: string | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
voidedAt: Date | null;
|
|
voidedBy: string | null;
|
|
voidReason: string | null;
|
|
}
|
|
|
|
/**
|
|
* Contact entity (tenant schema)
|
|
*/
|
|
export interface Contact {
|
|
id: string;
|
|
type: ContactType;
|
|
name: string;
|
|
tradeName: string | null;
|
|
rfc: string | null;
|
|
regimenFiscal: string | null;
|
|
usoCfdiDefault: string | null;
|
|
email: string | null;
|
|
phone: string | null;
|
|
mobile: string | null;
|
|
website: string | null;
|
|
addressStreet: string | null;
|
|
addressInterior: string | null;
|
|
addressExterior: string | null;
|
|
addressNeighborhood: string | null;
|
|
addressCity: string | null;
|
|
addressMunicipality: string | null;
|
|
addressState: string | null;
|
|
addressZip: string | null;
|
|
addressCountry: string;
|
|
bankName: string | null;
|
|
bankAccount: string | null;
|
|
bankClabe: string | null;
|
|
creditDays: number;
|
|
creditLimit: number;
|
|
balanceReceivable: number;
|
|
balancePayable: number;
|
|
category: string | null;
|
|
tags: string[] | null;
|
|
isActive: boolean;
|
|
notes: string | null;
|
|
createdBy: string | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Category entity (tenant schema)
|
|
*/
|
|
export interface Category {
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
description: string | null;
|
|
type: CategoryType;
|
|
parentId: string | null;
|
|
level: number;
|
|
path: string | null;
|
|
satKey: string | null;
|
|
budgetMonthly: number | null;
|
|
budgetYearly: number | null;
|
|
color: string | null;
|
|
icon: string | null;
|
|
displayOrder: number;
|
|
isActive: boolean;
|
|
isSystem: boolean;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Account entity (tenant schema)
|
|
*/
|
|
export interface Account {
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
description: string | null;
|
|
type: AccountType;
|
|
parentId: string | null;
|
|
level: number;
|
|
path: string | null;
|
|
satCode: string | null;
|
|
satNature: 'D' | 'A' | null;
|
|
balanceDebit: number;
|
|
balanceCredit: number;
|
|
balanceCurrent: number;
|
|
isActive: boolean;
|
|
isSystem: boolean;
|
|
allowsMovements: boolean;
|
|
displayOrder: number;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Alert entity (tenant schema)
|
|
*/
|
|
export interface Alert {
|
|
id: string;
|
|
type: string;
|
|
title: string;
|
|
message: string;
|
|
severity: AlertSeverity;
|
|
entityType: string | null;
|
|
entityId: string | null;
|
|
thresholdType: string | null;
|
|
thresholdValue: number | null;
|
|
currentValue: number | null;
|
|
actionUrl: string | null;
|
|
actionLabel: string | null;
|
|
actionData: Record<string, unknown> | null;
|
|
isRead: boolean;
|
|
isDismissed: boolean;
|
|
readAt: Date | null;
|
|
dismissedAt: Date | null;
|
|
dismissedBy: string | null;
|
|
isRecurring: boolean;
|
|
lastTriggeredAt: Date | null;
|
|
triggerCount: number;
|
|
autoResolved: boolean;
|
|
resolvedAt: Date | null;
|
|
resolvedBy: string | null;
|
|
resolutionNotes: string | null;
|
|
createdAt: Date;
|
|
expiresAt: Date | null;
|
|
}
|
|
|
|
/**
|
|
* Report entity (tenant schema)
|
|
*/
|
|
export interface Report {
|
|
id: string;
|
|
type: string;
|
|
name: string;
|
|
description: string | null;
|
|
periodStart: Date;
|
|
periodEnd: Date;
|
|
comparisonPeriodStart: Date | null;
|
|
comparisonPeriodEnd: Date | null;
|
|
status: ReportStatus;
|
|
parameters: Record<string, unknown> | null;
|
|
data: Record<string, unknown> | null;
|
|
fileUrl: string | null;
|
|
fileFormat: string | null;
|
|
isScheduled: boolean;
|
|
scheduleCron: string | null;
|
|
nextScheduledAt: Date | null;
|
|
lastGeneratedAt: Date | null;
|
|
isShared: boolean;
|
|
sharedWith: string[] | null;
|
|
shareToken: string | null;
|
|
shareExpiresAt: Date | null;
|
|
generatedBy: string | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Metric cache entry entity (tenant schema)
|
|
*/
|
|
export interface MetricCache {
|
|
id: string;
|
|
metricKey: string;
|
|
periodType: 'daily' | 'weekly' | 'monthly' | 'yearly';
|
|
periodStart: Date;
|
|
periodEnd: Date;
|
|
dimensionType: string | null;
|
|
dimensionId: string | null;
|
|
valueNumeric: number | null;
|
|
valueJson: Record<string, unknown> | null;
|
|
previousValue: number | null;
|
|
changePercent: number | null;
|
|
changeAbsolute: number | null;
|
|
computedAt: Date;
|
|
validUntil: Date | null;
|
|
isStale: boolean;
|
|
}
|
|
|
|
/**
|
|
* Setting entity (tenant schema)
|
|
*/
|
|
export interface Setting {
|
|
key: string;
|
|
value: string;
|
|
valueType: 'string' | 'integer' | 'boolean' | 'json';
|
|
category: string;
|
|
label: string | null;
|
|
description: string | null;
|
|
isSensitive: boolean;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Bank account entity (tenant schema)
|
|
*/
|
|
export interface BankAccount {
|
|
id: string;
|
|
bankName: string;
|
|
bankCode: string | null;
|
|
accountNumber: string | null;
|
|
clabe: string | null;
|
|
accountType: string | null;
|
|
alias: string | null;
|
|
currency: string;
|
|
balanceAvailable: number | null;
|
|
balanceCurrent: number | null;
|
|
balanceUpdatedAt: Date | null;
|
|
connectionProvider: string | null;
|
|
connectionId: string | null;
|
|
connectionStatus: string | null;
|
|
lastSyncAt: Date | null;
|
|
lastSyncError: string | null;
|
|
accountId: string | null;
|
|
isActive: boolean;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Budget item entity (tenant schema)
|
|
*/
|
|
export interface BudgetItem {
|
|
id: string;
|
|
year: number;
|
|
month: number;
|
|
categoryId: string | null;
|
|
accountId: string | null;
|
|
amountBudgeted: number;
|
|
amountActual: number;
|
|
amountVariance: number;
|
|
notes: string | null;
|
|
isLocked: boolean;
|
|
createdBy: string | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Attachment entity (tenant schema)
|
|
*/
|
|
export interface Attachment {
|
|
id: string;
|
|
entityType: string;
|
|
entityId: string;
|
|
fileName: string;
|
|
fileType: string | null;
|
|
fileSize: number | null;
|
|
fileUrl: string;
|
|
storageProvider: string;
|
|
storagePath: string | null;
|
|
uploadedBy: string | null;
|
|
createdAt: Date;
|
|
}
|