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/incoterm-repository

v0.0.1

Published

Biblioteca Angular especializada en la gestión de Incoterms (International Commercial Terms). Proporciona una API completa para administrar términos comerciales internacionales que definen las responsabilidades de compradores y vendedores en transacciones

Downloads

9

Readme

Incoterm Repository

Biblioteca Angular especializada en la gestión de Incoterms (International Commercial Terms). Proporciona una API completa para administrar términos comerciales internacionales que definen las responsabilidades de compradores y vendedores en transacciones comerciales.

📋 Características Principales

  • Gestión de Incoterms: Administración completa de términos comerciales internacionales
  • Códigos Estandarizados: Soporte para códigos Incoterm oficiales (EXW, FOB, CIF, DDP, etc.)
  • Control de Estados: Habilitar/deshabilitar Incoterms según necesidades del negocio
  • Búsqueda Avanzada: Filtrado por código, nombre y estado
  • Arquitectura Moderna: Basada en Angular 20.1.5 con inyección de dependencias
  • Patrón Repository: Separación clara entre lógica de negocio y acceso a datos
  • TypeScript Tipado: Interfaces completamente tipadas para mejor desarrollo

🚀 Instalación

npm install @cbm-common/incoterm-repository

⚙️ Configuración

1. Importar el Módulo

import { CbmIncotermModule } from '@cbm-common/incoterm-repository';

@NgModule({
  imports: [
    CbmIncotermModule.forRoot({
      baseUrl: 'https://api.cbm.com/incoterms'
    })
  ]
})
export class AppModule { }

2. Configuración Standalone (Angular 20+)

import { CbmIncotermModule } from '@cbm-common/incoterm-repository';

@Component({
  standalone: true,
  imports: [CbmIncotermModule.forRoot({
    baseUrl: 'https://api.cbm.com/incoterms'
  })]
})
export class AppComponent { }

📚 API Reference

Interfaz del Repositorio

interface ICbmIncotermRepository {
  // Listado paginado con filtros
  list(params: CbmIncotermModel.ListParams): Observable<CbmIncotermModel.ListResponse>;
}

💡 Uso Básico

Inyección del Servicio

import { CbmIncotermRepository } from '@cbm-common/incoterm-repository';

@Component({
  selector: 'app-incoterm-manager'
})
export class IncotermManagerComponent {
  incoterms: CbmIncotermModel.ListResponse.Data[] = [];

  constructor(private incotermRepository: CbmIncotermRepository) {
    this.loadIncoterms();
  }

  loadIncoterms() {
    const params: CbmIncotermModel.ListParams = {
      enabled: true,
      name: 'FOB'
    };

    this.incotermRepository.list(params).subscribe({
      next: (response) => {
        if (response.success) {
          this.incoterms = response.data;
          console.log('Incoterms cargados:', this.incoterms);
        }
      },
      error: (error) => {
        console.error('Error al cargar incoterms:', error);
      }
    });
  }
}

Filtrado por Código

searchByCode() {
  const params: CbmIncotermModel.ListParams = {
    code: 'CIF',
    enabled: true
  };

  this.incotermRepository.list(params).subscribe({
    next: (response) => {
      if (response.success && response.data.length > 0) {
        const cifIncoterm = response.data[0];
        console.log('Incoterm CIF encontrado:', cifIncoterm);
        console.log('Nombre:', cifIncoterm.name);
        console.log('Código:', cifIncoterm.code);
      }
    },
    error: (error) => console.error('Incoterm no encontrado:', error)
  });
}

Filtrado por Nombre

searchByName() {
  const params: CbmIncotermModel.ListParams = {
    name: 'Free',
    enabled: true
  };

  this.incotermRepository.list(params).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Incoterms que contienen "Free":', response.data);
        response.data.forEach(incoterm => {
          console.log(`${incoterm.code}: ${incoterm.name}`);
        });
      }
    },
    error: (error) => console.error('Error en búsqueda:', error)
  });
}

📊 Modelos de Datos

Parámetros de Búsqueda

interface ListParams {
  enabled?: boolean;  // Filtrar por estado activo/inactivo
  code?: string;      // Filtrar por código Incoterm
  name?: string;      // Filtrar por nombre (búsqueda parcial)
}

Respuesta de Listado

