|
@@ -0,0 +1,129 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * Genera la clave de acceso de 49 dígitos según el algoritmo del SRI
|
|
|
|
|
+ * Basado en las especificaciones del Servicio de Rentas Internas de Ecuador
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+interface GenerateAccessKeyParams {
|
|
|
|
|
+ fechaEmision: string // Formato: YYYY-MM-DD o DD/MM/YYYY
|
|
|
|
|
+ ruc: string // 13 dígitos
|
|
|
|
|
+ ambiente: string // "1" para pruebas, "2" para producción
|
|
|
|
|
+ establecimiento: string // 3 dígitos
|
|
|
|
|
+ puntoEmision: string // 3 dígitos
|
|
|
|
|
+ secuencial: string // 9 dígitos
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Formatea una fecha al formato DDMMYYYY
|
|
|
|
|
+ */
|
|
|
|
|
+function formatDateToDDMMYYYY(dateString: string): string {
|
|
|
|
|
+ let date: Date
|
|
|
|
|
+
|
|
|
|
|
+ // Detectar formato de fecha
|
|
|
|
|
+ if (dateString.includes('-')) {
|
|
|
|
|
+ // Formato YYYY-MM-DD
|
|
|
|
|
+ date = new Date(dateString)
|
|
|
|
|
+ } else if (dateString.includes('/')) {
|
|
|
|
|
+ // Formato DD/MM/YYYY
|
|
|
|
|
+ const [day, month, year] = dateString.split('/')
|
|
|
|
|
+ date = new Date(`${year}-${month}-${day}`)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new Error('Formato de fecha no válido. Use YYYY-MM-DD o DD/MM/YYYY')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const day = date.getDate()
|
|
|
|
|
+ const month = date.getMonth() + 1 // getMonth() returns 0-11
|
|
|
|
|
+ const year = date.getFullYear()
|
|
|
|
|
+
|
|
|
|
|
+ // Pad day and month with a leading zero if they are less than 10
|
|
|
|
|
+ const finalDay = day < 10 ? '0' + day : day.toString()
|
|
|
|
|
+ const finalMonth = month < 10 ? '0' + month : month.toString()
|
|
|
|
|
+
|
|
|
|
|
+ return `${finalDay}${finalMonth}${year}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Genera un número aleatorio de 8 dígitos
|
|
|
|
|
+ */
|
|
|
|
|
+function generateRandomEightDigitNumber(): string {
|
|
|
|
|
+ const min = 10000000
|
|
|
|
|
+ const max = 99999999
|
|
|
|
|
+ return (Math.floor(Math.random() * (max - min + 1)) + min).toString()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Calcula el dígito verificador usando el algoritmo de Módulo 11
|
|
|
|
|
+ */
|
|
|
|
|
+function generateVerificatorDigit(accessKey: string): number {
|
|
|
|
|
+ let addition = 0
|
|
|
|
|
+ let multiple = 7
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < accessKey.length; i++) {
|
|
|
|
|
+ addition += parseInt(accessKey.charAt(i)) * multiple
|
|
|
|
|
+ multiple > 2 ? multiple-- : (multiple = 7)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let result = 11 - (addition % 11)
|
|
|
|
|
+
|
|
|
|
|
+ // Reglas especiales del SRI
|
|
|
|
|
+ if (result === 10) result = 1
|
|
|
|
|
+ if (result === 11) result = 0
|
|
|
|
|
+
|
|
|
|
|
+ return result
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Genera la clave de acceso completa de 49 dígitos
|
|
|
|
|
+ */
|
|
|
|
|
+export function generateClaveAcceso(params: GenerateAccessKeyParams): string {
|
|
|
|
|
+ let claveAcceso = ''
|
|
|
|
|
+
|
|
|
|
|
+ // 1. Fecha de emisión (8 dígitos: DDMMYYYY)
|
|
|
|
|
+ claveAcceso += formatDateToDDMMYYYY(params.fechaEmision)
|
|
|
|
|
+
|
|
|
|
|
+ // 2. Tipo de comprobante (2 dígitos - siempre "01" para facturas)
|
|
|
|
|
+ claveAcceso += '01'
|
|
|
|
|
+
|
|
|
|
|
+ // 3. Número de RUC (13 dígitos)
|
|
|
|
|
+ claveAcceso += params.ruc
|
|
|
|
|
+
|
|
|
|
|
+ // 4. Tipo de ambiente (1 dígito)
|
|
|
|
|
+ claveAcceso += params.ambiente
|
|
|
|
|
+
|
|
|
|
|
+ // 5. Establecimiento (3 dígitos)
|
|
|
|
|
+ claveAcceso += params.establecimiento
|
|
|
|
|
+
|
|
|
|
|
+ // 6. Punto de emisión (3 dígitos)
|
|
|
|
|
+ claveAcceso += params.puntoEmision
|
|
|
|
|
+
|
|
|
|
|
+ // 7. Secuencial (9 dígitos)
|
|
|
|
|
+ claveAcceso += params.secuencial.padStart(9, '0')
|
|
|
|
|
+
|
|
|
|
|
+ // 8. Código numérico aleatorio (8 dígitos)
|
|
|
|
|
+ claveAcceso += generateRandomEightDigitNumber()
|
|
|
|
|
+
|
|
|
|
|
+ // 9. Tipo de emisión (1 dígito - siempre "1" para emisión normal)
|
|
|
|
|
+ claveAcceso += '1'
|
|
|
|
|
+
|
|
|
|
|
+ // 10. Dígito verificador (1 dígito)
|
|
|
|
|
+ claveAcceso += generateVerificatorDigit(claveAcceso)
|
|
|
|
|
+
|
|
|
|
|
+ return claveAcceso
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Valida que una clave de acceso tenga el formato correcto
|
|
|
|
|
+ */
|
|
|
|
|
+export function validateClaveAccesoFormat(claveAcceso: string): boolean {
|
|
|
|
|
+ // Debe tener exactamente 49 dígitos
|
|
|
|
|
+ if (claveAcceso.length !== 49) return false
|
|
|
|
|
+
|
|
|
|
|
+ // Debe contener solo dígitos
|
|
|
|
|
+ if (!/^\d+$/.test(claveAcceso)) return false
|
|
|
|
|
+
|
|
|
|
|
+ // Validar dígito verificador
|
|
|
|
|
+ const clave48 = claveAcceso.substring(0, 48)
|
|
|
|
|
+ const digitoVerificador = parseInt(claveAcceso.charAt(48))
|
|
|
|
|
+ const digitoCalculado = generateVerificatorDigit(clave48)
|
|
|
|
|
+
|
|
|
|
|
+ return digitoVerificador === digitoCalculado
|
|
|
|
|
+}
|