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/service-outsourcing-repository

v0.0.1

Published

Biblioteca Angular especializada en la gestión integral de servicios de outsourcing. Proporciona una API completa para administrar servicios tercerizados con códigos únicos, categorías, listas de precios, control de estados y funcionalidades avanzadas de

Readme

Service Outsourcing Repository

Biblioteca Angular especializada en la gestión integral de servicios de outsourcing. Proporciona una API completa para administrar servicios tercerizados con códigos únicos, categorías, listas de precios, control de estados y funcionalidades avanzadas de importación/exportación.

📋 Características Principales

  • Gestión Completa de Servicios: Administración de servicios de outsourcing con códigos únicos
  • Múltiples Listas de Precios: Soporte para diferentes listas de precios por servicio
  • Categorización Avanzada: Organización por categorías de outsourcing específicas
  • Control de Estados: Habilitar/deshabilitar servicios con razones de desactivación
  • Soft Delete y Restore: Eliminación lógica con posibilidad de restauración
  • Búsqueda por Código: Obtener servicios rápidamente mediante códigos únicos
  • Filtrado Avanzado: Búsqueda por múltiples criterios (estado, nombre, categoría, etc.)
  • Importación/Exportación Excel: Plantillas y procesamiento masivo de datos
  • Generación de PDFs: Documentos individuales por servicio
  • 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

🚀 Instalación

npm install @cbm-common/service-outsourcing-repository

⚙️ Configuración

1. Importar el Módulo

import { CbmServiceOutsourcingModule } from '@cbm-common/service-outsourcing-repository';

@NgModule({
  imports: [
    CbmServiceOutsourcingModule.forRoot({
      baseUrl: 'https://api.cbm.com/service-outsourcing'
    })
  ]
})
export class AppModule { }

2. Configuración Standalone (Angular 20+)

import { CbmServiceOutsourcingModule } from '@cbm-common/service-outsourcing-repository';

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

📚 API Reference

Interfaz del Repositorio

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

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

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

  // Crear nuevo servicio
  save(data: CbmOutsourcingServiceModel.SaveBody): Observable<CbmOutsourcingServiceModel.ConfirmResponse>;

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

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

  // Restaurar servicio eliminado
  restore(id: string, data: CbmOutsourcingServiceModel.RestoreBody): Observable<CbmOutsourcingServiceModel.ConfirmResponse>;

  // Eliminar servicio (soft delete)
  delete(id: string): Observable<CbmOutsourcingServiceModel.ConfirmResponse>;

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

  // Descargar plantilla Excel para carga
  downloadExcelCreateTemplate(): Observable<HttpResponse<Blob>>;

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

  // Descargar PDF individual
  downloadIndividualPdf(id: string): Observable<HttpResponse<Blob>>;
}

💡 Uso Básico

Inyección del Servicio

import { CbmServiceOutsourcingRepository } from '@cbm-common/service-outsourcing-repository';

@Component({
  selector: 'app-outsourcing-manager'
})
export class OutsourcingManagerComponent {
  services: CbmOutsourcingServiceModel.ListResponse.Item[] = [];

  constructor(private outsourcingRepository: CbmServiceOutsourcingRepository) {
    this.loadServices();
  }

  loadServices() {
    const params: CbmOutsourcingServiceModel.ListParams = {
      page: 1,
      size: 20,
      enabled: true,
      name: 'Desarrollo'
    };

    this.outsourcingRepository.list(params).subscribe({
      next: (response) => {
        if (response.success) {
          this.services = response.items;
          console.log('Servicios cargados:', this.services);
        }
      },
      error: (error) => {
        console.error('Error al cargar servicios:', error);
      }
    });
  }
}

Crear Nuevo Servicio

