16 KiB
16 KiB
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
@Controller('api/v1/finance/accounting')
@ApiTags('finance-accounting')
export class AccountingController {
// Chart of Accounts
@Get('accounts')
async getAccounts(@Query() query: AccountQueryDto): Promise<AccountDto[]>;
@Get('accounts/:id')
async getAccount(@Param('id') id: UUID): Promise<AccountDto>;
@Post('accounts')
async createAccount(@Body() dto: CreateAccountDto): Promise<AccountDto>;
@Put('accounts/:id')
async updateAccount(@Param('id') id: UUID, @Body() dto: UpdateAccountDto): Promise<AccountDto>;
@Get('accounts/tree')
async getAccountTree(): Promise<AccountTreeDto[]>;
// Cost Centers
@Get('cost-centers')
async getCostCenters(): Promise<CostCenterDto[]>;
@Post('cost-centers')
async createCostCenter(@Body() dto: CreateCostCenterDto): Promise<CostCenterDto>;
// Accounting Entries
@Get('entries')
async getEntries(@Query() query: EntryQueryDto): Promise<PaginatedResponse<EntryDto>>;
@Get('entries/:id')
async getEntry(@Param('id') id: UUID): Promise<EntryDetailDto>;
@Post('entries')
async createEntry(@Body() dto: CreateEntryDto): Promise<EntryDto>;
@Put('entries/:id')
async updateEntry(@Param('id') id: UUID, @Body() dto: UpdateEntryDto): Promise<EntryDto>;
@Post('entries/:id/post')
async postEntry(@Param('id') id: UUID): Promise<EntryDto>;
@Post('entries/:id/reverse')
async reverseEntry(@Param('id') id: UUID): Promise<EntryDto>;
// Period Management
@Post('periods/close')
async closePeriod(@Body() dto: ClosePeriodDto): Promise<PeriodDto>;
}
2. AccountsPayableController
@Controller('api/v1/finance/ap')
@ApiTags('finance-ap')
export class AccountsPayableController {
@Get()
async findAll(@Query() query: APQueryDto): Promise<PaginatedResponse<APDto>>;
@Get(':id')
async findOne(@Param('id') id: UUID): Promise<APDetailDto>;
@Post()
async create(@Body() dto: CreateAPDto): Promise<APDto>;
@Put(':id')
async update(@Param('id') id: UUID, @Body() dto: UpdateAPDto): Promise<APDto>;
@Get('aging')
async getAging(@Query() query: AgingQueryDto): Promise<AgingReportDto>;
@Get('by-supplier/:supplierId')
async getBySupplier(@Param('supplierId') supplierId: UUID): Promise<APDto[]>;
@Get('by-project/:projectId')
async getByProject(@Param('projectId') projectId: UUID): Promise<APDto[]>;
// Payments
@Post(':id/payments')
async addPayment(@Param('id') id: UUID, @Body() dto: CreatePaymentDto): Promise<PaymentDto>;
@Get(':id/payments')
async getPayments(@Param('id') id: UUID): Promise<PaymentDto[]>;
@Delete('payments/:paymentId')
async cancelPayment(@Param('paymentId') paymentId: UUID): Promise<void>;
// Dashboard
@Get('dashboard')
async getDashboard(): Promise<APDashboardDto>;
}
3. AccountsReceivableController
@Controller('api/v1/finance/ar')
@ApiTags('finance-ar')
export class AccountsReceivableController {
@Get()
async findAll(@Query() query: ARQueryDto): Promise<PaginatedResponse<ARDto>>;
@Get(':id')
async findOne(@Param('id') id: UUID): Promise<ARDetailDto>;
@Post()
async create(@Body() dto: CreateARDto): Promise<ARDto>;
@Get('aging')
async getAging(@Query() query: AgingQueryDto): Promise<AgingReportDto>;
@Get('by-customer/:customerId')
async getByCustomer(@Param('customerId') customerId: UUID): Promise<ARDto[]>;
@Get('by-project/:projectId')
async getByProject(@Param('projectId') projectId: UUID): Promise<ARDto[]>;
// Collections
@Post(':id/collections')
async addCollection(@Param('id') id: UUID, @Body() dto: CreateCollectionDto): Promise<CollectionDto>;
@Get(':id/collections')
async getCollections(@Param('id') id: UUID): Promise<CollectionDto[]>;
// Dashboard
@Get('dashboard')
async getDashboard(): Promise<ARDashboardDto>;
}
4. BankController
@Controller('api/v1/finance/bank')
@ApiTags('finance-bank')
export class BankController {
// Bank Accounts
@Get('accounts')
async getBankAccounts(): Promise<BankAccountDto[]>;
@Get('accounts/:id')
async getBankAccount(@Param('id') id: UUID): Promise<BankAccountDto>;
@Post('accounts')
async createBankAccount(@Body() dto: CreateBankAccountDto): Promise<BankAccountDto>;
// Movements
@Get('accounts/:id/movements')
async getMovements(
@Param('id') id: UUID,
@Query() query: MovementQueryDto
): Promise<PaginatedResponse<MovementDto>>;
@Post('accounts/:id/import')
@UseInterceptors(FileInterceptor('file'))
async importMovements(
@Param('id') id: UUID,
@UploadedFile() file: Express.Multer.File
): Promise<ImportResultDto>;
// Reconciliation
@Get('accounts/:id/reconciliation')
async getReconciliation(@Param('id') id: UUID, @Query() query: ReconciliationQueryDto): Promise<ReconciliationDto>;
@Post('accounts/:id/reconcile')
async startReconciliation(@Param('id') id: UUID, @Body() dto: StartReconciliationDto): Promise<ReconciliationDto>;
@Post('reconciliation/:id/match')
async matchMovement(@Param('id') id: UUID, @Body() dto: MatchMovementDto): Promise<MovementDto>;
@Post('reconciliation/:id/complete')
async completeReconciliation(@Param('id') id: UUID): Promise<ReconciliationDto>;
}
5. CashFlowController
@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<CashFlowProjectionDto>;
@Get('comparison/:projectId')
async getComparison(
@Param('projectId') projectId: UUID,
@Query() query: CashFlowQueryDto
): Promise<CashFlowComparisonDto>;
@Post('projection')
async createProjection(@Body() dto: CreateProjectionDto): Promise<CashFlowProjectionDto>;
@Put('projection/:id')
async updateProjection(@Param('id') id: UUID, @Body() dto: UpdateProjectionDto): Promise<CashFlowProjectionDto>;
@Post('generate/:projectId')
async generateProjection(@Param('projectId') projectId: UUID): Promise<CashFlowProjectionDto>;
}
6. ReportsController
@Controller('api/v1/finance/reports')
@ApiTags('finance-reports')
export class ReportsController {
@Get('balance/:projectId')
async getBalance(@Param('projectId') projectId: UUID, @Query() query: ReportQueryDto): Promise<BalanceSheetDto>;
@Get('income-statement/:projectId')
async getIncomeStatement(@Param('projectId') projectId: UUID, @Query() query: ReportQueryDto): Promise<IncomeStatementDto>;
@Get('trial-balance')
async getTrialBalance(@Query() query: ReportQueryDto): Promise<TrialBalanceDto>;
@Get('ledger/:accountId')
async getLedger(@Param('accountId') accountId: UUID, @Query() query: ReportQueryDto): Promise<LedgerDto>;
@Get('dashboard')
async getDashboard(@Query() query: DashboardQueryDto): Promise<FinanceDashboardDto>;
// Export
@Get('export/entries')
async exportEntries(@Query() query: ExportQueryDto): Promise<StreamableFile>;
@Post('export/contpaqi')
async exportToContpaqi(@Body() dto: ExportContpaqiDto): Promise<StreamableFile>;
@Post('export/sap')
async exportToSap(@Body() dto: ExportSapDto): Promise<{ status: string; batchId: string }>;
}
Services
AccountingService
@Injectable()
export class AccountingService {
async createEntry(tenantId: UUID, userId: UUID, dto: CreateEntryDto): Promise<EntryDto>;
async postEntry(tenantId: UUID, entryId: UUID, userId: UUID): Promise<EntryDto>;
async reverseEntry(tenantId: UUID, entryId: UUID, userId: UUID): Promise<EntryDto>;
async validateEntry(entry: AccountingEntry): Promise<boolean>;
async generateEntryFromPurchase(purchaseId: UUID): Promise<EntryDto>;
async generateEntryFromEstimation(estimationId: UUID): Promise<EntryDto>;
}
AccountsPayableService
@Injectable()
export class AccountsPayableService {
async create(tenantId: UUID, userId: UUID, dto: CreateAPDto): Promise<APDto>;
async addPayment(apId: UUID, userId: UUID, dto: CreatePaymentDto): Promise<PaymentDto>;
async getAging(tenantId: UUID, query: AgingQueryDto): Promise<AgingReportDto>;
async getOverdue(tenantId: UUID): Promise<APDto[]>;
async calculateBalance(apId: UUID): Promise<number>;
}
CashFlowService
@Injectable()
export class CashFlowService {
async generateProjection(tenantId: UUID, projectId: UUID, periodType: PeriodType): Promise<CashFlowProjectionDto>;
async getComparison(tenantId: UUID, projectId: UUID, query: CashFlowQueryDto): Promise<CashFlowComparisonDto>;
async calculateVariance(projectionId: UUID): Promise<VarianceDto>;
}
FinancialReportsService
@Injectable()
export class FinancialReportsService {
async generateBalanceSheet(tenantId: UUID, projectId: UUID, asOfDate: Date): Promise<BalanceSheetDto>;
async generateIncomeStatement(tenantId: UUID, projectId: UUID, period: Period): Promise<IncomeStatementDto>;
async generateTrialBalance(tenantId: UUID, period: Period): Promise<TrialBalanceDto>;
async generateLedger(tenantId: UUID, accountId: UUID, period: Period): Promise<LedgerDto>;
}
DTOs
Accounting DTOs
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
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
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
@Injectable()
export class ContpaqiIntegrationService {
async exportEntries(tenantId: UUID, entries: AccountingEntry[]): Promise<Buffer> {
const xml = this.generateXml(entries);
return Buffer.from(xml);
}
private generateXml(entries: AccountingEntry[]): string {
// Generate CONTPAQi compatible XML format
return `<?xml version="1.0" encoding="UTF-8"?>
<Polizas>
${entries.map(e => this.entryToXml(e)).join('\n')}
</Polizas>`;
}
}
SAP Integration
@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
Ultima actualizacion: 2025-12-05