interface ListResponse {
  success: boolean;              // Indicador de éxito de la operación
  data: ListResponse.Data[];     // Array de incoterms
}

namespace ListResponse {
  interface Data {
    _id: string;              // ID único del incoterm
    code?: string;            // Código Incoterm (ej: EXW, FOB, CIF)
    name?: string;            // Nombre completo del incoterm
    enabled?: boolean;        // Estado activo/inactivo
    created_user?: string;    // Usuario que creó el registro
    created_at?: number;      // Fecha de creación (timestamp)
    updated_user?: string;    // Usuario que actualizó el registro
    updated_at?: number;      // Fecha de actualización (timestamp)
  }
}

Ejemplo de Datos

{
  "success": true,
  "data": [
    {
      "_id": "64f1a2b3c4d5e6f7g8h9i0j1",
      "code": "FOB",
      "name": "Free On Board",
      "enabled": true,
      "created_user": "admin",
      "created_at": 1694000000000,
      "updated_user": "admin",
      "updated_at": 1694100000000
    },
    {
      "_id": "64f1a2b3c4d5e6f7g8h9i0j2",
      "code": "CIF",
      "name": "Cost, Insurance and Freight",
      "enabled": true,
      "created_user": "admin",
      "created_at": 1694000000000
    },
    {
      "_id": "64f1a2b3c4d5e6f7g8h9i0j3",
      "code": "DDP",
      "name": "Delivered Duty Paid",
      "enabled": false,
      "created_user": "admin",
      "created_at": 1694000000000,
      "updated_user": "admin",
      "updated_at": 1694200000000
    }
  ]
}

🔍 Casos de Uso Comunes

1. Selector de Incoterms en Formularios

@Component({
  selector: 'app-incoterm-selector'
})
export class IncotermSelectorComponent {
  incoterms: CbmIncotermModel.ListResponse.Data[] = [];
  selectedIncoterm: string = '';

  constructor(private incotermRepository: CbmIncotermRepository) {
    this.loadActiveIncoterms();
  }

  loadActiveIncoterms() {
    const params: CbmIncotermModel.ListParams = {
      enabled: true
    };

    this.incotermRepository.list(params).subscribe({
      next: (response) => {
        if (response.success) {
          this.incoterms = response.data.sort((a, b) =>
            (a.code || '').localeCompare(b.code || '')
          );
        }
      },
      error: (error) => {
        console.error('Error al cargar incoterms:', error);
      }
    });
  }

  onIncotermChange(incotermId: string) {
    const selected = this.incoterms.find(inc => inc._id === incotermId);
    if (selected) {
      console.log('Incoterm seleccionado:', selected.code, '-', selected.name);
      // Aquí puedes emitir el evento o actualizar el formulario
    }
  }

  getIncotermDisplayName(incoterm: CbmIncotermModel.ListResponse.Data): string {
    return `${incoterm.code} - ${incoterm.name}`;
  }
}

2. Gestión de Incoterms en Configuración

@Component({
  selector: 'app-incoterm-settings'
})
export class IncotermSettingsComponent {
  allIncoterms: CbmIncotermModel.ListResponse.Data[] = [];
  activeIncoterms: CbmIncotermModel.ListResponse.Data[] = [];
  inactiveIncoterms: CbmIncotermModel.ListResponse.Data[] = [];

  constructor(private incotermRepository: CbmIncotermRepository) {
    this.loadAllIncoterms();
  }

  loadAllIncoterms() {
    // Cargar todos los incoterms
    this.incotermRepository.list({}).subscribe({
      next: (response) => {
        if (response.success) {
          this.allIncoterms = response.data;
          this.categorizeIncoterms();
        }
      }
    });
  }

  private categorizeIncoterms() {
    this.activeIncoterms = this.allIncoterms.filter(inc => inc.enabled);
    this.inactiveIncoterms = this.allIncoterms.filter(inc => !inc.enabled);
  }

  getStats() {
    return {
      total: this.allIncoterms.length,
      active: this.activeIncoterms.length,
      inactive: this.inactiveIncoterms.length,
      activePercentage: Math.round((this.activeIncoterms.length / this.allIncoterms.length) * 100)
    };
  }

