@cbm-common/income-banking-transaction-repository
v0.0.1
Published
Repositorio Angular para la gestión completa de transacciones bancarias de ingresos en sistemas financieros. Implementa operaciones CRUD, consultas paginadas, sistema de reversiones y generación de asientos contables con integración completa al sistema CB
Readme
Income Banking Transaction Repository
Repositorio Angular para la gestión completa de transacciones bancarias de ingresos en sistemas financieros. Implementa operaciones CRUD, consultas paginadas, sistema de reversiones y generación de asientos contables con integración completa al sistema CBM.
📦 Instalación
npm install @cbm-common/income-banking-transaction-repository⚙️ Configuración
Configuración del Módulo
El módulo debe configurarse en el módulo raíz de la aplicación:
import { CbmIncomeBankingTransactionModule } from '@cbm-common/income-banking-transaction-repository';
@NgModule({
imports: [
CbmIncomeBankingTransactionModule.forRoot({
baseUrl: 'https://api.cbm.com/income-banking-transactions'
})
]
})
export class AppModule {}Configuración Standalone
Para aplicaciones standalone, configura el módulo en el bootstrap:
import { CbmIncomeBankingTransactionModule } from '@cbm-common/income-banking-transaction-repository';
bootstrapApplication(AppComponent, {
providers: [
CbmIncomeBankingTransactionModule.forRoot({
baseUrl: 'https://api.cbm.com/income-banking-transactions'
})
]
});🎯 Inyección de Dependencias
Inyección del Servicio
import { CbmIncomeBankingTransactionService } from '@cbm-common/income-banking-transaction-repository';
@Component({
selector: 'app-income-banking-transaction-manager',
standalone: true,
imports: [CbmIncomeBankingTransactionService]
})
export class IncomeBankingTransactionManagerComponent {
constructor(private incomeBankingTransactionService: CbmIncomeBankingTransactionService) {}
}Inyección del Repositorio
import { CbmIncomeBankingTransactionRepository } from '@cbm-common/income-banking-transaction-repository';
@Component({
selector: 'app-income-banking-transaction-list',
standalone: true,
imports: [CbmIncomeBankingTransactionRepository]
})
export class IncomeBankingTransactionListComponent {
constructor(private incomeBankingTransactionRepo: CbmIncomeBankingTransactionRepository) {}
}🏗️ Arquitectura del Repositorio
Patrón de Diseño
El repositorio sigue el patrón Repository Pattern con Dependency Injection:
CbmIncomeBankingTransactionModule
├── ICbmIncomeBankingTransactionModuleConfig (configuración)
├── CbmIncomeBankingTransactionService (implementa ICbmIncomeBankingTransactionRepository)
├── CbmIncomeBankingTransactionRepository (wrapper del service)
├── CbmIncomeBankingTransactionModel (modelos de datos)
└── HttpClient (cliente HTTP)Interfaz del Repositorio
export interface ICbmIncomeBankingTransactionRepository {
list(params: CbmIncomeBankingTransactionModel.ListParams): Observable<CbmIncomeBankingTransactionModel.ListResponse>;
getOne(id: string): Observable<CbmIncomeBankingTransactionModel.GetOneResponse>;
save(data: CbmIncomeBankingTransactionModel.SaveBody): Observable<CbmIncomeBankingTransactionModel.ConfirmResponse>;
generateOrRegenerateSeat(id: string): Observable<CbmIncomeBankingTransactionModel.ConfirmResponse>;
}📊 Operaciones Disponibles
Listado de Transacciones Bancarias
// Listado paginado con filtros avanzados
list(params: {
page: number; // Página actual
size: number; // Tamaño de página
date_begin?: number; // Fecha inicio (timestamp)
date_end?: number; // Fecha fin (timestamp)
reverse_status?: boolean; // Estado de reversión
document_number?: string; // Número de documento
operation_number?: number; // Número de operación
cost_center_id?: string; // ID de centro de costo
total?: number; // Monto total
financials_bank_id?: string; // ID del banco
}): Observable<ListResponse>Obtener Transacción Individual
// Obtener una transacción específica por ID
getOne(id: string): Observable<GetOneResponse>Crear Nueva Transacción
// Crear una nueva transacción bancaria de ingresos
save(data: SaveBody): Observable<ConfirmResponse>Generar/Regenerar Asiento
// Generar o regenerar asiento contable
generateOrRegenerateSeat(id: string): Observable<ConfirmResponse>📋 Modelos de Datos
ListResponse.Item
Información completa de una transacción bancaria de ingresos:
interface Item {
_id: string;
company_id: string;
company_branch_id: string;
// Información bancaria
financials_bank_id: string;
financials_bank_name: string;
financials_bank_account_number: string;
// Información del centro de costo
cost_center_id: string;
cost_center_name: string;
cost_center_code: string;
cost_center_code_manual: string;
// Información de la transacción
date: number; // Fecha (timestamp)
document_nomenclature: string; // Nomenclatura del documento
document_number: string; // Número de documento
operation_number: string; // Número de operación
movement_number: string; // Número de movimiento
// Información financiera
total: number; // Monto total
reason: string; // Motivo/Razón
beneficiary: string; // Beneficiario
// Información de estado
enabled: boolean; // Habilitado
reverse_status: boolean; // Estado de reversión
created_user: string; // Usuario creador
created_at: number; // Fecha de creación
// Información de reversión (opcional)
reverse_at?: number; // Fecha de reversión
reverse_reason?: string; // Razón de reversión
reverse_user?: string; // Usuario que revirtió
}SaveBody
Datos para crear una nueva transacción bancaria:
interface SaveBody {
// Información bancaria requerida
financials_bank_id: string;
financials_bank_name: string;
financials_bank_account_number: string;
// Información del centro de costo
cost_center_id: string;
cost_center_name: string;
cost_center_code: string;
cost_center_code_manual: string;
// Información de la transacción
date: number; // Fecha (timestamp)
operation_number: string; // Número de operación
total: number; // Monto total
reason: string; // Motivo/Razón
beneficiary: string; // Beneficiario
// Detalles contables
income_banking_transaction_detail: SaveBody.Detail[];
}
interface Detail {
account_id: string; // ID de la cuenta contable
account_name: string; // Nombre de la cuenta
account_code: string; // Código de la cuenta
value: number; // Valor del detalle
}GetOneResponse.Data
Datos completos de una transacción individual con detalles contables:
interface Data {
_id: string;
company_id: string;
company_branch_id: string;
// Información bancaria
financials_bank_id: string;
financials_bank_name: string;
financials_bank_account_number: string;
// Información del centro de costo
cost_center_id: string;
cost_center_name: string;
cost_center_code: string;
cost_center_code_manual: string;
// Información de la transacción
date: number;
document_nomenclature: string;
document_number: string;
operation_number: string;
movement_number: string;
// Información financiera
total: number;
reason: string;
beneficiary: string;
// Información de estado
enabled: boolean;
reverse_status: boolean;
created_user: string;
created_at: number;
// Información de reversión
reverse_at: number;
reverse_reason: string;
reverse_user: string;
// Detalles contables
income_banking_transaction_detail: Data.Detail[];
}
interface Detail {
_id: string;
income_banking_transaction_id: string;
account_id: string;
account_name: string;
account_code: string;
value: number;
created_user: string;
created_at: number;
}🚀 Ejemplos de Uso
Ejemplo Básico: Listado de Transacciones
import { CbmIncomeBankingTransactionService } from '@cbm-common/income-banking-transaction-repository';
import { CbmIncomeBankingTransactionModel } from '@cbm-common/income-banking-transaction-repository';
@Component({
selector: 'app-income-banking-transaction-list',
standalone: true,
template: `
<div class="transaction-list">
<h2>Transacciones Bancarias de Ingresos</h2>
<div class="filters">
<input [(ngModel)]="filters.document_number" placeholder="Número de documento">
<input [(ngModel)]="filters.operation_number" placeholder="Número de operación" type="number">
<select [(ngModel)]="filters.reverse_status">
<option [value]="undefined">Todos los estados</option>
<option [value]="true">Revertidos</option>
<option [value]="false">No revertidos</option>
</select>
<input [(ngModel)]="filters.date_begin" type="date" placeholder="Fecha inicio">
<input [(ngModel)]="filters.date_end" type="date" placeholder="Fecha fin">
<button (click)="loadTransactions()">Buscar</button>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>Número Documento</th>
<th>Operación</th>
<th>Banco</th>
<th>Centro de Costo</th>
<th>Monto Total</th>
<th>Fecha</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let transaction of transactions">
<td>{{ transaction.document_number }}</td>
<td>{{ transaction.operation_number }}</td>
<td>{{ transaction.financials_bank_name }}</td>
<td>{{ transaction.cost_center_name }}</td>
<td>{{ transaction.total | currency }}</td>
<td>{{ transaction.date | date:'dd/MM/yyyy' }}</td>
<td>
<span [class]="'status ' + (transaction.reverse_status ? 'reversed' : 'active')">
{{ transaction.reverse_status ? 'Revertido' : 'Activo' }}
</span>
</td>
<td>
<button (click)="viewTransaction(transaction._id)">Ver</button>
<button (click)="generateSeat(transaction._id)">Generar Asiento</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination" *ngIf="totalPages > 1">
<button [disabled]="currentPage === 1" (click)="changePage(currentPage - 1)">
Anterior
</button>
<span>Página {{ currentPage }} de {{ totalPages }}</span>
<button [disabled]="currentPage === totalPages" (click)="changePage(currentPage + 1)">
Siguiente
</button>
</div>
</div>
`,
styles: [`
.transaction-list { padding: 20px; }
.filters { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
.table-container { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
.status.active { color: green; }
.status.reversed { color: red; }
.pagination { display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 20px; }
`]
})
export class IncomeBankingTransactionListComponent implements OnInit {
transactions: CbmIncomeBankingTransactionModel.ListResponse.Item[] = [];
currentPage = 1;
pageSize = 10;
totalPages = 0;
filters: Partial<CbmIncomeBankingTransactionModel.ListParams> = {
document_number: '',
operation_number: undefined,
reverse_status: undefined,
date_begin: undefined,
date_end: undefined
};
constructor(private incomeBankingTransactionService: CbmIncomeBankingTransactionService) {}
ngOnInit() {
this.loadTransactions();
}
loadTransactions() {
// Convertir fechas a timestamps
const params: CbmIncomeBankingTransactionModel.ListParams = {
page: this.currentPage,
size: this.pageSize,
...this.filters
};
if (this.filters.date_begin) {
params.date_begin = new Date(this.filters.date_begin).getTime();
}
if (this.filters.date_end) {
params.date_end = new Date(this.filters.date_end).getTime();
}
this.incomeBankingTransactionService.list(params).subscribe({
next: (response) => {
this.transactions = response.items;
this.totalPages = response.pages;
},
error: (error) => {
console.error('Error al cargar transacciones:', error);
}
});
}
changePage(page: number) {
this.currentPage = page;
this.loadTransactions();
}
viewTransaction(id: string) {
this.incomeBankingTransactionService.getOne(id).subscribe({
next: (transaction) => {
console.log('Transacción:', transaction);
// Mostrar modal o navegar a detalle
}
});
}
generateSeat(id: string) {
this.incomeBankingTransactionService.generateOrRegenerateSeat(id).subscribe({
next: (response) => {
console.log('Asiento generado:', response);
// Mostrar mensaje de éxito
},
error: (error) => {
console.error('Error al generar asiento:', error);
}
});
}
}Ejemplo con Formulario de Creación
import { ReactiveFormsModule, FormArray } from '@angular/forms';
import { CbmIncomeBankingTransactionService } from '@cbm-common/income-banking-transaction-repository';
@Component({
selector: 'app-income-banking-transaction-form',
standalone: true,
imports: [ReactiveFormsModule, CbmIncomeBankingTransactionService],
template: `
<form [formGroup]="transactionForm" (ngSubmit)="onSubmit()">
<h2>Crear Nueva Transacción Bancaria de Ingresos</h2>
<div class="form-section">
<h3>Información Bancaria</h3>
<div class="form-group">
<label for="bank">Banco</label>
<select id="bank" formControlName="financials_bank_id">
<option *ngFor="let bank of banks" [value]="bank.id">
{{ bank.name }} - {{ bank.account_number }}
</option>
</select>
</div>
</div>
<div class="form-section">
<h3>Información del Centro de Costo</h3>
<div class="form-group">
<label for="costCenter">Centro de Costo</label>
<select id="costCenter" formControlName="cost_center_id">
<option *ngFor="let costCenter of costCenters" [value]="costCenter.id">
{{ costCenter.name }} ({{ costCenter.code }})
</option>
</select>
</div>
</div>
<div class="form-section">
<h3>Información de la Transacción</h3>
<div class="form-row">
<div class="form-group">
<label for="operationNumber">Número de Operación</label>
<input id="operationNumber" type="text" formControlName="operation_number">
</div>
<div class="form-group">
<label for="total">Monto Total</label>
<input id="total" type="number" formControlName="total" step="0.01">
</div>
</div>
<div class="form-group">
<label for="transactionDate">Fecha de Transacción</label>
<input id="transactionDate" type="date" formControlName="date">
</div>
<div class="form-group">
<label for="reason">Motivo/Razón</label>
<textarea id="reason" formControlName="reason" rows="3"></textarea>
</div>
<div class="form-group">
<label for="beneficiary">Beneficiario</label>
<input id="beneficiary" type="text" formControlName="beneficiary">
</div>
</div>
<div class="form-section">
<h3>Detalles Contables</h3>
<div formArrayName="details">
<div *ngFor="let detail of details.controls; let i = index" [formGroupName]="i" class="detail-row">
<div class="form-group">
<label>Cuenta Contable</label>
<select formControlName="account_id">
<option *ngFor="let account of accounts" [value]="account.id">
{{ account.code }} - {{ account.name }}
</option>
</select>
</div>
<div class="form-group">
<label>Valor</label>
<input type="number" formControlName="value" step="0.01">
</div>
<button type="button" (click)="removeDetail(i)">Remover</button>
</div>
</div>
<button type="button" (click)="addDetail()">Agregar Detalle</button>
</div>
<div class="form-actions">
<button type="button" (click)="onCancel()">Cancelar</button>
<button type="submit" [disabled]="transactionForm.invalid">Crear Transacción</button>
</div>
</form>
`,
styles: [`
form { max-width: 800px; margin: 0 auto; }
.form-section { margin-bottom: 30px; padding: 20px; border: 1px solid #ddd; border-radius: 8px; }
.form-section h3 { margin-top: 0; color: #333; }
.form-row { display: flex; gap: 15px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
input, select, textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.detail-row { display: flex; gap: 10px; align-items: end; margin-bottom: 10px; padding: 15px; background: #f9f9f9; }
.detail-row .form-group { flex: 1; margin-bottom: 0; }
.form-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; }
button { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
button[type="submit"] { background: #007bff; color: white; }
button[type="button"] { background: #6c757d; color: white; }
button:disabled { opacity: 0.6; cursor: not-allowed; }
`]
})
export class IncomeBankingTransactionFormComponent implements OnInit {
transactionForm!: FormGroup;
banks: any[] = [];
costCenters: any[] = [];
accounts: any[] = [];
constructor(
private fb: FormBuilder,
private incomeBankingTransactionService: CbmIncomeBankingTransactionService
) {}
ngOnInit() {
this.initForm();
this.loadReferenceData();
}
get details(): FormArray {
return this.transactionForm.get('details') as FormArray;
}
initForm() {
this.transactionForm = this.fb.group({
financials_bank_id: ['', Validators.required],
cost_center_id: ['', Validators.required],
operation_number: ['', Validators.required],
total: ['', [Validators.required, Validators.min(0)]],
date: ['', Validators.required],
reason: ['', Validators.required],
beneficiary: ['', Validators.required],
details: this.fb.array([])
});
}
loadReferenceData() {
// Cargar datos de referencia
this.banks = [
{ id: '1', name: 'Banco Pichincha', account_number: '1234567890' },
{ id: '2', name: 'Banco Guayaquil', account_number: '0987654321' }
];
this.costCenters = [
{ id: '1', name: 'Centro de Costo Principal', code: 'CCP001' },
{ id: '2', name: 'Centro de Costo Secundario', code: 'CCS002' }
];
this.accounts = [
{ id: '1', code: '110101', name: 'Caja General' },
{ id: '2', code: '110201', name: 'Banco Nacional' }
];
}
addDetail() {
const detailForm = this.fb.group({
account_id: ['', Validators.required],
value: ['', [Validators.required, Validators.min(0)]]
});
this.details.push(detailForm);
}
removeDetail(index: number) {
this.details.removeAt(index);
}
onSubmit() {
if (this.transactionForm.valid) {
const formData = this.transactionForm.value;
// Convertir fecha a timestamp
formData.date = new Date(formData.date).getTime();
// Preparar detalles con información adicional
formData.income_banking_transaction_detail = formData.details.map((detail: any) => {
const account = this.accounts.find(acc => acc.id === detail.account_id);
return {
account_id: detail.account_id,
account_name: account?.name || '',
account_code: account?.code || '',
value: detail.value
};
});
// Remover el array details del objeto final
delete formData.details;
this.incomeBankingTransactionService.save(formData).subscribe({
next: (response) => {
console.log('Transacción creada:', response);
this.transactionForm.reset();
this.details.clear();
},
error: (error) => {
console.error('Error al crear transacción:', error);
}
});
}
}
onCancel() {
this.transactionForm.reset();
this.details.clear();
}
}Ejemplo con Gestión de Estados y Reversiones
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-income-banking-transaction-dashboard',
standalone: true,
template: `
<div class="dashboard">
<div class="stats">
<div class="stat-card">
<h3>Total Transacciones</h3>
<span class="stat-number">{{ totalTransactions$ | async }}</span>
</div>
<div class="stat-card">
<h3>Monto Total Ingresos</h3>
<span class="stat-number">{{ totalAmount$ | async | currency }}</span>
</div>
<div class="stat-card">
<h3>Transacciones Activas</h3>
<span class="stat-number">{{ activeTransactions$ | async }}</span>
</div>
<div class="stat-card">
<h3>Transacciones Revertidas</h3>
<span class="stat-number">{{ reversedTransactions$ | async }}</span>
</div>
</div>
<div class="recent-transactions">
<h3>Transacciones Recientes</h3>
<div class="transaction-item" *ngFor="let transaction of recentTransactions$ | async">
<div class="transaction-info">
<span class="document">{{ transaction.document_number }}</span>
<span class="operation">{{ transaction.operation_number }}</span>
<span class="bank">{{ transaction.financials_bank_name }}</span>
</div>
<div class="transaction-amount">
{{ transaction.total | currency }}
</div>
<div class="transaction-status">
<span [class]="'status ' + (transaction.reverse_status ? 'reversed' : 'active')">
{{ transaction.reverse_status ? 'Revertido' : 'Activo' }}
</span>
</div>
</div>
</div>
</div>
`,
styles: [`
.dashboard { padding: 20px; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px; }
.stat-card { background: #f8f9fa; padding: 20px; border-radius: 8px; text-align: center; }
.stat-number { font-size: 2em; font-weight: bold; color: #007bff; }
.recent-transactions { background: white; padding: 20px; border-radius: 8px; }
.transaction-item { display: flex; justify-content: space-between; align-items: center; padding: 15px; border-bottom: 1px solid #eee; }
.transaction-info { display: flex; flex-direction: column; }
.transaction-amount { font-weight: bold; }
.status.active { color: green; }
.status.reversed { color: red; }
`]
})
export class IncomeBankingTransactionDashboardComponent implements OnInit {
private transactionsSubject = new BehaviorSubject<CbmIncomeBankingTransactionModel.ListResponse.Item[]>([]);
transactions$ = this.transactionsSubject.asObservable();
totalTransactions$ = this.transactions$.pipe(
map(transactions => transactions.length)
);
totalAmount$ = this.transactions$.pipe(
map(transactions => transactions.reduce((sum, transaction) => sum + (transaction.total || 0), 0))
);
activeTransactions$ = this.transactions$.pipe(
map(transactions => transactions.filter(t => !t.reverse_status).length)
);
reversedTransactions$ = this.transactions$.pipe(
map(transactions => transactions.filter(t => t.reverse_status).length)
);
recentTransactions$ = this.transactions$.pipe(
map(transactions => transactions.slice(0, 10))
);
constructor(private incomeBankingTransactionService: CbmIncomeBankingTransactionService) {}
ngOnInit() {
this.loadTransactions();
}
loadTransactions() {
const params: CbmIncomeBankingTransactionModel.ListParams = {
page: 1,
size: 100
};
this.incomeBankingTransactionService.list(params).subscribe({
next: (response) => {
this.transactionsSubject.next(response.items);
},
error: (error) => {
console.error('Error al cargar transacciones:', error);
}
});
}
}⚠️ Manejo de Errores
Errores de API
// Manejo de errores en operaciones bancarias
saveTransaction(transactionData: CbmIncomeBankingTransactionModel.SaveBody) {
this.incomeBankingTransactionService.save(transactionData).subscribe({
next: (response) => {
if (response.success) {
console.log('Transacción creada exitosamente');
this.showSuccessMessage('Transacción bancaria creada correctamente');
} else {
console.error('Error en la respuesta:', response);
this.showErrorMessage('Error al crear la transacción bancaria');
}
},
error: (error) => {
console.error('Error HTTP:', error);
if (error.status === 400) {
this.showErrorMessage('Datos inválidos. Verifique la información bancaria y financiera.');
} else if (error.status === 401) {
this.showErrorMessage('Sesión expirada. Inicie sesión nuevamente.');
} else if (error.status === 403) {
this.showErrorMessage('No tiene permisos para gestionar transacciones bancarias.');
} else if (error.status === 422) {
this.showErrorMessage('Error de validación. Verifique los montos y detalles contables.');
} else {
this.showErrorMessage('Error interno del servidor.');
}
}
});
}Validación de Datos Financieros
// Validaciones personalizadas para transacciones bancarias
export class BankingTransactionValidators {
static validTotalAmount(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (!value) return null;
return value > 0 ? null : { invalidTotalAmount: true };
}
static validOperationNumber(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (!value) return null;
// Validar formato de número de operación bancaria
const operationPattern = /^[A-Z0-9]{6,20}$/;
return operationPattern.test(value) ? null : { invalidOperationNumber: true };
}
static validDetailsSum(form: FormGroup): ValidationErrors | null {
const total = form.get('total')?.value;
const details = form.get('details') as FormArray;
if (!total || !details) return null;
const detailsSum = details.controls.reduce((sum, control) => {
return sum + (control.get('value')?.value || 0);
}, 0);
return Math.abs(detailsSum - total) < 0.01 ? null : { invalidDetailsSum: true };
}
}
// Uso en formularios reactivos
this.transactionForm = this.fb.group({
operation_number: ['', [Validators.required, BankingTransactionValidators.validOperationNumber]],
total: ['', [Validators.required, BankingTransactionValidators.validTotalAmount]],
details: this.fb.array([])
}, { validators: BankingTransactionValidators.validDetailsSum });🔧 Configuración Avanzada
Configuración de Headers HTTP Personalizados
// Configuración de headers para operaciones bancarias seguras
import { HTTP_INTERCEPTORS } from '@angular/common/http';
@NgModule({
providers: [
CbmIncomeBankingTransactionModule.forRoot({
baseUrl: 'https://api.cbm.com/income-banking-transactions'
}),
{
provide: HTTP_INTERCEPTORS,
useClass: BankingSecurityInterceptor,
multi: true
}
]
})
export class AppModule {}Configuración de Timeouts para Operaciones Grandes
// Timeouts específicos para operaciones bancarias que pueden ser lentas
generateSeatWithTimeout(id: string) {
return this.incomeBankingTransactionService.generateOrRegenerateSeat(id).pipe(
timeout(45000), // 45 segundos para operaciones contables complejas
catchError(error => {
if (error.name === 'TimeoutError') {
return throwError(() => new Error('La generación del asiento contable tomó demasiado tiempo'));
}
return throwError(() => error);
})
);
}Sistema de Reintentos para Operaciones Críticas
// Reintentos automáticos para operaciones bancarias importantes
saveCriticalTransaction(transactionData: CbmIncomeBankingTransactionModel.SaveBody, maxRetries = 3) {
return this.incomeBankingTransactionService.save(transactionData).pipe(
retry({
count: maxRetries,
delay: (error, retryCount) => {
console.log(`Reintento ${retryCount} para transacción bancaria`);
return timer(retryCount * 2000); // Espera incremental
}
}),
catchError(error => {
console.error(`Falló después de ${maxRetries} intentos:`, error);
return throwError(() => error);
})
);
}📋 Dependencias
Peer Dependencies (Requeridas)
{
"@angular/common": ">=20.1.5",
"@angular/core": ">=20.1.5"
}Dependencias Internas
{
"tslib": "^2.3.0"
}🛠️ Desarrollo
Estructura del Proyecto
income-banking-transaction-repository/
├── src/
│ ├── lib/
│ │ ├── income-banking-transaction.model.ts # Modelos de datos bancarios
│ │ ├── income-banking-transaction.module.ts # Configuración del módulo
│ │ ├── income-banking-transaction.service.ts # Servicio HTTP
│ │ ├── income-banking-transaction.repository.ts # Interfaz del repositorio
│ │ └── index.ts # Exportaciones
│ └── public-api.ts # API pública
├── ng-package.json # Configuración empaquetado
├── package.json # Dependencias
└── README.md # Esta documentaciónConstrucción
# Construir la librería
ng build income-banking-transaction-repository
# Construir en modo watch
ng build income-banking-transaction-repository --watch
# Construir para producción
ng build income-banking-transaction-repository --configuration productionPruebas
# Ejecutar pruebas unitarias
ng test income-banking-transaction-repository
# Ejecutar pruebas con coverage
ng test income-banking-transaction-repository --code-coverage
# Pruebas end-to-end
ng e2e income-banking-transaction-repository🎯 Mejores Prácticas
1. Gestión de Memoria y Recursos
// Limpiar suscripciones para evitar memory leaks
export class IncomeBankingTransactionListComponent implements OnDestroy {
private destroy$ = new Subject<void>();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
loadTransactions() {
this.incomeBankingTransactionService.list(params).pipe(
takeUntil(this.destroy$)
).subscribe({
next: (response) => {
this.transactions = response.items;
}
});
}
}2. Optimización de Rendimiento con Grandes Volúmenes
// Usar OnPush change detection para mejor rendimiento
@Component({
selector: 'app-income-banking-transaction-list',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `...`
})
export class IncomeBankingTransactionListComponent {
transactions$ = this.incomeBankingTransactionService.list(params).pipe(
shareReplay(1) // Compartir respuesta entre múltiples suscriptores
);
}3. Validación de Datos Bancarios Completa
// Servicio de validación para datos bancarios
@Injectable({ providedIn: 'root' })
export class BankingValidationService {
validateBankAccount(accountNumber: string): boolean {
// Validar formato de cuenta bancaria ecuatoriana
const accountPattern = /^\d{10,20}$/;
return accountPattern.test(accountNumber);
}
validateOperationNumber(operationNumber: string): boolean {
// Validar formato de número de operación
const operationPattern = /^[A-Z0-9]{6,20}$/;
return operationPattern.test(operationNumber);
}
validateTransactionAmounts(total: number, details: any[]): boolean {
const detailsSum = details.reduce((sum, detail) => sum + detail.value, 0);
return Math.abs(detailsSum - total) < 0.01; // Tolerancia de 1 centavo
}
validateBusinessRules(transaction: any): string[] {
const errors: string[] = [];
if (!this.validateBankAccount(transaction.financials_bank_account_number)) {
errors.push('Número de cuenta bancaria inválido');
}
if (!this.validateOperationNumber(transaction.operation_number)) {
errors.push('Número de operación inválido');
}
if (!this.validateTransactionAmounts(transaction.total, transaction.income_banking_transaction_detail)) {
errors.push('La suma de los detalles no coincide con el total');
}
return errors;
}
}4. Logging y Auditoría para Operaciones Financieras
// Servicio de logging para operaciones bancarias críticas
@Injectable({ providedIn: 'root' })
export class BankingAuditService {
logTransactionOperation(operation: string, transactionData: any, userId: string) {
const logEntry = {
timestamp: new Date().toISOString(),
operation,
transactionId: transactionData._id || 'NEW',
userId,
amount: transactionData.total,
bank: transactionData.financials_bank_name,
operationNumber: transactionData.operation_number,
details: transactionData.income_banking_transaction_detail?.length || 0
};
console.log(`[BANKING_AUDIT] ${JSON.stringify(logEntry)}`);
// Enviar a servicio de auditoría
this.auditService.record('banking_transaction', logEntry);
}
logError(operation: string, error: any, transactionData: any) {
const errorEntry = {
timestamp: new Date().toISOString(),
operation,
error: error.message,
status: error.status,
transactionData
};
console.error(`[BANKING_ERROR] ${JSON.stringify(errorEntry)}`);
// Enviar a servicio de monitoreo de errores
this.errorMonitoring.captureException(error, {
tags: { operation, module: 'income-banking-transaction' },
extra: { transactionData }
});
}
}🤝 Contribución
- Fork el repositorio
- Crea una rama para tu feature (
git checkout -b feature/nueva-funcionalidad) - Commit tus cambios (
git commit -am 'Agrega nueva funcionalidad') - Push a la rama (
git push origin feature/nueva-funcionalidad) - Abre un PullRequest
📄 Licencia
Este proyecto está bajo la Licencia MIT - ver el archivo LICENSE para más detalles.
📞 Soporte
Para soporte técnico o reportes de bugs, contacta al equipo de desarrollo de CBM:
- 📧 Email: [email protected]
- 💬 Slack: #desarrollo-cbm
- 📋 Issues: GitHub Issues
🔄 Changelog
v0.0.1
- ✅ Módulo de configuración con forRoot pattern
- ✅ Servicio que implementa interfaz del repositorio
- ✅ Operaciones CRUD completas (list, getOne, save)
- ✅ Generación/regeneración de asientos contables
- ✅ Sistema de reversiones de transacciones
- ✅ Modelos TypeScript bien tipados con datos bancarios y contables
- ✅ Detalles contables con cuentas y valores
- ✅ Integración completa con HttpClient
- ✅ Documentación completa en español
Nota: Esta librería está optimizada para sistemas financieros que requieren gestión completa de transacciones bancarias de ingresos con integración bancaria y contable. Incluye validaciones financieras robustas y sistema de auditoría para operaciones críticas.
