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/tax-retention-repository

v0.0.1

Published

Biblioteca Angular especializada en la gestión integral de retenciones de impuestos. Proporciona una API completa para administrar retenciones fiscales con códigos únicos, porcentajes, tipos de retención, control de estados y funcionalidades avanzadas de

Downloads

10

Readme

Tax Retention Repository

Biblioteca Angular especializada en la gestión integral de retenciones de impuestos. Proporciona una API completa para administrar retenciones fiscales con códigos únicos, porcentajes, tipos de retención, control de estados y funcionalidades avanzadas de búsqueda y paginación.

📋 Características Principales

  • Gestión Completa de Retenciones: Administración de retenciones de impuestos con códigos únicos
  • Múltiples Tipos de Retención: Soporte para diferentes tipos de retenciones fiscales
  • Configuración de Porcentajes: Gestión precisa de porcentajes de retención
  • Control de Estados: Habilitar/deshabilitar retenciones con razones de desactivación
  • Paginación Avanzada: Listado paginado con filtros complejos
  • Búsqueda por Múltiples Criterios: Filtrado por tipo, estado, descripción y código
  • Auditoría Completa: Registro de usuarios que crean, actualizan y cambian estados
  • 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/tax-retention-repository

⚙️ Configuración

1. Importar el Módulo

import { CbmTaxRetentionModule } from '@cbm-common/tax-retention-repository';

@NgModule({
  imports: [
    CbmTaxRetentionModule.forRoot({
      baseUrl: 'https://api.cbm.com/tax-retentions'
    })
  ]
})
export class AppModule { }

2. Configuración Standalone (Angular 20+)

import { CbmTaxRetentionModule } from '@cbm-common/tax-retention-repository';

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

📚 API Reference

Interfaz del Repositorio

interface ICbmTaxRetentionRepository {
  // Listado paginado con filtros avanzados
  listPaginated(params: CbmTaxRetentionModel.ListPaginatedParams): Observable<CbmTaxRetentionModel.ListPaginatedResponse>;

  // Listado simple con filtros básicos
  list(params: CbmTaxRetentionModel.ListParams): Observable<CbmTaxRetentionModel.ListResponse>;

  // Obtener retención por ID
  getOne(id: string): Observable<CbmTaxRetentionModel.GetOneResponse>;

  // Crear nueva retención
  save(data: CbmTaxRetentionModel.SaveBody): Observable<CbmTaxRetentionModel.ConfirmResponse>;

  // Actualizar retención existente
  update(id: string, data: CbmTaxRetentionModel.UpdateBody): Observable<CbmTaxRetentionModel.ConfirmResponse>;

  // Cambiar estado de la retención
  changeStatus(id: string, data: CbmTaxRetentionModel.ChangeStatusBody): Observable<CbmTaxRetentionModel.ConfirmResponse>;

  // Eliminar retención
  delete(id: string): Observable<CbmTaxRetentionModel.ConfirmResponse>;
}

💡 Uso Básico

Inyección del Servicio

import { CbmTaxRetentionRepository } from '@cbm-common/tax-retention-repository';

@Component({
  selector: 'app-tax-retention-manager'
})
export class TaxRetentionManagerComponent {
  retentions: CbmTaxRetentionModel.ListPaginatedResponse.Item[] = [];

  constructor(private taxRetentionRepository: CbmTaxRetentionRepository) {
    this.loadRetentions();
  }

  loadRetentions() {
    const params: CbmTaxRetentionModel.ListPaginatedParams = {
      page: 1,
      size: 20,
      enabled: true,
      type: 'IVA'
    };

    this.taxRetentionRepository.listPaginated(params).subscribe({
      next: (response) => {
        if (response.success) {
          this.retentions = response.items;
          console.log('Retenciones cargadas:', this.retentions);
          console.log(`Página ${response.pageNum} de ${response.pages}, Total: ${response.total}`);
        }
      },
      error: (error) => {
        console.error('Error al cargar retenciones:', error);
      }
    });
  }
}

