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/bank-movements-repository

v0.0.1

Published

Biblioteca Angular para la gestión de movimientos bancarios en el sistema CBM. Implementa el patrón Repository para proporcionar una capa de abstracción sobre las operaciones bancarias.

Readme

Bank Movements Repository

Biblioteca Angular para la gestión de movimientos bancarios en el sistema CBM. Implementa el patrón Repository para proporcionar una capa de abstracción sobre las operaciones bancarias.

📦 Instalación

npm install @cbm-common/bank-movements-repository

⚙️ Configuración

Módulo Principal (NgModules)

Importa el módulo en tu aplicación principal y configura la URL base de la API:

import { CbmBankMovementsModule } from '@cbm-common/bank-movements-repository';

@NgModule({
  imports: [
    // ... otros módulos
    CbmBankMovementsModule.forRoot({
      baseUrl: 'https://api.cbm.com/v1'
    })
  ],
  // ... otras configuraciones
})
export class AppModule { }

Aplicaciones Standalone

Para aplicaciones que utilizan el nuevo sistema standalone de Angular, configura el provider directamente:

import { bootstrapApplication } from '@angular/platform-browser';
import { BANK_MOVEMENTS_MODULE_CONFIG } from '@cbm-common/bank-movements-repository';

bootstrapApplication(AppComponent, {
  providers: [
    // ... otros providers
    {
      provide: BANK_MOVEMENTS_MODULE_CONFIG,
      useValue: {
        baseUrl: 'https://api.cbm.com/v1'
      }
    }
  ]
});

🏗️ Arquitectura

La biblioteca sigue el patrón Repository para separar la lógica de negocio de la implementación técnica:

CbmBankMovementsRepository (Interfaz + Implementación)
    ↓
CbmBankMovementsService (Lógica HTTP)
    ↓
HttpClient (Comunicación con API)

Componentes Principales

  • ICbmBankMovementsRepository: Interfaz que define el contrato del repositorio
  • CbmBankMovementsRepository: Implementación concreta del repositorio
  • CbmBankMovementsService: Servicio que maneja las llamadas HTTP
  • CbmBankMovementsModule: Módulo de configuración
  • CbmBankMovementsModel: Modelos de datos y tipos TypeScript

🚀 Uso Básico

Inyección de Dependencias

import { Component, inject } from '@angular/core';
import { CbmBankMovementsRepository } from '@cbm-common/bank-movements-repository';

@Component({
  selector: 'app-bank-movements',
  template: `
    <div class="movements-container">
      <h2>Movimientos Bancarios</h2>
      <button (click)="loadMovements()">Cargar Movimientos</button>

      <div *ngIf="loading">Cargando...</div>

      <div *ngFor="let movement of movements" class="movement-item">
        <span>{{ movement.operation_number }}</span>
        <span>{{ movement.value | currency }}</span>
        <span>{{ movement.date | date }}</span>
      </div>
    </div>
  `
})
export class BankMovementsComponent {
  private repository = inject(CbmBankMovementsRepository);

  movements: CbmBankMovementsModel.ListResponse.Item[] = [];
  loading = false;

  loadMovements() {
    this.loading = true;

    const params: CbmBankMovementsModel.ListParams = {
      page: 1,
      size: 20,
      bank_id: 'bank123',
      date_begin: new Date('2024-01-01').getTime(),
      date_end: new Date('2024-12-31').getTime()
    };

    this.repository.list(params).subscribe({
      next: (response) => {
        this.movements = response.items;
        this.loading = false;
      },
      error: (error) => {
        console.error('Error al cargar movimientos:', error);
        this.loading = false;
      }
    });
  }
}

Validación de Operaciones

import { Component, inject } from '@angular/core';
import { CbmBankMovementsRepository } from '@cbm-common/bank-movements-repository';

