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

v0.0.1

Published

Biblioteca Angular especializada en la gestión integral de kits de productos para el sistema CBM (Contabilidad y Facturación Moderna). Proporciona una API completa para operaciones CRUD, gestión de listas de precios, control de inventario y funcionalidade

Readme

Kit Repository

Biblioteca Angular especializada en la gestión integral de kits de productos para el sistema CBM (Contabilidad y Facturación Moderna). Proporciona una API completa para operaciones CRUD, gestión de listas de precios, control de inventario y funcionalidades avanzadas de importación/exportación.

📋 Características Principales

  • Gestión Completa de Kits: Crear, leer, actualizar y eliminar kits de productos
  • Búsqueda Avanzada: Filtrado por código, nombre, categoría, estado y fechas
  • Gestión de Items: Asociación de productos individuales con cantidades específicas
  • Listas de Precios: Múltiples listas de precios por kit con precios base e IVA
  • Control de Estados: Habilitar/deshabilitar kits con razones de desactivación
  • Búsqueda por Código: Obtener kits rápidamente mediante códigos únicos
  • Importación/Exportación Excel: Plantillas y procesamiento masivo de datos
  • Soft Delete: Eliminación lógica con preservación de datos históricos
  • Arquitectura Moderna: Basada en Angular 20.1.5 con inyección de dependencias

🚀 Instalación

npm install @cbm-common/kit-repository

⚙️ Configuración

1. Importar el Módulo

import { CbmKitModule } from '@cbm-common/kit-repository';

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

2. Configuración Standalone (Angular 20+)

import { CbmKitModule } from '@cbm-common/kit-repository';

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

📚 API Reference

Interfaz del Repositorio

interface ICbmKitRepository {
  // Listado paginado con filtros
  list(params: CbmKitModel.ListParams): Observable<CbmKitModel.ListResponse>;

  // Obtener kit por ID
  getOne(id: string): Observable<CbmKitModel.GetOneResponse>;

  // Obtener kit por código único
  getOneByCode(code: string): Observable<CbmKitModel.GetOneByCodeResponse>;

  // Crear nuevo kit
  save(data: CbmKitModel.SaveBody): Observable<CbmKitModel.ConfirmResponse>;

  // Actualizar kit existente
  update(id: string, data: CbmKitModel.UpdateBody): Observable<CbmKitModel.ConfirmResponse>;

  // Cambiar estado del kit
  changeStatus(id: string, data: CbmKitModel.ChangeStatusBody): Observable<CbmKitModel.ConfirmResponse>;

  // Eliminar kit (soft delete)
  delete(id: string): Observable<CbmKitModel.ConfirmResponse>;

  // Exportar a Excel
  downloadExcel(params: CbmKitModel.DownloadExcelParams): Observable<HttpResponse<Blob>>;

  // Descargar plantilla Excel para creación
  downloadExcelTemplateCreate(): Observable<HttpResponse<Blob>>;

  // Importar desde Excel
  importExcelCreate(formData: FormData): Observable<HttpResponse<Blob>>;
}

💡 Uso Básico

Inyección del Servicio

import { CbmKitRepository } from '@cbm-common/kit-repository';

@Component({
  selector: 'app-kit-manager'
})
export class KitManagerComponent {
  constructor(private kitRepository: CbmKitRepository) { }

  // Ejemplo de listado con filtros
  loadKits() {
    const params: CbmKitModel.ListParams = {
      page: 1,
      size: 20,
      code: 'KIT-001',
      enabled: true
    };

    this.kitRepository.list(params).subscribe({
      next: (response) => {
        console.log('Kits encontrados:', response.items);
        console.log('Total:', response.total);
      },
      error: (error) => console.error('Error al cargar kits:', error)
    });
  }
}

Crear un Nuevo Kit

createNewKit() {
  const kitData: CbmKitModel.SaveBody = {
    category_id: 'cat-123',
    tax_iva_id: 'iva-456',
    unit_measure_id: 'unit-789',
    tax_iva_code: 'IVA12',
    code: 'KIT-NEW-001',
    name: 'Kit de Herramientas Básicas',
    comment: 'Kit esencial para mantenimiento',
    iva: 12,
    automatic_code: false,
    price_lists: [
      {
        price_list_id: 'price-001',
        base_price: 100.00,
        tax_price: 112.00
      }
    ],
    items: [
      {
        item_id: 'item-001',
        amount: 2
      },
      {
        item_id: 'item-002',
        amount: 1
      }
    ]
  };

  this.kitRepository.save(kitData).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Kit creado exitosamente');
      }
    },
    error: (error) => console.error('Error al crear kit:', error)
  });
}

