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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@cbm-common/sri-payment-term-repository

v0.0.1

Published

Repositorio Angular para la gestión completa de plazos de pago del Servicio de Rentas Internas (SRI) de Ecuador. Implementa operaciones de consulta de formas de pago según la normativa tributaria ecuatoriana, con soporte para códigos y nombres estandariza

Readme

SRI Payment Term Repository

Repositorio Angular para la gestión completa de plazos de pago del Servicio de Rentas Internas (SRI) de Ecuador. Implementa operaciones de consulta de formas de pago según la normativa tributaria ecuatoriana, con soporte para códigos y nombres estandarizados del SRI.

📦 Instalación

npm install @cbm-common/sri-payment-term-repository

⚙️ Configuración

Configuración del Módulo

El módulo debe configurarse en el módulo raíz de la aplicación:

import { CbmSriPaymentTermModule } from '@cbm-common/sri-payment-term-repository';

@NgModule({
  imports: [
    CbmSriPaymentTermModule.forRoot({
      baseUrl: 'https://api.cbm.com/sri/payment-terms'
    })
  ]
})
export class AppModule {}

Configuración Standalone

Para aplicaciones standalone, configura el módulo en el bootstrap:

import { CbmSriPaymentTermModule } from '@cbm-common/sri-payment-term-repository';

bootstrapApplication(AppComponent, {
  providers: [
    CbmSriPaymentTermModule.forRoot({
      baseUrl: 'https://api.cbm.com/sri/payment-terms'
    })
  ]
});

🎯 Inyección de Dependencias

Inyección del Servicio

import { CbmSriPaymentTermService } from '@cbm-common/sri-payment-term-repository';

@Component({
  selector: 'app-payment-term-manager',
  standalone: true,
  imports: [CbmSriPaymentTermService]
})
export class PaymentTermManagerComponent {
  constructor(private paymentTermService: CbmSriPaymentTermService) {}
}

Inyección del Repositorio

import { CbmSriPaymentTermRepository } from '@cbm-common/sri-payment-term-repository';

@Component({
  selector: 'app-payment-term-list',
  standalone: true,
  imports: [CbmSriPaymentTermRepository]
})
export class PaymentTermListComponent {
  constructor(private paymentTermRepo: CbmSriPaymentTermRepository) {}
}

🏗️ Arquitectura del Repositorio

Patrón de Diseño

El repositorio sigue el patrón Repository Pattern con Dependency Injection:

CbmSriPaymentTermModule
├── ICbmSriPaymentTermModuleConfig (configuración)
├── CbmSriPaymentTermService (implementa ICbmSriPaymentTermRepository)
├── CbmSriPaymentTermRepository (wrapper del service)
├── CbmSriPaymentTermModel (modelos de datos)
└── HttpClient (cliente HTTP)

Interfaz del Repositorio

export interface ICbmSriPaymentTermRepository {
  list(params: CbmSriPaymentTermModel.ListParams): Observable<CbmSriPaymentTermModel.ListResponse>;
  getOne(id: string): Observable<CbmSriPaymentTermModel.GetOneResponse>;
}

📊 Operaciones Disponibles

Listado de Plazos de Pago del SRI

// Listado de plazos de pago con filtros
list(params: {
  name?: string; // Filtrar por nombre del plazo de pago
  code?: string; // Filtrar por código del SRI
}): Observable<ListResponse>

Consulta Individual

// Obtener un plazo de pago específico por ID
getOne(id: string): Observable<GetOneResponse>

📋 Modelos de Datos

ListResponse.Data

Información completa de un plazo de pago del SRI:

interface Data {
  _id: string;
  country_id: string;     // ID del país (Ecuador)
  code: string;           // Código oficial del SRI (ej: "01", "15", "30")
  name: string;           // Nombre descriptivo del plazo de pago
}

ListResponse

Respuesta del listado de plazos de pago:

interface ListResponse {
  success: boolean;       // Indica si la operación fue exitosa
  data: Data[];           // Array de plazos de pago
}

GetOneResponse.Data

Información detallada de un plazo de pago específico:

interface Data {
  _id: string;
  country_id?: string;    // ID del país
  code?: string;          // Código oficial del SRI
  name?: string;          // Nombre descriptivo
}

GetOneResponse

Respuesta de consulta individual:

interface GetOneResponse {
  success: boolean;       // Indica si la operación fue exitosa
  data: Data;             // Datos del plazo de pago
}

🚀 Ejemplos de Uso

Ejemplo Básico: Gestión de Plazos de Pago