@Component({
  selector: 'app-operation-validator',
  template: `
    <form (ngSubmit)="validateOperation()" #form="ngForm">
      <input [(ngModel)]="operationNumber" name="operation" placeholder="Número de operación" required>
      <input [(ngModel)]="bankId" name="bank" placeholder="ID del banco" required>
      <button type="submit" [disabled]="!form.valid">Validar</button>
    </form>

    <div *ngIf="validationResult">
      <h3>Resultado de Validación</h3>
      <p>Origen: {{ validationResult.data?.collection_origin_name }}</p>
      <p>Módulo: {{ validationResult.data?.event_module }}</p>
    </div>
  `
})
export class OperationValidatorComponent {
  private repository = inject(CbmBankMovementsRepository);

  operationNumber = '';
  bankId = '';
  validationResult: CbmBankMovementsModel.ValidationOperationResponse | null = null;

  validateOperation() {
    const params: CbmBankMovementsModel.ValidationOperationParams = {
      operation_number: this.operationNumber,
      bank_id: this.bankId
    };

    this.repository.validationOperation(params).subscribe({
      next: (response) => {
        this.validationResult = response;
      },
      error: (error) => {
        console.error('Error en validación:', error);
      }
    });
  }
}

📋 API Reference

ICbmBankMovementsRepository

Interfaz principal que define las operaciones disponibles:

interface ICbmBankMovementsRepository {
  list(params: CbmBankMovementsModel.ListParams): Observable<CbmBankMovementsModel.ListResponse>;
  validationOperation(params: CbmBankMovementsModel.ValidationOperationParams): Observable<CbmBankMovementsModel.ValidationOperationResponse>;
}

CbmBankMovementsService

Servicio que implementa la lógica HTTP:

@Injectable({ providedIn: 'root' })
export class CbmBankMovementsService {
  constructor(
    private http: HttpClient,
    @Inject(BANK_MOVEMENTS_MODULE_CONFIG) private config: ICbmBankMovementsModuleConfig
  ) {}

  list(params: CbmBankMovementsModel.ListParams): Observable<CbmBankMovementsModel.ListResponse>;
  validationOperation(params: CbmBankMovementsModel.ValidationOperationParams): Observable<CbmBankMovementsModel.ValidationOperationResponse>;
}

📊 Modelos de Datos

ListParams

Parámetros para consultar la lista de movimientos:

interface ListParams {
  page: number;              // Número de página (requerido)
  size: number;              // Tamaño de página (requerido)
  reverse_status?: boolean;  // Estado de reversión
  operation_number?: string; // Número de operación
  payment_term_id?: string;  // ID del término de pago
  bank_id?: string;          // ID del banco
  date_end?: number;         // Fecha fin (timestamp)
  date_begin?: number;       // Fecha inicio (timestamp)
}

ListResponse

Respuesta de la consulta de movimientos:

interface ListResponse {
  success: boolean;
  pageNum: number;
  pageSize: number;
  pages: number;
  total: number;
  items: ListResponse.Item[];
}

interface ListResponse.Item {
  _id?: string;
  company_id?: string;
  company_branch_id?: string;
  bank_id?: string;
  bank_name?: string;
  payment_term_id?: string;
  payment_term_code?: string;
  payment_term_name?: string;
  collection_origin_id?: string;
  collection_origin_name?: string;
  operation_number?: string;
  date?: number;
  value?: number;
  type?: string;
  created_user?: string;
  reverse_status?: boolean;
  created_at?: number;
  reverse_at?: number;
  reverse_reason?: string;
  reverse_user?: string;
}

ValidationOperationParams

Parámetros para validar una operación:

interface ValidationOperationParams {
  operation_number: string; // Número de operación (requerido)
  bank_id: string;          // ID del banco (requerido)
}

ValidationOperationResponse

Respuesta de la validación:

interface ValidationOperationResponse {
  success?: boolean;
  data?: {
    collection_origin_id: string;
    collection_origin_name: string;
    event_module: string;
  };
  message?: string;
}

🔧 Ejemplos Avanzados

Con Reactive Forms

