# Backend Specification: Finance Module **Version:** 1.0.0 **Fecha:** 2025-12-05 **Modulos:** MAE-014 (Finanzas y Controlling) --- ## Resumen | Metrica | Valor | |---------|-------| | Controllers | 6 | | Services | 8 | | Entities | 15 | | Endpoints | 50+ | | Tests Requeridos | 70+ | --- ## Estructura del Modulo ``` modules/finance/ +-- finance.module.ts +-- controllers/ | +-- accounting.controller.ts | +-- accounts-payable.controller.ts | +-- accounts-receivable.controller.ts | +-- bank.controller.ts | +-- cash-flow.controller.ts | +-- reports.controller.ts +-- services/ | +-- accounting.service.ts | +-- chart-of-accounts.service.ts | +-- accounts-payable.service.ts | +-- accounts-receivable.service.ts | +-- bank.service.ts | +-- reconciliation.service.ts | +-- cash-flow.service.ts | +-- financial-reports.service.ts +-- entities/ | +-- chart-of-accounts.entity.ts | +-- cost-center.entity.ts | +-- accounting-entry.entity.ts | +-- accounting-entry-line.entity.ts | +-- accounts-payable.entity.ts | +-- ap-payment.entity.ts | +-- accounts-receivable.entity.ts | +-- ar-collection.entity.ts | +-- bank-account.entity.ts | +-- bank-movement.entity.ts | +-- bank-reconciliation.entity.ts | +-- cash-flow-projection.entity.ts | +-- cash-flow-item.entity.ts +-- dto/ +-- integrations/ | +-- sap.integration.ts | +-- contpaqi.integration.ts +-- events/ ``` --- ## Controllers ### 1. AccountingController ```typescript @Controller('api/v1/finance/accounting') @ApiTags('finance-accounting') export class AccountingController { // Chart of Accounts @Get('accounts') async getAccounts(@Query() query: AccountQueryDto): Promise; @Get('accounts/:id') async getAccount(@Param('id') id: UUID): Promise; @Post('accounts') async createAccount(@Body() dto: CreateAccountDto): Promise; @Put('accounts/:id') async updateAccount(@Param('id') id: UUID, @Body() dto: UpdateAccountDto): Promise; @Get('accounts/tree') async getAccountTree(): Promise; // Cost Centers @Get('cost-centers') async getCostCenters(): Promise; @Post('cost-centers') async createCostCenter(@Body() dto: CreateCostCenterDto): Promise; // Accounting Entries @Get('entries') async getEntries(@Query() query: EntryQueryDto): Promise>; @Get('entries/:id') async getEntry(@Param('id') id: UUID): Promise; @Post('entries') async createEntry(@Body() dto: CreateEntryDto): Promise; @Put('entries/:id') async updateEntry(@Param('id') id: UUID, @Body() dto: UpdateEntryDto): Promise; @Post('entries/:id/post') async postEntry(@Param('id') id: UUID): Promise; @Post('entries/:id/reverse') async reverseEntry(@Param('id') id: UUID): Promise; // Period Management @Post('periods/close') async closePeriod(@Body() dto: ClosePeriodDto): Promise; } ``` ### 2. AccountsPayableController ```typescript @Controller('api/v1/finance/ap') @ApiTags('finance-ap') export class AccountsPayableController { @Get() async findAll(@Query() query: APQueryDto): Promise>; @Get(':id') async findOne(@Param('id') id: UUID): Promise; @Post() async create(@Body() dto: CreateAPDto): Promise; @Put(':id') async update(@Param('id') id: UUID, @Body() dto: UpdateAPDto): Promise; @Get('aging') async getAging(@Query() query: AgingQueryDto): Promise; @Get('by-supplier/:supplierId') async getBySupplier(@Param('supplierId') supplierId: UUID): Promise; @Get('by-project/:projectId') async getByProject(@Param('projectId') projectId: UUID): Promise; // Payments @Post(':id/payments') async addPayment(@Param('id') id: UUID, @Body() dto: CreatePaymentDto): Promise; @Get(':id/payments') async getPayments(@Param('id') id: UUID): Promise; @Delete('payments/:paymentId') async cancelPayment(@Param('paymentId') paymentId: UUID): Promise; // Dashboard @Get('dashboard') async getDashboard(): Promise; } ``` ### 3. AccountsReceivableController ```typescript @Controller('api/v1/finance/ar') @ApiTags('finance-ar') export class AccountsReceivableController { @Get() async findAll(@Query() query: ARQueryDto): Promise>; @Get(':id') async findOne(@Param('id') id: UUID): Promise; @Post() async create(@Body() dto: CreateARDto): Promise; @Get('aging') async getAging(@Query() query: AgingQueryDto): Promise; @Get('by-customer/:customerId') async getByCustomer(@Param('customerId') customerId: UUID): Promise; @Get('by-project/:projectId') async getByProject(@Param('projectId') projectId: UUID): Promise; // Collections @Post(':id/collections') async addCollection(@Param('id') id: UUID, @Body() dto: CreateCollectionDto): Promise; @Get(':id/collections') async getCollections(@Param('id') id: UUID): Promise; // Dashboard @Get('dashboard') async getDashboard(): Promise; } ``` ### 4. BankController ```typescript @Controller('api/v1/finance/bank') @ApiTags('finance-bank') export class BankController { // Bank Accounts @Get('accounts') async getBankAccounts(): Promise; @Get('accounts/:id') async getBankAccount(@Param('id') id: UUID): Promise; @Post('accounts') async createBankAccount(@Body() dto: CreateBankAccountDto): Promise; // Movements @Get('accounts/:id/movements') async getMovements( @Param('id') id: UUID, @Query() query: MovementQueryDto ): Promise>; @Post('accounts/:id/import') @UseInterceptors(FileInterceptor('file')) async importMovements( @Param('id') id: UUID, @UploadedFile() file: Express.Multer.File ): Promise; // Reconciliation @Get('accounts/:id/reconciliation') async getReconciliation(@Param('id') id: UUID, @Query() query: ReconciliationQueryDto): Promise; @Post('accounts/:id/reconcile') async startReconciliation(@Param('id') id: UUID, @Body() dto: StartReconciliationDto): Promise; @Post('reconciliation/:id/match') async matchMovement(@Param('id') id: UUID, @Body() dto: MatchMovementDto): Promise; @Post('reconciliation/:id/complete') async completeReconciliation(@Param('id') id: UUID): Promise; } ``` ### 5. CashFlowController ```typescript @Controller('api/v1/finance/cash-flow') @ApiTags('finance-cash-flow') export class CashFlowController { @Get('projection/:projectId') async getProjection( @Param('projectId') projectId: UUID, @Query() query: CashFlowQueryDto ): Promise; @Get('comparison/:projectId') async getComparison( @Param('projectId') projectId: UUID, @Query() query: CashFlowQueryDto ): Promise; @Post('projection') async createProjection(@Body() dto: CreateProjectionDto): Promise; @Put('projection/:id') async updateProjection(@Param('id') id: UUID, @Body() dto: UpdateProjectionDto): Promise; @Post('generate/:projectId') async generateProjection(@Param('projectId') projectId: UUID): Promise; } ``` ### 6. ReportsController ```typescript @Controller('api/v1/finance/reports') @ApiTags('finance-reports') export class ReportsController { @Get('balance/:projectId') async getBalance(@Param('projectId') projectId: UUID, @Query() query: ReportQueryDto): Promise; @Get('income-statement/:projectId') async getIncomeStatement(@Param('projectId') projectId: UUID, @Query() query: ReportQueryDto): Promise; @Get('trial-balance') async getTrialBalance(@Query() query: ReportQueryDto): Promise; @Get('ledger/:accountId') async getLedger(@Param('accountId') accountId: UUID, @Query() query: ReportQueryDto): Promise; @Get('dashboard') async getDashboard(@Query() query: DashboardQueryDto): Promise; // Export @Get('export/entries') async exportEntries(@Query() query: ExportQueryDto): Promise; @Post('export/contpaqi') async exportToContpaqi(@Body() dto: ExportContpaqiDto): Promise; @Post('export/sap') async exportToSap(@Body() dto: ExportSapDto): Promise<{ status: string; batchId: string }>; } ``` --- ## Services ### AccountingService ```typescript @Injectable() export class AccountingService { async createEntry(tenantId: UUID, userId: UUID, dto: CreateEntryDto): Promise; async postEntry(tenantId: UUID, entryId: UUID, userId: UUID): Promise; async reverseEntry(tenantId: UUID, entryId: UUID, userId: UUID): Promise; async validateEntry(entry: AccountingEntry): Promise; async generateEntryFromPurchase(purchaseId: UUID): Promise; async generateEntryFromEstimation(estimationId: UUID): Promise; } ``` ### AccountsPayableService ```typescript @Injectable() export class AccountsPayableService { async create(tenantId: UUID, userId: UUID, dto: CreateAPDto): Promise; async addPayment(apId: UUID, userId: UUID, dto: CreatePaymentDto): Promise; async getAging(tenantId: UUID, query: AgingQueryDto): Promise; async getOverdue(tenantId: UUID): Promise; async calculateBalance(apId: UUID): Promise; } ``` ### CashFlowService ```typescript @Injectable() export class CashFlowService { async generateProjection(tenantId: UUID, projectId: UUID, periodType: PeriodType): Promise; async getComparison(tenantId: UUID, projectId: UUID, query: CashFlowQueryDto): Promise; async calculateVariance(projectionId: UUID): Promise; } ``` ### FinancialReportsService ```typescript @Injectable() export class FinancialReportsService { async generateBalanceSheet(tenantId: UUID, projectId: UUID, asOfDate: Date): Promise; async generateIncomeStatement(tenantId: UUID, projectId: UUID, period: Period): Promise; async generateTrialBalance(tenantId: UUID, period: Period): Promise; async generateLedger(tenantId: UUID, accountId: UUID, period: Period): Promise; } ``` --- ## DTOs ### Accounting DTOs ```typescript export class CreateAccountDto { @IsString() @MaxLength(30) code: string; @IsString() @MaxLength(200) name: string; @IsEnum(AccountType) accountType: AccountType; @IsEnum(AccountNature) nature: AccountNature; @IsOptional() @IsUUID() parentId?: string; @IsOptional() @IsBoolean() costCenterRequired?: boolean; @IsOptional() @IsBoolean() projectRequired?: boolean; } export class CreateEntryDto { @IsEnum(EntryType) entryType: EntryType; @IsDateString() entryDate: string; @IsString() description: string; @IsOptional() @IsString() reference?: string; @IsOptional() @IsUUID() projectId?: string; @IsOptional() @IsUUID() costCenterId?: string; @IsArray() @ValidateNested({ each: true }) @Type(() => CreateEntryLineDto) lines: CreateEntryLineDto[]; } export class CreateEntryLineDto { @IsUUID() accountId: string; @IsOptional() @IsString() description?: string; @IsNumber() debitAmount: number; @IsNumber() creditAmount: number; @IsOptional() @IsUUID() costCenterId?: string; @IsOptional() @IsUUID() projectId?: string; } ``` ### AP/AR DTOs ```typescript export class CreateAPDto { @IsString() @MaxLength(50) documentNumber: string; @IsUUID() supplierId: string; @IsNumber() subtotal: number; @IsNumber() taxAmount: number; @IsNumber() totalAmount: number; @IsDateString() documentDate: string; @IsDateString() dueDate: string; @IsOptional() @IsUUID() projectId?: string; @IsOptional() @IsUUID() purchaseOrderId?: string; } export class CreatePaymentDto { @IsDateString() paymentDate: string; @IsNumber() amount: number; @IsEnum(PaymentMethod) paymentMethod: PaymentMethod; @IsOptional() @IsUUID() bankAccountId?: string; @IsOptional() @IsString() reference?: string; } export class AgingReportDto { suppliers: { supplierId: string; supplierName: string; current: number; days1_30: number; days31_60: number; days61_90: number; daysOver90: number; total: number; }[]; summary: { totalCurrent: number; total1_30: number; total31_60: number; total61_90: number; totalOver90: number; grandTotal: number; }; } ``` ### Cash Flow DTOs ```typescript export class CashFlowProjectionDto { projectId: string; periodType: PeriodType; periods: { periodDate: string; projectedIncome: number; projectedExpenses: number; actualIncome: number; actualExpenses: number; openingBalance: number; projectedClosing: number; actualClosing?: number; }[]; summary: { totalProjectedIncome: number; totalProjectedExpenses: number; netProjectedFlow: number; totalActualIncome: number; totalActualExpenses: number; netActualFlow: number; variance: number; variancePercentage: number; }; } export class CashFlowComparisonDto { projectId: string; period: { start: string; end: string }; projectedVsActual: { category: string; projected: number; actual: number; variance: number; variancePercentage: number; }[]; insights: string[]; } ``` --- ## Integrations ### CONTPAQi Integration ```typescript @Injectable() export class ContpaqiIntegrationService { async exportEntries(tenantId: UUID, entries: AccountingEntry[]): Promise { const xml = this.generateXml(entries); return Buffer.from(xml); } private generateXml(entries: AccountingEntry[]): string { // Generate CONTPAQi compatible XML format return ` ${entries.map(e => this.entryToXml(e)).join('\n')} `; } } ``` ### SAP Integration ```typescript @Injectable() export class SapIntegrationService { constructor(private readonly httpService: HttpService) {} async exportEntries(tenantId: UUID, entries: AccountingEntry[]): Promise<{ status: string; batchId: string }> { // Call SAP RFC/API const response = await this.httpService.post( `${this.sapEndpoint}/finance/entries`, this.transformToSapFormat(entries) ).toPromise(); return { status: 'sent', batchId: response.data.batchId }; } } ``` --- ## Referencias - [DDL-SPEC-finance.md](../../04-modelado/database-design/schemas/DDL-SPEC-finance.md) - [FINANCE-CONTEXT.md](../../04-modelado/domain-models/FINANCE-CONTEXT.md) - [EPIC-MAE-014](../../08-epicas/EPIC-MAE-014-finanzas.md) --- *Ultima actualizacion: 2025-12-05*