| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- import * as soap from 'soap'
- import { formatSRIError, isCriticalError, isRecoverableError } from './sri-error-codes'
- /**
- * Endpoints del SRI
- * Ambiente de pruebas (celcer): Para testing
- * Ambiente de producción: Para facturas reales
- */
- const SRI_ENDPOINTS = {
- pruebas: {
- recepcion: 'https://celcer.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantesOffline?wsdl',
- autorizacion: 'https://celcer.sri.gob.ec/comprobantes-electronicos-ws/AutorizacionComprobantesOffline?wsdl',
- },
- produccion: {
- recepcion: 'https://cel.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantesOffline?wsdl',
- autorizacion: 'https://cel.sri.gob.ec/comprobantes-electronicos-ws/AutorizacionComprobantesOffline?wsdl',
- },
- }
- export interface RespuestaRecepcion {
- RespuestaRecepcionComprobante: {
- estado: string
- comprobantes?: {
- comprobante: Array<{
- claveAcceso: string
- mensajes?: {
- mensaje: Array<{
- identificador: string
- mensaje: string
- informacionAdicional?: string
- tipo: string
- }>
- }
- }>
- }
- }
- }
- export interface AutorizacionItem {
- estado: string
- numeroAutorizacion?: string
- fechaAutorizacion?: string
- ambiente?: string
- comprobante?: string
- mensajes?: {
- mensaje: {
- identificador: string
- mensaje: string
- informacionAdicional?: string
- tipo: string
- } | Array<{
- identificador: string
- mensaje: string
- informacionAdicional?: string
- tipo: string
- }>
- }
- }
- export interface RespuestaAutorizacion {
- RespuestaAutorizacionComprobante: {
- claveAccesoConsultada: string
- numeroComprobantes: string
- autorizaciones?: {
- autorizacion: AutorizacionItem | AutorizacionItem[]
- }
- }
- }
- /**
- * Envía un documento XML firmado al SRI
- * @param signedDoc XML del documento firmado
- * @param ambiente 'pruebas' o 'produccion'
- * @returns Respuesta del SRI
- */
- export async function sendDocToSRI(
- signedDoc: string,
- ambiente: 'pruebas' | 'produccion' = 'pruebas'
- ): Promise<RespuestaRecepcion> {
- console.log('Enviando documento al SRI...')
- const url = SRI_ENDPOINTS[ambiente].recepcion
- return new Promise((resolve, reject) => {
- soap.createClient(
- url,
- { wsdl_options: { timeout: 10000 } },
- function (err, client) {
- if (err) {
- console.error('Error creando cliente SOAP:', err)
- reject(new Error(`Error de conexión con el SRI: ${err.message}`))
- } else {
- // Codificar el XML a base64
- const args = { xml: Buffer.from(signedDoc).toString('base64') }
- client.validarComprobante(args, function (err: any, result: RespuestaRecepcion) {
- if (err) {
- console.error('Error validando comprobante:', err)
- reject(new Error(`Error al validar comprobante: ${err.message}`))
- } else {
- const estado = result?.RespuestaRecepcionComprobante?.estado
- if (estado !== 'RECIBIDA') {
- console.error('Documento no recibido:', JSON.stringify(result, null, 2))
- // Extraer y formatear mensajes de error
- const mensajes = result?.RespuestaRecepcionComprobante?.comprobantes?.comprobante
- if (mensajes) {
- const mensajesArray = Array.isArray(mensajes) ? mensajes : [mensajes]
- mensajesArray.forEach((comp: any) => {
- const msgs = comp.mensajes?.mensaje
- if (msgs) {
- const mensajesMsgArray = Array.isArray(msgs) ? msgs : [msgs]
- mensajesMsgArray.forEach((msg: any) => {
- const errorFormateado = formatSRIError(
- msg.identificador,
- msg.mensaje,
- msg.informacionAdicional
- )
- console.error('\n' + errorFormateado + '\n')
- if (isCriticalError(msg.identificador)) {
- console.error('⚠️ Este es un error crítico que requiere intervención administrativa')
- } else if (isRecoverableError(msg.identificador)) {
- console.error('🔧 Este error puede solucionarse corrigiendo el documento')
- }
- })
- }
- })
- }
- // En lugar de rechazar, resolvemos para que el endpoint pueda manejar el estado
- console.log(`⚠️ Documento con estado: ${estado} - El endpoint decidirá cómo manejarlo`)
- resolve(result)
- } else {
- console.log('✅ Documento recibido exitosamente')
- resolve(result)
- }
- }
- })
- }
- }
- )
- })
- }
- /**
- * Consulta el estado de autorización de un documento en el SRI
- * @param accessKey Clave de acceso del documento (49 dígitos)
- * @param ambiente 'pruebas' o 'produccion'
- * @returns Respuesta de autorización del SRI
- */
- export async function checkAuthorizationStatus(
- accessKey: string,
- ambiente: 'pruebas' | 'produccion' = 'pruebas'
- ): Promise<RespuestaAutorizacion> {
- console.log('Consultando estado de autorización...')
- const url = SRI_ENDPOINTS[ambiente].autorizacion
- return new Promise((resolve, reject) => {
- soap.createClient(
- url,
- { wsdl_options: { timeout: 10000 } },
- function (err, client) {
- if (err) {
- console.error('Error creando cliente SOAP:', err)
- reject(new Error(`Error de conexión con el SRI: ${err.message}`))
- } else {
- const args = { claveAccesoComprobante: accessKey }
- client.autorizacionComprobante(args, function (err: any, result: RespuestaAutorizacion) {
- if (err) {
- console.error('Error consultando autorización:', err)
- reject(new Error(`Error al consultar autorización: ${err.message}`))
- } else {
- console.log('Estado consultado exitosamente')
- console.log('Respuesta completa del SRI:', JSON.stringify(result, null, 2))
- resolve(result)
- }
- })
- }
- }
- )
- })
- }
- /**
- * Hace polling del estado de autorización hasta que el documento sea procesado
- * @param accessKey Clave de acceso del documento
- * @param ambiente 'pruebas' o 'produccion'
- * @param maxAttempts Número máximo de intentos
- * @param delayMs Delay entre intentos en milisegundos
- * @returns Respuesta final de autorización
- */
- export async function longPollDoc({
- accessKey,
- ambiente = 'pruebas',
- maxAttempts = 10,
- delayMs = 3000,
- }: {
- accessKey: string
- ambiente?: 'pruebas' | 'produccion'
- maxAttempts?: number
- delayMs?: number
- }): Promise<RespuestaAutorizacion> {
- console.log(`Iniciando polling para clave de acceso: ${accessKey}`)
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
- console.log(`Intento ${attempt} de ${maxAttempts}...`)
- try {
- const result = await checkAuthorizationStatus(accessKey, ambiente)
- // La autorización puede venir como objeto o array
- let autorizacion = result.RespuestaAutorizacionComprobante.autorizaciones?.autorizacion
- if (Array.isArray(autorizacion)) {
- autorizacion = autorizacion[0]
- }
- if (autorizacion) {
- const estado = autorizacion.estado
- console.log(`Estado actual del documento: ${estado}`)
- // Si hay mensajes, mostrarlos con formato mejorado
- if (autorizacion.mensajes?.mensaje) {
- const mensajes = Array.isArray(autorizacion.mensajes.mensaje)
- ? autorizacion.mensajes.mensaje
- : [autorizacion.mensajes.mensaje]
- console.log('\n📨 Mensajes del SRI:')
- mensajes.forEach((msg: any, idx: number) => {
- console.log(`\n--- Mensaje ${idx + 1} ---`)
- // Formatear error con información detallada
- const errorFormateado = formatSRIError(
- msg.identificador,
- msg.mensaje,
- msg.informacionAdicional
- )
- console.log(errorFormateado)
- // Indicar tipo de error
- if (isCriticalError(msg.identificador)) {
- console.log('\n⚠️ ERROR CRÍTICO: Requiere intervención administrativa del SRI')
- } else if (isRecoverableError(msg.identificador)) {
- console.log('\n🔧 ERROR RECUPERABLE: Puedes corregir el documento y volver a enviarlo')
- }
- })
- console.log('\n' + '─'.repeat(50) + '\n')
- }
- // Estados finales
- if (estado === 'AUTORIZADO' || estado === 'NO AUTORIZADO' || estado === 'DEVUELTA') {
- if (estado === 'AUTORIZADO') {
- console.log(`✅ Estado final alcanzado: ${estado}`)
- if (autorizacion.numeroAutorizacion) {
- console.log(`🎫 Número de autorización: ${autorizacion.numeroAutorizacion}`)
- }
- } else {
- console.log(`Estado final alcanzado: ${estado}`)
- }
- return result
- } else {
- console.log(`⏳ Documento en procesamiento (estado: ${estado})`)
- }
- } else {
- console.log('⚠️ No se encontró información de autorización en la respuesta')
- console.log('Número de comprobantes:', result.RespuestaAutorizacionComprobante.numeroComprobantes)
- }
- // Esperar antes del siguiente intento
- if (attempt < maxAttempts) {
- console.log(`Esperando ${delayMs}ms antes del siguiente intento...`)
- await new Promise((resolve) => setTimeout(resolve, delayMs))
- }
- } catch (error) {
- console.error(`Error en intento ${attempt}:`, error)
- // Si es el último intento, lanzar el error
- if (attempt === maxAttempts) {
- throw error
- }
- // Esperar antes de reintentar
- await new Promise((resolve) => setTimeout(resolve, delayMs))
- }
- }
- throw new Error(
- `No se pudo obtener el estado del documento después de ${maxAttempts} intentos`
- )
- }
|