import { CbmSriPaymentTermService } from '@cbm-common/sri-payment-term-repository';
import { CbmSriPaymentTermModel } from '@cbm-common/sri-payment-term-repository';

@Component({
  selector: 'app-payment-term-list',
  standalone: true,
  template: `
    <div class="payment-term-list">
      <h2>Plazos de Pago del SRI</h2>

      <!-- Filtros de búsqueda -->
      <div class="filters">
        <input [(ngModel)]="filters.name" placeholder="Nombre del plazo">
        <input [(ngModel)]="filters.code" placeholder="Código SRI">
        <button (click)="loadPaymentTerms()">Buscar</button>
        <button (click)="clearFilters()">Limpiar</button>
      </div>

      <!-- Tabla de plazos de pago -->
      <div class="table-container">
        <table>
          <thead>
            <tr>
              <th>Código SRI</th>
              <th>Nombre del Plazo</th>
              <th>País</th>
              <th>Acciones</th>
            </tr>
          </thead>
          <tbody>
            <tr *ngFor="let term of paymentTerms">
              <td><strong>{{ term.code }}</strong></td>
              <td>{{ term.name }}</td>
              <td>{{ getCountryName(term.country_id) }}</td>
              <td>
                <button (click)="viewPaymentTerm(term)">Ver Detalle</button>
                <button (click)="selectPaymentTerm(term)">Seleccionar</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <!-- Paginación -->
      <div class="pagination" *ngIf="paymentTerms.length > 0">
        <button [disabled]="currentPage === 1" (click)="changePage(currentPage - 1)">
          Anterior
        </button>
        <span>Página {{ currentPage }}</span>
        <button [disabled]="paymentTerms.length < pageSize" (click)="changePage(currentPage + 1)">
          Siguiente
        </button>
      </div>

      <!-- Modal de detalle -->
      <div class="modal" *ngIf="selectedTerm" (click)="closeModal()">
        <div class="modal-content" (click)="$event.stopPropagation()">
          <h3>Detalle del Plazo de Pago</h3>
          <div class="term-detail">
            <div class="detail-row">
              <label>Código SRI:</label>
              <span>{{ selectedTerm.code }}</span>
            </div>
            <div class="detail-row">
              <label>Nombre:</label>
              <span>{{ selectedTerm.name }}</span>
            </div>
            <div class="detail-row">
              <label>País:</label>
              <span>{{ getCountryName(selectedTerm.country_id) }}</span>
            </div>
            <div class="detail-row">
              <label>ID:</label>
              <span>{{ selectedTerm._id }}</span>
            </div>
          </div>
          <div class="modal-actions">
            <button (click)="closeModal()">Cerrar</button>
          </div>
        </div>
      </div>
    </div>
  `,
  styles: [`
    .payment-term-list { padding: 20px; }
    .filters { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; align-items: center; }
    .table-container { overflow-x: auto; }
    table { width: 100%; border-collapse: collapse; }
    th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
    th { background-color: #f8f9fa; font-weight: bold; }
    .pagination { display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 20px; }
    button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; }
    button:hover { opacity: 0.8; }
    button:disabled { opacity: 0.5; cursor: not-allowed; }

    /* Modal styles */
    .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
    .modal-content { background: white; padding: 20px; border-radius: 8px; max-width: 500px; width: 90%; }
    .term-detail { margin: 20px 0; }
    .detail-row { display: flex; margin-bottom: 10px; }
    .detail-row label { font-weight: bold; min-width: 120px; margin-right: 10px; }
    .detail-row span { flex: 1; }
    .modal-actions { display: flex; justify-content: flex-end; margin-top: 20px; }
  `]
})
export class PaymentTermListComponent implements OnInit {
  paymentTerms: CbmSriPaymentTermModel.ListResponse.Data[] = [];
  selectedTerm: CbmSriPaymentTermModel.ListResponse.Data | null = null;
  currentPage = 1;
  pageSize = 20;

  filters: Partial<CbmSriPaymentTermModel.ListParams> = {
    name: '',
    code: ''
  };

  constructor(private paymentTermService: CbmSriPaymentTermService) {}

  ngOnInit() {
    this.loadPaymentTerms();
  }

  loadPaymentTerms() {
    const params: CbmSriPaymentTermModel.ListParams = {
      ...this.filters
    };

    // Remover filtros vacíos
    Object.keys(params).forEach(key => {
      if (!params[key as keyof CbmSriPaymentTermModel.ListParams]) {
        delete params[key as keyof CbmSriPaymentTermModel.ListParams];
      }
    });

    this.paymentTermService.list(params).subscribe({
      next: (response) => {
        if (response.success) {
          this.paymentTerms = response.data;
        }
      },
      error: (error) => {
        console.error('Error al cargar plazos de pago:', error);
      }
    });
  }