Actualizar un Kit Existente

updateExistingKit(kitId: string) {
  const updateData: CbmKitModel.UpdateBody = {
    name: 'Kit de Herramientas Avanzadas',
    comment: 'Kit actualizado con nuevas herramientas',
    price_lists: {
      data: [
        {
          _id: 'price-list-id-123',
          price_list_id: 'price-001',
          base_price: 150.00,
          tax_price: 168.00
        }
      ],
      deleted_records: ['old-price-id-456']
    },
    items: {
      data: [
        {
          _id: 'item-relation-id-789',
          item_id: 'item-003',
          amount: 3
        }
      ],
      deleted_records: ['old-item-relation-id-101']
    }
  };

  this.kitRepository.update(kitId, updateData).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Kit actualizado exitosamente');
      }
    },
    error: (error) => console.error('Error al actualizar kit:', error)
  });
}

Buscar por Código

findKitByCode() {
  const code = 'KIT-001';

  this.kitRepository.getOneByCode(code).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Kit encontrado:', response.data);
        console.log('Nombre:', response.data.name);
        console.log('Items asociados:', response.data.items);
      }
    },
    error: (error) => console.error('Kit no encontrado:', error)
  });
}

Cambiar Estado del Kit

toggleKitStatus(kitId: string, enable: boolean) {
  const statusData: CbmKitModel.ChangeStatusBody = {
    enabled: enable,
    disabled_reason: enable ? undefined : 'Kit temporalmente fuera de stock'
  };

  this.kitRepository.changeStatus(kitId, statusData).subscribe({
    next: (response) => {
      if (response.success) {
        console.log(`Kit ${enable ? 'habilitado' : 'deshabilitado'} exitosamente`);
      }
    },
    error: (error) => console.error('Error al cambiar estado:', error)
  });
}

📊 Modelos de Datos

ListParams - Parámetros de Búsqueda

interface ListParams {
  page: number;              // Número de página (1-based)
  size: number;              // Tamaño de página
  code?: string;             // Filtrar por código
  name?: string;             // Filtrar por nombre
  category_id?: string;      // Filtrar por categoría
  enabled?: boolean;         // Filtrar por estado
  from_date?: number;        // Fecha desde (timestamp)
  to_date?: number;          // Fecha hasta (timestamp)
}

Kit - Estructura Principal

interface Kit {
  _id: string;                    // ID único del kit
  company_id?: string;           // ID de la empresa
  company_branch_id?: string;    // ID de la sucursal
  category_id?: string;          // ID de la categoría
  tax_iva_id?: string;           // ID del impuesto IVA
  unit_measure_id?: string;      // ID de la unidad de medida
  tax_iva_code?: string;         // Código del IVA
  code?: string;                 // Código único del kit
  name?: string;                 // Nombre del kit
  comment?: string;              // Comentarios adicionales
  iva?: number;                  // Porcentaje de IVA
  automatic_code?: boolean;      // Generación automática de código
  enabled?: boolean;             // Estado del kit
  created_user?: string;         // Usuario que creó
  deleted_at?: number;           // Fecha de eliminación (soft delete)
  created_at?: number;           // Fecha de creación
  updated_at?: number;           // Fecha de actualización
  updated_user?: string;         // Usuario que actualizó
  disabled_reason?: string;      // Razón de desactivación

  // Relaciones
  category?: Category;           // Información de la categoría
  unit_measure?: UnitMeasure;    // Información de la unidad de medida
  tax_iva?: TaxIva;              // Información del impuesto IVA
  price_lists?: PriceList[];     // Listas de precios asociadas
  items?: KitItem[];             // Items que componen el kit
  deleted_items?: KitItem[];     // Items eliminados (soft delete)
}

Item del Kit