Listado Simple con Filtros

loadActiveRetentions() {
  const params: CbmTaxRetentionModel.ListParams = {
    enabled: true,
    type: 'ISR'
  };

  this.taxRetentionRepository.list(params).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Retenciones activas ISR:', response.data);
        response.data.forEach(retention => {
          console.log(`${retention.code}: ${retention.percentage}% - ${retention.description}`);
        });
      }
    },
    error: (error) => console.error('Error al cargar retenciones:', error)
  });
}

Obtener Retención por ID

getRetentionDetails(retentionId: string) {
  this.taxRetentionRepository.getOne(retentionId).subscribe({
    next: (response) => {
      if (response.success) {
        const retention = response.data;
        console.log('Detalles de retención:', retention);
        console.log('Código:', retention.code);
        console.log('Porcentaje:', retention.percentage);
        console.log('Tipo:', retention.type);
        console.log('Estado:', retention.enabled ? 'Activa' : 'Inactiva');
        if (!retention.enabled && retention.disabled_reason) {
          console.log('Razón de inactividad:', retention.disabled_reason);
        }
      }
    },
    error: (error) => console.error('Retención no encontrada:', error)
  });
}

Crear Nueva Retención

createNewRetention() {
  const retentionData: CbmTaxRetentionModel.SaveBody = {
    code: 'IVA-12',
    percentage: 12.00,
    description: 'Retención IVA 12% para servicios profesionales',
    type: 'IVA'
  };

  this.taxRetentionRepository.save(retentionData).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Retención creada exitosamente');
        this.loadRetentions(); // Recargar lista
      } else {
        console.error('Error al crear retención:', response.message);
      }
    },
    error: (error) => console.error('Error al crear retención:', error)
  });
}

Actualizar Retención Existente

updateRetention(retentionId: string) {
  const updateData: CbmTaxRetentionModel.UpdateBody = {
    percentage: 15.00,
    description: 'Retención IVA 15% actualizada para servicios profesionales',
    type: 'IVA'
  };

  this.taxRetentionRepository.update(retentionId, updateData).subscribe({
    next: (response) => {
      if (response.success) {
        console.log('Retención actualizada exitosamente');
        this.loadRetentions();
      } else {
        console.error('Error al actualizar retención:', response.message);
      }
    },
    error: (error) => console.error('Error al actualizar retención:', error)
  });
}

Cambiar Estado de Retención

toggleRetentionStatus(retentionId: string, enable: boolean) {
  const statusData: CbmTaxRetentionModel.ChangeStatusBody = {
    enabled: enable,
    disabled_reason: enable ? undefined : 'Retención suspendida temporalmente por cambios regulatorios'
  };

  this.taxRetentionRepository.changeStatus(retentionId, statusData).subscribe({
    next: (response) => {
      if (response.success) {
        console.log(`Retención ${enable ? 'habilitada' : 'deshabilitada'} exitosamente`);
        this.loadRetentions();
      } else {
        console.error('Error al cambiar estado:', response.message);
      }
    },
    error: (error) => console.error('Error al cambiar estado:', error)
  });
}

📊 Modelos de Datos

Parámetros de Paginación

interface ListPaginatedParams extends ListParams {
  page: number;              // Número de página (1-based)
  size: number;              // Tamaño de página
  code_description_filter?: string; // Filtro por código o descripción
}

Parámetros de Listado Simple

interface ListParams {
  type?: string;      // Filtrar por tipo de retención
  enabled?: boolean;  // Filtrar por estado activo/inactivo
  description?: string; // Filtrar por descripción (búsqueda parcial)
}

Respuesta de Paginación

interface ListPaginatedResponse {
  success: boolean;                    // Indicador de éxito
  pageNum: number;                     // Número de página actual
  pageSize: number;                    // Tamaño de página
  pages: number;                       // Total de páginas
  total: number;                       // Total de registros
  items: ListPaginatedResponse.Item[]; // Array de retenciones
}