  clearFilters() {
    this.filters = {
      name: '',
      code: ''
    };
    this.currentPage = 1;
    this.loadPaymentTerms();
  }

  changePage(page: number) {
    this.currentPage = page;
    this.loadPaymentTerms();
  }

  viewPaymentTerm(term: CbmSriPaymentTermModel.ListResponse.Data) {
    this.selectedTerm = term;
  }

  closeModal() {
    this.selectedTerm = null;
  }

  selectPaymentTerm(term: CbmSriPaymentTermModel.ListResponse.Data) {
    // Implementar selección del plazo de pago
    console.log('Plazo de pago seleccionado:', term);
  }

  getCountryName(countryId: string): string {
    // Implementar mapeo de IDs de países a nombres
    const countries: { [key: string]: string } = {
      'ECU': 'Ecuador',
      'COL': 'Colombia',
      'PER': 'Perú'
    };
    return countries[countryId] || countryId;
  }
}

Ejemplo con Selector de Plazos de Pago

import { CbmSriPaymentTermService } from '@cbm-common/sri-payment-term-repository';
import { FormControl, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-payment-term-selector',
  standalone: true,
  imports: [ReactiveFormsModule, CbmSriPaymentTermService],
  template: `
    <div class="payment-term-selector">
      <h3>Seleccionar Plazo de Pago</h3>

      <div class="selector-container">
        <div class="search-section">
          <input
            [formControl]="searchControl"
            placeholder="Buscar por código o nombre..."
            (input)="onSearchInput($event)">
        </div>

        <div class="terms-list" *ngIf="filteredTerms.length > 0">
          <div
            *ngFor="let term of filteredTerms"
            class="term-option"
            [class.selected]="selectedTerm?._id === term._id"
            (click)="selectTerm(term)">
            <div class="term-info">
              <div class="term-code">{{ term.code }}</div>
              <div class="term-name">{{ term.name }}</div>
            </div>
            <div class="term-check" *ngIf="selectedTerm?._id === term._id">
              ✓
            </div>
          </div>
        </div>

        <div class="no-results" *ngIf="searchControl.value && filteredTerms.length === 0">
          <p>No se encontraron plazos de pago que coincidan con "{{ searchControl.value }}"</p>
        </div>

        <div class="loading" *ngIf="loading">
          <p>Cargando plazos de pago...</p>
        </div>
      </div>

      <div class="selected-term" *ngIf="selectedTerm">
        <h4>Plazo Seleccionado:</h4>
        <div class="selected-info">
          <strong>{{ selectedTerm.code }}</strong> - {{ selectedTerm.name }}
        </div>
      </div>
    </div>
  `,
  styles: [`
    .payment-term-selector { max-width: 400px; }
    .selector-container { border: 1px solid #ddd; border-radius: 8px; overflow: hidden; }
    .search-section { padding: 15px; background: #f8f9fa; }
    .search-section input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }

    .terms-list { max-height: 300px; overflow-y: auto; }
    .term-option { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; cursor: pointer; border-bottom: 1px solid #eee; }
    .term-option:hover { background: #f8f9fa; }
    .term-option.selected { background: #e3f2fd; }
    .term-info { flex: 1; }
    .term-code { font-weight: bold; color: #1976d2; }
    .term-name { font-size: 14px; color: #666; margin-top: 2px; }
    .term-check { color: #1976d2; font-weight: bold; }

    .no-results { padding: 20px; text-align: center; color: #666; }
    .loading { padding: 20px; text-align: center; color: #666; }
    .selected-term { margin-top: 15px; padding: 10px; background: #e8f5e8; border-radius: 4px; }
    .selected-info { margin-top: 5px; }
  `]
})
export class PaymentTermSelectorComponent implements OnInit {
  searchControl = new FormControl('');
  allTerms: CbmSriPaymentTermModel.ListResponse.Data[] = [];
  filteredTerms: CbmSriPaymentTermModel.ListResponse.Data[] = [];
  selectedTerm: CbmSriPaymentTermModel.ListResponse.Data | null = null;
  loading = false;

  constructor(private paymentTermService: CbmSriPaymentTermService) {}

  ngOnInit() {
    this.loadAllTerms();
  }