interface KitItem {
  _id: string;           // ID de la relación kit-item
  kit_id?: string;       // ID del kit
  item_id?: string;      // ID del item/producto
  amount?: number;       // Cantidad del item en el kit
  created_user?: string; // Usuario que creó
  created_at?: number;   // Fecha de creación
  updated_at?: number;   // Fecha de actualización
  updated_user?: string; // Usuario que actualizó

  // Información completa del item
  item?: {
    _id: string;
    code?: string;
    barcode?: string;
    name?: string;
    comment?: string;
    iva?: number;
    cost?: number;
    automatic_code?: boolean;
    batch_control?: boolean;
    series_control?: boolean;
    tax_ice?: boolean;
    tax_irbpn?: boolean;
    enabled?: boolean;
    // ... otros campos del item
  };
}

Lista de Precios

interface KitPriceList {
  _id: string;              // ID de la relación kit-lista de precios
  price_list_id?: string;   // ID de la lista de precios
  kit_id?: string;          // ID del kit
  base_price?: number;      // Precio base
  tax_price?: number;       // Precio con impuestos
  created_user?: string;    // Usuario que creó
  created_at?: number;      // Fecha de creación
  updated_at?: number;      // Fecha de actualización
  updated_user?: string;    // Usuario que actualizó

  // Información de la lista de precios
  price_list?: {
    _id: string;
    name?: string;
    factor_cost?: string;
    factor_type?: string;
    percentage?: number;
    default?: boolean;
    enabled?: boolean;
    // ... otros campos
  };
}

📈 Funcionalidades Avanzadas

Exportación a Excel

exportKitsToExcel() {
  const params: CbmKitModel.DownloadExcelParams = {
    timezone: 'America/Guayaquil',
    locale: 'es_EC',
    code: 'KIT-',
    enabled: true,
    from_date: Date.now() - (30 * 24 * 60 * 60 * 1000), // Últimos 30 días
    to_date: Date.now()
  };

  this.kitRepository.downloadExcel(params).subscribe({
    next: (response) => {
      const blob = new Blob([response.body!], {
        type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
      });
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.download = 'kits.xlsx';
      link.click();
      window.URL.revokeObjectURL(url);
    },
    error: (error) => console.error('Error al exportar:', error)
  });
}

Importación desde Excel

importKitsFromExcel(file: File) {
  const formData = new FormData();
  formData.append('file', file);

  this.kitRepository.importExcelCreate(formData).subscribe({
    next: (response) => {
      console.log('Importación completada');
      // Procesar respuesta del servidor
    },
    error: (error) => console.error('Error en importación:', error)
  });
}

Descargar Plantilla Excel

downloadTemplate() {
  this.kitRepository.downloadExcelTemplateCreate().subscribe({
    next: (response) => {
      const blob = new Blob([response.body!], {
        type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
      });
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.download = 'plantilla-kits.xlsx';
      link.click();
      window.URL.revokeObjectURL(url);
    },
    error: (error) => console.error('Error al descargar plantilla:', error)
  });
}

🔍 Casos de Uso Comunes

1. Gestión de Inventario de Kits

@Component({
  selector: 'app-inventory-manager'
})
export class InventoryManagerComponent {
  kits: CbmKitModel.ListResponse.Item[] = [];
  totalKits = 0;

  constructor(private kitRepository: CbmKitRepository) {
    this.loadInventory();
  }

  loadInventory() {
    const params: CbmKitModel.ListParams = {
      page: 1,
      size: 50,
      enabled: true
    };

    this.kitRepository.list(params).subscribe({
      next: (response) => {
        this.kits = response.items;
        this.totalKits = response.total;
      }
    });
  }

  updateKitStock(kitId: string, newStock: number) {
    // Actualizar cantidades de items en el kit
    this.kitRepository.getOne(kitId).subscribe({
      next: (response) => {
        if (response.success) {
          const updateData: CbmKitModel.UpdateBody = {
            items: {
              data: response.data.items?.map(item => ({
                _id: item._id,
                item_id: item.item_id!,
                amount: newStock
              })) || []
            }
          };

          this.kitRepository.update(kitId, updateData).subscribe({
            next: () => this.loadInventory()
          });
        }
      }
    });
  }
}

2. Sistema de Precios Dinámicos