  searchIncoterms(searchTerm: string) {
    if (!searchTerm.trim()) {
      this.categorizeIncoterms();
      return;
    }

    const filtered = this.allIncoterms.filter(inc =>
      inc.code?.toLowerCase().includes(searchTerm.toLowerCase()) ||
      inc.name?.toLowerCase().includes(searchTerm.toLowerCase())
    );

    this.activeIncoterms = filtered.filter(inc => inc.enabled);
    this.inactiveIncoterms = filtered.filter(inc => !inc.enabled);
  }
}

3. Dashboard de Incoterms

@Component({
  selector: 'app-incoterm-dashboard'
})
export class IncotermDashboardComponent {
  incoterms: CbmIncotermModel.ListResponse.Data[] = [];
  stats = {
    totalIncoterms: 0,
    activeIncoterms: 0,
    inactiveIncoterms: 0,
    recentlyUpdated: 0
  };

  constructor(private incotermRepository: CbmIncotermRepository) {
    this.loadDashboardData();
  }

  loadDashboardData() {
    this.incotermRepository.list({}).subscribe({
      next: (response) => {
        if (response.success) {
          this.incoterms = response.data;
          this.calculateStats();
        }
      },
      error: (error) => {
        console.error('Error al cargar datos del dashboard:', error);
      }
    });
  }

  private calculateStats() {
    this.stats.totalIncoterms = this.incoterms.length;
    this.stats.activeIncoterms = this.incoterms.filter(inc => inc.enabled).length;
    this.stats.inactiveIncoterms = this.incoterms.filter(inc => !inc.enabled).length;

    // Contar incoterms actualizados en las últimas 24 horas
    const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
    this.stats.recentlyUpdated = this.incoterms.filter(inc =>
      inc.updated_at && inc.updated_at > oneDayAgo
    ).length;
  }

  getIncotermsByStatus(enabled: boolean): CbmIncotermModel.ListResponse.Data[] {
    return this.incoterms.filter(inc => inc.enabled === enabled);
  }

  getMostUsedIncoterms(): CbmIncotermModel.ListResponse.Data[] {
    // En un caso real, esto vendría de estadísticas de uso
    return this.incoterms.slice(0, 5);
  }
}

4. Validador de Incoterms

@Injectable({ providedIn: 'root' })
export class IncotermValidatorService {
  private validCodes = ['EXW', 'FCA', 'CPT', 'CIP', 'DAT', 'DAP', 'DDP', 'FAS', 'FOB', 'CFR', 'CIF'];

  constructor(private incotermRepository: CbmIncotermRepository) {}

  validateIncotermCode(code: string): boolean {
    return this.validCodes.includes(code.toUpperCase());
  }

  validateIncotermExists(code: string): Observable<boolean> {
    const params: CbmIncotermModel.ListParams = {
      code: code.toUpperCase(),
      enabled: true
    };

    return this.incotermRepository.list(params).pipe(
      map(response => response.success && response.data.length > 0),
      catchError(() => of(false))
    );
  }

  getIncotermByCode(code: string): Observable<CbmIncotermModel.ListResponse.Data | null> {
    const params: CbmIncotermModel.ListParams = {
      code: code.toUpperCase(),
      enabled: true
    };

    return this.incotermRepository.list(params).pipe(
      map(response => {
        if (response.success && response.data.length > 0) {
          return response.data[0];
        }
        return null;
      }),
      catchError(() => of(null))
    );
  }

  getValidationErrors(code: string): string[] {
    const errors: string[] = [];

    if (!code || !code.trim()) {
      errors.push('El código del incoterm es obligatorio');
    } else if (!this.validateIncotermCode(code)) {
      errors.push(`El código "${code}" no es un código Incoterm válido`);
    }

    return errors;
  }
}

🏗️ Arquitectura y Patrones

Patrón Repository

La biblioteca implementa el patrón Repository para mantener la separación entre la lógica de negocio y el acceso a datos:

// Interfaz del repositorio
export interface ICbmIncotermRepository {
  list(params: CbmIncotermModel.ListParams): Observable<CbmIncotermModel.ListResponse>;
}

// Implementación del servicio
@Injectable({ providedIn: 'root' })
export class CbmIncotermService implements ICbmIncotermRepository {
  constructor(
    private readonly http: HttpClient,
    @Inject(INCOTERM_MODULE_CONFIG)
    private readonly config: ICbmIncotermModuleConfig
  ) {}