namespace ListPaginatedResponse {
  interface Item {
    _id: string;                    // ID único de la retención
    code: string;                   // Código único de la retención
    percentage: number;             // Porcentaje de retención
    description: string;            // Descripción de la retención
    type: string;                   // Tipo de retención (IVA, ISR, etc.)
    enabled: boolean;               // Estado activo/inactivo
    created_at: number;             // Fecha de creación (timestamp)
    created_user: string;           // Usuario que creó
    updated_at?: number;            // Fecha de actualización (timestamp)
    updated_user?: string;          // Usuario que actualizó
    disabled_reason: string;        // Razón de desactivación
    user_inactive_at: number;       // Fecha de inactivación
    user_inactive_id: string;       // ID del usuario que inactivó
    user_inactive_name: string;     // Nombre del usuario que inactivó
    user_active_at: number;         // Fecha de activación
    user_active_id: string;         // ID del usuario que activó
    user_active_name: string;       // Nombre del usuario que activó
  }
}

Respuesta de Listado Simple

interface ListResponse {
  success: boolean;              // Indicador de éxito
  data: ListResponse.Data[];     // Array de retenciones
}

namespace ListResponse {
  interface Data {
    _id: string;                    // ID único de la retención
    code: string;                   // Código único de la retención
    percentage: number;             // Porcentaje de retención
    description: string;            // Descripción de la retención
    type: string;                   // Tipo de retención
    enabled: boolean;               // Estado activo/inactivo
    created_at: number;             // Fecha de creación (timestamp)
    created_user: string;           // Usuario que creó
    updated_at?: number;            // Fecha de actualización
    updated_user?: string;          // Usuario que actualizó
    disabled_reason: string;        // Razón de desactivación
    user_inactive_at: number;       // Fecha de inactivación
    user_inactive_id: string;       // ID del usuario que inactivó
    user_inactive_name: string;     // Nombre del usuario que inactivó
    user_active_at: number;         // Fecha de activación
    user_active_id: string;         // ID del usuario que activó
    user_active_name: string;       // Nombre del usuario que activó
  }
}

Detalles de una Retención

interface GetOneResponse {
  success: boolean;           // Indicador de éxito
  data: GetOneResponse.Data;  // Datos de la retención
}

namespace GetOneResponse {
  interface Data {
    _id?: string;                    // ID único de la retención
    code?: string;                   // Código único de la retención
    percentage?: number;             // Porcentaje de retención
    description?: string;            // Descripción de la retención
    type?: string;                   // Tipo de retención
    enabled?: boolean;               // Estado activo/inactivo
    deleted?: boolean;               // Indicador de eliminación lógica
    created_at?: number;             // Fecha de creación (timestamp)
    created_user?: string;           // Usuario que creó
    updated_at?: number;             // Fecha de actualización (timestamp)
    updated_user?: string;           // Usuario que actualizó
    disabled_reason?: string;        // Razón de desactivación
    user_inactive_at?: number;       // Fecha de inactivación
    user_inactive_id?: string;       // ID del usuario que inactivó
    user_inactive_name?: string;     // Nombre del usuario que inactivó
    user_active_at?: number;         // Fecha de activación
    user_active_id?: string;         // ID del usuario que activó
    user_active_name?: string;       // Nombre del usuario que activó
  }
}

Datos para Crear Retención

interface SaveBody {
  code: string;        // Código único (requerido)
  percentage: number;  // Porcentaje de retención (requerido)
  description: string; // Descripción (requerido)
  type: string;        // Tipo de retención (requerido)
}

Datos para Actualizar Retención

interface UpdateBody {
  code?: string;        // Código único (opcional)
  percentage?: number;  // Porcentaje de retención (opcional)
  description?: string; // Descripción (opcional)
  type?: string;        // Tipo de retención (opcional)
}