@Component({
  selector: 'app-price-manager'
})
export class PriceManagerComponent {
  constructor(private kitRepository: CbmKitRepository) {}

  applyBulkPriceIncrease(percentage: number) {
    // Obtener todos los kits activos
    const params: CbmKitModel.ListParams = {
      page: 1,
      size: 1000,
      enabled: true
    };

    this.kitRepository.list(params).subscribe({
      next: (response) => {
        // Procesar cada kit para actualizar precios
        response.items.forEach(kit => {
          if (kit._id) {
            this.updateKitPrices(kit._id, percentage);
          }
        });
      }
    });
  }

  private updateKitPrices(kitId: string, percentage: number) {
    this.kitRepository.getOne(kitId).subscribe({
      next: (response) => {
        if (response.success && response.data.price_lists) {
          const updatedPrices = response.data.price_lists.map(price => ({
            _id: price._id,
            price_list_id: price.price_list_id!,
            base_price: price.base_price! * (1 + percentage / 100),
            tax_price: price.tax_price! * (1 + percentage / 100)
          }));

          const updateData: CbmKitModel.UpdateBody = {
            price_lists: {
              data: updatedPrices
            }
          };

          this.kitRepository.update(kitId, updateData).subscribe();
        }
      }
    });
  }
}

3. Dashboard de Kits

@Component({
  selector: 'app-kit-dashboard'
})
export class KitDashboardComponent {
  kitStats = {
    total: 0,
    active: 0,
    inactive: 0,
    recent: 0
  };

  constructor(private kitRepository: CbmKitRepository) {
    this.loadDashboardStats();
  }

  loadDashboardStats() {
    // Estadísticas generales
    const allParams: CbmKitModel.ListParams = {
      page: 1,
      size: 1
    };

    this.kitRepository.list(allParams).subscribe({
      next: (response) => {
        this.kitStats.total = response.total;
      }
    });

    // Kits activos
    const activeParams: CbmKitModel.ListParams = {
      page: 1,
      size: 1,
      enabled: true
    };

    this.kitRepository.list(activeParams).subscribe({
      next: (response) => {
        this.kitStats.active = response.total;
        this.kitStats.inactive = this.kitStats.total - response.total;
      }
    });

    // Kits recientes (últimos 7 días)
    const recentParams: CbmKitModel.ListParams = {
      page: 1,
      size: 1,
      from_date: Date.now() - (7 * 24 * 60 * 60 * 1000)
    };

    this.kitRepository.list(recentParams).subscribe({
      next: (response) => {
        this.kitStats.recent = response.total;
      }
    });
  }
}

🏗️ Arquitectura y Patrones

Patrón Repository

La biblioteca implementa el patrón Repository para abstraer el acceso a datos:

// Interfaz del repositorio
export interface ICbmKitRepository {
  // Métodos de negocio
}

// Implementación concreta
@Injectable({providedIn: 'root'})
export class CbmKitRepository implements ICbmKitRepository {
  constructor(private service: CbmKitService) {}

  // Delegación al servicio
  list(params: CbmKitModel.ListParams) {
    return this.service.list(params);
  }
}

// Servicio HTTP
@Injectable({providedIn: 'root'})
export class CbmKitService implements ICbmKitRepository {
  constructor(
    private readonly http: HttpClient,
    @Inject(KIT_MODULE_CONFIG) private readonly config: ICbmKitModuleConfig
  ) {}

  // Implementación HTTP
  list(params: CbmKitModel.ListParams) {
    return this.http.get<CbmKitModel.ListResponse>(
      this.config.baseUrl,
      { params: { ...params } }
    );
  }
}

Configuración por Módulo

// Configuración centralizada
@NgModule({
  imports: [
    CbmKitModule.forRoot({
      baseUrl: environment.apiUrl + '/kits'
    })
  ]
})
export class AppModule { }

🔧 Mejores Prácticas

1. Manejo de Errores

@Component({
  selector: 'app-kit-handler'
})
export class KitHandlerComponent {
  constructor(private kitRepository: CbmKitRepository) {}