createNewService() {
  const serviceData: CbmOutsourcingServiceModel.SaveBody = {
    category_outsourcing_id: 'cat-123',
    tax_iva_id: 'iva-456',
    tax_iva_code: 'IVA12',
    code: 'OUT-001',
    barcode: '789456123',
    name: 'Desarrollo de Software Personalizado',
    comment: 'Servicio de desarrollo a medida',
    iva: 12,
    tax_ice: false,
    automatic_code: false,
    price_lists: [
      {
        price_list_id: 'price-001',
        base_price: 5000.00,
        tax_price: 5600.00
      }
    ]
  };

  this.outsourcingRepository.save(serviceData).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Servicio creado exitosamente');
        this.loadServices(); // Recargar lista
      }
    },
    error: (error) => console.error('Error al crear servicio:', error)
  });
}

Actualizar Servicio Existente

updateExistingService(serviceId: string) {
  const updateData: CbmOutsourcingServiceModel.UpdateBody = {
    name: 'Desarrollo de Software Empresarial',
    comment: 'Servicio actualizado con nuevas funcionalidades',
    price_lists: {
      data: [
        {
          _id: 'price-list-id-123',
          price_list_id: 'price-001',
          base_price: 6500.00,
          tax_price: 7280.00
        }
      ],
      deleted_records: ['old-price-id-456']
    }
  };

  this.outsourcingRepository.update(serviceId, updateData).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Servicio actualizado exitosamente');
        this.loadServices();
      }
    },
    error: (error) => console.error('Error al actualizar servicio:', error)
  });
}

Buscar por Código

findServiceByCode() {
  const code = 'OUT-001';

  this.outsourcingRepository.getOneByCode(code).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Servicio encontrado:', response.data);
        console.log('Nombre:', response.data.name);
        console.log('Categoría:', response.data.category_name);
        console.log('Listas de precios:', response.data.price_list_service_outsourcing);
      }
    },
    error: (error) => console.error('Servicio no encontrado:', error)
  });
}

Cambiar Estado del Servicio

toggleServiceStatus(serviceId: string, enable: boolean) {
  const statusData: CbmOutsourcingServiceModel.ChangeStatusBody = {
    enabled: enable,
    disabled_reason: enable ? undefined : 'Servicio temporalmente suspendido'
  };

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

Restaurar Servicio Eliminado

restoreDeletedService(serviceId: string) {
  const restoreData: CbmOutsourcingServiceModel.RestoreBody = {
    reason: 'Restauración solicitada por el cliente'
  };

  this.outsourcingRepository.restore(serviceId, restoreData).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Servicio restaurado exitosamente');
        this.loadServices();
      }
    },
    error: (error) => console.error('Error al restaurar servicio:', error)
  });
}

📊 Modelos de Datos

Parámetros de Búsqueda

interface ListParams {
  page: number;              // Número de página (1-based)
  size: number;              // Tamaño de página
  deleted?: boolean;         // Incluir servicios eliminados
  enabled?: boolean;         // Filtrar por estado activo
  code_filter?: string;      // Filtrar por código
  name?: string;             // Filtrar por nombre
  company_branch_id?: string; // Filtrar por sucursal
  category_id?: string;      // Filtrar por categoría
  tax_iva_id?: string;       // Filtrar por impuesto IVA
  price_list_id?: string;    // Filtrar por lista de precios
}

Servicio de Outsourcing