Datos para Cambiar Estado

interface ChangeStatusBody {
  enabled: boolean;          // Nuevo estado (requerido)
  disabled_reason?: string;  // Razón de desactivación (opcional, requerido si enabled=false)
}

Respuesta de Confirmación

interface ConfirmResponse {
  success: boolean;  // Indicador de éxito
  message: string;   // Mensaje descriptivo
  data?: any;        // Datos adicionales (opcional)
}

Ejemplo de Datos

{
  "_id": "64f1a2b3c4d5e6f7g8h9i0j1",
  "code": "IVA-12",
  "percentage": 12.00,
  "description": "Retención IVA 12% para servicios profesionales",
  "type": "IVA",
  "enabled": true,
  "created_at": 1694000000000,
  "created_user": "admin",
  "updated_at": 1694100000000,
  "updated_user": "admin",
  "disabled_reason": "",
  "user_inactive_at": 0,
  "user_inactive_id": "",
  "user_inactive_name": "",
  "user_active_at": 1694000000000,
  "user_active_id": "admin",
  "user_active_name": "Administrador"
}

🔍 Casos de Uso Comunes

1. Gestión de Retenciones Fiscales

@Component({
  selector: 'app-tax-retention-management'
})
export class TaxRetentionManagementComponent {
  retentions: CbmTaxRetentionModel.ListPaginatedResponse.Item[] = [];
  currentPage = 1;
  pageSize = 20;
  totalPages = 0;
  totalRetentions = 0;

  constructor(private taxRetentionRepository: CbmTaxRetentionRepository) {
    this.loadRetentions();
  }

  loadRetentions(page: number = 1) {
    const params: CbmTaxRetentionModel.ListPaginatedParams = {
      page: page,
      size: this.pageSize,
      enabled: true
    };

    this.taxRetentionRepository.listPaginated(params).subscribe({
      next: (response) => {
        if (response.success) {
          this.retentions = response.items;
          this.currentPage = response.pageNum;
          this.totalPages = response.pages;
          this.totalRetentions = response.total;
        }
      },
      error: (error) => {
        console.error('Error al cargar retenciones:', error);
      }
    });
  }

  onPageChange(page: number) {
    this.loadRetentions(page);
  }

  searchRetentions(searchTerm: string) {
    const params: CbmTaxRetentionModel.ListPaginatedParams = {
      page: 1,
      size: this.pageSize,
      code_description_filter: searchTerm
    };

    this.taxRetentionRepository.listPaginated(params).subscribe({
      next: (response) => {
        if (response.success) {
          this.retentions = response.items;
          this.totalPages = response.pages;
          this.totalRetentions = response.total;
        }
      }
    });
  }
}

2. Selector de Retenciones por Tipo

@Component({
  selector: 'app-retention-type-selector'
})
export class RetentionTypeSelectorComponent {
  retentionTypes = ['IVA', 'ISR', 'ICA', 'FUENTE'];
  selectedType = '';
  retentionsByType: { [key: string]: CbmTaxRetentionModel.ListResponse.Data[] } = {};

  constructor(private taxRetentionRepository: CbmTaxRetentionRepository) {
    this.loadRetentionsByTypes();
  }

  loadRetentionsByTypes() {
    this.retentionTypes.forEach(type => {
      const params: CbmTaxRetentionModel.ListParams = {
        type: type,
        enabled: true
      };

      this.taxRetentionRepository.list(params).subscribe({
        next: (response) => {
          if (response.success) {
            this.retentionsByType[type] = response.data;
          }
        }
      });
    });
  }

  onTypeChange(type: string) {
    this.selectedType = type;
    console.log(`Retenciones de tipo ${type}:`, this.retentionsByType[type]);
  }

  getRetentionsForType(type: string): CbmTaxRetentionModel.ListResponse.Data[] {
    return this.retentionsByType[type] || [];
  }

