├── doc ├── 69148738398949.png ├── wspci.md ├── wsaa.md └── wsfev1.md ├── .eslintrc.json ├── index.js ├── package.json ├── lib ├── utils.js ├── tickets │ ├── ta.js │ └── tra.js ├── wsaa.js ├── wspci.js ├── wsfex.js └── wsfe.js ├── .gitignore └── README.md /doc/69148738398949.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egnuez/afipjs/HEAD/doc/69148738398949.png -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "extends": "standard", 8 | "rules":{ 9 | "comma-dangle": 0 10 | } 11 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * The entry point. 5 | * 6 | * @module Afipjs 7 | */ 8 | 9 | module.exports= { 10 | Wsaa : require('./lib/wsaa').Wsaa, 11 | Wsfe : require('./lib/wsfe').Wsfe, 12 | Wsfex : require('./lib/wsfex').Wsfex, 13 | Wspci: require('./lib/wspci').Wspci, 14 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afipjs", 3 | "version": "1.1.5", 4 | "description": "Libreria para uso de los Webservices de AFIP con Node.js", 5 | "main": "afip.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/emilianito/afipjs.git" 12 | }, 13 | "keywords": [ 14 | "afip", 15 | "node", 16 | "javascript", 17 | "webservices" 18 | ], 19 | "author": " (example.com)", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/emilianito/afipjs/issues" 23 | }, 24 | "homepage": "https://github.com/emilianito/afipjs#readme", 25 | "dependencies": { 26 | "@fidm/x509": "^1.2.1", 27 | "bwip-js": "^1.7.4", 28 | "node-forge": "^1.3.1", 29 | "soap": "^0.36.0", 30 | "xml2js": "^0.4.23" 31 | }, 32 | "devDependencies": { 33 | "eslint": "^7.32.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | String.prototype.allReplace = function(replaces) { 2 | var retStr = this; 3 | return Object.keys(replaces).reduce( 4 | (acc, search) => acc.replace(new RegExp(search, 'g'), replaces[search]), retStr ); 5 | }; 6 | 7 | Date.prototype.toIsoString = function() { 8 | var tzo = -this.getTimezoneOffset(), 9 | dif = tzo >= 0 ? '+' : '-', 10 | pad = function(num) { 11 | var norm = Math.floor(Math.abs(num)); 12 | return (norm < 10 ? '0' : '') + norm; 13 | }; 14 | return this.getFullYear() + 15 | '-' + pad(this.getMonth() + 1) + 16 | '-' + pad(this.getDate()) + 17 | 'T' + pad(this.getHours()) + 18 | ':' + pad(this.getMinutes()) + 19 | ':' + pad(this.getSeconds()) + 20 | dif + pad(tzo / 60) + 21 | ':' + pad(tzo % 60); 22 | }; 23 | 24 | Date.prototype.subTime= function(h,m){ 25 | this.setHours(this.getHours()-h); 26 | this.setMinutes(this.getMinutes()-m); 27 | return this; 28 | }; 29 | 30 | Date.prototype.addTime= function(h,m){ 31 | this.setHours(this.getHours()+h); 32 | this.setMinutes(this.getMinutes()+m); 33 | return this; 34 | }; 35 | -------------------------------------------------------------------------------- /lib/tickets/ta.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const xml2js = require('xml2js'); 4 | 5 | class TA { 6 | 7 | constructor(loginCmsReturn){ 8 | this.TA = loginCmsReturn; 9 | this.TA_parsed = this._parse(); 10 | } 11 | 12 | _parse(){ 13 | let TA_parsed = ""; 14 | xml2js.Parser().parseString(this.TA, function (err, result) { 15 | if (err) { 16 | TA_parsed = {} 17 | return 18 | } 19 | if (!result) { 20 | TA_parsed = {} 21 | return 22 | } 23 | const header = result.loginTicketResponse.header[0]; 24 | const credentials = result.loginTicketResponse.credentials[0]; 25 | const cuit = header.destination[0].match(/CUIT (\d{11})/i)[1]; 26 | TA_parsed = { 27 | generationTime:header.generationTime[0], 28 | expirationTime:header.expirationTime[0], 29 | token:credentials.token[0], 30 | sign:credentials.sign[0], 31 | cuit:cuit 32 | }; 33 | }); 34 | return TA_parsed; 35 | } 36 | 37 | isValid(){ 38 | if(this.TA_parsed.expirationTime){ 39 | let expiration = new Date(this.TA_parsed.expirationTime); 40 | expiration = Math.floor(expiration.getTime() / 1000); 41 | let now = Math.floor((new Date()).getTime() / 1000); 42 | return (expiration > now); 43 | } 44 | return false; 45 | } 46 | 47 | }; 48 | 49 | module.exports= { 50 | TA 51 | }; -------------------------------------------------------------------------------- /lib/wsaa.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { TRA } = require('./tickets/tra'); 4 | const { TA } = require('./tickets/ta'); 5 | const { Certificate } = require('@fidm/x509'); 6 | const url_wdsl_devel = 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms?WSDL' 7 | const url_wdsl_prod = 'https://wsaa.afip.gov.ar/ws/services/LoginCms?WSDL' 8 | 9 | const _default = { 10 | config : { 11 | prod: false, 12 | service: "wsfe", 13 | debug: false, 14 | } 15 | }; 16 | 17 | class Wsaa { 18 | constructor(config){ 19 | this.config = { 20 | ..._default.config, 21 | ...config, 22 | }; 23 | this.config.url_wdsl = 24 | (this.config.prod)?url_wdsl_prod:url_wdsl_devel; 25 | } 26 | 27 | setCertificate(txtCertificate){ 28 | this.txtCertificate = txtCertificate 29 | let cert = Certificate.fromPEM(txtCertificate) 30 | this.certificate = { 31 | validFrom: cert.validFrom, 32 | validTo: cert.validTo, 33 | version: cert.version, 34 | serialNumber: cert.serialNumber, 35 | issuer: { 36 | CN: cert.issuer.commonName, 37 | C: cert.issuer.countryName, 38 | O: cert.issuer.organizationName, 39 | }, 40 | subject: { 41 | CN: cert.subject.commonName, 42 | SN: cert.subject.serialName, 43 | } 44 | } 45 | } 46 | 47 | setKey(txtKey){ 48 | this.txtKey = txtKey 49 | } 50 | 51 | createTRA(){ 52 | return new TRA(this.config, this.txtCertificate, this.txtKey) 53 | } 54 | 55 | createTAFromString(strTA){ 56 | return new TA(strTA) 57 | } 58 | 59 | } 60 | 61 | module.exports= { 62 | Wsaa 63 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | # Edit at https://www.gitignore.io/?templates=node 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional REPL history 58 | .node_repl_history 59 | 60 | # Output of 'npm pack' 61 | *.tgz 62 | 63 | # Yarn Integrity file 64 | .yarn-integrity 65 | 66 | # dotenv environment variables file 67 | .env 68 | .env.test 69 | 70 | # parcel-bundler cache (https://parceljs.org/) 71 | .cache 72 | 73 | # next.js build output 74 | .next 75 | 76 | # nuxt.js build output 77 | .nuxt 78 | 79 | # vuepress build output 80 | .vuepress/dist 81 | 82 | # Serverless directories 83 | .serverless/ 84 | 85 | # FuseBox cache 86 | .fusebox/ 87 | 88 | # DynamoDB Local files 89 | .dynamodb/ 90 | 91 | # End of https://www.gitignore.io/api/node 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AFIPJS 2 | 3 | Libreria para uso de los Webservices de AFIP con Node.js 4 | 5 | ## Instalacion 6 | 7 | ```bash 8 | npm install afipjs 9 | ``` 10 | ## Ejemplo rapido de uso 11 | 12 | ```javascript 13 | const { Wsaa, Wsfe } = require('./afipjs'); 14 | var wsaa = new Wsaa(); 15 | let miTRA = wsaa.createTRA(); 16 | const miTA = await miTRA.supplyTA(); 17 | const wsfe = new Wsfe(miTA); 18 | const puntoDeVenta = 1; 19 | const ultimoAutorizado = 48 20 | const factura = { 21 | FeCAEReq:{ 22 | FeCabReq:{ 23 | CantReg:1, 24 | PtoVta:puntoDeVenta, 25 | CbteTipo:11 26 | }, 27 | FeDetReq:{ 28 | FECAEDetRequest:{ 29 | Concepto:1, 30 | DocTipo:80, 31 | DocNro:"23000000000", 32 | CbteDesde:ultimoAutorizado + 1, 33 | CbteHasta:ultimoAutorizado + 1, 34 | CbteFch:'20190407', 35 | ImpTotal:1.00, 36 | ImpTotConc:0.00, 37 | ImpNeto:1.00, 38 | ImpOpEx:0.00, 39 | ImpTrib:0.00, 40 | ImpIVA:0.00, 41 | MonId:"PES", 42 | MonCotiz:1 43 | } 44 | } 45 | } 46 | }; 47 | response = await wsfe.FECAESolicitar(factura); 48 | console.dir(response, { depth: null }); 49 | ``` 50 | 51 | ```javascript 52 | { FECAESolicitarResult: 53 | { FeCabResp: 54 | { Cuit: '20278650988', 55 | PtoVta: 1, 56 | CbteTipo: 11, 57 | FchProceso: '20190407162734', 58 | CantReg: 1, 59 | Resultado: 'A', 60 | Reproceso: 'N' }, 61 | FeDetResp: 62 | { FECAEDetResponse: 63 | [ { Concepto: 1, 64 | DocTipo: 80, 65 | DocNro: '23000000000', 66 | CbteDesde: '49', 67 | CbteHasta: '49', 68 | CbteFch: '20190407', 69 | Resultado: 'A', 70 | CAE: '69148738398949', 71 | CAEFchVto: '20190417' } ] } } } 72 | ``` 73 | 74 | ### Documentos: 75 | 76 | - Autenticacion [WSAA](doc/wsaa.md) 77 | - Factura electronica [WSFEV1](doc/wsfev1.md) -------------------------------------------------------------------------------- /lib/wspci.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const soap = require('soap'); 4 | const SERVICE_NAME = "ws_sr_constancia_inscripcion" 5 | 6 | const _default = { 7 | config: { 8 | prod: false, 9 | url_wdsl_devel: "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA5?WSDL", 10 | url_wdsl_prod: "https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA5?WSDL", 11 | service: SERVICE_NAME, 12 | debug: false, 13 | url_wdsl: this.url_wdsl_devel, 14 | } 15 | }; 16 | 17 | class Wspci { 18 | static SERVICE_NAME = SERVICE_NAME 19 | constructor (TA, config){ 20 | this.config = { 21 | ..._default.config, 22 | ...config, 23 | }; 24 | this.config.url_wdsl = 25 | (this.config.prod)? 26 | this.config.url_wdsl_prod : 27 | this.config.url_wdsl_devel; 28 | this.setTA(TA); 29 | } 30 | 31 | _callWspci (service, args){ 32 | let debug = this.config.debug; 33 | let wdsl = this.config.url_wdsl; 34 | return new Promise(function(resolve, reject){ 35 | soap.createClient(wdsl, { returnFault: true }, function (err, client) { 36 | if(err) return reject(err); 37 | client[service](args, function (err, result, rawResponse, soapHeader, rawRequest) { 38 | if(debug){ 39 | console.log('Request: ', rawRequest); 40 | console.log('Response:', rawResponse); 41 | } 42 | if(err) return reject(err); 43 | return resolve(result); 44 | }); 45 | }); 46 | }); 47 | } 48 | 49 | setTA(TA){ 50 | this.TA = TA; 51 | if (TA != undefined){ 52 | this.hAuth = { 53 | token:this.TA.TA_parsed.token, 54 | sign:this.TA.TA_parsed.sign, 55 | }; 56 | } 57 | } 58 | 59 | dummy (){ 60 | return this._callWspci("dummy", {}); 61 | }; 62 | } 63 | 64 | [ 65 | 'getPersona_v2', 66 | ].forEach((service) => { 67 | Wspci.prototype[service] = function (args) { 68 | return this._callWspci(service, { 69 | ...this.hAuth, 70 | ...args, 71 | }); 72 | } 73 | }); 74 | 75 | module.exports= { 76 | Wspci 77 | }; -------------------------------------------------------------------------------- /lib/tickets/tra.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const forge = require('node-forge'); 4 | const xml2js = require('xml2js'); 5 | const soap = require('soap'); 6 | const { TA } = require('./ta'); 7 | require('../utils'); 8 | 9 | const TRA_tpl = "" + 10 | "" + 11 | "
" + 12 | "_unique_id_" + 13 | "_generation_time_" + 14 | "_expiration_time_" + 15 | "
" + 16 | "_service_" + 17 | "
"; 18 | 19 | class TRA { 20 | 21 | constructor(config, pem, key){ 22 | 23 | this.config = config; 24 | 25 | const now = new Date(); 26 | 27 | this.TRA = TRA_tpl.allReplace({ 28 | _unique_id_: Math.floor(now.valueOf() / 1000), 29 | _generation_time_: now.subTime(0,1).toIsoString(), 30 | _expiration_time_: now.addTime(0,2).toIsoString(), 31 | _service_:this.config.service, 32 | }); 33 | 34 | this.TRA_parsed = this._parse(); 35 | this.cms = this._createCms(pem, key); 36 | 37 | } 38 | 39 | _parse(){ 40 | let TRA_parsed = ""; 41 | xml2js.Parser().parseString(this.TRA, function (err, result) { 42 | TRA_parsed = err?err:result; 43 | }); 44 | 45 | return TRA_parsed; 46 | } 47 | 48 | _createCms(pem, key){ 49 | var p7 = forge.pkcs7.createSignedData(); 50 | p7.content = forge.util.createBuffer(this.TRA, 'utf8'); 51 | p7.addCertificate(pem); 52 | p7.addSigner({ 53 | key: key, 54 | certificate: pem, 55 | }); 56 | p7.sign(); 57 | let cms = forge.pkcs7.messageToPem(p7).allReplace({ 58 | "-----BEGIN PKCS7-----":"", 59 | "-----END PKCS7-----":"" 60 | }).allReplace({"\r\n":""}); 61 | return cms 62 | } 63 | 64 | supplicateTA(){ 65 | 66 | var url = this.config.url_wdsl; 67 | var args = {in0: this.cms}; 68 | 69 | var soapOptions = { 70 | returnFault: true, 71 | }; 72 | 73 | let debug = this.config.debug; 74 | 75 | return new Promise(function (resolve, reject){ 76 | soap.createClient(url, soapOptions, function (err, client) { 77 | if( err ) return reject( err ); 78 | client.loginCms(args, function (err, result, rawResponse, soapHeader, rawRequest) { 79 | if (debug){ 80 | console.log('Header: ', soapHeader); 81 | console.log('Request: ', rawRequest); 82 | console.log('Response:', rawResponse); 83 | } 84 | if( err ) return reject( err ); 85 | return resolve( new TA(result.loginCmsReturn) ); 86 | }); 87 | }); 88 | }); 89 | 90 | } 91 | 92 | createTAFromString(s){ 93 | return new TA(s) 94 | } 95 | 96 | }; 97 | 98 | module.exports= { 99 | TRA 100 | }; -------------------------------------------------------------------------------- /doc/wspci.md: -------------------------------------------------------------------------------- 1 | ### WSPCI 2 | 3 | La clase Wspci permite consultar datos de un contribuyente dado un numero de CUIT 4 | 5 | 1. Obtener un token para el webservice correspondiente 6 | 7 | ```javascript 8 | const { Wsaa, Wspci } = require('./afipjs') 9 | var pem = fs.readFileSync('path/to/CertTest.pem', 'utf8') 10 | var key = fs.readFileSync('path/to/keyTest.pem', 'utf8') 11 | const conf = { 12 | prod: false, 13 | debug: true, 14 | service: Wspci.SERVICE_NAME, 15 | } 16 | const wsaa = new Wsaa(conf) 17 | wsaa.setCertificate(pem) 18 | wsaa.setKey(key) 19 | ``` 20 | 21 | 2. Crear un TRA 22 | 23 | ```javascript 24 | const tra = wsaa.createTRA() 25 | ``` 26 | 27 | 3. Pedir un TA 28 | 29 | ```javascript 30 | const newTa = await tra.supplicateTA() 31 | ``` 32 | 33 | 3. Crear el cliente y verificar si funciona el Web Service 34 | 35 | ```javascript 36 | const wspci = new Wspci(TA); 37 | const response = await wspci.dummy(); 38 | console.log(response) 39 | ``` 40 | 41 | 4. Consultar un CUIT 42 | 43 | ```javascript 44 | response = await wspci.getPersona_v2({ 45 | cuitRepresentada: '20278650988', 46 | idPersona: '23246180709' 47 | }); 48 | console.dir(response, { depth: null }); 49 | ``` 50 | 51 | ```javascript 52 | { 53 | personaReturn: { 54 | datosGenerales: { 55 | apellido: 'MEASURE', 56 | domicilioFiscal: { 57 | codPostal: '1644', 58 | descripcionProvincia: 'BUENOS AIRES', 59 | direccion: 'CARLOS PELLEGRINI 3966', 60 | idProvincia: 1, 61 | localidad: 'VICTORIA', 62 | tipoDomicilio: 'FISCAL' 63 | }, 64 | estadoClave: 'ACTIVO', 65 | idPersona: '23246180709', 66 | mesCierre: 12, 67 | nombre: 'ROMARICO', 68 | tipoClave: 'CUIT', 69 | tipoPersona: 'FISICA' 70 | }, 71 | datosMonotributo: { 72 | actividad: [ 73 | { 74 | descripcionActividad: 'PRODUCCIÓN DE LANA Y PELO DE OVEJA Y CABRA (CRUDA)', 75 | idActividad: '14710', 76 | nomenclador: 883, 77 | orden: 2, 78 | periodo: 201403 79 | }, 80 | { 81 | descripcionActividad: 'SERVICIOS DE CONSULTORES EN EQUIPO DE INFORMÁTICA', 82 | idActividad: '620200', 83 | nomenclador: 883, 84 | orden: 1, 85 | periodo: 201311 86 | }, 87 | { 88 | descripcionActividad: 'SERVICIOS PERSONALES N.C.P.', 89 | idActividad: '960990', 90 | nomenclador: 883, 91 | orden: 3, 92 | periodo: 201603 93 | } 94 | ], 95 | actividadMonotributista: { 96 | descripcionActividad: 'SERVICIOS DE CONSULTORES EN EQUIPO DE INFORMÁTICA', 97 | idActividad: '620200', 98 | nomenclador: 883, 99 | orden: 1, 100 | periodo: 201311 101 | }, 102 | categoriaMonotributo: { 103 | descripcionCategoria: 'B LOCACIONES DE SERVICIO', 104 | idCategoria: 36, 105 | idImpuesto: 20, 106 | periodo: 202102 107 | }, 108 | impuesto: [ 109 | { 110 | descripcionImpuesto: 'MONOTRIBUTO', 111 | idImpuesto: 20, 112 | periodo: 200903 113 | } 114 | ] 115 | }, 116 | metadata: { fechaHora: 2021-07-22T16:57:54.798Z, servidor: 'setiwsh2' } 117 | } 118 | } 119 | ``` -------------------------------------------------------------------------------- /lib/wsfex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const soap = require('soap'); 4 | const bwipjs = require('bwip-js'); 5 | const fs = require('fs'); 6 | const { TA } = require('./tickets/ta'); 7 | const FE_DUMMY = "FEXDummy"; 8 | 9 | const _default = { 10 | config : { 11 | prod:false, 12 | url_wdsl_devel:"https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?WSDL", 13 | url_wdsl_prod:"https://servicios1.afip.gov.ar/wsfexv1/service.asmx?WSDL", 14 | service:"wsfex", 15 | debug: true, 16 | url_wdsl:this.url_wdsl_devel, 17 | cuit:"", 18 | } 19 | }; 20 | 21 | class Wsfex { 22 | 23 | constructor (TA, config){ 24 | 25 | this.config = { 26 | ..._default.config, 27 | ...config, 28 | }; 29 | 30 | this.config.url_wdsl = 31 | (this.config.prod)? 32 | this.config.url_wdsl_prod : 33 | this.config.url_wdsl_devel; 34 | 35 | this.setTA(TA); 36 | 37 | } 38 | 39 | /** 40 | * Llama al webservice de AFIP especificado en el primer parametro con los datos 41 | * especifiados en el el segundo argumento. 42 | * @param {String} service 43 | * @param {Object} args 44 | * @returns {Promise} Devuelve una promesa con la respuesta 45 | */ 46 | 47 | _callWsfex (service, args){ 48 | 49 | let debug = this.config.debug; 50 | let wdsl = this.config.url_wdsl; 51 | 52 | return new Promise(function(resolve, reject){ 53 | 54 | soap.createClient(wdsl, { returnFault: true }, function (err, client) { 55 | if(err) return reject(err); 56 | client[service](args, function (err, result, rawResponse, soapHeader, rawRequest) { 57 | if(debug){ 58 | console.log('Request: ', rawRequest); 59 | console.log('Response:', rawResponse); 60 | } 61 | if(err) return reject(err); 62 | return resolve(result); 63 | }); 64 | }); 65 | }); 66 | } 67 | 68 | setTA(TA){ 69 | 70 | this.TA = TA; 71 | 72 | if (TA != undefined){ 73 | this.hAuth = { 74 | Auth:{ 75 | Token:this.TA.TA_parsed.token, 76 | Sign:this.TA.TA_parsed.sign, 77 | Cuit:this.TA.TA_parsed.cuit 78 | } 79 | }; 80 | } 81 | } 82 | 83 | FEDummy (args){ 84 | return this._callWsfex(FE_DUMMY, args); 85 | }; 86 | 87 | } 88 | 89 | [ 90 | 'FEXGetPARAM_Idiomas', 91 | 'FEXGetPARAM_Cbte_Tipo', 92 | 'FEXGetPARAM_DST_CUIT', 93 | 'FEXGetPARAM_DST_pais', 94 | 'FEXGetPARAM_PtoVenta', 95 | 'FEXGetPARAM_MON', 96 | 'FEXGetPARAM_MON_CON_COTIZACION', 97 | 'FEXGetPARAM_Ctz', 98 | 'FEXGetPARAM_Tipo_Expo', 99 | 'FEXGetLast_ID', 100 | 'FEXGetCMP', 101 | 'FEXGetPARAM_UMed', 102 | 'FEXAuthorize' 103 | ].forEach((service) => { 104 | Wsfex.prototype[service] = function (args) { 105 | return this._callWsfex(service, { 106 | ...this.hAuth, 107 | ...args, 108 | }); 109 | } 110 | }); 111 | 112 | 113 | [ 114 | 'FEXGetLast_CMP', 115 | ].forEach((service) => { 116 | Wsfex.prototype[service] = function (args) { 117 | const Auth = { ... this.hAuth.Auth, ...args} 118 | return this._callWsfex(service, { 119 | Auth 120 | }); 121 | } 122 | }); 123 | 124 | 125 | module.exports= { 126 | Wsfex 127 | }; -------------------------------------------------------------------------------- /lib/wsfe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const soap = require('soap'); 4 | const bwipjs = require('bwip-js'); 5 | const fs = require('fs'); 6 | const { TA } = require('./tickets/ta'); 7 | const FE_DUMMY = "FEDummy"; 8 | 9 | const _default = { 10 | config : { 11 | prod:false, 12 | url_wdsl_devel:"https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL", 13 | url_wdsl_prod:"https://servicios1.afip.gov.ar/wsfev1/service.asmx?WSDL", 14 | service:"wsfe", 15 | debug:false, 16 | url_wdsl:this.url_wdsl_devel, 17 | cuit:"", 18 | } 19 | }; 20 | 21 | class Wsfe { 22 | 23 | constructor (TA, config){ 24 | 25 | this.config = { 26 | ..._default.config, 27 | ...config, 28 | }; 29 | 30 | this.config.url_wdsl = 31 | (this.config.prod)? 32 | this.config.url_wdsl_prod : 33 | this.config.url_wdsl_devel; 34 | 35 | this.setTA(TA); 36 | 37 | } 38 | 39 | /** 40 | * Llama al webservice de AFIP especificado en el primer parametro con los datos 41 | * especifiados en el el segundo argumento. 42 | * @param {String} service 43 | * @param {Object} args 44 | * @returns {Promise} Devuelve una promesa con la respuesta 45 | */ 46 | 47 | _callWsfe (service, args){ 48 | 49 | let debug = this.config.debug; 50 | let wdsl = this.config.url_wdsl; 51 | 52 | return new Promise(function(resolve, reject){ 53 | 54 | soap.createClient(wdsl, { returnFault: true }, function (err, client) { 55 | if(err) return reject(err); 56 | client[service](args, function (err, result, rawResponse, soapHeader, rawRequest) { 57 | if(debug){ 58 | console.log('Request: ', rawRequest); 59 | console.log('Response:', rawResponse); 60 | } 61 | if(err) return reject(err); 62 | return resolve(result); 63 | }); 64 | }); 65 | }); 66 | } 67 | 68 | setTA(TA){ 69 | 70 | this.TA = TA; 71 | 72 | if (TA != undefined){ 73 | this.hAuth = { 74 | Auth:{ 75 | Token:this.TA.TA_parsed.token, 76 | Sign:this.TA.TA_parsed.sign, 77 | Cuit:this.TA.TA_parsed.cuit 78 | } 79 | }; 80 | } 81 | } 82 | 83 | FEDummy (args){ 84 | return this._callWsfe(FE_DUMMY, args); 85 | }; 86 | 87 | /** 88 | * Genera los archivos PNG con los codigos de barra y devuelve los 89 | * codigos de barra en una lista. 90 | * La entrada a este servicio es la salida de FECAESolititar, 91 | * recordar que devuelve una lista de facturas autorizadas. 92 | * @param {*} invoices 93 | */ 94 | 95 | BarCodes(invoices){ 96 | 97 | const cuit = invoices.FECAESolicitarResult.FeCabResp.Cuit; 98 | const point = (""+invoices.FECAESolicitarResult.FeCabResp.PtoVta).padStart(4,0); 99 | const type = invoices.FECAESolicitarResult.FeCabResp.CbteTipo; 100 | const BarcodePrefix = `${cuit}${point}${type}`; 101 | var barCodes = []; 102 | 103 | invoices.FECAESolicitarResult.FeDetResp.FeDetResp.forEach(invoice => { 104 | 105 | const cae = invoice.CAE; 106 | const caeVto = invoice.CAEFchVto; 107 | 108 | const barcode = `${BarcodePrefix}${cae}${caeVto}`; 109 | const file = `${cae}.png`; 110 | barCodes.push(file); 111 | 112 | bwipjs.toBuffer({ 113 | bcid: 'interleaved2of5', 114 | text: `${barcode}`, 115 | scale: 1, 116 | height: 12, 117 | includetext: true, 118 | includecheck: true, 119 | includecheckintext: true, 120 | textxalign: 'justify', 121 | backgroundcolor: "FFFFFF", 122 | }, function (err, png) { 123 | if (err) { 124 | console.log(err); 125 | } else { 126 | fs.writeFileSync(file, png); 127 | } 128 | }); 129 | 130 | }); 131 | 132 | return barCodes; 133 | } 134 | 135 | DocType2Code(type) { 136 | return { 137 | C:"11" 138 | } 139 | }; 140 | 141 | } 142 | 143 | [ 144 | 145 | 'FEParamGetTiposIva', 146 | 'FECompUltimoAutorizado', 147 | 'FECAESolicitar', 148 | 'FECompConsultar', 149 | 'FEParamGetTiposCbte', 150 | 'FEParamGetTiposConcepto', 151 | 'FEParamGetTiposDoc', 152 | 'FEParamGetTiposMonedas', 153 | 'FEParamGetTiposOpcional', 154 | 'FEParamGetTiposTributos', 155 | 'FEParamGetPtosVenta', 156 | 'FEParamGetCotizacion' 157 | 158 | ].forEach((service) => { 159 | Wsfe.prototype[service] = function (args) { 160 | return this._callWsfe(service, { 161 | ...this.hAuth, 162 | ...args, 163 | }); 164 | } 165 | }); 166 | 167 | module.exports= { 168 | Wsfe 169 | }; -------------------------------------------------------------------------------- /doc/wsaa.md: -------------------------------------------------------------------------------- 1 | ### WSAA 2 | 3 | La clase Wsaa permite manejar Tickets, la clase Wsfe1 permite acceder a los servicio de facturacion. 4 | 5 | Opciones de configuracion: 6 | 7 | - *debug*: Muestra (o no) datos de ejecucion como los datos enviados y recibidos a/y desde el webservice. 8 | - *prod*: Indica si se va a utilizar el entoro de Produccion o el Homologacion. 9 | - *service*: Indica para que web service se solicitara acceso, por ahora solo wsfe1 esta disponible. 10 | 11 | 12 | ```javascript 13 | const { Wsaa, Wsfe } = require('./afipjs'); 14 | var pem = fs.readFileSync('path/to/CertTest.pem', 'utf8') 15 | var key = fs.readFileSync('path/to/keyTest.pem', 'utf8') 16 | const conf = { 17 | prod: false, 18 | debug: true, 19 | } 20 | const wsaa = new Wsaa(conf) 21 | wsaa.setCertificate(pem) 22 | wsaa.setKey(key) 23 | console.log(wsaa.certificate) 24 | ``` 25 | 26 | ```javascript 27 | { 28 | validFrom: 2021-02-06T21:03:28.000Z, 29 | validTo: 2023-02-06T21:03:28.000Z, 30 | version: 3, 31 | serialNumber: '6e28eedea2f859c3', 32 | issuer: { CN: 'Computadores Test', C: 'AR', O: 'AFIP' }, 33 | subject: { CN: 'IbrickInvoice', SN: 'CUIT 20278650988' } 34 | } 35 | ``` 36 | 37 | ### Crear un TRA 38 | 39 | Un TRA es un Solicitud o Requerimiento de Ticket de Acceso (TA), necesario para luego poder llamar a los demas servicios, como los de Facturacion Electronica por ejemplo. 40 | Una TRA no es mas que un XML que se encripta con tu certificado. 41 | Para generar un TRA se usa el metodo *createTRA* 42 | 43 | ```javascript 44 | const tra = wsaa.createTRA() 45 | console.log(tra) 46 | ``` 47 | 48 | ```javascript 49 | TRA { 50 | config: { 51 | prod: false, 52 | service: 'wsfe', 53 | debug: true, 54 | url_wdsl: 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms?WSDL' 55 | }, 56 | TRA: '
16127335112021-02-07T18:30:51-03:002021-02-07T18:32:51-03:00
wsfe
', 57 | TRA_parsed: { 58 | loginTicketRequest: { '$': [Object], header: [Array], service: [Array] } 59 | }, 60 | cms: 'MIIGFgYJKoZIhvcNAQcCoIIGBzCCBgMCAQExCzAJBgUrDgM......' 61 | } 62 | ``` 63 | 64 | ```javascript 65 | try { 66 | const newTa = await tra.supplicateTA() 67 | console.log(newTa) 68 | } catch(err) { 69 | console.log(err.root) 70 | } 71 | ``` 72 | 73 | ```javascript 74 | TA { 75 | TA: '\n' + 76 | '\n' + 77 | '
\n' + 78 | ' CN=wsaahomo, O=AFIP, C=AR, SERIALNUMBER=CUIT 33693450239\n' + 79 | ' SERIALNUMBER=CUIT 20278650988, CN=ibrickinvoice\n' + 80 | ' 3326504285\n' + 81 | ' 2021-02-07T18:31:52.043-03:00\n' + 82 | ' 2021-02-08T06:31:52.043-03:00\n' + 83 | '
\n' + 84 | ' \n' + 85 | ' PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0i...\n' + 86 | ' PnZJ3icY7WAWWiVWzbA7PQ...\n' + 87 | ' \n' + 88 | '
', 89 | TA_parsed: { 90 | generationTime: '2021-02-07T18:31:52.043-03:00', 91 | expirationTime: '2021-02-08T06:31:52.043-03:00', 92 | token: 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZG....', 93 | sign: 'PnZJ3icY7WAWWiVWzbA7PQV7UNbcsYzlcPCDlf...', 94 | cuit: '20278650988' 95 | } 96 | } 97 | ``` 98 | 99 | # Tomar un TA desde un string y verificar su validez 100 | 101 | ```javascript 102 | const prev_ta = '\n' + 103 | '\n' + 104 | '
\n' + 105 | ' CN=wsaahomo, O=AFIP, C=AR, SERIALNUMBER=CUIT 33693450239\n' + 106 | ' SERIALNUMBER=CUIT 20278650988, CN=ibrickinvoice\n' + 107 | ' 940678721\n' + 108 | ' 2021-02-07T16:38:37.317-03:00\n' + 109 | ' 2021-02-08T04:38:37.317-03:00\n' + 110 | '
\n' + 111 | ' \n' + 112 | ' PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNv....\n' + 113 | ' tBgdeCigauhYL2i7L5C4FAKTP6SxxhrEo7KQKsAyAIC...\n' + 114 | ' \n' + 115 | '
' 116 | 117 | const ta = wsaa.createTAFromString(prev_ta) 118 | console.log(ta) 119 | console.log(ta.isValid()) 120 | ``` 121 | 122 | ```javascript 123 | TA { 124 | TA: '\n' + 125 | '\n' + 126 | '
\n' + 127 | ' CN=wsaahomo, O=AFIP, C=AR, SERIALNUMBER=CUIT 33693450239\n' + 128 | ' SERIALNUMBER=CUIT 20278650988, CN=ibrickinvoice\n' + 129 | ' 940678721\n' + 130 | ' 2021-02-07T16:38:37.317-03:00\n' + 131 | ' 2021-02-08T04:38:37.317-03:00\n' + 132 | '
\n' + 133 | ' \n' + 134 | ' PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGlu...\n' + 135 | ' tBgdeCigauhYL2i7L5C4FAKTP6Sxx...\n' + 136 | ' \n' + 137 | '
', 138 | TA_parsed: { 139 | generationTime: '2021-02-07T16:38:37.317-03:00', 140 | expirationTime: '2021-02-08T04:38:37.317-03:00', 141 | token: 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNv...', 142 | sign: 'tBgdeCigauhYL2i7L5C4FAKTP6Sxxh..', 143 | cuit: '20278650988' 144 | } 145 | } 146 | true 147 | ``` 148 | 149 | Ahora que ya hay un ticket valido creado, se puede empezar a utilizar los servicio de Factura Electronica, provisto por el Webservice Wsfe1, para es necesario crear un objeto Wsfe1 y luego llamar al servicio que necesitemos: 150 | 151 | - Factura electronica [WSFEV1](wsfev1.md) 152 | -------------------------------------------------------------------------------- /doc/wsfev1.md: -------------------------------------------------------------------------------- 1 | ## WSFEV1 2 | 3 | Este es el WebService que permite hacer facturacion electronica entre otras cosas. 4 | 5 | Los parametros de cada servicio se pueden encontrar en la [documentacion oficial] de la AFIP(http://www.afip.gob.ar/ws/documentacion/ws-factura-electronica.asp) 6 | 7 | A continuacion de da algunos ejemplos de los servicios que, al menos yo, utlizo mas a menudo. 8 | 9 | ### FEDummy (para saber si el server esta funcionando bien) 10 | 11 | ```javascript 12 | const wsfe = new Wsfe(TA); 13 | const response = await wsfe.FEDummy({}); 14 | console.log(response); 15 | ``` 16 | 17 | ```javascript 18 | { FEDummyResult: { AppServer: 'OK', DbServer: 'OK', AuthServer: 'OK' } } 19 | ``` 20 | 21 | ### FEParamGetTiposIva (Obtiene los tipo de IVA) 22 | 23 | ```javascript 24 | response = await wsfe.FEParamGetTiposIva({}); 25 | console.dir(response, { depth: null }); 26 | ``` 27 | 28 | ```javascript 29 | { FEParamGetTiposIvaResult: 30 | { ResultGet: 31 | { IvaTipo: 32 | [ { Id: '3', Desc: '0%', FchDesde: '20090220', FchHasta: 'NULL' }, 33 | { Id: '4', Desc: '10.5%', FchDesde: '20090220', FchHasta: 'NULL' }, 34 | { Id: '5', Desc: '21%', FchDesde: '20090220', FchHasta: 'NULL' }, 35 | { Id: '6', Desc: '27%', FchDesde: '20090220', FchHasta: 'NULL' }, 36 | { Id: '8', Desc: '5%', FchDesde: '20141020', FchHasta: 'NULL' }, 37 | { Id: '9', Desc: '2.5%', FchDesde: '20141020', FchHasta: 'NULL' } ] } } } 38 | ``` 39 | 40 | ### FECompUltimoAutorizado (Obtiene el numero del ultimo comprobate autrizado) 41 | 42 | ```javascript 43 | response = await wsfe.FECompUltimoAutorizado({ 44 | PtoVta:1, 45 | CbteTipo:11 46 | }); 47 | console.dir(response, { depth: null }); 48 | ``` 49 | 50 | ```javascript 51 | { FECompUltimoAutozizadoResult: { PtoVta: 1, CbteTipo: 11, CbteNro: 48 } } 52 | ``` 53 | 54 | ### FECAESolicitar (Autoriza un comprobante) 55 | 56 | Este es el servicio que mas utilizo, con el objetivo de conseguir el CAE. 57 | 58 | ```javascript 59 | const puntoDeVenta = 1; 60 | const ultimoAutorizado = 48 // desde FECompUltimoAutorizado 61 | 62 | const factura = { 63 | FeCAEReq:{ 64 | FeCabReq:{ 65 | CantReg:1, 66 | PtoVta:puntoDeVenta, 67 | CbteTipo:11 68 | }, 69 | FeDetReq:{ 70 | FECAEDetRequest:{ 71 | Concepto:1, 72 | DocTipo:80, 73 | DocNro:"23000000000", 74 | CbteDesde:ultimoAutorizado + 1, 75 | CbteHasta:ultimoAutorizado + 1, 76 | CbteFch:'20190407', 77 | ImpTotal:1.00, 78 | ImpTotConc:0.00, 79 | ImpNeto:1.00, 80 | ImpOpEx:0.00, 81 | ImpTrib:0.00, 82 | ImpIVA:0.00, 83 | MonId:"PES", 84 | MonCotiz:1 85 | } 86 | } 87 | } 88 | }; 89 | 90 | response = await wsfe.FECAESolicitar(factura); 91 | console.dir(response, { depth: null }); 92 | ``` 93 | 94 | ```javascript 95 | { FECAESolicitarResult: 96 | { FeCabResp: 97 | { Cuit: '20278650988', 98 | PtoVta: 1, 99 | CbteTipo: 11, 100 | FchProceso: '20190407162734', 101 | CantReg: 1, 102 | Resultado: 'A', 103 | Reproceso: 'N' }, 104 | FeDetResp: 105 | { FeDetResp: 106 | [ { Concepto: 1, 107 | DocTipo: 80, 108 | DocNro: '23000000000', 109 | CbteDesde: '49', 110 | CbteHasta: '49', 111 | CbteFch: '20190407', 112 | Resultado: 'A', 113 | CAE: '69148738398949', 114 | CAEFchVto: '20190417' } ] } } } 115 | ``` 116 | 117 | #### CAE obtenido: 69148738398949 118 | 119 | ### FECompConsultar (Consulta un comprobante autorizado) 120 | 121 | ```javascript 122 | response = await wsfe.FECompConsultar({ 123 | FeCompConsReq:{ 124 | PtoVta:1, 125 | CbteTipo:11, 126 | CbteNro:49, 127 | } 128 | }); 129 | console.log(response); 130 | ``` 131 | 132 | ```javascript 133 | { FECompConsultarResult: 134 | { ResultGet: 135 | { Concepto: 1, 136 | DocTipo: 80, 137 | DocNro: '23000000000', 138 | CbteDesde: '49', 139 | CbteHasta: '49', 140 | CbteFch: '20190407', 141 | ImpTotal: '1', 142 | ImpTotConc: '0', 143 | ImpNeto: '1', 144 | ImpOpEx: '0', 145 | ImpTrib: '0', 146 | ImpIVA: '0', 147 | FchServDesde: '', 148 | FchServHasta: '', 149 | FchVtoPago: '', 150 | MonId: 'PES', 151 | MonCotiz: '1', 152 | Resultado: 'A', 153 | CodAutorizacion: '69148738398949', 154 | EmisionTipo: 'CAE', 155 | FchVto: '20190417', 156 | FchProceso: '20190407162734', 157 | PtoVta: 1, 158 | CbteTipo: 11 } } } 159 | ``` 160 | 161 | ### Generando codigos de barras 162 | 163 | ```javascript 164 | console.log(wsfe.BarCodes(response)); 165 | ``` 166 | 167 | Devuelve una lista con los archivos generados, en este caso 1 solo. 168 | 169 | ``` javascript 170 | [ '69148738398949.png' ] 171 | ``` 172 | 173 | ![Codigo de barras ](69148738398949.png) 174 | 175 | ## Mas servicios: 176 | 177 | - FECompUltimoAutorizado 178 | - FECAESolicitar 179 | - FECompConsultar 180 | - FEParamGetTiposIva 181 | - FEParamGetTiposCbte 182 | - FEParamGetTiposConcepto 183 | - FEParamGetTiposDoc 184 | - FEParamGetTiposMonedas 185 | - FEParamGetTiposOpcional 186 | - FEParamGetTiposTributos 187 | - FEParamGetPtosVenta 188 | - FEParamGetCotizacion 189 | 190 | La descripcion y parametros de cada uno puede encontrarse en la [documentacion oficial](https://www.afip.gob.ar/ws/documentacion/ws-factura-electronica.asp) 191 | 192 | ### Autenticacion [WSAA](wsaa.md) 193 | 194 | ## Errores 195 | 196 | ### Prueba de llamada a un servicio con un token vencido 197 | 198 | ```javascript 199 | const miTA2 = wsaa.createTAFromFile(); 200 | console.log(miTA2.TA_parsed); 201 | console.log(miTA2.isValid()); 202 | ``` 203 | 204 | ```javascript 205 | { generationTime: '2019-04-07T15:27:06.415-03:00', 206 | expirationTime: '2019-04-08T03:27:06.415-03:00', 207 | token: 208 | 'PD94bWwgdmVyc...', 209 | sign: 210 | 'VMUtFuumdowJIA...', 211 | cuit: '20278650988' } 212 | false 213 | ``` 214 | 215 | ```javascript 216 | response = await wsfe.FEParamGetTiposIva({}); 217 | console.dir(response, { depth: null }); 218 | ``` 219 | 220 | ```javascript 221 | { FEParamGetTiposIvaResult: 222 | { Errors: 223 | { Err: 224 | [ { Code: 600, 225 | Msg: 226 | 'ValidacionDeToken: No validaron las fechas del token GenTime, ExpTime, NowUTC: 1554661566 (4/7/2019 6:25:36 PM), 1554704826 (4/8/2019 6:27:06 AM), 4/20/2 227 | 019 2:23:35 AM' } ] } } } 228 | ``` 229 | 230 | ### Llamar a un servicio del Webservice WSFE con un token valido pero obtenido para el Webservice WSFEX: 231 | 232 | ```javascript 233 | var wsaa = new Wsaa({service:'wsfex'}); 234 | const miTA2 = wsaa.createTAFromFile(); 235 | const wsfe = new Wsfe(miTA2); 236 | response = await wsfe.FEParamGetTiposCbte({}); 237 | console.dir(response, { depth: null }); 238 | ``` 239 | 240 | ```javascript 241 | { FEParamGetTiposCbteResult: 242 | { Errors: 243 | { Err: 244 | [ { Code: 600, 245 | Msg: 246 | 'ValidacionDeToken: No valido Id Sistema: wsfe(Id Sistema de token es: wsfex)' } ] } } } 247 | ``` 248 | 249 | ### Prueba sin conexion a internet: 250 | 251 | ```javascript 252 | try { 253 | response = await wsfe.FEParamGetTiposIva({}); 254 | console.dir(response, { depth: null }); 255 | }catch(err){ 256 | console.log(err); 257 | } 258 | ``` 259 | 260 | ```javascript 261 | { Error: getaddrinfo ENOTFOUND wswhomo.afip.gov.ar wswhomo.afip.gov.ar:443 262 | at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:57:26) 263 | errno: 'ENOTFOUND', 264 | code: 'ENOTFOUND', 265 | syscall: 'getaddrinfo', 266 | hostname: 'wswhomo.afip.gov.ar', 267 | host: 'wswhomo.afip.gov.ar', 268 | port: 443 } 269 | ``` 270 | ### El webservice no responde: 271 | 272 | ```javascript 273 | try { 274 | response = await wsfe.FEParamGetTiposIva({}); 275 | console.dir(response, { depth: null }); 276 | }catch(err){ 277 | console.log(err); 278 | } 279 | ``` 280 | 281 | ```javascript 282 | { Error: connect ETIMEDOUT 200.1.116.57:80 283 | at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1113:14) 284 | errno: 'ETIMEDOUT', 285 | code: 'ETIMEDOUT', 286 | syscall: 'connect', 287 | address: '200.1.116.57', 288 | port: 403 } 289 | ``` 290 | 291 | ### Autorizar en lote: 292 | 293 | Ejemplo de autorizacion de 2 facturas en la misma llamada. 294 | 295 | ```javascript 296 | const puntoDeVenta = 1; 297 | const ultimoAutorizado = 60; 298 | 299 | const facturas = { 300 | FeCAEReq:{ 301 | FeCabReq:{ 302 | CantReg:2, 303 | PtoVta:puntoDeVenta, 304 | CbteTipo:11 305 | }, 306 | FeDetReq:{ 307 | FECAEDetRequest:[{ 308 | Concepto:1, 309 | DocTipo:80, 310 | DocNro:"23000000000", 311 | CbteDesde:ultimoAutorizado + 1, 312 | CbteHasta:ultimoAutorizado + 1, 313 | CbteFch:'20200130', 314 | ImpTotal:1.00, 315 | ImpTotConc:0.00, 316 | ImpNeto:1.00, 317 | ImpOpEx:0.00, 318 | ImpTrib:0.00, 319 | ImpIVA:0.00, 320 | MonId:"PES", 321 | MonCotiz:1 322 | },{ 323 | Concepto:1, 324 | DocTipo:80, 325 | DocNro:"23000000000", 326 | CbteDesde:ultimoAutorizado + 2, 327 | CbteHasta:ultimoAutorizado + 2, 328 | CbteFch:'20200130', 329 | ImpTotal:2.00, 330 | ImpTotConc:0.00, 331 | ImpNeto:2.00, 332 | ImpOpEx:0.00, 333 | ImpTrib:0.00, 334 | ImpIVA:0.00, 335 | MonId:"PES", 336 | MonCotiz:1 337 | }] 338 | } 339 | } 340 | }; 341 | 342 | response = await wsfe.FECAESolicitar(facturas); 343 | console.dir(response, { depth: null }); 344 | ``` 345 | 346 | ```javascript 347 | { FECAESolicitarResult: 348 | { FeCabResp: 349 | { Cuit: '20278650988', 350 | PtoVta: 1, 351 | CbteTipo: 11, 352 | FchProceso: '20200130152933', 353 | CantReg: 2, 354 | Resultado: 'A', 355 | Reproceso: 'N' }, 356 | FeDetResp: 357 | { FECAEDetResponse: 358 | [ { Concepto: 1, 359 | DocTipo: 80, 360 | DocNro: '23000000000', 361 | CbteDesde: '61', 362 | CbteHasta: '61', 363 | CbteFch: '20200130', 364 | Resultado: 'A', 365 | CAE: '70058815371300', 366 | CAEFchVto: '20200209' }, 367 | { Concepto: 1, 368 | DocTipo: 80, 369 | DocNro: '23000000000', 370 | CbteDesde: '62', 371 | CbteHasta: '62', 372 | CbteFch: '20200130', 373 | Resultado: 'A', 374 | CAE: '70058815371313', 375 | CAEFchVto: '20200209' } ] } } } 376 | 377 | ``` 378 | 379 | Aconsejo mirar en la documentacion oficial los posibles errores que puede devolver este tipo de llamadas, 380 | "4.1.5 Operatoria ante errores" del Manual del desarrollador --------------------------------------------------------------------------------