  loadAllTerms() {
    this.loading = true;
    this.paymentTermService.list({}).subscribe({
      next: (response) => {
        if (response.success) {
          this.allTerms = response.data;
          this.filteredTerms = [...this.allTerms];
        }
        this.loading = false;
      },
      error: (error) => {
        console.error('Error al cargar plazos de pago:', error);
        this.loading = false;
      }
    });
  }

  onSearchInput(event: any) {
    const searchTerm = event.target.value.toLowerCase();
    this.filterTerms(searchTerm);
  }

  filterTerms(searchTerm: string) {
    if (!searchTerm) {
      this.filteredTerms = [...this.allTerms];
    } else {
      this.filteredTerms = this.allTerms.filter(term =>
        term.code.toLowerCase().includes(searchTerm) ||
        term.name.toLowerCase().includes(searchTerm)
      );
    }
  }

  selectTerm(term: CbmSriPaymentTermModel.ListResponse.Data) {
    this.selectedTerm = term;
    // Emitir evento de selección
    console.log('Plazo de pago seleccionado:', term);
  }
}

Ejemplo con Integración en Facturación

import { CbmSriPaymentTermService } from '@cbm-common/sri-payment-term-repository';

@Component({
  selector: 'app-invoice-payment-term',
  standalone: true,
  template: `
    <div class="invoice-payment-term">
      <h3>Información de Pago - Factura Electrónica</h3>

      <div class="payment-section">
        <div class="form-group">
          <label for="paymentTerm">Forma de Pago (SRI)</label>
          <select id="paymentTerm" [(ngModel)]="selectedPaymentTermId" (change)="onPaymentTermChange()">
            <option value="">Seleccione forma de pago</option>
            <option *ngFor="let term of paymentTerms" [value]="term._id">
              {{ term.code }} - {{ term.name }}
            </option>
          </select>
        </div>

        <div class="term-details" *ngIf="selectedPaymentTerm">
          <div class="detail-card">
            <h4>Detalles del Plazo de Pago</h4>
            <div class="detail-grid">
              <div class="detail-item">
                <label>Código SRI:</label>
                <span>{{ selectedPaymentTerm.code }}</span>
              </div>
              <div class="detail-item">
                <label>Descripción:</label>
                <span>{{ selectedPaymentTerm.name }}</span>
              </div>
              <div class="detail-item">
                <label>País:</label>
                <span>{{ getCountryName(selectedPaymentTerm.country_id) }}</span>
              </div>
            </div>
          </div>

          <div class="sri-compliance" *ngIf="isSriCompliant()">
            <div class="compliance-indicator compliant">
              ✓ Compatible con normativa SRI
            </div>
          </div>
        </div>

        <div class="payment-info" *ngIf="selectedPaymentTerm">
          <div class="info-item">
            <label>Condición de pago:</label>
            <span>{{ getPaymentCondition(selectedPaymentTerm.code) }}</span>
          </div>
          <div class="info-item">
            <label>Plazo máximo:</label>
            <span>{{ getMaxTermDays(selectedPaymentTerm.code) }} días</span>
          </div>
        </div>
      </div>
    </div>
  `,
  styles: [`
    .invoice-payment-term { padding: 20px; border: 1px solid #ddd; border-radius: 8px; }
    .payment-section { max-width: 600px; }
    .form-group { margin-bottom: 20px; }
    .form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
    .form-group select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }

    .term-details { margin-top: 20px; }
    .detail-card { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 15px; }
    .detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; margin-top: 10px; }
    .detail-item { display: flex; flex-direction: column; }
    .detail-item label { font-size: 12px; color: #666; margin-bottom: 2px; }
    .detail-item span { font-weight: bold; }

    .sri-compliance { margin-top: 15px; }
    .compliance-indicator { padding: 8px 12px; border-radius: 4px; font-weight: bold; text-align: center; }
    .compliance-indicator.compliant { background: #d4edda; color: #155724; }

    .payment-info { background: #fff3cd; padding: 15px; border-radius: 8px; }
    .info-item { display: flex; justify-content: space-between; margin-bottom: 8px; }
    .info-item:last-child { margin-bottom: 0; }
    .info-item label { font-weight: bold; }
  `]
})
export class InvoicePaymentTermComponent implements OnInit {
  paymentTerms: CbmSriPaymentTermModel.ListResponse.Data[] = [];
  selectedPaymentTermId = '';
  selectedPaymentTerm: CbmSriPaymentTermModel.ListResponse.Data | null = null;

  constructor(private paymentTermService: CbmSriPaymentTermService) {}

  ngOnInit() {
    this.loadPaymentTerms();
  }

