feat: Implement Phase 1 & 2 - Full monorepo architecture

## Backend API (apps/api)
- Express.js server with TypeScript
- JWT authentication with access/refresh tokens
- Multi-tenant middleware (schema per tenant)
- Complete CRUD routes: auth, cfdis, transactions, contacts, categories, metrics, alerts
- SAT integration: CFDI 4.0 XML parser, FIEL authentication
- Metrics engine: 50+ financial metrics (Core, Startup, Enterprise)
- Rate limiting, CORS, Helmet security

## Frontend Web (apps/web)
- Next.js 14 with App Router
- Authentication pages: login, register, forgot-password
- Dashboard layout with Sidebar and Header
- Dashboard pages: overview, cash-flow, revenue, expenses, metrics
- Zustand stores for auth and UI state
- Theme support with flash prevention

## Database Package (packages/database)
- PostgreSQL migrations with multi-tenant architecture
- Public schema: plans, tenants, users, sessions, subscriptions
- Tenant schema: sat_credentials, cfdis, transactions, contacts, accounts, alerts
- Tenant management functions
- Seed data for plans and super admin

## Shared Package (packages/shared)
- TypeScript types: auth, tenant, financial, metrics, reports
- Zod validation schemas for all entities
- Utility functions for formatting

## UI Package (packages/ui)
- Chart components: LineChart, BarChart, AreaChart, PieChart
- Data components: DataTable, MetricCard, KPICard, AlertBadge
- PeriodSelector and Skeleton components

## Infrastructure
- Docker Compose: PostgreSQL 15, Redis 7, MinIO, Mailhog
- Makefile with 25+ development commands
- Development scripts: dev-setup.sh, dev-down.sh
- Complete .env.example template

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 11:05:24 +00:00
parent c1321c3f0c
commit a9b1994c48
110 changed files with 40788 additions and 0 deletions

View File

@@ -0,0 +1,676 @@
/**
* @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
*/
// 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;
}