  getRetentionCodesForType(type: string): string[] {
    return this.getRetentionsForType(type).map(r => r.code);
  }
}

3. Dashboard de Retenciones

@Component({
  selector: 'app-tax-retention-dashboard'
})
export class TaxRetentionDashboardComponent {
  stats = {
    totalRetentions: 0,
    activeRetentions: 0,
    inactiveRetentions: 0,
    retentionsByType: new Map<string, number>(),
    averagePercentage: 0
  };

  recentRetentions: CbmTaxRetentionModel.ListResponse.Data[] = [];

  constructor(private taxRetentionRepository: CbmTaxRetentionRepository) {
    this.loadDashboardData();
  }

  loadDashboardData() {
    // Cargar todas las retenciones para estadísticas
    this.taxRetentionRepository.list({}).subscribe({
      next: (response) => {
        if (response.success) {
          this.calculateStats(response.data);
          this.loadRecentRetentions();
        }
      }
    });
  }

  private calculateStats(retentions: CbmTaxRetentionModel.ListResponse.Data[]) {
    this.stats.totalRetentions = retentions.length;
    this.stats.activeRetentions = retentions.filter(r => r.enabled).length;
    this.stats.inactiveRetentions = retentions.filter(r => !r.enabled).length;

    // Calcular retenciones por tipo
    retentions.forEach(retention => {
      const current = this.stats.retentionsByType.get(retention.type) || 0;
      this.stats.retentionsByType.set(retention.type, current + 1);
    });

    // Calcular porcentaje promedio
    const activeRetentions = retentions.filter(r => r.enabled);
    this.stats.averagePercentage = activeRetentions.reduce((sum, r) => sum + r.percentage, 0) / activeRetentions.length;
  }

  private loadRecentRetentions() {
    // Cargar retenciones recientes (últimos 30 días)
    const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);

    this.taxRetentionRepository.list({ enabled: true }).subscribe({
      next: (response) => {
        if (response.success) {
          this.recentRetentions = response.data
            .filter(r => r.created_at && r.created_at > thirtyDaysAgo)
            .sort((a, b) => (b.created_at || 0) - (a.created_at || 0))
            .slice(0, 10);
        }
      }
    });
  }

  getTypeDistribution(): { type: string; count: number; percentage: number }[] {
    return Array.from(this.stats.retentionsByType.entries()).map(([type, count]) => ({
      type,
      count,
      percentage: Math.round((count / this.stats.totalRetentions) * 100)
    }));
  }
}

4. Calculadora de Retenciones

@Injectable({ providedIn: 'root' })
export class TaxRetentionCalculatorService {
  constructor(private taxRetentionRepository: CbmTaxRetentionRepository) {}

  calculateRetention(baseAmount: number, retentionCode: string): Observable<number> {
    return this.taxRetentionRepository.list({
      code: retentionCode,
      enabled: true
    }).pipe(
      map(response => {
        if (response.success && response.data.length > 0) {
          const retention = response.data[0];
          return (baseAmount * retention.percentage) / 100;
        }
        throw new Error(`Retención ${retentionCode} no encontrada o inactiva`);
      })
    );
  }

  calculateMultipleRetentions(baseAmount: number, retentionCodes: string[]): Observable<{ code: string; amount: number }[]> {
    const calculations = retentionCodes.map(code =>
      this.calculateRetention(baseAmount, code).pipe(
        map(amount => ({ code, amount }))
      )
    );

    return forkJoin(calculations);
  }

  getRetentionSummary(baseAmount: number, type: string): Observable<{
    retentions: CbmTaxRetentionModel.ListResponse.Data[];
    totalRetention: number;
    netAmount: number;
  }> {
    return this.taxRetentionRepository.list({
      type: type,
      enabled: true
    }).pipe(
      map(response => {
        if (response.success) {
          const totalRetention = response.data.reduce((sum, retention) => {
            return sum + (baseAmount * retention.percentage) / 100;
          }, 0);

          return {
            retentions: response.data,
            totalRetention: totalRetention,
            netAmount: baseAmount - totalRetention
          };
        }
        throw new Error('Error al obtener retenciones');
      })
    );
  }
}