  loadPaymentTerms() {
    this.paymentTermService.list({}).subscribe({
      next: (response) => {
        if (response.success) {
          this.paymentTerms = response.data;
        }
      },
      error: (error) => {
        console.error('Error al cargar plazos de pago:', error);
      }
    });
  }

  onPaymentTermChange() {
    this.selectedPaymentTerm = this.paymentTerms.find(term => term._id === this.selectedPaymentTermId) || null;
  }

  isSriCompliant(): boolean {
    if (!this.selectedPaymentTerm) return false;
    // Verificar que el código esté en la lista oficial del SRI
    const sriCodes = ['01', '15', '16', '17', '18', '19', '20', '21'];
    return sriCodes.includes(this.selectedPaymentTerm.code);
  }

  getPaymentCondition(code: string): string {
    const conditions: { [key: string]: string } = {
      '01': 'Contado',
      '15': 'Crédito 15 días',
      '16': 'Crédito 30 días',
      '17': 'Crédito 45 días',
      '18': 'Crédito 60 días',
      '19': 'Crédito 90 días',
      '20': 'Crédito 120 días',
      '21': 'Crédito más de 120 días'
    };
    return conditions[code] || 'No especificado';
  }

  getMaxTermDays(code: string): number {
    const maxDays: { [key: string]: number } = {
      '01': 0,   // Contado
      '15': 15,  // 15 días
      '16': 30,  // 30 días
      '17': 45,  // 45 días
      '18': 60,  // 60 días
      '19': 90,  // 90 días
      '20': 120, // 120 días
      '21': 365  // Más de 120 días (usando 365 como referencia)
    };
    return maxDays[code] || 0;
  }

  getCountryName(countryId: string): string {
    const countries: { [key: string]: string } = {
      'ECU': 'Ecuador'
    };
    return countries[countryId] || countryId;
  }
}

⚠️ Manejo de Errores

Errores de Consulta

// Manejo de errores en consultas de plazos de pago
loadPaymentTermsWithErrorHandling(params: CbmSriPaymentTermModel.ListParams) {
  this.paymentTermService.list(params).subscribe({
    next: (response) => {
      if (response.success) {
        this.paymentTerms = response.data;
        this.showSuccessMessage(`Se encontraron ${response.data.length} plazos de pago`);
      } else {
        this.showErrorMessage('Error en la respuesta del servidor');
      }
    },
    error: (error) => {
      console.error('Error HTTP:', error);

      if (error.status === 400) {
        this.showErrorMessage('Parámetros de búsqueda inválidos. Verifique los filtros.');
      } else if (error.status === 401) {
        this.showErrorMessage('Sesión expirada. Inicie sesión nuevamente.');
      } else if (error.status === 403) {
        this.showErrorMessage('No tiene permisos para consultar plazos de pago.');
      } else if (error.status === 500) {
        this.showErrorMessage('Error interno del servidor. Intente nuevamente.');
      } else {
        this.showErrorMessage('Error de conexión. Verifique su conexión a internet.');
      }
    }
  });
}

Validación de Códigos SRI

// Servicio de validación para códigos de plazos de pago del SRI
@Injectable({ providedIn: 'root' })
export class SriPaymentTermValidator {
  // Códigos oficiales del SRI para formas de pago
  private readonly sriCodes = [
    '01', // Contado
    '15', // Crédito 15 días
    '16', // Crédito 30 días
    '17', // Crédito 45 días
    '18', // Crédito 60 días
    '19', // Crédito 90 días
    '20', // Crédito 120 días
    '21'  // Crédito más de 120 días
  ];

  validateSriCode(code: string): boolean {
    return this.sriCodes.includes(code);
  }

  getSriCodeDescription(code: string): string {
    const descriptions: { [key: string]: string } = {
      '01': 'Contado',
      '15': 'Crédito hasta 15 días',
      '16': 'Crédito hasta 30 días',
      '17': 'Crédito hasta 45 días',
      '18': 'Crédito hasta 60 días',
      '19': 'Crédito hasta 90 días',
      '20': 'Crédito hasta 120 días',
      '21': 'Crédito más de 120 días'
    };
    return descriptions[code] || 'Código no reconocido';
  }

  getMaxDaysForCode(code: string): number {
    const maxDays: { [key: string]: number } = {
      '01': 0,
      '15': 15,
      '16': 30,
      '17': 45,
      '18': 60,
      '19': 90,
      '20': 120,
      '21': 365 // Más de 120 días
    };
    return maxDays[code] || 0;
  }