interface Item {
  _id: string;                    // ID único del servicio
  company_id: string;             // ID de la empresa
  company_branch_id: string;      // ID de la sucursal
  category_outsourcing_id: string; // ID de la categoría de outsourcing
  tax_iva_id: string;             // ID del impuesto IVA
  tax_iva_code: string;           // Código del IVA
  automatic_code: boolean;        // Generación automática de código
  code: string;                   // Código único del servicio
  barcode: string;                // Código de barras
  name: string;                   // Nombre del servicio
  comment: string;                // Comentarios adicionales
  iva: number;                    // Porcentaje de IVA
  tax_ice: boolean;               // Aplica impuesto ICE
  enabled: boolean;               // Estado activo/inactivo
  created_user: string;           // Usuario que creó
  created_at: number;             // Fecha de creación (timestamp)
  category_name: string;          // Nombre de la categoría
  tax_iva: TaxIva;                // Información del impuesto IVA
  price_list_service_outsourcing: PriceListOutsourcing[]; // Listas de precios
  disabled_reason?: string;       // Razón de desactivación
  updated_at?: number;            // Fecha de actualización
  updated_user?: string;          // Usuario que actualizó
  loading?: boolean;              // Estado de carga (UI)
  cost?: number;                  // Costo del servicio
  bulk_load?: boolean;            // Carga masiva
  deleted_at?: number;            // Fecha de eliminación (soft delete)
  deleted?: boolean;              // Indicador de eliminación
  deleted_user?: string;          // Usuario que eliminó
  company_branch_name: string;    // Nombre de la sucursal
  tax_iva_description: string;    // Descripción del IVA
  tax_iva_rate: number;           // Tasa del IVA
}

Impuesto IVA

interface TaxIva {
  _id: string;           // ID único del impuesto
  country_id?: string;   // ID del país
  code?: string;         // Código del impuesto
  percentage?: number;   // Porcentaje del impuesto
  description?: string;  // Descripción del impuesto
  enabled?: boolean;     // Estado activo
  created_at?: number;   // Fecha de creación
  created_user?: string; // Usuario que creó
}

Lista de Precios

interface PriceListOutsourcing {
  _id: string;              // ID único de la relación precio-servicio
  price_list_id?: string;   // ID de la lista de precios
  price_list_name?: string; // Nombre de la lista de precios
  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ó
  company_id?: string;      // ID de la empresa
  company_branch_id?: string; // ID de la sucursal
  country_id?: string;      // ID del país
  service_outsourcing_id?: string; // ID del servicio
}

Datos para Crear Servicio

interface SaveBody {
  category_outsourcing_id: string; // ID de categoría (requerido)
  tax_iva_id: string;              // ID del IVA (requerido)
  tax_iva_code: string;            // Código del IVA (requerido)
  code: string;                    // Código único (requerido)
  barcode: string;                 // Código de barras (requerido)
  name: string;                    // Nombre (requerido)
  comment: string;                 // Comentarios (requerido)
  iva: number;                     // Porcentaje IVA (requerido)
  tax_ice: boolean;                // Aplica ICE (requerido)
  automatic_code: boolean;         // Código automático (requerido)
  price_lists?: PriceListData[];   // Listas de precios (opcional)
}

Ejemplo de Datos

{
  "_id": "64f1a2b3c4d5e6f7g8h9i0j1",
  "company_id": "company-123",
  "company_branch_id": "branch-456",
  "category_outsourcing_id": "cat-out-789",
  "tax_iva_id": "iva-101",
  "tax_iva_code": "IVA12",
  "automatic_code": false,
  "code": "OUT-001",
  "barcode": "789456123",
  "name": "Desarrollo de Software Personalizado",
  "comment": "Servicio de desarrollo a medida",
  "iva": 12,
  "tax_ice": false,
  "enabled": true,
  "created_user": "admin",
  "created_at": 1694000000000,
  "category_name": "Desarrollo de Software",
  "tax_iva": {
    "_id": "iva-101",
    "code": "IVA12",
    "percentage": 12,
    "description": "IVA 12%"
  },
  "price_list_service_outsourcing": [
    {
      "_id": "price-123",
      "price_list_name": "Lista General",
      "base_price": 5000.00,
      "tax_price": 5600.00
    }
  ],
  "company_branch_name": "Sucursal Principal",
  "tax_iva_description": "IVA 12%",
  "tax_iva_rate": 12
}

🔍 Casos de Uso Comunes

1. Sistema de Gestión de Servicios