  safeKitOperation() {
    this.kitRepository.list({ page: 1, size: 10 }).subscribe({
      next: (response) => {
        if (response.success) {
          // Procesar datos
          this.processKits(response.items);
        } else {
          // Manejar respuesta no exitosa
          this.showErrorMessage('Error en la respuesta del servidor');
        }
      },
      error: (error) => {
        // Manejar errores HTTP
        if (error.status === 404) {
          this.showErrorMessage('Recurso no encontrado');
        } else if (error.status === 403) {
          this.showErrorMessage('Acceso denegado');
        } else {
          this.showErrorMessage('Error de conexión');
        }
      }
    });
  }

  private processKits(kits: CbmKitModel.ListResponse.Item[]) {
    // Lógica de procesamiento
  }

  private showErrorMessage(message: string) {
    // Mostrar mensaje de error al usuario
  }
}

2. Optimización de Rendimiento

@Component({
  selector: 'app-optimized-kit-list'
})
export class OptimizedKitListComponent {
  private kitsCache = new Map<string, CbmKitModel.ListResponse.Item>();
  private cacheExpiry = 5 * 60 * 1000; // 5 minutos

  constructor(private kitRepository: CbmKitRepository) {}

  getKitWithCache(kitId: string): Observable<CbmKitModel.GetOneResponse> {
    const cached = this.kitsCache.get(kitId);
    if (cached && this.isCacheValid(cached)) {
      return of({
        success: true,
        data: cached as any
      });
    }

    return this.kitRepository.getOne(kitId).pipe(
      tap(response => {
        if (response.success) {
          this.kitsCache.set(kitId, response.data as any);
        }
      })
    );
  }

  private isCacheValid(kit: CbmKitModel.ListResponse.Item): boolean {
    const now = Date.now();
    const updatedAt = kit.updated_at || kit.created_at || 0;
    return (now - updatedAt) < this.cacheExpiry;
  }
}

3. Validación de Datos

@Component({
  selector: 'app-kit-validator'
})
export class KitValidatorComponent {
  constructor(private kitRepository: CbmKitRepository) {}

  validateAndSaveKit(kitData: CbmKitModel.SaveBody): Observable<boolean> {
    return this.validateKitData(kitData).pipe(
      switchMap(isValid => {
        if (!isValid) {
          return of(false);
        }
        return this.kitRepository.save(kitData).pipe(
          map(response => response.success),
          catchError(() => of(false))
        );
      })
    );
  }

  private validateKitData(data: CbmKitModel.SaveBody): Observable<boolean> {
    const errors: string[] = [];

    if (!data.code?.trim()) {
      errors.push('El código es obligatorio');
    }

    if (!data.name?.trim()) {
      errors.push('El nombre es obligatorio');
    }

    if (!data.category_id) {
      errors.push('La categoría es obligatoria');
    }

    if (data.iva === undefined || data.iva < 0) {
      errors.push('El IVA debe ser un valor válido');
    }

    if (errors.length > 0) {
      console.error('Errores de validación:', errors);
      return of(false);
    }

    return of(true);
  }
}

📋 Lista de Verificación para Implementación

  • [ ] ✅ Configurar el módulo CbmKitModule.forRoot() en el AppModule
  • [ ] ✅ Inyectar CbmKitRepository en los componentes que lo necesiten
  • [ ] ✅ Implementar manejo de errores para todas las operaciones
  • [ ] ✅ Validar datos antes de enviar al servidor
  • [ ] ✅ Implementar indicadores de carga durante las operaciones
  • [ ] ✅ Manejar estados de error y respuestas no exitosas
  • [ ] ✅ Implementar caché si es necesario para mejorar rendimiento
  • [ ] ✅ Probar todas las operaciones CRUD
  • [ ] ✅ Verificar funcionamiento de importación/exportación Excel
  • [ ] ✅ Documentar casos de uso específicos de la aplicación

🔗 Dependencias

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

📝 Notas de Versión

v0.0.1

  • ✅ Implementación inicial del repositorio de kits
  • ✅ Soporte completo para operaciones CRUD
  • ✅ Gestión de listas de precios y items relacionados
  • ✅ Funcionalidades de importación/exportación Excel
  • ✅ Arquitectura basada en patrón Repository
  • ✅ Configuración simplificada con forRoot()
  • ✅ Compatibilidad con Angular 20.1.5

🤝 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 Kits v0.0.1