  validatePaymentTermForInvoice(paymentTermCode: string, invoiceTotal: number): ValidationResult {
    const result: ValidationResult = { isValid: true, errors: [] };

    // Validar que el código sea oficial del SRI
    if (!this.validateSriCode(paymentTermCode)) {
      result.errors.push('El código de forma de pago no es válido según el SRI');
    }

    // Para facturas grandes, validar plazos de pago razonables
    if (invoiceTotal > 10000 && paymentTermCode === '21') {
      result.errors.push('Para montos superiores a $10,000 el plazo no puede exceder 120 días');
    }

    result.isValid = result.errors.length === 0;
    return result;
  }
}

// Uso en componentes
export class InvoiceFormComponent {
  constructor(private sriValidator: SriPaymentTermValidator) {}

  validatePaymentTerm() {
    const validation = this.sriValidator.validatePaymentTermForInvoice(
      this.invoiceForm.value.paymentTermCode,
      this.invoiceForm.value.total
    );

    if (!validation.isValid) {
      this.showValidationErrors(validation.errors);
    }
  }
}

🔧 Configuración Avanzada

Configuración de Headers para Consultas Seguras

// Configuración de headers para consultas de plazos de pago con autenticación
import { HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  providers: [
    CbmSriPaymentTermModule.forRoot({
      baseUrl: 'https://api.cbm.com/sri/payment-terms'
    }),
    {
      provide: HTTP_INTERCEPTORS,
      useClass: SriPaymentTermAuthInterceptor,
      multi: true
    }
  ]
})
export class AppModule {}

Configuración de Timeouts para Consultas

// Timeouts específicos para consultas de plazos de pago
loadPaymentTermsWithTimeout(params: CbmSriPaymentTermModel.ListParams, timeoutMs = 10000) {
  return this.paymentTermService.list(params).pipe(
    timeout(timeoutMs),
    catchError(error => {
      if (error.name === 'TimeoutError') {
        return throwError(() => new Error('La consulta de plazos de pago tomó demasiado tiempo'));
      }
      return throwError(() => error);
    })
  );
}

Sistema de Reintentos para Consultas

// Reintentos automáticos para consultas fallidas
loadPaymentTermsWithRetry(params: CbmSriPaymentTermModel.ListParams, maxRetries = 3) {
  return this.paymentTermService.list(params).pipe(
    retry({
      count: maxRetries,
      delay: (error, retryCount) => {
        console.log(`Reintento ${retryCount} para consulta de plazos de pago`);
        return timer(retryCount * 1000);
      }
    }),
    catchError(error => {
      console.error(`Falló después de ${maxRetries} intentos:`, error);
      return throwError(() => error);
    })
  );
}

📋 Dependencias

Peer Dependencies (Requeridas)

{
  "@angular/common": ">=20.1.5",
  "@angular/core": ">=20.1.5"
}

Dependencias Internas

{
  "tslib": "^2.3.0"
}

🛠️ Desarrollo

Estructura del Proyecto

sri-payment-term-repository/
├── src/
│   ├── lib/
│   │   ├── sri-payment-term.model.ts    # Modelos de datos para plazos de pago
│   │   ├── sri-payment-term.module.ts   # Configuración del módulo
│   │   ├── sri-payment-term.service.ts  # Servicio HTTP para plazos de pago
│   │   ├── sri-payment-term.repository.ts # Interfaz del repositorio
│   │   └── index.ts                     # Exportaciones públicas
│   └── public-api.ts                    # API pública
├── ng-package.json                      # Configuración empaquetado
├── package.json                         # Dependencias
└── README.md                            # Esta documentación

Construcción

# Construir la librería
ng build sri-payment-term-repository

# Construir en modo watch
ng build sri-payment-term-repository --watch

# Construir para producción
ng build sri-payment-term-repository --configuration production

Pruebas

# Ejecutar pruebas unitarias
ng test sri-payment-term-repository

# Ejecutar pruebas con coverage
ng test sri-payment-term-repository --code-coverage

# Pruebas end-to-end
ng e2e sri-payment-term-repository

🎯 Mejores Prácticas

1. Gestión de Memoria y Recursos

// Limpiar suscripciones para evitar memory leaks
export class PaymentTermListComponent implements OnDestroy {
  private destroy$ = new Subject<void>();

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  loadPaymentTerms() {
    this.paymentTermService.list(params).pipe(
      takeUntil(this.destroy$)
    ).subscribe({
      next: (response) => {
        if (response.success) {
          this.paymentTerms = response.data;
        }
      }
    });
  }
}

2. Optimización de Rendimiento con Caché