@Component({
  selector: 'app-service-management'
})
export class ServiceManagementComponent {
  services: CbmOutsourcingServiceModel.ListResponse.Item[] = [];
  filteredServices: CbmOutsourcingServiceModel.ListResponse.Item[] = [];

  constructor(private outsourcingRepository: CbmServiceOutsourcingRepository) {
    this.loadAllServices();
  }

  loadAllServices() {
    const params: CbmOutsourcingServiceModel.ListParams = {
      page: 1,
      size: 100,
      enabled: true
    };

    this.outsourcingRepository.list(params).subscribe({
      next: (response) => {
        if (response.success) {
          this.services = response.items;
          this.filteredServices = response.items;
        }
      }
    });
  }

  filterByCategory(categoryId: string) {
    this.filteredServices = this.services.filter(
      service => service.category_outsourcing_id === categoryId
    );
  }

  getServicesByPriceRange(minPrice: number, maxPrice: number) {
    return this.services.filter(service => {
      const prices = service.price_list_service_outsourcing || [];
      return prices.some(price =>
        price.base_price && price.base_price >= minPrice && price.base_price <= maxPrice
      );
    });
  }
}

2. Dashboard de Servicios de Outsourcing

@Component({
  selector: 'app-outsourcing-dashboard'
})
export class OutsourcingDashboardComponent {
  stats = {
    totalServices: 0,
    activeServices: 0,
    inactiveServices: 0,
    deletedServices: 0,
    servicesByCategory: new Map<string, number>(),
    totalRevenue: 0
  };

  constructor(private outsourcingRepository: CbmServiceOutsourcingRepository) {
    this.loadDashboardStats();
  }

  loadDashboardStats() {
    // Cargar servicios activos
    const activeParams: CbmOutsourcingServiceModel.ListParams = {
      page: 1,
      size: 1000,
      enabled: true,
      deleted: false
    };

    this.outsourcingRepository.list(activeParams).subscribe({
      next: (response) => {
        if (response.success) {
          this.stats.activeServices = response.total;
          this.calculateRevenue(response.items);
          this.calculateCategoryStats(response.items);
        }
      }
    });

    // Cargar servicios inactivos
    const inactiveParams: CbmOutsourcingServiceModel.ListParams = {
      page: 1,
      size: 1000,
      enabled: false,
      deleted: false
    };

    this.outsourcingRepository.list(inactiveParams).subscribe({
      next: (response) => {
        if (response.success) {
          this.stats.inactiveServices = response.total;
        }
      }
    });

    // Cargar servicios eliminados
    const deletedParams: CbmOutsourcingServiceModel.ListParams = {
      page: 1,
      size: 1000,
      deleted: true
    };

    this.outsourcingRepository.list(deletedParams).subscribe({
      next: (response) => {
        if (response.success) {
          this.stats.deletedServices = response.total;
          this.stats.totalServices = this.stats.activeServices +
                                   this.stats.inactiveServices +
                                   this.stats.deletedServices;
        }
      }
    });
  }

  private calculateRevenue(services: CbmOutsourcingServiceModel.ListResponse.Item[]) {
    this.stats.totalRevenue = services.reduce((total, service) => {
      const prices = service.price_list_service_outsourcing || [];
      const maxPrice = Math.max(...prices.map(p => p.tax_price || 0));
      return total + maxPrice;
    }, 0);
  }

  private calculateCategoryStats(services: CbmOutsourcingServiceModel.ListResponse.Item[]) {
    services.forEach(service => {
      const category = service.category_name;
      const current = this.stats.servicesByCategory.get(category) || 0;
      this.stats.servicesByCategory.set(category, current + 1);
    });
  }
}

3. Generador de Catálogos de Servicios

@Component({
  selector: 'app-service-catalog'
})
export class ServiceCatalogComponent {
  catalogData: any[] = [];

  constructor(private outsourcingRepository: CbmServiceOutsourcingRepository) {
    this.generateCatalog();
  }