5. Validador de Retenciones

@Injectable({ providedIn: 'root' })
export class TaxRetentionValidatorService {
  constructor(private taxRetentionRepository: CbmTaxRetentionRepository) {}

  validateRetentionCode(code: string): Observable<boolean> {
    return this.taxRetentionRepository.list({
      code: code,
      enabled: true
    }).pipe(
      map(response => response.success && response.data.length > 0)
    );
  }

  validateRetentionData(data: CbmTaxRetentionModel.SaveBody): string[] {
    const errors: string[] = [];

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

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

    if (!data.description?.trim()) {
      errors.push('La descripción es obligatoria');
    }

    if (!data.type?.trim()) {
      errors.push('El tipo de retención es obligatorio');
    }

    return errors;
  }

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

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

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

  checkDuplicateCode(code: string, excludeId?: string): Observable<boolean> {
    return this.taxRetentionRepository.list({ code }).pipe(
      map(response => {
        if (!response.success) return false;
        if (excludeId) {
          return response.data.some(r => r._id !== excludeId);
        }
        return response.data.length > 0;
      })
    );
  }
}

🏗️ 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 ICbmTaxRetentionRepository {
  listPaginated(params: CbmTaxRetentionModel.ListPaginatedParams): Observable<CbmTaxRetentionModel.ListPaginatedResponse>;
  list(params: CbmTaxRetentionModel.ListParams): Observable<CbmTaxRetentionModel.ListResponse>;
  getOne(id: string): Observable<CbmTaxRetentionModel.GetOneResponse>;
  save(data: CbmTaxRetentionModel.SaveBody): Observable<CbmTaxRetentionModel.ConfirmResponse>;
  update(id: string, data: CbmTaxRetentionModel.UpdateBody): Observable<CbmTaxRetentionModel.ConfirmResponse>;
  changeStatus(id: string, data: CbmTaxRetentionModel.ChangeStatusBody): Observable<CbmTaxRetentionModel.ConfirmResponse>;
  delete(id: string): Observable<CbmTaxRetentionModel.ConfirmResponse>;
}

// Implementación del servicio
@Injectable({ providedIn: 'root' })
export class CbmTaxRetentionService implements ICbmTaxRetentionRepository {
  constructor(
    private readonly http: HttpClient,
    @Inject(TAX_RETENTION_MODULE_CONFIG)
    private readonly config: ICbmTaxRetentionModuleConfig
  ) { }

  listPaginated(params: CbmTaxRetentionModel.ListPaginatedParams): Observable<CbmTaxRetentionModel.ListPaginatedResponse> {
    return this.http.get<CbmTaxRetentionModel.ListPaginatedResponse>(`${this.config.baseUrl}/list/paginated`, { params: { ...params } });
  }

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

// Wrapper del repositorio
@Injectable({ providedIn: 'root' })
export class CbmTaxRetentionRepository implements ICbmTaxRetentionRepository {
  constructor(private service: CbmTaxRetentionService) { }

  listPaginated(params: CbmTaxRetentionModel.ListPaginatedParams): Observable<CbmTaxRetentionModel.ListPaginatedResponse> {
    return this.service.listPaginated(params);
  }

  // ... delegación a service
}

Configuración Centralizada

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

🔧 Mejores Prácticas

Manejo de Errores

@Component({
  selector: 'app-safe-retention-handler'
})
export class SafeRetentionHandlerComponent {
  retentions: CbmTaxRetentionModel.ListPaginatedResponse.Item[] = [];
  loading = false;
  error: string | null = null;

  constructor(private taxRetentionRepository: CbmTaxRetentionRepository) {}

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