// Implementar caché para plazos de pago (cambian raramente)
@Injectable({ providedIn: 'root' })
export class SriPaymentTermCacheService {
  private cache = new Map<string, { data: any; timestamp: number }>();
  private readonly CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 horas

  get(key: string): any | null {
    const cached = this.cache.get(key);
    if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {
      return cached.data;
    }
    this.cache.delete(key);
    return null;
  }

  set(key: string, data: any) {
    this.cache.set(key, { data, timestamp: Date.now() });
  }

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

// Uso en componentes
export class PaymentTermSelectorComponent {
  constructor(
    private paymentTermService: CbmSriPaymentTermService,
    private cacheService: SriPaymentTermCacheService
  ) {}

  loadPaymentTerms() {
    const cacheKey = 'all_payment_terms';
    const cachedData = this.cacheService.get(cacheKey);

    if (cachedData) {
      this.paymentTerms = cachedData;
      return;
    }

    this.paymentTermService.list({}).subscribe({
      next: (response) => {
        if (response.success) {
          this.paymentTerms = response.data;
          this.cacheService.set(cacheKey, response.data);
        }
      }
    });
  }
}

3. Validación Completa de Plazos de Pago

// Servicio completo de validación de plazos de pago del SRI
@Injectable({ providedIn: 'root' })
export class SriPaymentTermValidationService {
  // Códigos oficiales del SRI Ecuador
  private readonly officialSriCodes = new Set([
    '01', // Sin utilización del sistema financiero
    '15', // Compensación de deudas
    '16', // Tarjeta de débito
    '17', // Dinero electrónico
    '18', // Tarjeta de crédito
    '19', // Otros con utilización del sistema financiero
    '20', // Endoso de títulos
    '21'  // Otros
  ]);

  validateSriPaymentTerm(term: CbmSriPaymentTermModel.ListResponse.Data): ValidationResult {
    const result: ValidationResult = { isValid: true, errors: [] };

    // Validar código oficial del SRI
    if (!this.officialSriCodes.has(term.code)) {
      result.errors.push(`Código "${term.code}" no es oficial del SRI Ecuador`);
    }

    // Validar formato del código (2 dígitos)
    if (!/^\d{2}$/.test(term.code)) {
      result.errors.push('El código debe tener exactamente 2 dígitos');
    }

    // Validar nombre descriptivo
    if (!term.name || term.name.trim().length < 3) {
      result.errors.push('El nombre debe tener al menos 3 caracteres');
    }

    // Validar país (debe ser Ecuador para SRI)
    if (term.country_id !== 'ECU') {
      result.errors.push('Los plazos de pago del SRI deben estar asociados a Ecuador');
    }

    result.isValid = result.errors.length === 0;
    return result;
  }

  validatePaymentTermForDocument(
    paymentTermCode: string,
    documentType: string,
    documentTotal: number
  ): ValidationResult {
    const result: ValidationResult = { isValid: true, errors: [] };

    // Validar código oficial
    if (!this.officialSriCodes.has(paymentTermCode)) {
      result.errors.push('Código de forma de pago no válido para SRI');
    }

    // Validaciones específicas por tipo de documento
    if (documentType === 'invoice') {
      // Para facturas, validar límites de crédito
      if (documentTotal > 50000 && ['20', '21'].includes(paymentTermCode)) {
        result.errors.push('Para montos superiores a $50,000 el plazo máximo es de 120 días');
      }
    }

    if (documentType === 'credit_note') {
      // Notas de crédito deben ser al contado
      if (paymentTermCode !== '01') {
        result.errors.push('Las notas de crédito deben tener forma de pago al contado');
      }
    }

    result.isValid = result.errors.length === 0;
    return result;
  }

  getPaymentTermDescription(code: string): string {
    const descriptions: { [key: string]: string } = {
      '01': 'Sin utilización del sistema financiero',
      '15': 'Compensación de deudas',
      '16': 'Tarjeta de débito',
      '17': 'Dinero electrónico',
      '18': 'Tarjeta de crédito',
      '19': 'Otros con utilización del sistema financiero',
      '20': 'Endoso de títulos',
      '21': 'Otros'
    };
    return descriptions[code] || 'Descripción no disponible';
  }