  generateCatalog() {
    const params: CbmOutsourcingServiceModel.ListParams = {
      page: 1,
      size: 500,
      enabled: true
    };

    this.outsourcingRepository.list(params).subscribe({
      next: (response) => {
        if (response.success) {
          this.catalogData = response.items.map(service => ({
            codigo: service.code,
            nombre: service.name,
            categoria: service.category_name,
            descripcion: service.comment,
            precio_base: this.getMinPrice(service),
            precio_impuestos: this.getMaxPrice(service),
            iva: service.iva,
            ice: service.tax_ice,
            sucursal: service.company_branch_name
          }));
        }
      }
    });
  }

  private getMinPrice(service: CbmOutsourcingServiceModel.ListResponse.Item): number {
    const prices = service.price_list_service_outsourcing || [];
    return Math.min(...prices.map(p => p.base_price || 0));
  }

  private getMaxPrice(service: CbmOutsourcingServiceModel.ListResponse.Item): number {
    const prices = service.price_list_service_outsourcing || [];
    return Math.max(...prices.map(p => p.tax_price || 0));
  }
}

4. Sistema de Importación/Exportación

@Component({
  selector: 'app-data-management'
})
export class DataManagementComponent {
  constructor(private outsourcingRepository: CbmServiceOutsourcingRepository) {}

  exportServicesToExcel() {
    const params: CbmOutsourcingServiceModel.DownloadExcelParams = {
      enabled: true,
      deleted: false
    };

    this.outsourcingRepository.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 = `servicios-outsourcing-${new Date().toISOString().split('T')[0]}.xlsx`;
        link.click();
        window.URL.revokeObjectURL(url);
      },
      error: (error) => console.error('Error al exportar:', error)
    });
  }

  downloadTemplate() {
    this.outsourcingRepository.downloadExcelCreateTemplate().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-servicios-outsourcing.xlsx';
        link.click();
        window.URL.revokeObjectURL(url);
      }
    });
  }

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

    this.outsourcingRepository.importExcelCreate(formData).subscribe({
      next: (response) => {
        console.log('Importación completada');
        // Recargar lista de servicios
        this.loadServices();
      },
      error: (error) => console.error('Error en importación:', error)
    });
  }

  downloadServicePdf(serviceId: string) {
    this.outsourcingRepository.downloadIndividualPdf(serviceId).subscribe({
      next: (response) => {
        const blob = new Blob([response.body!], { type: 'application/pdf' });
        const url = window.URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = `servicio-${serviceId}.pdf`;
        link.click();
        window.URL.revokeObjectURL(url);
      }
    });
  }
}

🏗️ 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 ICbmServiceOutsourcingRepository {
  list(params: CbmOutsourcingServiceModel.ListParams): Observable<CbmOutsourcingServiceModel.ListResponse>;
  getOne(id: string): Observable<CbmOutsourcingServiceModel.GetOneResponse>;
  save(data: CbmOutsourcingServiceModel.SaveBody): Observable<CbmOutsourcingServiceModel.ConfirmResponse>;
  // ... otros métodos
}

// Implementación del servicio
@Injectable({ providedIn: 'root' })
export class CbmServiceOutsourcingService implements ICbmServiceOutsourcingRepository {
  constructor(
    private readonly http: HttpClient,
    @Inject(SERVICE_OUTSOURCING_MODULE_CONFIG) private readonly config: ICbmServiceOutsourcingModuleConfig
  ) {}

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

  // ... implementación de métodos
}

// Wrapper del repositorio
@Injectable({ providedIn: 'root' })
export class CbmServiceOutsourcingRepository implements ICbmServiceOutsourcingRepository {
  constructor(private service: CbmServiceOutsourcingService) {}

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

  // ... delegación a service
}

Configuración Centralizada

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

🔧 Mejores Prácticas

Manejo de Errores

@Component({
  selector: 'app-safe-service-handler'
})
export class SafeServiceHandlerComponent {
  constructor(private outsourcingRepository: CbmServiceOutsourcingRepository) {}

