@cbm-common/financial-bank-repository
v0.0.1
Published
Repositorio Angular para la gestión completa de bancos financieros y cuentas bancarias con integración contable, términos de pago y sistema de auditoría integral. Soporta operaciones de compras, ventas, secuencias de cheques y gestión de tarjetas de crédi
Downloads
8
Readme
Financial Bank Repository
Repositorio Angular para la gestión completa de bancos financieros y cuentas bancarias con integración contable, términos de pago y sistema de auditoría integral. Soporta operaciones de compras, ventas, secuencias de cheques y gestión de tarjetas de crédito/débito.
📦 Instalación
npm install @cbm-common/financial-bank-repository⚙️ Configuración
Configuración del Módulo
El módulo debe configurarse en el módulo raíz de la aplicación:
import { CbmFinancialBankModule } from '@cbm-common/financial-bank-repository';
@NgModule({
imports: [
CbmFinancialBankModule.forRoot({
baseUrl: 'https://api.cbm.com/financial-banks'
})
]
})
export class AppModule {}Configuración Standalone
Para aplicaciones standalone, configura el módulo en el bootstrap:
import { CbmFinancialBankModule } from '@cbm-common/financial-bank-repository';
bootstrapApplication(AppComponent, {
providers: [
CbmFinancialBankModule.forRoot({
baseUrl: 'https://api.cbm.com/financial-banks'
})
]
});🎯 Inyección de Dependencias
Inyección del Servicio
import { CbmFinancialBankService } from '@cbm-common/financial-bank-repository';
@Component({
selector: 'app-bank-manager',
standalone: true,
imports: [CbmFinancialBankService]
})
export class BankManagerComponent {
constructor(private bankService: CbmFinancialBankService) {}
}Inyección del Repositorio
import { CbmFinancialBankRepository } from '@cbm-common/financial-bank-repository';
@Component({
selector: 'app-bank-list',
standalone: true,
imports: [CbmFinancialBankRepository]
})
export class BankListComponent {
constructor(private bankRepo: CbmFinancialBankRepository) {}
}🏗️ Arquitectura del Repositorio
Patrón de Diseño
El repositorio sigue el patrón Repository Pattern con Dependency Injection:
CbmFinancialBankModule
├── ICbmFinancialBankModuleConfig (configuración)
├── CbmFinancialBankService (implementa ICbmFinancialBankRepository)
├── CbmFinancialBankRepository (wrapper del service)
├── CbmFinancialBankModel (modelos de datos)
└── HttpClient (cliente HTTP)Interfaz del Repositorio
export interface ICbmFinancialBankRepository {
list(params: CbmFinancialBankModel.ListParams): Observable<CbmFinancialBankModel.ListResponse>;
getOne(id: string): Observable<CbmFinancialBankModel.GetOneResponse>;
save(data: CbmFinancialBankModel.SaveBody): Observable<CbmFinancialBankModel.ConfirmResponse>;
update(id: string, data: CbmFinancialBankModel.UpdateBody): Observable<CbmFinancialBankModel.ConfirmResponse>;
changeStatus(id: string, data: CbmFinancialBankModel.ChangeStatusBody): Observable<CbmFinancialBankModel.ConfirmResponse>;
restore(id: string): Observable<CbmFinancialBankModel.ConfirmResponse>;
delete(id: string): Observable<CbmFinancialBankModel.ConfirmResponse>;
}📊 Operaciones Disponibles
Listado de Bancos Financieros
// Listado de bancos con filtros avanzados
list(params: {
deleted?: boolean; // Incluir eliminados (opcional)
purchases?: boolean; // Filtrar por bancos de compras
sales?: boolean; // Filtrar por bancos de ventas
enabled?: boolean; // Filtrar por estado habilitado
name?: string; // Filtrar por nombre del banco
payment_term_code?: string; // Filtrar por código de plazo de pago
}): Observable<ListResponse>Consulta Individual
// Obtener información detallada de un banco específico
getOne(id: string): Observable<GetOneResponse>Creación de Banco Financiero
// Crear una nueva cuenta bancaria
save(data: {
bank_id?: string; // ID del banco (opcional)
account_id?: string; // ID de la cuenta (opcional)
payment_term_id: string; // ID del plazo de pago (requerido)
name: string; // Nombre de la cuenta (requerido)
bank_name?: string; // Nombre del banco (opcional)
bank_cash?: string; // Tipo de efectivo del banco (opcional)
bank_account_type?: string; // Tipo de cuenta bancaria (opcional)
account_number?: string; // Número de cuenta (opcional)
check_sequence?: number; // Secuencia de cheques (opcional)
sales: boolean; // Habilitado para ventas (requerido)
purchases: boolean; // Habilitado para compras (requerido)
card_number: string; // Número de tarjeta (requerido)
}): Observable<ConfirmResponse>Actualización de Banco Financiero
// Actualizar información de una cuenta bancaria
update(id: string, data: {
bank_id?: string; // ID del banco
account_id?: string; // ID de la cuenta
payment_term_id?: string; // ID del plazo de pago
name?: string; // Nombre de la cuenta
bank_name?: string; // Nombre del banco
bank_cash?: string; // Tipo de efectivo del banco
bank_account_type?: string; // Tipo de cuenta bancaria
account_number?: string; // Número de cuenta
check_sequence?: number; // Secuencia de cheques
sales?: boolean; // Habilitado para ventas
purchases?: boolean; // Habilitado para compras
card_number?: string; // Número de tarjeta
}): Observable<ConfirmResponse>Cambio de Estado
// Cambiar el estado de una cuenta bancaria
changeStatus(id: string, data: {
enabled: boolean; // Nuevo estado (true/false)
disabled_reason?: string; // Razón de deshabilitación (opcional)
}): Observable<ConfirmResponse>Restauración de Banco
// Restaurar un banco previamente eliminado
restore(id: string): Observable<ConfirmResponse>Eliminación de Banco
// Eliminar permanentemente un banco
delete(id: string): Observable<ConfirmResponse>📋 Modelos de Datos
ListResponse.Data
Información completa de una cuenta bancaria con auditoría:
interface Data {
_id: string;
company_id: string; // ID de la compañía
bank_id: string; // ID del banco
account_id: string; // ID de la cuenta
payment_term_id: string; // ID del plazo de pago
name: string; // Nombre de la cuenta
bank_name: string; // Nombre del banco
bank_account_type: string; // Tipo de cuenta bancaria
bank_cash: string; // Tipo de efectivo del banco
account_number: string; // Número de cuenta
check_sequence: number; // Secuencia de cheques
sales: boolean; // Habilitado para ventas
purchases: boolean; // Habilitado para compras
company_NIF: string; // NIF de la compañía
company_address: string; // Dirección de la compañía
company_trade_name: string; // Nombre comercial
company_business_name: string; // Razón social
enabled: boolean; // Estado de la cuenta
deleted: boolean; // Estado de eliminación
created_user: string; // Usuario que creó
created_at: number; // Timestamp de creación
updated_at: number; // Timestamp de actualización
updated_user: string; // Usuario que actualizó
deleted_at: number; // Timestamp de eliminación
deleted_user: string; // Usuario que eliminó
payment_term_code: string; // Código del plazo de pago
card_number: string; // Número de tarjeta
detail_account: Data.DetailAccount[]; // Cuentas contables detalladas
payment_term: Data.PaymentTerm; // Información del plazo de pago
type: string; // Tipo de cuenta
}Información de Plazo de Pago
interface PaymentTerm {
name: string; // Nombre del plazo de pago
code: string; // Código del plazo de pago
}Cuentas Contables Detalladas
interface DetailAccount {
_id: string; // ID de la cuenta contable
code: string; // Código de la cuenta
name: string; // Nombre de la cuenta
}🚀 Ejemplos de Uso
Ejemplo Básico: Gestión de Cuentas Bancarias
import { CbmFinancialBankService } from '@cbm-common/financial-bank-repository';
import { CbmFinancialBankModel } from '@cbm-common/financial-bank-repository';
@Component({
selector: 'app-bank-management',
standalone: true,
template: `
<div class="bank-management">
<h2>Gestión de Cuentas Bancarias</h2>
<!-- Formulario de creación/edición -->
<div class="form-section">
<h3>{{ isEditing ? 'Editar' : 'Crear' }} Cuenta Bancaria</h3>
<form (ngSubmit)="onSubmit()" #bankForm="ngForm">
<div class="form-group">
<label for="name">Nombre de la Cuenta:</label>
<input
id="name"
type="text"
[(ngModel)]="bankFormData.name"
name="name"
required
placeholder="Ej: Cuenta Principal">
</div>
<div class="form-group">
<label for="bankName">Nombre del Banco:</label>
<input
id="bankName"
type="text"
[(ngModel)]="bankFormData.bank_name"
name="bankName"
placeholder="Ej: Banco Pichincha">
</div>
<div class="form-group">
<label for="accountNumber">Número de Cuenta:</label>
<input
id="accountNumber"
type="text"
[(ngModel)]="bankFormData.account_number"
name="accountNumber"
placeholder="Ej: 1234567890">
</div>
<div class="form-group">
<label for="cardNumber">Número de Tarjeta:</label>
<input
id="cardNumber"
type="text"
[(ngModel)]="bankFormData.card_number"
name="cardNumber"
required
placeholder="Ej: **** **** **** 1234">
</div>
<div class="form-row">
<div class="form-group">
<label for="sales">Habilitado para Ventas:</label>
<input
id="sales"
type="checkbox"
[(ngModel)]="bankFormData.sales"
name="sales">
</div>
<div class="form-group">
<label for="purchases">Habilitado para Compras:</label>
<input
id="purchases"
type="checkbox"
[(ngModel)]="bankFormData.purchases"
name="purchases">
</div>
</div>
<div class="form-actions">
<button type="submit" [disabled]="!bankForm.valid">
{{ isEditing ? 'Actualizar' : 'Crear' }} Cuenta
</button>
<button type="button" (click)="cancelEdit()">Cancelar</button>
</div>
</form>
</div>
<!-- Tabla de cuentas bancarias -->
<div class="table-section">
<h3>Cuentas Bancarias Configuradas</h3>
<!-- Filtros -->
<div class="filters">
<input
[(ngModel)]="filters.name"
placeholder="Buscar por nombre..."
(input)="applyFilters()">
<select [(ngModel)]="filters.enabled" (change)="applyFilters()">
<option value="">Todos los estados</option>
<option value="true">Habilitadas</option>
<option value="false">Deshabilitadas</option>
</select>
<select [(ngModel)]="filters.sales" (change)="applyFilters()">
<option value="">Todas las cuentas</option>
<option value="true">Solo Ventas</option>
<option value="false">Solo Compras</option>
</select>
</div>
<!-- Tabla -->
<div class="table-container">
<table>
<thead>
<tr>
<th>Nombre</th>
<th>Banco</th>
<th>Número de Cuenta</th>
<th>Ventas</th>
<th>Compras</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let bank of filteredBanks">
<td>{{ bank.name }}</td>
<td>{{ bank.bank_name || 'N/A' }}</td>
<td>{{ bank.account_number || 'N/A' }}</td>
<td>
<span class="indicator" [class.active]="bank.sales">
{{ bank.sales ? '✓' : '✗' }}
</span>
</td>
<td>
<span class="indicator" [class.active]="bank.purchases">
{{ bank.purchases ? '✓' : '✗' }}
</span>
</td>
<td>
<span class="status" [class.enabled]="bank.enabled" [class.disabled]="!bank.enabled">
{{ bank.enabled ? 'Habilitada' : 'Deshabilitada' }}
</span>
</td>
<td>
<button (click)="editBank(bank)">Editar</button>
<button (click)="toggleStatus(bank)" [disabled]="!bank.enabled">
{{ bank.enabled ? 'Deshabilitar' : 'Habilitar' }}
</button>
<button (click)="viewDetails(bank)">Ver Detalles</button>
<button (click)="deleteBank(bank)" class="danger" *ngIf="bank.deleted">
Eliminar
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Modal de detalles -->
<div class="modal" *ngIf="selectedBank" (click)="closeModal()">
<div class="modal-content" (click)="$event.stopPropagation()">
<h3>Detalles de la Cuenta Bancaria</h3>
<div class="bank-details">
<div class="detail-row">
<label>ID:</label>
<span>{{ selectedBank._id }}</span>
</div>
<div class="detail-row">
<label>Nombre:</label>
<span>{{ selectedBank.name }}</span>
</div>
<div class="detail-row">
<label>Banco:</label>
<span>{{ selectedBank.bank_name || 'No especificado' }}</span>
</div>
<div class="detail-row">
<label>Número de Cuenta:</label>
<span>{{ selectedBank.account_number || 'No especificado' }}</span>
</div>
<div class="detail-row">
<label>Tipo de Cuenta:</label>
<span>{{ selectedBank.bank_account_type || 'No especificado' }}</span>
</div>
<div class="detail-row">
<label>Secuencia de Cheques:</label>
<span>{{ selectedBank.check_sequence || 'N/A' }}</span>
</div>
<div class="detail-row">
<label>Número de Tarjeta:</label>
<span>{{ selectedBank.card_number }}</span>
</div>
<div class="detail-row">
<label>Plazo de Pago:</label>
<span>{{ selectedBank.payment_term?.name }} ({{ selectedBank.payment_term?.code }})</span>
</div>
<div class="detail-row">
<label>Estado:</label>
<span class="status" [class.enabled]="selectedBank.enabled">
{{ selectedBank.enabled ? 'Habilitada' : 'Deshabilitada' }}
</span>
</div>
<div class="detail-row">
<label>Creado por:</label>
<span>{{ selectedBank.created_user }} ({{ formatDate(selectedBank.created_at) }})</span>
</div>
<div class="detail-row" *ngIf="selectedBank.updated_user">
<label>Actualizado por:</label>
<span>{{ selectedBank.updated_user }} ({{ formatDate(selectedBank.updated_at) }})</span>
</div>
</div>
<div class="modal-actions">
<button (click)="closeModal()">Cerrar</button>
</div>
</div>
</div>
</div>
`,
styles: [`
.bank-management { padding: 20px; }
.form-section, .table-section { margin-bottom: 30px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-group input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.form-row { display: flex; gap: 20px; }
.form-row .form-group { flex: 1; }
.form-actions { display: flex; gap: 10px; margin-top: 20px; }
.filters { display: flex; gap: 10px; margin-bottom: 20px; }
.table-container { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #f8f9fa; font-weight: bold; }
.status.enabled { color: #28a745; font-weight: bold; }
.status.disabled { color: #dc3545; font-weight: bold; }
.indicator.active { color: #28a745; font-weight: bold; }
.indicator:not(.active) { color: #dc3545; }
button { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { opacity: 0.8; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
button.danger { background-color: #dc3545; color: white; }
/* Modal styles */
.modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
.modal-content { background: white; padding: 20px; border-radius: 8px; max-width: 600px; width: 90%; max-height: 80%; overflow-y: auto; }
.bank-details { margin: 20px 0; }
.detail-row { display: flex; margin-bottom: 10px; align-items: center; }
.detail-row label { font-weight: bold; min-width: 140px; margin-right: 10px; }
.detail-row span { flex: 1; }
.modal-actions { display: flex; justify-content: flex-end; margin-top: 20px; }
`]
})
export class BankManagementComponent implements OnInit {
banks: CbmFinancialBankModel.ListResponse.Data[] = [];
filteredBanks: CbmFinancialBankModel.ListResponse.Data[] = [];
selectedBank: CbmFinancialBankModel.ListResponse.Data | null = null;
isEditing = false;
currentBankId = '';
filters: Partial<CbmFinancialBankModel.ListParams> = {
name: '',
enabled: undefined,
sales: undefined
};
bankFormData: Partial<CbmFinancialBankModel.SaveBody> = {
name: '',
sales: false,
purchases: false,
card_number: '',
payment_term_id: ''
};
constructor(private bankService: CbmFinancialBankService) {}
ngOnInit() {
this.loadBanks();
}
loadBanks() {
this.bankService.list({}).subscribe({
next: (response) => {
if (response.success) {
this.banks = response.data || [];
this.applyFilters();
}
},
error: (error) => {
console.error('Error al cargar bancos:', error);
}
});
}
applyFilters() {
let filtered = [...this.banks];
if (this.filters.name) {
filtered = filtered.filter(bank =>
bank.name.toLowerCase().includes(this.filters.name!.toLowerCase()) ||
(bank.bank_name && bank.bank_name.toLowerCase().includes(this.filters.name!.toLowerCase()))
);
}
if (this.filters.enabled !== undefined) {
filtered = filtered.filter(bank => bank.enabled === (this.filters.enabled === 'true'));
}
if (this.filters.sales !== undefined) {
filtered = filtered.filter(bank => bank.sales === (this.filters.sales === 'true'));
}
this.filteredBanks = filtered;
}
onSubmit() {
if (this.isEditing) {
this.updateBank();
} else {
this.createBank();
}
}
createBank() {
this.bankService.save(this.bankFormData as CbmFinancialBankModel.SaveBody).subscribe({
next: (response) => {
if (response.success) {
this.resetForm();
this.loadBanks();
alert('Cuenta bancaria creada exitosamente');
}
},
error: (error) => {
console.error('Error al crear cuenta bancaria:', error);
}
});
}
updateBank() {
this.bankService.update(this.currentBankId, this.bankFormData as CbmFinancialBankModel.UpdateBody).subscribe({
next: (response) => {
if (response.success) {
this.resetForm();
this.loadBanks();
alert('Cuenta bancaria actualizada exitosamente');
}
},
error: (error) => {
console.error('Error al actualizar cuenta bancaria:', error);
}
});
}
editBank(bank: CbmFinancialBankModel.ListResponse.Data) {
this.isEditing = true;
this.currentBankId = bank._id;
this.bankFormData = {
name: bank.name,
bank_name: bank.bank_name,
account_number: bank.account_number,
card_number: bank.card_number,
sales: bank.sales,
purchases: bank.purchases,
payment_term_id: bank.payment_term_id
};
}
toggleStatus(bank: CbmFinancialBankModel.ListResponse.Data) {
const newStatus = !bank.enabled;
const reason = newStatus ? '' : prompt('Razón de deshabilitación:') || '';
this.bankService.changeStatus(bank._id, {
enabled: newStatus,
disabled_reason: reason
}).subscribe({
next: (response) => {
if (response.success) {
this.loadBanks();
alert(`Cuenta bancaria ${newStatus ? 'habilitada' : 'deshabilitada'} exitosamente`);
}
},
error: (error) => {
console.error('Error al cambiar estado:', error);
}
});
}
deleteBank(bank: CbmFinancialBankModel.ListResponse.Data) {
if (confirm('¿Está seguro de que desea eliminar permanentemente esta cuenta bancaria?')) {
this.bankService.delete(bank._id).subscribe({
next: (response) => {
if (response.success) {
this.loadBanks();
alert('Cuenta bancaria eliminada exitosamente');
}
},
error: (error) => {
console.error('Error al eliminar cuenta bancaria:', error);
}
});
}
}
viewDetails(bank: CbmFinancialBankModel.ListResponse.Data) {
this.selectedBank = bank;
}
closeModal() {
this.selectedBank = null;
}
cancelEdit() {
this.resetForm();
}
resetForm() {
this.isEditing = false;
this.currentBankId = '';
this.bankFormData = {
name: '',
sales: false,
purchases: false,
card_number: '',
payment_term_id: ''
};
}
formatDate(timestamp?: number): string {
if (!timestamp) return 'N/A';
return new Date(timestamp).toLocaleDateString();
}
}Ejemplo de Selector de Cuentas Bancarias
import { CbmFinancialBankService } from '@cbm-common/financial-bank-repository';
@Component({
selector: 'app-bank-selector',
standalone: true,
template: `
<div class="bank-selector">
<h3>Seleccionar Cuenta Bancaria</h3>
<div class="selector-container">
<div class="search-section">
<input
[formControl]="searchControl"
placeholder="Buscar por nombre o banco..."
(input)="onSearchInput($event)">
<button (click)="clearSearch()" *ngIf="searchControl.value">✕</button>
</div>
<div class="type-filters">
<button
[class.active]="activeFilter === 'all'"
(click)="setFilter('all')">
Todas
</button>
<button
[class.active]="activeFilter === 'sales'"
(click)="setFilter('sales')">
Ventas
</button>
<button
[class.active]="activeFilter === 'purchases'"
(click)="setFilter('purchases')">
Compras
</button>
</div>
<div class="banks-list" *ngIf="filteredBanks.length > 0">
<div
*ngFor="let bank of filteredBanks"
class="bank-option"
[class.selected]="selectedBank?._id === bank._id"
(click)="selectBank(bank)">
<div class="bank-info">
<div class="bank-main">
<div class="bank-name">{{ bank.name }}</div>
<div class="bank-details">
<span *ngIf="bank.bank_name">{{ bank.bank_name }}</span>
<span *ngIf="bank.account_number"> • {{ bank.account_number }}</span>
</div>
</div>
<div class="bank-capabilities">
<span class="capability" [class.enabled]="bank.sales">Ventas</span>
<span class="capability" [class.enabled]="bank.purchases">Compras</span>
</div>
</div>
<div class="bank-status" [class.enabled]="bank.enabled">
{{ bank.enabled ? '●' : '○' }}
</div>
</div>
</div>
<div class="no-results" *ngIf="searchControl.value && filteredBanks.length === 0">
<p>No se encontraron cuentas bancarias que coincidan con "{{ searchControl.value }}"</p>
</div>
<div class="loading" *ngIf="loading">
<p>Cargando cuentas bancarias...</p>
</div>
</div>
<div class="selected-bank" *ngIf="selectedBank">
<h4>Cuenta Seleccionada:</h4>
<div class="selected-info">
<strong>{{ selectedBank.name }}</strong>
<div class="bank-meta">
<span *ngIf="selectedBank.bank_name">{{ selectedBank.bank_name }}</span>
<span *ngIf="selectedBank.account_number"> • {{ selectedBank.account_number }}</span>
</div>
<div class="capabilities">
<span class="capability" [class.enabled]="selectedBank.sales">Ventas</span>
<span class="capability" [class.enabled]="selectedBank.purchases">Compras</span>
</div>
</div>
</div>
</div>
`,
styles: [`
.bank-selector { max-width: 400px; }
.selector-container { border: 1px solid #ddd; border-radius: 8px; overflow: hidden; }
.search-section { padding: 15px; background: #f8f9fa; display: flex; align-items: center; }
.search-section input { flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.search-section button { margin-left: 10px; padding: 8px; border: none; border-radius: 4px; cursor: pointer; }
.type-filters { padding: 10px 15px; background: #f8f9fa; border-bottom: 1px solid #eee; display: flex; gap: 5px; }
.type-filters button { padding: 6px 12px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; }
.type-filters button.active { background: #007bff; color: white; border-color: #007bff; }
.banks-list { max-height: 300px; overflow-y: auto; }
.bank-option { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; cursor: pointer; border-bottom: 1px solid #eee; }
.bank-option:hover { background: #f8f9fa; }
.bank-option.selected { background: #e3f2fd; }
.bank-info { flex: 1; }
.bank-main { margin-bottom: 5px; }
.bank-name { font-weight: bold; color: #1976d2; }
.bank-details { font-size: 12px; color: #666; margin-top: 2px; }
.bank-capabilities { display: flex; gap: 8px; }
.capability.enabled { background: #28a745; color: white; padding: 2px 6px; border-radius: 10px; font-size: 10px; }
.capability:not(.enabled) { background: #dc3545; color: white; padding: 2px 6px; border-radius: 10px; font-size: 10px; }
.bank-status.enabled { color: #28a745; font-size: 18px; }
.bank-status:not(.enabled) { color: #dc3545; font-size: 18px; }
.no-results { padding: 20px; text-align: center; color: #666; }
.loading { padding: 20px; text-align: center; color: #666; }
.selected-bank { margin-top: 15px; padding: 10px; background: #e8f5e8; border-radius: 4px; }
.selected-info { margin-top: 5px; }
.bank-meta { font-size: 12px; color: #666; margin-top: 2px; }
.capabilities { margin-top: 8px; }
`]
})
export class BankSelectorComponent implements OnInit {
searchControl = new FormControl('');
allBanks: CbmFinancialBankModel.ListResponse.Data[] = [];
filteredBanks: CbmFinancialBankModel.ListResponse.Data[] = [];
selectedBank: CbmFinancialBankModel.ListResponse.Data | null = null;
loading = false;
activeFilter: 'all' | 'sales' | 'purchases' = 'all';
constructor(private bankService: CbmFinancialBankService) {}
ngOnInit() {
this.loadAllBanks();
}
loadAllBanks() {
this.loading = true;
this.bankService.list({ enabled: true }).subscribe({
next: (response) => {
if (response.success) {
this.allBanks = response.data || [];
this.applyFilters();
}
this.loading = false;
},
error: (error) => {
console.error('Error al cargar bancos:', error);
this.loading = false;
}
});
}
onSearchInput(event: any) {
const searchTerm = event.target.value.toLowerCase();
this.filterBanks(searchTerm);
}
setFilter(filter: 'all' | 'sales' | 'purchases') {
this.activeFilter = filter;
this.applyFilters();
}
filterBanks(searchTerm: string) {
let filtered = [...this.allBanks];
// Aplicar filtro de búsqueda
if (searchTerm) {
filtered = filtered.filter(bank =>
bank.name.toLowerCase().includes(searchTerm) ||
(bank.bank_name && bank.bank_name.toLowerCase().includes(searchTerm))
);
}
// Aplicar filtro de tipo
if (this.activeFilter === 'sales') {
filtered = filtered.filter(bank => bank.sales);
} else if (this.activeFilter === 'purchases') {
filtered = filtered.filter(bank => bank.purchases);
}
this.filteredBanks = filtered;
}
applyFilters() {
this.filterBanks(this.searchControl.value || '');
}
selectBank(bank: CbmFinancialBankModel.ListResponse.Data) {
this.selectedBank = bank;
// Emitir evento de selección
console.log('Cuenta bancaria seleccionada:', bank);
}
clearSearch() {
this.searchControl.setValue('');
this.applyFilters();
}
}Ejemplo de Integración con Sistema de Pagos
import { CbmFinancialBankService } from '@cbm-common/financial-bank-repository';
@Component({
selector: 'app-payment-processor',
standalone: true,
template: `
<div class="payment-processor">
<h3>Procesador de Pagos</h3>
<div class="payment-form">
<div class="form-group">
<label for="amount">Monto a Pagar:</label>
<input
id="amount"
type="number"
[(ngModel)]="paymentAmount"
step="0.01"
min="0.01"
placeholder="0.00">
</div>
<div class="form-group">
<label for="paymentType">Tipo de Pago:</label>
<select id="paymentType" [(ngModel)]="paymentType">
<option value="transfer">Transferencia Bancaria</option>
<option value="check">Cheque</option>
<option value="card">Tarjeta de Crédito/Débito</option>
</select>
</div>
<div class="form-group" *ngIf="paymentType === 'transfer' || paymentType === 'check'">
<label for="bankAccount">Cuenta Bancaria:</label>
<select id="bankAccount" [(ngModel)]="selectedBankId" (change)="onBankChange()">
<option value="">Seleccionar cuenta...</option>
<option *ngFor="let bank of availableBanks" [value]="bank._id">
{{ bank.name }} - {{ bank.bank_name }}
</option>
</select>
</div>
<div class="bank-details" *ngIf="selectedBank && (paymentType === 'transfer' || paymentType === 'check')">
<div class="detail-card">
<h4>Información de la Cuenta Seleccionada</h4>
<div class="detail-grid">
<div class="detail-item">
<label>Banco:</label>
<span>{{ selectedBank.bank_name }}</span>
</div>
<div class="detail-item">
<label>Número de Cuenta:</label>
<span>{{ selectedBank.account_number }}</span>
</div>
<div class="detail-item">
<label>Tipo de Cuenta:</label>
<span>{{ selectedBank.bank_account_type }}</span>
</div>
<div class="detail-item" *ngIf="paymentType === 'check'">
<label>Próximo Cheque:</label>
<span>#{{ selectedBank.check_sequence || 'N/A' }}</span>
</div>
<div class="detail-item">
<label>Número de Tarjeta:</label>
<span>{{ selectedBank.card_number }}</span>
</div>
</div>
</div>
</div>
<div class="payment-actions">
<button (click)="processPayment()" [disabled]="!canProcessPayment()">
Procesar Pago
</button>
<button (click)="resetForm()">Limpiar</button>
</div>
</div>
<div class="payment-history" *ngIf="paymentHistory.length > 0">
<h4>Historial de Pagos Recientes</h4>
<div class="history-list">
<div *ngFor="let payment of paymentHistory" class="history-item">
<div class="payment-info">
<span class="amount">${{ payment.amount }}</span>
<span class="type">{{ payment.type }}</span>
<span class="date">{{ formatDate(payment.date) }}</span>
</div>
<div class="payment-status" [class.success]="payment.success">
{{ payment.success ? 'Exitoso' : 'Fallido' }}
</div>
</div>
</div>
</div>
</div>
`,
styles: [`
.payment-processor { padding: 20px; max-width: 600px; }
.payment-form { background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-group input, .form-group select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.bank-details { margin-top: 20px; }
.detail-card { background: white; padding: 15px; border-radius: 8px; border: 1px solid #ddd; }
.detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; margin-top: 10px; }
.detail-item { display: flex; flex-direction: column; }
.detail-item label { font-size: 12px; color: #666; margin-bottom: 2px; }
.detail-item span { font-weight: bold; }
.payment-actions { display: flex; gap: 10px; margin-top: 20px; }
button { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { opacity: 0.8; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
.payment-history { background: white; padding: 15px; border-radius: 8px; border: 1px solid #ddd; }
.history-list { margin-top: 10px; }
.history-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; }
.history-item:last-child { border-bottom: none; }
.payment-info { display: flex; flex-direction: column; }
.amount { font-weight: bold; font-size: 16px; }
.type { font-size: 12px; color: #666; }
.date { font-size: 11px; color: #999; }
.payment-status.success { color: #28a745; font-weight: bold; }
.payment-status:not(.success) { color: #dc3545; font-weight: bold; }
`]
})
export class PaymentProcessorComponent implements OnInit {
availableBanks: CbmFinancialBankModel.ListResponse.Data[] = [];
selectedBank: CbmFinancialBankModel.ListResponse.Data | null = null;
selectedBankId = '';
paymentAmount = 0;
paymentType: 'transfer' | 'check' | 'card' = 'transfer';
paymentHistory: any[] = [];
constructor(private bankService: CbmFinancialBankService) {}
ngOnInit() {
this.loadAvailableBanks();
}
loadAvailableBanks() {
// Cargar bancos habilitados para el tipo de pago seleccionado
const params: Partial<CbmFinancialBankModel.ListParams> = { enabled: true };
if (this.paymentType === 'transfer' || this.paymentType === 'check') {
// Para transferencias y cheques, necesitamos bancos con cuentas
params.purchases = true; // O sales dependiendo del contexto
}
this.bankService.list(params).subscribe({
next: (response) => {
if (response.success) {
this.availableBanks = response.data || [];
}
},
error: (error) => {
console.error('Error al cargar bancos:', error);
}
});
}
onBankChange() {
this.selectedBank = this.availableBanks.find(b => b._id === this.selectedBankId) || null;
}
canProcessPayment(): boolean {
if (this.paymentAmount <= 0) return false;
if (this.paymentType === 'transfer' || this.paymentType === 'check') {
return !!this.selectedBank;
}
return true;
}
processPayment() {
if (!this.canProcessPayment()) return;
const paymentData = {
amount: this.paymentAmount,
type: this.paymentType,
bank: this.selectedBank,
date: new Date(),
success: Math.random() > 0.1 // Simulación de éxito
};
// Aquí iría la lógica real de procesamiento de pago
console.log('Procesando pago:', paymentData);
// Agregar al historial
this.paymentHistory.unshift(paymentData);
// Limpiar formulario si fue exitoso
if (paymentData.success) {
this.resetForm();
alert('Pago procesado exitosamente');
} else {
alert('Error al procesar el pago');
}
}
resetForm() {
this.paymentAmount = 0;
this.selectedBankId = '';
this.selectedBank = null;
this.paymentType = 'transfer';
}
formatDate(date: Date): string {
return date.toLocaleDateString();
}
}⚠️ Manejo de Errores
Validación de Cuentas Bancarias
// Servicio de validación para cuentas bancarias
@Injectable({ providedIn: 'root' })
export class BankValidator {
validateBankData(bankData: Partial<CbmFinancialBankModel.SaveBody>): ValidationResult {
const result: ValidationResult = { isValid: true, errors: [] };
// Validar nombre
if (!bankData.name || bankData.name.trim().length < 3) {
result.errors.push('El nombre de la cuenta debe tener al menos 3 caracteres');
}
// Validar número de tarjeta
if (!bankData.card_number || bankData.card_number.length < 4) {
result.errors.push('El número de tarjeta es requerido y debe tener al menos 4 caracteres');
}
// Validar que al menos una opción esté habilitada
if (!bankData.sales && !bankData.purchases) {
result.errors.push('La cuenta debe estar habilitada para ventas o compras');
}
// Validar secuencia de cheques
if (bankData.check_sequence && bankData.check_sequence < 1) {
result.errors.push('La secuencia de cheques debe ser mayor a 0');
}
result.isValid = result.errors.length === 0;
return result;
}
validateBankForPayment(bank: CbmFinancialBankModel.ListResponse.Data, paymentType: string): ValidationResult {
const result: ValidationResult = { isValid: true, errors: [] };
// Validar que el banco esté habilitado
if (!bank.enabled) {
result.errors.push('La cuenta bancaria no está habilitada');
}
// Validar tipo de pago
if (paymentType === 'check' && !bank.check_sequence) {
result.errors.push('La cuenta no tiene secuencia de cheques configurada');
}
if ((paymentType === 'transfer' || paymentType === 'check') && !bank.account_number) {
result.errors.push('La cuenta no tiene número de cuenta configurado');
}
result.isValid = result.errors.length === 0;
return result;
}
}
// Uso en componentes
export class BankFormComponent {
constructor(private validator: BankValidator) {}
validateBank() {
const validation = this.validator.validateBankData(this.bankForm.value);
if (!validation.isValid) {
this.showValidationErrors(validation.errors);
}
}
}Manejo de Errores en Operaciones
// Manejo de errores específico para operaciones bancarias
loadBanksWithErrorHandling(params: CbmFinancialBankModel.ListParams) {
this.bankService.list(params).subscribe({
next: (response) => {
if (response.success) {
this.banks = response.data;
this.showSuccessMessage(`Se encontraron ${response.data.length} cuentas bancarias`);
} else {
this.showErrorMessage('Error en la respuesta del servidor');
}
},
error: (error) => {
console.error('Error HTTP:', error);
if (error.status === 400) {
this.showErrorMessage('Parámetros de búsqueda inválidos. Verifique los filtros.');
} 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 consultar cuentas bancarias.');
} else if (error.status === 404) {
this.showErrorMessage('No se encontraron cuentas bancarias.');
} else if (error.status === 500) {
this.showErrorMessage('Error interno del servidor. Intente nuevamente.');
} else {
this.showErrorMessage('Error de conexión. Verifique su conexión a internet.');
}
}
});
}🔧 Configuración Avanzada
Servicio de Caché para Cuentas Bancarias
// Servicio de caché para cuentas bancarias con invalidación automática
@Injectable({ providedIn: 'root' })
export class BankCacheService {
private cache = new Map<string, { data: any; timestamp: number }>();
private readonly CACHE_DURATION = 10 * 60 * 1000; // 10 minutos
get(key: string): any | null {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {
return cached.data;
}
this.cache.delete(key);
return null;
}
set(key: string, data: any) {
this.cache.set(key, { data, timestamp: Date.now() });
}
invalidateBankCache() {
// Invalidar caché cuando se modifica una cuenta bancaria
this.cache.clear();
}
getEnabledBanks(): Observable<CbmFinancialBankModel.ListResponse> {
const cacheKey = 'enabled_banks';
const cached = this.get(cacheKey);
if (cached) {
return of(cached);
}
return this.bankService.list({ enabled: true }).pipe(
tap(response => {
if (response.success) {
this.set(cacheKey, response);
}
})
);
}
constructor(private bankService: CbmFinancialBankService) {}
}Servicio de Auditoría para Operaciones Bancarias
// Servicio de auditoría para operaciones de cuentas bancarias
@Injectable({ providedIn: 'root' })
export class BankAuditService {
logBankOperation(
operation: string,
bankData: Partial<CbmFinancialBankModel.ListResponse.Data>,
userId: string,
additionalData?: any
) {
const auditEntry = {
timestamp: new Date().toISOString(),
operation: `BANK_${operation.toUpperCase()}`,
bankId: bankData._id,
bankName: bankData.name,
accountNumber: bankData.account_number,
userId,
companyId: bankData.company_id,
additionalData,
severity: this.getOperationSeverity(operation)
};
console.log(`[BANK_AUDIT] ${JSON.stringify(auditEntry)}`);
// Enviar a servicio de auditoría
this.auditService.record('bank_operation', auditEntry);
}
logBankStatusChange(
bankId: string,
oldStatus: boolean,
newStatus: boolean,
reason: string,
userId: string
) {
const auditEntry = {
timestamp: new Date().toISOString(),
operation: 'BANK_STATUS_CHANGE',
bankId,
oldStatus,
newStatus,
reason,
userId,
severity: newStatus ? 'INFO' : 'WARNING'
};
console.log(`[BANK_STATUS_AUDIT] ${JSON.stringify(auditEntry)}`);
// Alertar sobre cambios críticos
if (!newStatus && reason.includes('fraude')) {
this.alertService.notifyBankSecurityIssue(auditEntry);
}
}
private getOperationSeverity(operation: string): string {
const severityMap: { [key: string]: string } = {
'create': 'INFO',
'update': 'INFO',
'delete': 'WARNING',
'status_change': 'WARNING'
};
return severityMap[operation] || 'INFO';
}
constructor(
private auditService: AuditService,
private alertService: AlertService
) {}
}📋 Dependencias
Peer Dependencies (Requeridas)
{
"@angular/common": ">=20.1.5",
"@angular/core": ">=20.1.5"
}Dependencias Internas
{
"tslib": "^2.3.0"
}🛠️ Desarrollo
Estructura del Proyecto
financial-bank-repository/
├── src/
│ ├── lib/
│ │ ├── financial-bank.model.ts # Modelos de datos para bancos
│ │ ├── financial-bank.module.ts # Configuración del módulo
│ │ ├── financial-bank.service.ts # Servicio HTTP para bancos
│ │ ├── financial-bank.repository.ts # Interfaz del repositorio
│ │ └── index.ts # Exportaciones públicas
│ └── 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 financial-bank-repository
# Construir en modo watch
ng build financial-bank-repository --watch
# Construir para producción
ng build financial-bank-repository --configuration productionPruebas
# Ejecutar pruebas unitarias
ng test financial-bank-repository
# Ejecutar pruebas con coverage
ng test financial-bank-repository --code-coverage
# Pruebas end-to-end
ng e2e financial-bank-repository🎯 Mejores Prácticas
1. Gestión de Secuencias de Cheques
// Servicio para gestión automática de secuencias de cheques
@Injectable({ providedIn: 'root' })
export class CheckSequenceManager {
constructor(private bankService: CbmFinancialBankService) {}
// Obtener próximo número de cheque
getNextCheckNumber(bankId: string): Observable<number> {
return this.bankService.getOne(bankId).pipe(
map(response => {
if (response.success && response.data) {
return response.data.check_sequence || 1;
}
throw new Error('No se pudo obtener la secuencia de cheques');
})
);
}
// Actualizar secuencia de cheques después de usar uno
updateCheckSequence(bankId: string, usedChecks: number = 1): Observable<any> {
return this.bankService.getOne(bankId).pipe(
switchMap(response => {
if (response.success && response.data) {
const currentSequence = response.data.check_sequence || 1;
const newSequence = currentSequence + usedChecks;
return this.bankService.update(bankId, {
check_sequence: newSequence
});
}
throw new Error('No se pudo actualizar la secuencia de cheques');
})
);
}
// Validar disponibilidad de cheques
validateCheckAvailability(bankId: string, requiredChecks: number): Observable<boolean> {
return this.getNextCheckNumber(bankId).pipe(
map(sequence => {
// Aquí iría lógica para validar contra inventario de cheques
// Por simplicidad, asumimos que siempre hay cheques disponibles
return true;
})
);
}
}2. Servicio de Integración Contable
// Servicio para integración con sistema contable
@Injectable({ providedIn: 'root' })
export class AccountingIntegrationService {
constructor(private bankService: CbmFinancialBankService) {}
// Sincronizar cuentas bancarias con plan contable
syncBankAccountsWithChartOfAccounts(): Observable<any> {
return this.bankService.list({ enabled: true }).pipe(
switchMap(response => {
if (response.success && response.data) {
const syncOperations = response.data.map(bank => {
return this.syncBankToAccounting(bank);
});
return forkJoin(syncOperations);
}
return of([]);
})
);
}
// Sincronizar cuenta bancaria individual
private syncBankToAccounting(bank: CbmFinancialBankModel.ListResponse.Data): Observable<any> {
const accountingEntry = {
accountCode: this.generateAccountCode(bank),
accountName: bank.name,
bankName: bank.bank_name,
accountNumber: bank.account_number,
accountType: bank.bank_account_type,
detailAccounts: bank.detail_account
};
// Aquí iría la integración con el sistema contable
console.log('Sincronizando cuenta bancaria con contabilidad:', accountingEntry);
return of({ success: true });
}
// Generar código de cuenta contable
private generateAccountCode(bank: CbmFinancialBankModel.ListResponse.Data): string {
// Lógica para generar códigos de cuenta contable
// Ejemplo: 1101-001 (Banco Principal)
const baseCode = '1101'; // Código base para bancos
const bankSuffix = bank._id.slice(-3).toUpperCase();
return `${baseCode}-${bankSuffix}`;
}
// Obtener cuentas contables relacionadas
getRelatedAccountingAccounts(bankId: string): Observable<any[]> {
return this.bankService.getOne(bankId).pipe(
map(response => {
if (response.success && response.data) {
return response.data.detail_account || [];
}
return [];
})
);
}
}3. Servicio de Validación de Pagos
// Servicio para validación de pagos y transacciones
@Injectable({ providedIn: 'root' })
export class PaymentValidationService {
constructor(private bankService: CbmFinancialBankService) {}
// Validar capacidad de pago
validatePaymentCapacity(
bankId: string,
paymentAmount: number,
paymentType: string
): Observable<PaymentValidationResult> {
return this.bankService.getOne(bankId).pipe(
map(response => {
if (!response.success || !response.data) {
return { isValid: false, errors: ['Cuenta bancaria no encontrada'] };
}
const bank = response.data;
const errors: string[] = [];
// Validar estado de la cuenta
if (!bank.enabled) {
errors.push('La cuenta bancaria no está habilitada');
}
// Validar tipo de pago
if (paymentType === 'check' && !bank.check_sequence) {
errors.push('La cuenta no tiene secuencia de cheques configurada');
}
if ((paymentType === 'transfer' || paymentType === 'check') && !bank.account_number) {
errors.push('La cuenta no tiene número de cuenta configurado');
}
// Validar límites de monto (simplificado)
if (paymentAmount > 100000 && paymentType === 'check') {
errors.push('Los montos superiores a $100,000 deben realizarse por transferencia');
}
return {
isValid: errors.length === 0,
errors,
warnings: this.generatePaymentWarnings(bank, paymentAmount, paymentType)
};
})
);
}
// Generar advertencias de pago
private generatePaymentWarnings(
bank: CbmFinancialBankModel.ListResponse.Data,
amount: number,
type: string
): string[] {
const warnings: string[] = [];
if (amount > 50000 && type === 'check') {
warnings.push('Considere usar transferencia para montos superiores a $50,000');
}
if (type === 'check' && bank.check_sequence && bank.check_sequence > 990) {
warnings.push('La secuencia de cheques está próxima a agotarse');
}
return warnings;
}
// Validar múltiples pagos
validateBulkPayments(payments: PaymentRequest[]): Observable<BulkValidationResult> {
const validations = payments.map(payment =>
this.validatePaymentCapacity(payment.bankId, payment.amount, payment.type)
);
return forkJoin(validations).pipe(
map(results => {
const validPayments = results.filter(r => r.isValid);
const invalidPayments = results.filter(r => !r.isValid);
return {
totalPayments: payments.length,
validPayments: validPayments.length,
invalidPayments: invalidPayments.length,
results
};
})
);
}
}
// Interfaces auxiliares
export interface PaymentValidationResult {
isValid: boolean;
errors: string[];
warnings: string[];
}
export interface BulkValidationResult {
totalPayments: number;
validPayments: number;
invalidPayments: number;
results: PaymentValidationResult[];
}
export interface PaymentRequest {
bankId: string;
amount: number;
type: string;
}🤝 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 patrón
forRootsimplificado - ✅ Servicio que implementa interfaz del repositorio directamente
- ✅ Operaciones CRUD completas para gestión de cuentas bancarias
- ✅ Modelo de datos completo con auditoría y integración contable
- ✅ Soporte para secuencias de cheques y números de tarjeta
- ✅ 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 cuentas bancarias, integración contable y procesamiento de pagos con soporte para múltiples tipos de transacciones.
