@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
CbmKitRepositoryen 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:
- Fork el repositorio
- Crear una rama para la nueva funcionalidad
- Implementar los cambios siguiendo los patrones establecidos
- Agregar pruebas unitarias si es necesario
- 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