  safeLoadServices() {
    const params: CbmOutsourcingServiceModel.ListParams = {
      page: 1,
      size: 20,
      enabled: true
    };

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

  private processServices(services: CbmOutsourcingServiceModel.ListResponse.Item[]) {
    // Procesar servicios de manera segura
    services.forEach(service => {
      if (service.code && service.name) {
        // Procesar servicio válido
      } else {
        console.warn(`Servicio inválido: ${service._id}`);
      }
    });
  }

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

Validación de Datos

@Injectable({ providedIn: 'root' })
export class ServiceValidationService {
  constructor(private outsourcingRepository: CbmServiceOutsourcingRepository) {}

  validateServiceData(data: CbmOutsourcingServiceModel.SaveBody): string[] {
    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_outsourcing_id) {
      errors.push('La categoría es obligatoria');
    }

    if (!data.tax_iva_id) {
      errors.push('El impuesto IVA es obligatorio');
    }

    if (data.iva === undefined || data.iva < 0 || data.iva > 100) {
      errors.push('El porcentaje de IVA debe estar entre 0 y 100');
    }

    if (data.price_lists && data.price_lists.length > 0) {
      data.price_lists.forEach((price, index) => {
        if (!price.price_list_id) {
          errors.push(`La lista de precios ${index + 1} debe tener un ID`);
        }
        if (price.base_price === undefined || price.base_price < 0) {
          errors.push(`El precio base ${index + 1} debe ser válido`);
        }
      });
    }

    return errors;
  }

  validateAndSave(data: CbmOutsourcingServiceModel.SaveBody): Observable<boolean> {
    const errors = this.validateServiceData(data);

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

    return this.outsourcingRepository.save(data).pipe(
      map(response => response.success),
      catchError(() => of(false))
    );
  }
}

Caché de Servicios

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

  constructor(private outsourcingRepository: CbmServiceOutsourcingRepository) {}

  getServices(params: CbmOutsourcingServiceModel.ListParams, forceRefresh = false): Observable<CbmOutsourcingServiceModel.ListResponse.Item[]> {
    const cacheKey = JSON.stringify(params);
    const cached = this.cache.get(cacheKey);

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

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

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

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

📋 Lista de Verificación para Implementación

  • [ ] ✅ Configurar el módulo CbmServiceOutsourcingModule.forRoot() en el AppModule
  • [ ] ✅ Inyectar CbmServiceOutsourcingRepository en los componentes que lo necesiten
  • [ ] ✅ Implementar manejo de errores para todas las operaciones CRUD
  • [ ] ✅ Validar datos antes de enviar al servidor
  • [ ] ✅ Implementar indicadores de carga durante las operaciones
  • [ ] ✅ Manejar estados de error y respuestas no exitosas
  • [ ] ✅ Considerar implementar caché para servicios (cambian con frecuencia moderada)
  • [ ] ✅ Probar todas las operaciones CRUD
  • [ ] ✅ Verificar funcionamiento de importación/exportación Excel
  • [ ] ✅ Probar generación de PDFs individuales
  • [ ] ✅ 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 servicios de outsourcing
  • ✅ Soporte completo para operaciones CRUD
  • ✅ Gestión de listas de precios múltiples
  • ✅ Control de estados con razones de desactivación
  • ✅ Soft delete y restore de servicios
  • ✅ Búsqueda por código único
  • ✅ Filtrado avanzado por múltiples criterios
  • ✅ Funcionalidades de importación/exportación Excel
  • ✅ Generación de PDFs individuales
  • ✅ Arquitectura basada en patrón Repository
  • ✅ Configuración simplificada con forRoot()
  • ✅ Compatibilidad con Angular 20.1.5
  • ✅ Interfaces TypeScript tipadas
  • ✅ Documentación completa en español

🤝 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 Servicios de Outsourcing v0.0.1