    const params: CbmTaxRetentionModel.ListPaginatedParams = {
      page: 1,
      size: 20,
      enabled: true
    };

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

Caché de Retenciones

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

  constructor(private taxRetentionRepository: CbmTaxRetentionRepository) {}

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

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

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

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

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

  // Método específico para obtener retenciones activas
  getActiveRetentions(): Observable<CbmTaxRetentionModel.ListResponse.Data[]> {
    return this.getRetentions({ enabled: true });
  }

  // Método específico para obtener retenciones por tipo
  getRetentionsByType(type: string): Observable<CbmTaxRetentionModel.ListResponse.Data[]> {
    return this.getRetentions({ type, enabled: true });
  }
}

Servicio de Utilidades para Retenciones

@Injectable({ providedIn: 'root' })
export class TaxRetentionUtilsService {
  // Tipos de retención comunes
  private readonly RETENTION_TYPES = {
    'IVA': 'Impuesto al Valor Agregado',
    'ISR': 'Impuesto Sobre la Renta',
    'ICA': 'Impuesto de Industria y Comercio',
    'FUENTE': 'Retención en la Fuente'
  };

  getRetentionTypeDescription(type: string): string {
    return this.RETENTION_TYPES[type.toUpperCase()] || `Tipo de retención: ${type}`;
  }

  getAllRetentionTypes(): string[] {
    return Object.keys(this.RETENTION_TYPES);
  }

  formatRetentionDisplay(retention: CbmTaxRetentionModel.ListResponse.Data): string {
    return `${retention.code} - ${retention.percentage}% (${retention.type})`;
  }

  groupRetentionsByType(retentions: CbmTaxRetentionModel.ListResponse.Data[]): Map<string, CbmTaxRetentionModel.ListResponse.Data[]> {
    const groups = new Map<string, CbmTaxRetentionModel.ListResponse.Data[]>();

    retentions.forEach(retention => {
      const type = retention.type;
      if (!groups.has(type)) {
        groups.set(type, []);
      }
      groups.get(type)!.push(retention);
    });

    return groups;
  }

  calculateTotalRetention(baseAmount: number, retentions: CbmTaxRetentionModel.ListResponse.Data[]): number {
    return retentions.reduce((total, retention) => {
      return total + (baseAmount * retention.percentage) / 100;
    }, 0);
  }

  validateRetentionPercentage(percentage: number): boolean {
    return percentage >= 0 && percentage <= 100;
  }

  generateRetentionCode(type: string, percentage: number): string {
    return `${type.toUpperCase()}-${percentage}`;
  }
}

📋 Lista de Verificación para Implementación

  • [ ] ✅ Configurar el módulo CbmTaxRetentionModule.forRoot() en el AppModule
  • [ ] ✅ Inyectar CbmTaxRetentionRepository en los componentes que lo necesiten
  • [ ] ✅ Implementar manejo de errores para todas las operaciones CRUD
  • [ ] ✅ Validar datos antes de enviar al servidor (códigos, porcentajes, tipos)
  • [ ] ✅ Implementar indicadores de carga durante las operaciones
  • [ ] ✅ Manejar estados de error y respuestas no exitosas
  • [ ] ✅ Considerar implementar caché para retenciones (cambian con frecuencia moderada)
  • [ ] ✅ Probar todas las operaciones CRUD
  • [ ] ✅ Verificar funcionamiento de paginación y filtros
  • [ ] ✅ Probar búsqueda por código y descripción
  • [ ] ✅ 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 retenciones de impuestos
  • ✅ Soporte completo para operaciones CRUD
  • ✅ Gestión de códigos, porcentajes, tipos y descripciones
  • ✅ Control de estados con razones de desactivación
  • ✅ Paginación avanzada con filtros complejos
  • ✅ Búsqueda por código, descripción y tipo
  • ✅ Auditoría completa de usuarios y fechas
  • ✅ 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 cálculo
  • ✅ 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 Retenciones de Impuestos v0.0.1