import { Component, inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { CbmBankMovementsRepository } from '@cbm-common/bank-movements-repository';

@Component({
  selector: 'app-movements-filter',
  template: `
    <form [formGroup]="filterForm" (ngSubmit)="applyFilters()">
      <div class="form-row">
        <input formControlName="bank_id" placeholder="ID del banco">
        <input formControlName="operation_number" placeholder="Número de operación">
      </div>

      <div class="form-row">
        <input formControlName="date_begin" type="date">
        <input formControlName="date_end" type="date">
      </div>

      <div class="form-row">
        <select formControlName="reverse_status">
          <option [value]="null">Todos</option>
          <option [value]="true">Revertidos</option>
          <option [value]="false">No revertidos</option>
        </select>
      </div>

      <button type="submit" [disabled]="filterForm.invalid">Aplicar Filtros</button>
    </form>
  `
})
export class MovementsFilterComponent {
  private fb = inject(FormBuilder);
  private repository = inject(CbmBankMovementsRepository);

  filterForm: FormGroup = this.fb.group({
    bank_id: [''],
    operation_number: [''],
    date_begin: [''],
    date_end: [''],
    reverse_status: [null]
  });

  applyFilters() {
    const filters = this.filterForm.value;

    // Convertir fechas a timestamps
    if (filters.date_begin) {
      filters.date_begin = new Date(filters.date_begin).getTime();
    }
    if (filters.date_end) {
      filters.date_end = new Date(filters.date_end).getTime();
    }

    const params: CbmBankMovementsModel.ListParams = {
      page: 1,
      size: 20,
      ...filters
    };

    this.repository.list(params).subscribe({
      next: (response) => {
        console.log('Movimientos filtrados:', response.items);
      }
    });
  }
}

Con Signals (Angular 17+)

import { Component, inject, signal, computed } from '@angular/core';
import { CbmBankMovementsRepository } from '@cbm-common/bank-movements-repository';

@Component({
  selector: 'app-movements-signals',
  template: `
    <div class="movements-dashboard">
      <h2>Dashboard de Movimientos</h2>

      <div class="stats">
        <div class="stat">
          <span class="label">Total Movimientos:</span>
          <span class="value">{{ totalMovements() }}</span>
        </div>
        <div class="stat">
          <span class="label">Valor Total:</span>
          <span class="value">{{ totalValue() | currency }}</span>
        </div>
      </div>

      <button (click)="loadMovements()" [disabled]="loading()">
        {{ loading() ? 'Cargando...' : 'Cargar Movimientos' }}
      </button>

      <div *ngIf="error()" class="error">
        Error: {{ error() }}
      </div>

      <div class="movements-list">
        <div *ngFor="let movement of movements()" class="movement-card">
          <h3>{{ movement.operation_number }}</h3>
          <p>Banco: {{ movement.bank_name }}</p>
          <p>Valor: {{ movement.value | currency }}</p>
          <p>Fecha: {{ movement.date | date:'short' }}</p>
          <p *ngIf="movement.reverse_status" class="reversed">
            ⚠️ Movimiento revertido
          </p>
        </div>
      </div>
    </div>
  `
})
export class MovementsSignalsComponent {
  private repository = inject(CbmBankMovementsRepository);

  // Signals
  movements = signal<CbmBankMovementsModel.ListResponse.Item[]>([]);
  loading = signal(false);
  error = signal<string | null>(null);

  // Computed signals
  totalMovements = computed(() => this.movements().length);
  totalValue = computed(() =>
    this.movements().reduce((sum, movement) => sum + (movement.value || 0), 0)
  );

  loadMovements() {
    this.loading.set(true);
    this.error.set(null);

    const params: CbmBankMovementsModel.ListParams = {
      page: 1,
      size: 50
    };

    this.repository.list(params).subscribe({
      next: (response) => {
        this.movements.set(response.items);
        this.loading.set(false);
      },
      error: (err) => {
        this.error.set(err.message || 'Error desconocido');
        this.loading.set(false);
      }
    });
  }
}

Paginación Personalizada

import { Component, inject } from '@angular/core';
import { CbmBankMovementsRepository } from '@cbm-common/bank-movements-repository';

@Component({
  selector: 'app-paginated-movements',
  template: `
    <div class="pagination-controls">
      <button (click)="previousPage()" [disabled]="currentPage === 1">
        ← Anterior
      </button>

      <span>Página {{ currentPage }} de {{ totalPages }}</span>

      <button (click)="nextPage()" [disabled]="currentPage === totalPages">
        Siguiente →
      </button>
    </div>

    <div class="page-size-selector">
      <label>Elementos por página:</label>
      <select [(ngModel)]="pageSize" (change)="changePageSize()">
        <option [value]="10">10</option>
        <option [value]="20">20</option>
        <option [value]="50">50</option>
      </select>
    </div>
  `
})
export class PaginatedMovementsComponent {
  private repository = inject(CbmBankMovementsRepository);

  currentPage = 1;
  pageSize = 20;
  totalPages = 1;
  totalItems = 0;

  movements: CbmBankMovementsModel.ListResponse.Item[] = [];

  loadPage(page: number = 1) {
    const params: CbmBankMovementsModel.ListParams = {
      page,
      size: this.pageSize
    };

    this.repository.list(params).subscribe({
      next: (response) => {
        this.movements = response.items;
        this.currentPage = response.pageNum;
        this.totalPages = response.pages;
        this.totalItems = response.total;
      }
    });
  }

  previousPage() {
    if (this.currentPage > 1) {
      this.loadPage(this.currentPage - 1);
    }
  }

  nextPage() {
    if (this.currentPage < this.totalPages) {
      this.loadPage(this.currentPage + 1);
    }
  }

  changePageSize() {
    this.currentPage = 1;
    this.loadPage(1);
  }
}

⚠️ Manejo de Errores

La biblioteca utiliza HttpClient de Angular, por lo que los errores HTTP se manejan de forma estándar:

import { HttpErrorResponse } from '@angular/common/http';

this.repository.list(params).subscribe({
  next: (response) => {
    // Operación exitosa
    console.log('Movimientos cargados:', response.items);
  },
  error: (error: HttpErrorResponse) => {
    switch (error.status) {
      case 400:
        console.error('Parámetros inválidos:', error.error);
        break;
      case 401:
        console.error('No autorizado - revisar token de autenticación');
        // Redirigir a login
        break;
      case 403:
        console.error('Acceso denegado');
        break;
      case 404:
        console.error('Recurso no encontrado');
        break;
      case 500:
        console.error('Error interno del servidor');
        break;
      default:
        console.error('Error desconocido:', error.message);
    }
  }
});

Estrategias de Reintento

import { retry, catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

this.repository.list(params).pipe(
  retry({
    count: 3,
    delay: (error, retryCount) => {
      console.log(`Reintento ${retryCount} después del error:`, error);
      return retryCount * 1000; // Delay incremental
    }
  }),
  catchError((error) => {
    console.error('Error después de reintentos:', error);
    return throwError(() => error);
  })
).subscribe({
  next: (response) => {
    // Manejar respuesta exitosa
  },
  error: (error) => {
    // Manejar error final
  }
});

🔄 Integración con RxJS

Operadores Avanzados

import { map, filter, switchMap, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { Subject } from 'rxjs';

@Component({...})
export class AdvancedMovementsComponent {
  private searchSubject = new Subject<string>();
  private repository = inject(CbmBankMovementsRepository);

  movements$ = this.searchSubject.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap(searchTerm => {
      const params: CbmBankMovementsModel.ListParams = {
        page: 1,
        size: 20,
        operation_number: searchTerm
      };
      return this.repository.list(params);
    }),
    map(response => response.items),
    catchError(error => {
      console.error('Error en búsqueda:', error);
      return [];
    })
  );

  onSearchChange(searchTerm: string) {
    this.searchSubject.next(searchTerm);
  }
}

Caching con ShareReplay

import { shareReplay, refCount } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class CachedBankMovementsService {
  private cache$?: Observable<CbmBankMovementsModel.ListResponse>;

  constructor(private repository: CbmBankMovementsRepository) {}

  getMovements(params: CbmBankMovementsModel.ListParams) {
    if (!this.cache$) {
      this.cache$ = this.repository.list(params).pipe(
        shareReplay(1, 300000) // Cache por 5 minutos
      );
    }
    return this.cache$;
  }

  clearCache() {
    this.cache$ = undefined;
  }
}

📋 Dependencias

Peer Dependencies (Requeridas)

{
  "@angular/core": ">=20.1.5",
  "@angular/common": ">=20.1.5",
  "rxjs": ">=7.0.0"
}

Dependencias Internas

{
  "tslib": "^2.3.0"
}

🛠️ Desarrollo

Estructura del Proyecto

bank-movements-repository/
├── src/
│   ├── lib/
│   │   ├── bank-movements.model.ts      # Modelos de datos
│   │   ├── bank-movements.module.ts     # Configuración del módulo
│   │   ├── bank-movements.repository.ts # Implementación del repositorio
│   │   ├── bank-movements.service.ts    # Servicio HTTP
│   │   └── index.ts                     # Exportaciones públicas
│   └── public-api.ts                    # API pública
├── ng-package.json                      # Configuración de empaquetado
├── package.json                         # Dependencias y configuración
└── README.md                            # Esta documentación

Construcción

# Construir la librería
ng build bank-movements-repository

# Construir en modo watch
ng build bank-movements-repository --watch

# Construir para producción
ng build bank-movements-repository --configuration production

Pruebas

# Ejecutar pruebas unitarias
ng test bank-movements-repository

# Ejecutar pruebas con coverage
ng test bank-movements-repository --code-coverage

# Ejecutar pruebas en modo watch
ng test bank-movements-repository --watch

Publicación

# Construir para distribución
ng build bank-movements-repository --configuration production

# Navegar al directorio de distribución
cd dist/bank-movements-repository

# Publicar en npm
npm publish

🎯 Mejores Prácticas

1. Configuración Centralizada

// config.ts
export const BANK_MOVEMENTS_CONFIG = {
  baseUrl: environment.apiUrl,
  timeout: 30000,
  retries: 3
};

// app.module.ts
CbmBankMovementsModule.forRoot(BANK_MOVEMENTS_CONFIG)

2. Manejo de Estado Global

import { Store } from '@ngrx/store';

@Injectable({ providedIn: 'root' })
export class BankMovementsFacade {
  movements$ = this.store.select(selectMovements);
  loading$ = this.store.select(selectMovementsLoading);

  constructor(
    private store: Store,
    private repository: CbmBankMovementsRepository
  ) {}

  loadMovements(params: CbmBankMovementsModel.ListParams) {
    this.store.dispatch(loadMovements({ params }));
  }
}

3. Interceptores HTTP Personalizados

@Injectable()
export class BankMovementsInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.url.includes('/bank-movements')) {
      const authReq = req.clone({
        headers: req.headers.set('Authorization', `Bearer ${this.getToken()}`)
      });
      return next.handle(authReq);
    }
    return next.handle(req);
  }
}

🤝 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 Pull Request

📄 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

  • ✅ Implementación inicial del repositorio de movimientos bancarios
  • ✅ Soporte para consultas paginadas con filtros
  • ✅ Validación de operaciones bancarias
  • ✅ Patrón Repository con interfaz
  • ✅ Configuración flexible via InjectionToken
  • ✅ Documentación completa en español

Nota: Esta biblioteca está diseñada para trabajar con la API de CBM v1. Asegúrate de que tu servidor backend sea compatible con los endpoints documentados.

Running end-to-end tests

For end-to-end (e2e) testing, run:

ng e2e

Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.

Additional Resources

For more information on using the Angular CLI, including detailed command references, visit the Angular CLI Overview and Command Reference page.