@cbm-common/outgoing-banking-transaction-repository
v0.0.1
Published
Repositorio Angular para la gestión completa de transacciones bancarias de salida (egresos/pagos) en sistemas financieros. Implementa operaciones CRUD, consultas paginadas, sistema de reversiones y generación de asientos contables con integración completa
Readme
Outgoing Banking Transaction Repository
Repositorio Angular para la gestión completa de transacciones bancarias de salida (egresos/pagos) 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/outgoing-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 { CbmOutgoingBankingTransactionModule } from '@cbm-common/outgoing-banking-transaction-repository';
@NgModule({
imports: [
CbmOutgoingBankingTransactionModule.forRoot({
baseUrl: 'https://api.cbm.com/outgoing-banking-transactions'
})
]
})
export class AppModule {}Configuración Standalone
Para aplicaciones standalone, configura el módulo en el bootstrap:
import { CbmOutgoingBankingTransactionModule } from '@cbm-common/outgoing-banking-transaction-repository';
bootstrapApplication(AppComponent, {
providers: [
CbmOutgoingBankingTransactionModule.forRoot({
baseUrl: 'https://api.cbm.com/outgoing-banking-transactions'
})
]
});🎯 Inyección de Dependencias
Inyección del Servicio
import { CbmOutgoingBankingTransactionService } from '@cbm-common/outgoing-banking-transaction-repository';
@Component({
selector: 'app-outgoing-banking-transaction-manager',
standalone: true,
imports: [CbmOutgoingBankingTransactionService]
})
export class OutgoingBankingTransactionManagerComponent {
constructor(private outgoingBankingTransactionService: CbmOutgoingBankingTransactionService) {}
}Inyección del Repositorio
import { CbmOutgoingBankingTransactionRepository } from '@cbm-common/outgoing-banking-transaction-repository';
@Component({
selector: 'app-outgoing-banking-transaction-list',
standalone: true,
imports: [CbmOutgoingBankingTransactionRepository]
})
export class OutgoingBankingTransactionListComponent {
constructor(private outgoingBankingTransactionRepo: CbmOutgoingBankingTransactionRepository) {}
}🏗️ Arquitectura del Repositorio
Patrón de Diseño
El repositorio sigue el patrón Repository Pattern con Dependency Injection:
CbmOutgoingBankingTransactionModule
├── ICbmOutgoingBankingTransactionModuleConfig (configuración)
├── CbmOutgoingBankingTransactionService (implementa ICbmOutgoingBankingTransactionRepository)
├── CbmOutgoingBankingTransactionRepository (wrapper del service)
├── CbmOutgoingBankingTransactionModel (modelos de datos)
└── HttpClient (cliente HTTP)Interfaz del Repositorio
export interface ICbmOutgoingBankingTransactionRepository {
list(params: CbmOutgoingBankingTransactionModel.ListParams): Observable<CbmOutgoingBankingTransactionModel.ListResponse>;
getOne(id: string): Observable<CbmOutgoingBankingTransactionModel.GetOneResponse>;
save(data: CbmOutgoingBankingTransactionModel.SaveBody): Observable<CbmOutgoingBankingTransactionModel.ConfirmResponse>;
generateOrRegenerateSeat(id: string): Observable<CbmOutgoingBankingTransactionModel.ConfirmResponse>;
}📊 Operaciones Disponibles
Listado de Transacciones Bancarias de Salida
// 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 de Salida
// Crear una nueva transacción bancaria de egreso/pago
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 salida:
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 del egreso
reason: string; // Motivo/Razón del pago
beneficiary: string; // Beneficiario del pago
// 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 de salida:
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 del egreso
reason: string; // Motivo/Razón del pago
beneficiary: string; // Beneficiario del pago
// Detalles contables
outgoing_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
outgoing_banking_transaction_detail: Data.Detail[];
}
interface Detail {
_id: string;
outgoing_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 de Salida
import { CbmOutgoingBankingTransactionService } from '@cbm-common/outgoing-banking-transaction-repository';
import { CbmOutgoingBankingTransactionModel } from '@cbm-common/outgoing-banking-transaction-repository';
@Component({
selector: 'app-outgoing-banking-transaction-list',
standalone: true,
template: `
<div class="transaction-list">
<h2>Transacciones Bancarias de Salida</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 OutgoingBankingTransactionListComponent implements OnInit {
transactions: CbmOutgoingBankingTransactionModel.ListResponse.Item[] = [];
currentPage = 1;
pageSize = 10;
totalPages = 0;
filters: Partial<CbmOutgoingBankingTransactionModel.ListParams> = {
document_number: '',
operation_number: undefined,
reverse_status: undefined,
date_begin: undefined,
date_end: undefined
};
constructor(private outgoingBankingTransactionService: CbmOutgoingBankingTransactionService) {}
ngOnInit() {
this.loadTransactions();
}
loadTransactions() {
// Convertir fechas a timestamps
const params: CbmOutgoingBankingTransactionModel.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.outgoingBankingTransactionService.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.outgoingBankingTransactionService.getOne(id).subscribe({
next: (transaction) => {
console.log('Transacción:', transaction);
// Mostrar modal o navegar a detalle
}
});
}
generateSeat(id: string) {
this.outgoingBankingTransactionService.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 de Egresos
import { ReactiveFormsModule, FormArray } from '@angular/forms';
import { CbmOutgoingBankingTransactionService } from '@cbm-common/outgoing-banking-transaction-repository';
@Component({
selector: 'app-outgoing-banking-transaction-form',
standalone: true,
imports: [ReactiveFormsModule, CbmOutgoingBankingTransactionService],
template: `
<form [formGroup]="transactionForm" (ngSubmit)="onSubmit()">
<h2>Crear Nueva Transacción Bancaria de Salida</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 del Egreso</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 del Egreso</label>
<input id="total" type="number" formControlName="total" step="0.01">
</div>
</div>
<div class="form-group">
<label for="transactionDate">Fecha del Egreso</label>
<input id="transactionDate" type="date" formControlName="date">
</div>
<div class="form-group">
<label for="reason">Motivo del Pago</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 del Egreso</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 Egreso</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 OutgoingBankingTransactionFormComponent implements OnInit {
transactionForm!: FormGroup;
banks: any[] = [];
costCenters: any[] = [];
accounts: any[] = [];
constructor(
private fb: FormBuilder,
private outgoingBankingTransactionService: CbmOutgoingBankingTransactionService
) {}
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: '510101', name: 'Gastos Administrativos' },
{ id: '2', code: '510201', name: 'Gastos de Ventas' }
];
}
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.outgoing_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.outgoingBankingTransactionService.save(formData).subscribe({
next: (response) => {
console.log('Egreso creado:', response);
this.transactionForm.reset();
this.details.clear();
},
error: (error) => {
console.error('Error al crear egreso:', error);
}
});
}
}
onCancel() {
this.transactionForm.reset();
this.details.clear();
}
}⚠️ Manejo de Errores
Errores de API
// Manejo de errores en operaciones bancarias de salida
saveOutgoingTransaction(transactionData: CbmOutgoingBankingTransactionModel.SaveBody) {
this.outgoingBankingTransactionService.save(transactionData).subscribe({
next: (response) => {
if (response.success) {
console.log('Egreso creado exitosamente');
this.showSuccessMessage('Egreso bancario creado correctamente');
} else {
console.error('Error en la respuesta:', response);
this.showErrorMessage('Error al crear el egreso bancario');
}
},
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 egresos bancarios.');
} 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 de Egresos
// Validaciones personalizadas para egresos bancarios
export class OutgoingTransactionValidators {
static validOutgoingAmount(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (!value) return null;
return value > 0 ? null : { invalidOutgoingAmount: true };
}
static validBeneficiary(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (!value) return null;
// Validar que el beneficiario tenga al menos nombre y apellido o razón social
const beneficiaryPattern = /^[a-zA-ZÀ-ÿ\s]{3,}$/;
return beneficiaryPattern.test(value.trim()) ? null : { invalidBeneficiary: true };
}
static validOutgoingDetailsSum(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 : { invalidOutgoingDetailsSum: true };
}
}
// Uso en formularios reactivos
this.transactionForm = this.fb.group({
operation_number: ['', [Validators.required, BankingTransactionValidators.validOperationNumber]],
total: ['', [Validators.required, OutgoingTransactionValidators.validOutgoingAmount]],
beneficiary: ['', [Validators.required, OutgoingTransactionValidators.validBeneficiary]],
details: this.fb.array([])
}, { validators: OutgoingTransactionValidators.validOutgoingDetailsSum });🔧 Configuración Avanzada
Configuración de Headers para Operaciones de Egreso
// Configuración de headers para operaciones bancarias de salida seguras
import { HTTP_INTERCEPTORS } from '@angular/common/http';
@NgModule({
providers: [
CbmOutgoingBankingTransactionModule.forRoot({
baseUrl: 'https://api.cbm.com/outgoing-banking-transactions'
}),
{
provide: HTTP_INTERCEPTORS,
useClass: OutgoingBankingSecurityInterceptor,
multi: true
}
]
})
export class AppModule {}Configuración de Timeouts para Egresos de Alto Valor
// Timeouts específicos para egresos que requieren validaciones adicionales
generateSeatWithTimeout(id: string) {
return this.outgoingBankingTransactionService.generateOrRegenerateSeat(id).pipe(
timeout(60000), // 60 segundos para operaciones contables complejas de egreso
catchError(error => {
if (error.name === 'TimeoutError') {
return throwError(() => new Error('La generación del asiento contable de egreso tomó demasiado tiempo'));
}
return throwError(() => error);
})
);
}Sistema de Reintentos para Operaciones Críticas de Pago
// Reintentos automáticos para operaciones de egreso importantes
saveCriticalOutgoingTransaction(transactionData: CbmOutgoingBankingTransactionModel.SaveBody, maxRetries = 3) {
return this.outgoingBankingTransactionService.save(transactionData).pipe(
retry({
count: maxRetries,
delay: (error, retryCount) => {
console.log(`Reintento ${retryCount} para egreso bancario`);
return timer(retryCount * 3000); // Espera incremental más larga para operaciones críticas
}
}),
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
outgoing-banking-transaction-repository/
├── src/
│ ├── lib/
│ │ ├── outgoing-banking-transaction.model.ts # Modelos de datos bancarios de salida
│ │ ├── outgoing-banking-transaction.module.ts # Configuración del módulo
│ │ ├── outgoing-banking-transaction.service.ts # Servicio HTTP
│ │ ├── outgoing-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 outgoing-banking-transaction-repository
# Construir en modo watch
ng build outgoing-banking-transaction-repository --watch
# Construir para producción
ng build outgoing-banking-transaction-repository --configuration productionPruebas
# Ejecutar pruebas unitarias
ng test outgoing-banking-transaction-repository
# Ejecutar pruebas con coverage
ng test outgoing-banking-transaction-repository --code-coverage
# Pruebas end-to-end
ng e2e outgoing-banking-transaction-repository🎯 Mejores Prácticas
1. Gestión de Memoria y Recursos en Operaciones de Egreso
// Limpiar suscripciones para evitar memory leaks
export class OutgoingBankingTransactionListComponent implements OnDestroy {
private destroy$ = new Subject<void>();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
loadTransactions() {
this.outgoingBankingTransactionService.list(params).pipe(
takeUntil(this.destroy$)
).subscribe({
next: (response) => {
this.transactions = response.items;
}
});
}
}2. Optimización de Rendimiento para Grandes Volúmenes de Egresos
// Usar OnPush change detection para mejor rendimiento
@Component({
selector: 'app-outgoing-banking-transaction-list',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `...`
})
export class OutgoingBankingTransactionListComponent {
transactions$ = this.outgoingBankingTransactionService.list(params).pipe(
shareReplay(1) // Compartir respuesta entre múltiples suscriptores
);
}3. Validación Completa de Datos de Egreso
// Servicio de validación para datos bancarios de salida
@Injectable({ providedIn: 'root' })
export class OutgoingBankingValidationService {
validateBankAccount(accountNumber: string): boolean {
// Validar formato de cuenta bancaria ecuatoriana
const accountPattern = /^\d{10,20}$/;
return accountPattern.test(accountNumber);
}
validateBeneficiary(beneficiary: string): boolean {
// Validar beneficiario (persona natural o jurídica)
const beneficiaryPattern = /^[a-zA-ZÀ-ÿ\s]{3,}$/;
return beneficiaryPattern.test(beneficiary.trim());
}
validateOutgoingTransaction(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.validateBeneficiary(transaction.beneficiary)) {
errors.push('Beneficiario inválido');
}
if (transaction.total <= 0) {
errors.push('Monto del egreso debe ser mayor a cero');
}
return errors;
}
validateBusinessRules(transaction: any): string[] {
const errors: string[] = [];
// Reglas de negocio específicas para egresos
if (transaction.total > 50000) {
errors.push('Egresos mayores a $50,000 requieren aprobación especial');
}
if (!transaction.reason || transaction.reason.trim().length < 10) {
errors.push('La razón del egreso debe tener al menos 10 caracteres');
}
return errors;
}
}4. Logging y Auditoría para Operaciones de Pago
// Servicio de auditoría para operaciones bancarias de salida críticas
@Injectable({ providedIn: 'root' })
export class OutgoingBankingAuditService {
logOutgoingTransaction(operation: string, transactionData: any, userId: string) {
const auditEntry = {
timestamp: new Date().toISOString(),
operation,
transactionId: transactionData._id || 'NEW',
userId,
amount: transactionData.total,
bank: transactionData.financials_bank_name,
beneficiary: transactionData.beneficiary,
operationNumber: transactionData.operation_number,
reason: transactionData.reason,
details: transactionData.outgoing_banking_transaction_detail?.length || 0
};
console.log(`[OUTGOING_BANKING_AUDIT] ${JSON.stringify(auditEntry)}`);
// Enviar a servicio de auditoría financiera
this.financialAuditService.record('outgoing_banking_transaction', auditEntry);
}
logError(operation: string, error: any, transactionData: any) {
const errorEntry = {
timestamp: new Date().toISOString(),
operation,
error: error.message,
status: error.status,
transactionData,
severity: 'HIGH' // Los errores de egreso son críticos
};
console.error(`[OUTGOING_BANKING_ERROR] ${JSON.stringify(errorEntry)}`);
// Enviar a servicio de monitoreo de errores críticos
this.criticalErrorMonitoring.captureException(error, {
tags: { operation, module: 'outgoing-banking-transaction', severity: 'high' },
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 de egreso
- ✅ Modelos TypeScript bien tipados con datos bancarios de salida
- ✅ Detalles contables para egresos 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 salida (egresos/pagos) con integración bancaria y contable. Incluye validaciones financieras robustas y sistema de auditoría para operaciones críticas de pago.
