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 🙏

© 2025 – Pkg Stats / Ryan Hefner

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

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

Pruebas

# 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

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