npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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ón

Construcció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 production

Pruebas

# 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

  1. Fork el repositorio
  2. Crea una rama para tu feature (git checkout -b feature/nueva-funcionalidad)
  3. Commit tus cambios (git commit -am 'Agrega nueva funcionalidad')
  4. Push a la rama (git push origin feature/nueva-funcionalidad)
  5. 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:

🔄 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.