  getMaxCreditDays(code: string): number {
    // Días máximos de crédito según código SRI
    const creditDays: { [key: string]: number } = {
      '01': 0,   // Contado
      '15': 0,   // Compensación (contado)
      '16': 0,   // Tarjeta débito (contado)
      '17': 0,   // Dinero electrónico (contado)
      '18': 30,  // Tarjeta crédito (hasta 30 días)
      '19': 30,  // Otros financieros (hasta 30 días)
      '20': 120, // Endoso títulos (hasta 120 días)
      '21': 365  // Otros (sin límite específico)
    };
    return creditDays[code] || 0;
  }
}

// Uso en formularios de facturación
export class InvoiceFormComponent {
  constructor(private sriValidation: SriPaymentTermValidationService) {}

  validateSriCompliance() {
    const validation = this.sriValidation.validatePaymentTermForDocument(
      this.invoiceForm.value.paymentTermCode,
      'invoice',
      this.invoiceForm.value.total
    );

    if (!validation.isValid) {
      this.showSriValidationErrors(validation.errors);
    }
  }
}

4. Logging y Auditoría para Operaciones de Plazos de Pago

// Servicio de auditoría para operaciones de plazos de pago del SRI
@Injectable({ providedIn: 'root' })
export class SriPaymentTermAuditService {
  logPaymentTermSelection(
    paymentTermCode: string,
    documentId: string,
    documentType: string,
    userId: string
  ) {
    const auditEntry = {
      timestamp: new Date().toISOString(),
      operation: 'SRI_PAYMENT_TERM_SELECTED',
      paymentTermCode,
      documentId,
      documentType,
      userId,
      sriCompliant: this.isSriCompliantCode(paymentTermCode),
      severity: 'INFO'
    };

    console.log(`[SRI_PAYMENT_TERM_AUDIT] ${JSON.stringify(auditEntry)}`);

    // Enviar a servicio de auditoría tributaria
    this.taxAuditService.record('sri_payment_term_selection', auditEntry);
  }

  logPaymentTermValidation(
    paymentTermCode: string,
    documentType: string,
    validationResult: ValidationResult,
    userId: string
  ) {
    const auditEntry = {
      timestamp: new Date().toISOString(),
      operation: 'SRI_PAYMENT_TERM_VALIDATION',
      paymentTermCode,
      documentType,
      validationPassed: validationResult.isValid,
      validationErrors: validationResult.errors,
      userId,
      severity: validationResult.isValid ? 'INFO' : 'WARNING'
    };

    console.log(`[SRI_VALIDATION_AUDIT] ${JSON.stringify(auditEntry)}`);

    if (!validationResult.isValid) {
      // Enviar a servicio de alertas de cumplimiento tributario
      this.complianceAlertService.notifyValidationFailure(auditEntry);
    }
  }

  private isSriCompliantCode(code: string): boolean {
    const sriCodes = ['01', '15', '16', '17', '18', '19', '20', '21'];
    return sriCodes.includes(code);
  }

  logBulkPaymentTermOperation(
    paymentTerms: string[],
    documentIds: string[],
    operation: string,
    userId: string
  ) {
    const auditEntry = {
      timestamp: new Date().toISOString(),
      operation: `BULK_${operation}`,
      paymentTermCount: paymentTerms.length,
      documentCount: documentIds.length,
      userId,
      severity: 'INFO'
    };

    console.log(`[BULK_SRI_OPERATION] ${JSON.stringify(auditEntry)}`);

    // Enviar a servicio de auditoría masiva
    this.bulkAuditService.record('sri_bulk_operation', auditEntry);
  }
}

🤝 Contribución

  1. Fork el repositorio
  2. Crea una rama para tu feature (git checkout -b feature/nueva-funcionalidad)
  3. Commit tus cambios (git commit -am 'Agrega nueva funcionalidad')
  4. Push a la rama (git push origin feature/nueva-funcionalidad)
  5. Abre un PullRequest

📄 Licencia

Este proyecto está bajo la Licencia MIT - ver el archivo LICENSE para más detalles.

📞 Soporte

Para soporte técnico o reportes de bugs, contacta al equipo de desarrollo de CBM:

🔄 Changelog

v0.0.1

  • ✅ Módulo de configuración con forRoot pattern
  • ✅ Servicio que implementa interfaz del repositorio
  • ✅ Operaciones de listado y consulta individual de plazos de pago
  • ✅ Modelos TypeScript bien tipados con datos del SRI
  • ✅ Integración completa con HttpClient
  • ✅ Documentación completa en español

Nota: Esta librería está optimizada para sistemas de facturación electrónica que requieren cumplimiento con la normativa del Servicio de Rentas Internas (SRI) de Ecuador. Incluye validaciones específicas para códigos oficiales de formas de pago, compatibilidad con facturación electrónica y reglas de negocio tributarias ecuatorianas.