  list(params: CbmIncotermModel.ListParams): Observable<CbmIncotermModel.ListResponse> {
    return this.http.get<CbmIncotermModel.ListResponse>(this.config.baseUrl, { params: { ...params } });
  }
}

// Wrapper del repositorio
@Injectable({ providedIn: 'root' })
export class CbmIncotermRepository implements ICbmIncotermRepository {
  constructor(private service: CbmIncotermService) {}

  list(params: CbmIncotermModel.ListParams): Observable<CbmIncotermModel.ListResponse> {
    return this.service.list(params);
  }
}

Configuración Centralizada

// Configuración del módulo
@NgModule({
  imports: [
    CbmIncotermModule.forRoot({
      baseUrl: environment.apiUrl + '/incoterms'
    })
  ]
})
export class AppModule {}

🔧 Mejores Prácticas

Manejo de Errores

@Component({
  selector: 'app-safe-incoterm-handler'
})
export class SafeIncotermHandlerComponent {
  incoterms: CbmIncotermModel.ListResponse.Data[] = [];
  loading = false;
  error: string | null = null;

  constructor(private incotermRepository: CbmIncotermRepository) {}

  safeLoadIncoterms() {
    this.loading = true;
    this.error = null;

    const params: CbmIncotermModel.ListParams = {
      enabled: true
    };

    this.incotermRepository.list(params).subscribe({
      next: (response) => {
        this.loading = false;
        if (response.success && response.data) {
          this.incoterms = response.data;
        } else {
          this.error = 'Respuesta inválida del servidor';
        }
      },
      error: (error) => {
        this.loading = false;
        if (error.status === 404) {
          this.error = 'Servicio de incoterms no disponible';
        } else if (error.status === 500) {
          this.error = 'Error interno del servidor';
        } else {
          this.error = 'Error de conexión';
        }
      }
    });
  }
}

Caché de Incoterms

@Injectable({ providedIn: 'root' })
export class IncotermCacheService {
  private cache = new Map<string, CbmIncotermModel.ListResponse.Data[]>();
  private readonly CACHE_DURATION = 30 * 60 * 1000; // 30 minutos

  constructor(private incotermRepository: CbmIncotermRepository) {}

  getIncoterms(params: CbmIncotermModel.ListParams = {}, forceRefresh = false): Observable<CbmIncotermModel.ListResponse.Data[]> {
    const cacheKey = JSON.stringify(params);
    const cached = this.cache.get(cacheKey);

    if (cached && !forceRefresh && !this.isCacheExpired(cached)) {
      return of(cached);
    }

    return this.incotermRepository.list(params).pipe(
      map(response => {
        if (response.success) {
          this.cache.set(cacheKey, response.data);
          return response.data;
        }
        throw new Error('Error al obtener incoterms');
      })
    );
  }

  private isCacheExpired(incoterms: CbmIncotermModel.ListResponse.Data[]): boolean {
    if (incoterms.length === 0) return true;
    const mostRecent = Math.max(...incoterms.map(inc => inc.updated_at || inc.created_at || 0));
    return Date.now() - mostRecent > this.CACHE_DURATION;
  }

  invalidateCache() {
    this.cache.clear();
  }

  // Método específico para obtener incoterms activos
  getActiveIncoterms(): Observable<CbmIncotermModel.ListResponse.Data[]> {
    return this.getIncoterms({ enabled: true });
  }

  // Método específico para buscar por código
  getIncotermByCode(code: string): Observable<CbmIncotermModel.ListResponse.Data | null> {
    return this.getIncoterms({ code, enabled: true }).pipe(
      map(incoterms => incoterms.find(inc => inc.code === code) || null)
    );
  }
}

Servicio de Utilidades para Incoterms

@Injectable({ providedIn: 'root' })
export class IncotermUtilsService {
  // Códigos Incoterm estándar con sus descripciones
  private readonly INCOTERM_DEFINITIONS = {
    'EXW': 'Ex Works - El vendedor entrega la mercancía en sus instalaciones',
    'FCA': 'Free Carrier - El vendedor entrega al transportista designado',
    'CPT': 'Carriage Paid To - El vendedor paga el transporte hasta el destino',
    'CIP': 'Carriage and Insurance Paid To - Transporte y seguro pagados por el vendedor',
    'DAT': 'Delivered at Terminal - Entregado en terminal designada',
    'DAP': 'Delivered at Place - Entregado en lugar convenido',
    'DDP': 'Delivered Duty Paid - Entregado con derechos pagados',
    'FAS': 'Free Alongside Ship - Libre al costado del buque',
    'FOB': 'Free On Board - Libre a bordo',
    'CFR': 'Cost and Freight - Costo y flete',
    'CIF': 'Cost, Insurance and Freight - Costo, seguro y flete'
  };

  getIncotermDescription(code: string): string {
    return this.INCOTERM_DEFINITIONS[code.toUpperCase()] || `Incoterm ${code} - Descripción no disponible`;
  }

  getAllIncotermCodes(): string[] {
    return Object.keys(this.INCOTERM_DEFINITIONS);
  }

  isValidIncotermCode(code: string): boolean {
    return code.toUpperCase() in this.INCOTERM_DEFINITIONS;
  }

  formatIncotermDisplay(incoterm: CbmIncotermModel.ListResponse.Data): string {
    if (incoterm.code && incoterm.name) {
      return `${incoterm.code} - ${incoterm.name}`;
    }
    return incoterm.name || incoterm.code || 'Incoterm sin nombre';
  }

  groupIncotermsByCategory(incoterms: CbmIncotermModel.ListResponse.Data[]): Map<string, CbmIncotermModel.ListResponse.Data[]> {
    const groups = new Map<string, CbmIncotermModel.ListResponse.Data[]>();

    incoterms.forEach(incoterm => {
      const category = this.getIncotermCategory(incoterm.code || '');
      if (!groups.has(category)) {
        groups.set(category, []);
      }
      groups.get(category)!.push(incoterm);
    });

    return groups;
  }

  private getIncotermCategory(code: string): string {
    const firstLetter = code.charAt(0).toUpperCase();
    const categories = {
      'E': 'Salida',
      'F': 'Transporte Principal no Pagado',
      'C': 'Transporte Principal Pagado',
      'D': 'Llegada'
    };
    return categories[firstLetter] || 'Sin Categoría';
  }
}

📋 Lista de Verificación para Implementación

  • [ ] ✅ Configurar el módulo CbmIncotermModule.forRoot() en el AppModule
  • [ ] ✅ Inyectar CbmIncotermRepository en los componentes que lo necesiten
  • [ ] ✅ Implementar manejo de errores para todas las operaciones
  • [ ] ✅ Validar códigos Incoterm antes de usarlos
  • [ ] ✅ Implementar indicadores de carga durante las operaciones
  • [ ] ✅ Manejar estados de error y respuestas no exitosas
  • [ ] ✅ Considerar implementar caché para incoterms (cambian con poca frecuencia)
  • [ ] ✅ Probar todas las operaciones de búsqueda y filtrado
  • [ ] ✅ Verificar funcionamiento con diferentes códigos Incoterm
  • [ ] ✅ Documentar casos de uso específicos de la aplicación

🔗 Dependencias

{
  "peerDependencies": {
    "@angular/common": "20.1.5",
    "@angular/core": "20.1.5"
  },
  "dependencies": {
    "tslib": "2.3.0"
  }
}

📝 Notas de Versión

v0.0.1

  • ✅ Implementación inicial del repositorio de incoterms
  • ✅ Soporte completo para listado con filtros avanzados
  • ✅ Gestión de códigos y nombres de incoterms
  • ✅ Control de estados activo/inactivo
  • ✅ Arquitectura basada en patrón Repository
  • ✅ Configuración simplificada con forRoot()
  • ✅ Compatibilidad con Angular 20.1.5
  • ✅ Interfaces TypeScript completamente tipadas
  • ✅ Documentación completa en español
  • ✅ Ejemplos prácticos de implementación
  • ✅ Servicios de utilidad para validación y formato
  • ✅ Soporte para caché de datos
  • ✅ Manejo robusto de errores

🤝 Contribución

Para contribuir a esta biblioteca:

  1. Fork el repositorio
  2. Crear una rama para la nueva funcionalidad
  3. Implementar los cambios siguiendo los patrones establecidos
  4. Agregar pruebas unitarias si es necesario
  5. Enviar un Pull Request con descripción detallada

📞 Soporte

Para soporte técnico o consultas sobre el uso de la biblioteca, contactar al equipo de desarrollo de CBM.


CBM (Contabilidad y Facturación Moderna) - Repositorio de Incoterms v0.0.1