@ramiidv/arca-mtxca 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,374 +1,466 @@
1
- import { afipSoapCall, ensureArray } from '@ramiidv/arca-common';
2
- import { MTXCA_ENDPOINTS, MTXCA_NAMESPACE } from './constants.js';
3
- function extractObservaciones(result) {
4
- const obs = result['arrayObservaciones'];
5
- if (!obs)
6
- return [];
7
- const arr = ensureArray(obs['codigoDescripcion']);
8
- return arr.map((o) => ({
9
- code: Number(o['codigo'] ?? 0),
10
- msg: String(o['descripcion'] ?? ''),
11
- }));
1
+ import { afipSoapCall, checkServiceErrors } from '@ramiidv/arca-common';
2
+ import { MTXCA_NAMESPACE } from './constants.js';
3
+ import { buildComprobante, XsdFields } from './serialize.js';
4
+ import { ResponseReader } from './parse.js';
5
+ const SERVICE = 'WSMTXCA';
6
+ function isResultado(value) {
7
+ return value === 'A' || value === 'O' || value === 'R';
12
8
  }
13
- function extractErrores(result) {
14
- const errs = result['arrayErrores'];
15
- if (!errs)
16
- return [];
17
- const arr = ensureArray(errs['codigoDescripcion']);
18
- return arr.map((e) => ({
19
- code: Number(e['codigo'] ?? 0),
20
- msg: String(e['descripcion'] ?? ''),
21
- }));
9
+ function validated(build) {
10
+ const f = new XsdFields();
11
+ const value = build(f);
12
+ f.throwIfErrors('Parámetros inválidos');
13
+ return value;
22
14
  }
23
- function extractItems(result) {
24
- const items = result['arrayItems'];
25
- if (!items)
26
- return [];
27
- const arr = ensureArray(items['item']);
28
- return arr.map((i) => ({
29
- codigo: i['codigo'] != null ? String(i['codigo']) : undefined,
30
- descripcion: String(i['descripcion'] ?? ''),
31
- cantidad: Number(i['cantidad'] ?? 0),
32
- unidadMedida: Number(i['unidadMedida'] ?? 0),
33
- precioUnitario: Number(i['precioUnitario'] ?? 0),
34
- importeItem: Number(i['importeItem'] ?? 0),
35
- codigoCondicionIVA: Number(i['codigoCondicionIVA'] ?? 0),
36
- importeIVA: Number(i['importeIVA'] ?? 0),
37
- importeBonificacion: i['importeBonificacion'] != null
38
- ? Number(i['importeBonificacion'])
39
- : undefined,
40
- }));
15
+ function validCaea(caea) {
16
+ return validated((f) => f.codigoAutorizacion('caea', caea, true));
41
17
  }
42
18
  /**
43
- * Low-level client for the WSMTXCA web service.
44
- * Wraps each SOAP method. Does not handle authentication.
19
+ * Cliente de bajo nivel de WSMTXCA: una función por operación del WSDL. No
20
+ * maneja la autenticación (recibe `MtxcaAuth`) ni la numeración.
21
+ *
22
+ * - Valida los requests contra el XSD y redondea importes (`ArcaValidationError`).
23
+ * - Las consultas lanzan `ArcaServiceError` si ARCA devuelve `arrayErrores`.
24
+ * - Las emisiones (`autorizar*`, `informar*`, `solicitarCAEA`) usan
25
+ * `kind: 'emision'`: no se reintentan y, si no hay respuesta, el
26
+ * `ArcaSoapError` tiene `uncertain: true`.
45
27
  */
46
28
  export class MtxcaClient {
47
- endpoint;
48
- soapOptions;
49
- onEvent;
29
+ config;
50
30
  constructor(config) {
51
- this.endpoint = config.production
52
- ? MTXCA_ENDPOINTS.production
53
- : MTXCA_ENDPOINTS.testing;
54
- this.soapOptions = config.soapOptions;
55
- this.onEvent = config.onEvent;
31
+ if (!config?.endpoint)
32
+ throw new TypeError('MtxcaClient: endpoint es obligatorio');
33
+ this.config = config;
34
+ }
35
+ get endpoint() {
36
+ return this.config.endpoint;
37
+ }
38
+ call(method, params, kind, opts, requestElement = `${method}Request`) {
39
+ const { endpoint, timeout, retries, retryDelayMs, onEvent, fetch, legacyTls } = this.config;
40
+ return afipSoapCall(endpoint, MTXCA_NAMESPACE, method, params, {
41
+ timeout,
42
+ retries,
43
+ retryDelayMs,
44
+ onEvent,
45
+ fetch,
46
+ legacyTls,
47
+ kind,
48
+ signal: opts?.signal,
49
+ requestElement,
50
+ responseElement: `${method}Response`,
51
+ soapActionFromMethod: true,
52
+ });
53
+ }
54
+ static authRequest(auth) {
55
+ return { token: auth.token, sign: auth.sign, cuitRepresentada: auth.cuitRepresentada };
56
+ }
57
+ /** Llamada autenticada: `authRequest` primero, como en todos los request del XSD. */
58
+ callAuth(method, auth, params, kind, opts) {
59
+ return this.call(method, { authRequest: MtxcaClient.authRequest(auth), ...params }, kind, opts);
56
60
  }
57
- call(method, params) {
58
- return afipSoapCall(this.endpoint, MTXCA_NAMESPACE, method, params, { ...this.soapOptions, onEvent: this.onEvent });
61
+ /** Consulta: lanza `ArcaServiceError` si la respuesta trae `arrayErrores`. */
62
+ async consulta(method, auth, params, opts) {
63
+ const result = await this.callAuth(method, auth, params, 'consulta', opts);
64
+ checkServiceErrors(result, `${SERVICE} ${method}`);
65
+ return result;
59
66
  }
60
67
  // =========================================================================
61
- // Facturacion
68
+ // Estado
69
+ // =========================================================================
70
+ /** `dummy`: estado de los servidores. No requiere autenticación. */
71
+ async dummy(opts) {
72
+ const result = await this.call('dummy', {}, 'consulta', opts, null);
73
+ const r = new ResponseReader('dummy', 'consulta');
74
+ return {
75
+ appserver: r.str(result, 'appserver'),
76
+ authserver: r.str(result, 'authserver'),
77
+ dbserver: r.str(result, 'dbserver'),
78
+ };
79
+ }
80
+ // =========================================================================
81
+ // CAE
62
82
  // =========================================================================
63
83
  /**
64
- * Autoriza un comprobante con detalle de articulos.
84
+ * `autorizarComprobante`. Valida contra el XSD y redondea; no valida la
85
+ * coherencia de importes (eso lo hace `ArcaMtxca.autorizar`). Un rechazo
86
+ * devuelve `resultado: 'R'` con `errores` (no lanza).
65
87
  */
66
- async autorizarComprobante(auth, request) {
67
- const itemsArray = request.invoice.items.map((item) => {
68
- const mapped = {
69
- descripcion: item.descripcion,
70
- cantidad: item.cantidad,
71
- unidadMedida: item.unidadMedida,
72
- precioUnitario: item.precioUnitario,
73
- importeItem: item.importeItem,
74
- codigoCondicionIVA: item.codigoCondicionIVA,
75
- importeIVA: item.importeIVA,
76
- };
77
- if (item.codigo != null)
78
- mapped['codigo'] = item.codigo;
79
- if (item.importeBonificacion != null)
80
- mapped['importeBonificacion'] = item.importeBonificacion;
81
- return mapped;
82
- });
83
- const comprobante = {
84
- codigoTipoComprobante: request.cbteTipo,
85
- numeroPuntoVenta: request.ptoVta,
86
- codigoConcepto: request.invoice.concepto,
87
- codigoTipoDocumento: request.invoice.docTipo,
88
- numeroDocumento: request.invoice.docNro,
89
- fechaEmision: request.invoice.cbteFch,
90
- importeTotal: request.invoice.importeTotal,
91
- arrayItems: {
92
- item: itemsArray,
93
- },
88
+ async autorizarComprobante(auth, comprobante, opts) {
89
+ return this.autorizarCAE('autorizarComprobante', auth, comprobante, opts);
90
+ }
91
+ /** `autorizarAjusteIVA`: comprobante de ajuste de IVA con CAE (ND/NC 2, 3, 7, 8, 52, 53). */
92
+ async autorizarAjusteIVA(auth, comprobante, opts) {
93
+ return this.autorizarCAE('autorizarAjusteIVA', auth, comprobante, opts);
94
+ }
95
+ async autorizarCAE(method, auth, comprobante, opts) {
96
+ const comprobanteCAERequest = buildComprobante(comprobante, 'cae');
97
+ const result = await this.callAuth(method, auth, { comprobanteCAERequest }, 'emision', opts);
98
+ const r = new ResponseReader(method, 'emision');
99
+ const resultado = this.resultado(r, result, method);
100
+ const base = {
101
+ observaciones: r.codigosDescripciones(result['arrayObservaciones'], 'arrayObservaciones'),
102
+ errores: r.codigosDescripciones(result['arrayErrores'], 'arrayErrores'),
103
+ evento: r.evento(result),
94
104
  };
95
- // Optional fields
96
- if (request.invoice.codigoMoneda != null) {
97
- comprobante['codigoMoneda'] = request.invoice.codigoMoneda;
98
- }
99
- if (request.invoice.cotizacionMoneda != null) {
100
- comprobante['cotizacionMoneda'] = request.invoice.cotizacionMoneda;
101
- }
102
- if (request.invoice.importeGravado != null) {
103
- comprobante['importeGravado'] = request.invoice.importeGravado;
104
- }
105
- if (request.invoice.importeNoGravado != null) {
106
- comprobante['importeNoGravado'] = request.invoice.importeNoGravado;
107
- }
108
- if (request.invoice.importeExento != null) {
109
- comprobante['importeExento'] = request.invoice.importeExento;
110
- }
111
- if (request.invoice.importeOtrosTributos != null) {
112
- comprobante['importeOtrosTributos'] = request.invoice.importeOtrosTributos;
113
- }
114
- // Subtotales de IVA
115
- if (request.invoice.subtotalesIVA && request.invoice.subtotalesIVA.length > 0) {
116
- comprobante['arraySubtotalesIVA'] = {
117
- subtotalIVA: request.invoice.subtotalesIVA.map((s) => ({
118
- codigo: s.codigo,
119
- baseImponible: s.baseImponible,
120
- importe: s.importe,
121
- })),
122
- };
123
- }
124
- // Otros tributos
125
- if (request.invoice.otrosTributos && request.invoice.otrosTributos.length > 0) {
126
- comprobante['arrayOtrosTributos'] = {
127
- otroTributo: request.invoice.otrosTributos.map((t) => {
128
- const mapped = {
129
- codigo: t.codigoTributo,
130
- baseImponible: t.baseImponible,
131
- importe: t.importe,
132
- };
133
- if (t.descripcion != null)
134
- mapped['descripcion'] = t.descripcion;
135
- if (t.alicuota != null)
136
- mapped['alicuota'] = t.alicuota;
137
- return mapped;
138
- }),
139
- };
140
- }
141
- // Comprobantes asociados
142
- if (request.invoice.comprobanteAsociado && request.invoice.comprobanteAsociado.length > 0) {
143
- comprobante['arrayComprobantesAsociados'] = {
144
- comprobanteAsociado: request.invoice.comprobanteAsociado.map((c) => {
145
- const mapped = {
146
- codigoTipoComprobante: c.codigoTipoComprobante,
147
- numeroPuntoVenta: c.numeroPuntoVenta,
148
- numeroComprobante: c.numeroComprobante,
149
- };
150
- if (c.cuit != null)
151
- mapped['cuit'] = c.cuit;
152
- return mapped;
153
- }),
154
- };
155
- }
156
- // Fechas de servicio
157
- if (request.invoice.fechaDesde != null) {
158
- comprobante['fechaDesde'] = request.invoice.fechaDesde;
159
- }
160
- if (request.invoice.fechaHasta != null) {
161
- comprobante['fechaHasta'] = request.invoice.fechaHasta;
162
- }
163
- if (request.invoice.fechaVtoPago != null) {
164
- comprobante['fechaVtoPago'] = request.invoice.fechaVtoPago;
165
- }
166
- // Observaciones
167
- if (request.invoice.observaciones != null) {
168
- comprobante['observaciones'] = request.invoice.observaciones;
169
- }
170
- const result = await this.call('autorizarComprobante', {
171
- authRequest: {
172
- token: auth.Token,
173
- sign: auth.Sign,
174
- cuitRepresentada: auth.Cuit,
175
- },
176
- comprobanteCAERequest: comprobante,
177
- });
178
- const comprobanteResponse = (result['comprobanteResponse'] ?? result);
105
+ if (resultado === 'R')
106
+ return { resultado, ...base };
107
+ const cr = r.obj(result, 'comprobanteResponse');
179
108
  return {
180
- resultado: String(comprobanteResponse['resultado'] ?? ''),
181
- cae: comprobanteResponse['CAE'] != null
182
- ? String(comprobanteResponse['CAE'])
183
- : undefined,
184
- caeFchVto: comprobanteResponse['fechaVencimientoCAE'] != null
185
- ? String(comprobanteResponse['fechaVencimientoCAE'])
186
- : undefined,
187
- cbteNro: Number(comprobanteResponse['numeroComprobante'] ?? 0),
188
- observaciones: extractObservaciones(result),
189
- errores: extractErrores(result),
109
+ resultado,
110
+ comprobante: {
111
+ cuit: r.str(cr, 'cuit', 'comprobanteResponse.cuit'),
112
+ codigoTipoComprobante: r.num(cr, 'codigoTipoComprobante', 'comprobanteResponse.codigoTipoComprobante'),
113
+ numeroPuntoVenta: r.num(cr, 'numeroPuntoVenta', 'comprobanteResponse.numeroPuntoVenta'),
114
+ numeroComprobante: r.num(cr, 'numeroComprobante', 'comprobanteResponse.numeroComprobante'),
115
+ fechaEmision: r.str(cr, 'fechaEmision', 'comprobanteResponse.fechaEmision'),
116
+ cae: r.str(cr, 'CAE', 'comprobanteResponse.CAE'),
117
+ fechaVencimientoCAE: r.str(cr, 'fechaVencimientoCAE', 'comprobanteResponse.fechaVencimientoCAE'),
118
+ },
119
+ ...base,
190
120
  };
191
121
  }
192
122
  /**
193
- * Consulta un comprobante previamente autorizado.
123
+ * `resultado` es obligatorio. Si falta y hay `arrayErrores`, es un rechazo
124
+ * (`ArcaServiceError`); si falta sin errores, la respuesta es ilegible
125
+ * (`ArcaSoapError` con `uncertain`).
194
126
  */
195
- async consultarComprobante(auth, cbteTipo, ptoVta, cbteNro) {
196
- const result = await this.call('consultarComprobante', {
197
- authRequest: {
198
- token: auth.Token,
199
- sign: auth.Sign,
200
- cuitRepresentada: auth.Cuit,
201
- },
202
- consultaComprobanteRequest: {
203
- codigoTipoComprobante: cbteTipo,
204
- numeroPuntoVenta: ptoVta,
205
- numeroComprobante: cbteNro,
206
- },
127
+ resultado(r, result, method) {
128
+ if (!r.has(result, 'resultado')) {
129
+ checkServiceErrors(result, `${SERVICE} ${method}`);
130
+ throw r.invalid('resultado');
131
+ }
132
+ const resultado = r.str(result, 'resultado');
133
+ if (!isResultado(resultado))
134
+ throw r.invalid('resultado', `valor "${resultado}" inesperado en`);
135
+ return resultado;
136
+ }
137
+ // =========================================================================
138
+ // CAEA
139
+ // =========================================================================
140
+ /**
141
+ * `solicitarCAEA`. `periodo` AAAAMM, `orden` 1 (primera quincena) o 2.
142
+ * Lanza `ArcaServiceError` si ARCA lo rechaza. Es una emisión: si el
143
+ * resultado es incierto, recuperalo con `consultarCAEAEntreFechas`.
144
+ */
145
+ async solicitarCAEA(auth, periodo, orden, opts) {
146
+ const solicitudCAEA = validated((f) => {
147
+ const p = f.int('periodo', periodo, true, 190001, 999912);
148
+ if (p !== undefined && (p % 100 < 1 || p % 100 > 12))
149
+ f.fail('periodo', 'formato AAAAMM', periodo);
150
+ return { periodo: p, orden: f.int('orden', orden, true, 1, 2) };
207
151
  });
208
- const comp = (result['comprobante'] ?? result);
152
+ const result = await this.callAuth('solicitarCAEA', auth, { solicitudCAEA }, 'emision', opts);
153
+ checkServiceErrors(result, `${SERVICE} solicitarCAEA`);
154
+ const r = new ResponseReader('solicitarCAEA', 'emision');
155
+ return this.parseCAEA(r, r.obj(result, 'CAEAResponse'), 'CAEAResponse');
156
+ }
157
+ /** `consultarCAEA`: datos de un CAEA otorgado. */
158
+ async consultarCAEA(auth, caea, opts) {
159
+ const result = await this.consulta('consultarCAEA', auth, { CAEA: validCaea(caea) }, opts);
160
+ const r = new ResponseReader('consultarCAEA', 'consulta');
161
+ return this.parseCAEA(r, r.obj(result, 'CAEAResponse'), 'CAEAResponse');
162
+ }
163
+ /** `consultarCAEAEntreFechas`: CAEAs otorgados con vigencia entre dos fechas. */
164
+ async consultarCAEAEntreFechas(auth, fechaDesde, fechaHasta, opts) {
165
+ const params = validated((f) => ({
166
+ fechaDesde: f.date('fechaDesde', fechaDesde, true),
167
+ fechaHasta: f.date('fechaHasta', fechaHasta, true),
168
+ }));
169
+ const result = await this.consulta('consultarCAEAEntreFechas', auth, params, opts);
170
+ const r = new ResponseReader('consultarCAEAEntreFechas', 'consulta');
171
+ return r.list(result['arrayCAEAResponse'], 'CAEAResponse').map((c, i) => this.parseCAEA(r, c, `arrayCAEAResponse.CAEAResponse[${i}]`));
172
+ }
173
+ parseCAEA(r, c, path) {
209
174
  return {
210
- codigoTipoComprobante: Number(comp['codigoTipoComprobante'] ?? 0),
211
- numeroPuntoVenta: Number(comp['numeroPuntoVenta'] ?? 0),
212
- numeroComprobante: Number(comp['numeroComprobante'] ?? 0),
213
- fechaEmision: String(comp['fechaEmision'] ?? ''),
214
- codigoTipoDocumento: Number(comp['codigoTipoDocumento'] ?? 0),
215
- numeroDocumento: Number(comp['numeroDocumento'] ?? 0),
216
- importeTotal: Number(comp['importeTotal'] ?? 0),
217
- importeNoGravado: Number(comp['importeNoGravado'] ?? 0),
218
- importeGravado: Number(comp['importeGravado'] ?? 0),
219
- importeExento: Number(comp['importeExento'] ?? 0),
220
- importeOtrosTributos: Number(comp['importeOtrosTributos'] ?? 0),
221
- importeSubtotal: Number(comp['importeSubtotal'] ?? 0),
222
- codigoMoneda: String(comp['codigoMoneda'] ?? 'PES'),
223
- cotizacionMoneda: Number(comp['cotizacionMoneda'] ?? 1),
224
- resultado: String(comp['resultado'] ?? ''),
225
- codigoAutorizacion: comp['codigoAutorizacion'] != null
226
- ? String(comp['codigoAutorizacion'])
227
- : undefined,
228
- fechaVencimiento: comp['fechaVencimiento'] != null
229
- ? String(comp['fechaVencimiento'])
230
- : undefined,
231
- observaciones: extractObservaciones(comp),
232
- items: extractItems(comp),
175
+ fechaProceso: r.str(c, 'fechaProceso', `${path}.fechaProceso`),
176
+ caea: r.str(c, 'CAEA', `${path}.CAEA`),
177
+ periodo: r.num(c, 'periodo', `${path}.periodo`),
178
+ orden: r.num(c, 'orden', `${path}.orden`),
179
+ fechaDesde: r.str(c, 'fechaDesde', `${path}.fechaDesde`),
180
+ fechaHasta: r.str(c, 'fechaHasta', `${path}.fechaHasta`),
181
+ fechaTopeInforme: r.str(c, 'fechaTopeInforme', `${path}.fechaTopeInforme`),
182
+ observaciones: r.codigosDescripciones(c['arrayObservaciones'], `${path}.arrayObservaciones`),
233
183
  };
234
184
  }
235
- /**
236
- * Consulta el ultimo comprobante autorizado.
237
- */
238
- async consultarUltimoComprobanteAutorizado(auth, ptoVta, cbteTipo) {
239
- const result = await this.call('consultarUltimoComprobanteAutorizado', {
240
- authRequest: {
241
- token: auth.Token,
242
- sign: auth.Sign,
243
- cuitRepresentada: auth.Cuit,
244
- },
245
- consultaUltimoComprobanteAutorizadoRequest: {
246
- codigoTipoComprobante: cbteTipo,
247
- numeroPuntoVenta: ptoVta,
185
+ /** `informarComprobanteCAEA`. Un rechazo devuelve `resultado: 'R'` con `errores`. */
186
+ async informarComprobanteCAEA(auth, comprobante, opts) {
187
+ return this.informarCAEA('informarComprobanteCAEA', auth, comprobante, opts);
188
+ }
189
+ /** `informarAjusteIVACAEA`. Un rechazo devuelve `resultado: 'R'` con `errores`. */
190
+ async informarAjusteIVACAEA(auth, comprobante, opts) {
191
+ return this.informarCAEA('informarAjusteIVACAEA', auth, comprobante, opts);
192
+ }
193
+ async informarCAEA(method, auth, comprobante, opts) {
194
+ const comprobanteCAEARequest = buildComprobante(comprobante, 'caea');
195
+ const result = await this.callAuth(method, auth, { comprobanteCAEARequest }, 'emision', opts);
196
+ const r = new ResponseReader(method, 'emision');
197
+ const resultado = this.resultado(r, result, method);
198
+ const base = {
199
+ observaciones: r.codigosDescripciones(result['arrayObservaciones'], 'arrayObservaciones'),
200
+ errores: r.codigosDescripciones(result['arrayErrores'], 'arrayErrores'),
201
+ evento: r.evento(result),
202
+ };
203
+ if (resultado === 'R')
204
+ return { resultado, fechaProceso: r.optStr(result, 'fechaProceso'), ...base };
205
+ const cr = r.obj(result, 'comprobanteCAEAResponse');
206
+ return {
207
+ resultado,
208
+ fechaProceso: r.str(result, 'fechaProceso'),
209
+ comprobante: {
210
+ caea: r.str(cr, 'CAEA', 'comprobanteCAEAResponse.CAEA'),
211
+ codigoTipoComprobante: r.num(cr, 'codigoTipoComprobante', 'comprobanteCAEAResponse.codigoTipoComprobante'),
212
+ numeroPuntoVenta: r.num(cr, 'numeroPuntoVenta', 'comprobanteCAEAResponse.numeroPuntoVenta'),
213
+ numeroComprobante: r.num(cr, 'numeroComprobante', 'comprobanteCAEAResponse.numeroComprobante'),
248
214
  },
249
- });
250
- return Number(result['numeroComprobante'] ?? 0);
215
+ ...base,
216
+ };
217
+ }
218
+ /** `informarCAEANoUtilizado`: informa que un CAEA no se usó en ningún punto de venta. */
219
+ async informarCAEANoUtilizado(auth, caea, opts) {
220
+ return this.caeaNoUtilizado('informarCAEANoUtilizado', auth, { CAEA: validCaea(caea) }, opts);
221
+ }
222
+ /** `informarCAEANoUtilizadoPtoVta`: informa que un CAEA no se usó en un punto de venta. */
223
+ async informarCAEANoUtilizadoPtoVta(auth, caea, numeroPuntoVenta, opts) {
224
+ const ptoVta = validated((f) => f.int('numeroPuntoVenta', numeroPuntoVenta, true, 1, 99998));
225
+ return this.caeaNoUtilizado('informarCAEANoUtilizadoPtoVta', auth, { CAEA: validCaea(caea), numeroPuntoVenta: ptoVta }, opts);
226
+ }
227
+ async caeaNoUtilizado(method, auth, params, opts) {
228
+ const result = await this.callAuth(method, auth, params, 'emision', opts);
229
+ const r = new ResponseReader(method, 'emision');
230
+ const resultado = this.resultado(r, result, method);
231
+ const aprobado = resultado !== 'R';
232
+ const conPtoVta = 'numeroPuntoVenta' in params;
233
+ return {
234
+ resultado,
235
+ fechaProceso: aprobado ? r.str(result, 'fechaProceso') : r.optStr(result, 'fechaProceso'),
236
+ caea: aprobado ? r.str(result, 'CAEA') : r.optStr(result, 'CAEA'),
237
+ ...(conPtoVta ? { numeroPuntoVenta: aprobado ? r.num(result, 'numeroPuntoVenta') : r.optNum(result, 'numeroPuntoVenta') } : {}),
238
+ errores: r.codigosDescripciones(result['arrayErrores'], 'arrayErrores'),
239
+ evento: r.evento(result),
240
+ };
241
+ }
242
+ /** `consultarPtosVtaCAEANoInformados`: puntos de venta que todavía no informaron movimientos para el CAEA. */
243
+ async consultarPtosVtaCAEANoInformados(auth, caea, opts) {
244
+ const result = await this.consulta('consultarPtosVtaCAEANoInformados', auth, { CAEA: validCaea(caea) }, opts);
245
+ const r = new ResponseReader('consultarPtosVtaCAEANoInformados', 'consulta');
246
+ return this.parsePuntosVenta(r, r.list(result['arrayPuntosVenta'], 'puntoVenta'));
251
247
  }
252
248
  // =========================================================================
253
- // Parametros
249
+ // Consultas de comprobantes
254
250
  // =========================================================================
255
251
  /**
256
- * Health check. No requiere autenticacion.
252
+ * `consultarUltimoComprobanteAutorizado`. Lanza `ArcaServiceError` ante
253
+ * cualquier error, incluido 1502 (no hay comprobantes para ese punto de venta
254
+ * y tipo); `ArcaMtxca.ultimoComprobante` lo traduce a 0.
257
255
  */
258
- async dummy() {
259
- const result = await this.call('dummy', {});
256
+ async consultarUltimoComprobanteAutorizado(auth, ptoVta, cbteTipo, opts) {
257
+ const consultaUltimoComprobanteAutorizadoRequest = validated((f) => ({
258
+ codigoTipoComprobante: f.int('cbteTipo', cbteTipo, true),
259
+ numeroPuntoVenta: f.int('ptoVta', ptoVta, true, 1, 99998),
260
+ }));
261
+ const result = await this.consulta('consultarUltimoComprobanteAutorizado', auth, { consultaUltimoComprobanteAutorizadoRequest }, opts);
262
+ return new ResponseReader('consultarUltimoComprobanteAutorizado', 'consulta').num(result, 'numeroComprobante');
263
+ }
264
+ /** `consultarComprobante`: datos de un comprobante autorizado (CAE) o informado (CAEA). */
265
+ async consultarComprobante(auth, cbteTipo, ptoVta, cbteNro, opts) {
266
+ const consultaComprobanteRequest = validated((f) => ({
267
+ codigoTipoComprobante: f.int('cbteTipo', cbteTipo, true),
268
+ numeroPuntoVenta: f.int('ptoVta', ptoVta, true, 1, 99998),
269
+ numeroComprobante: f.int('cbteNro', cbteNro, true, 1, 99999999),
270
+ }));
271
+ const result = await this.consulta('consultarComprobante', auth, { consultaComprobanteRequest }, opts);
272
+ const r = new ResponseReader('consultarComprobante', 'consulta');
260
273
  return {
261
- appserver: String(result['appserver'] ?? ''),
262
- dbserver: String(result['dbserver'] ?? ''),
263
- authserver: String(result['authserver'] ?? ''),
274
+ comprobante: parseComprobante(r, r.obj(result, 'comprobante')),
275
+ observaciones: r.codigosDescripciones(result['arrayObservaciones'], 'arrayObservaciones'),
276
+ evento: r.evento(result),
264
277
  };
265
278
  }
266
- async consultarTiposComprobante(auth) {
267
- return this.getParam(auth, 'consultarTiposComprobante', 'arrayTiposComprobante', 'codigoDescripcion');
279
+ // =========================================================================
280
+ // Parámetros
281
+ // =========================================================================
282
+ async codigos(method, container, auth, opts, params = {}) {
283
+ const result = await this.consulta(method, auth, params, opts);
284
+ const r = new ResponseReader(method, 'consulta');
285
+ return r.requiredList(result, container, 'codigoDescripcion').map((cd, i) => r.codigoDescripcion(cd, `${container}[${i}]`));
268
286
  }
269
- async consultarTiposDocumento(auth) {
270
- return this.getParam(auth, 'consultarTiposDocumento', 'arrayTiposDocumento', 'codigoDescripcion');
287
+ consultarTiposComprobante(auth, opts) {
288
+ return this.codigos('consultarTiposComprobante', 'arrayTiposComprobante', auth, opts);
271
289
  }
272
- async consultarAlicuotasIVA(auth) {
273
- return this.getParam(auth, 'consultarAlicuotasIVA', 'arrayAlicuotasIVA', 'codigoDescripcion');
290
+ consultarTiposDocumento(auth, opts) {
291
+ return this.codigos('consultarTiposDocumento', 'arrayTiposDocumento', auth, opts);
274
292
  }
275
- async consultarCondicionesIVA(auth) {
276
- return this.getParam(auth, 'consultarCondicionesIVA', 'arrayCondicionesIVA', 'codigoDescripcion');
293
+ consultarAlicuotasIVA(auth, opts) {
294
+ return this.codigos('consultarAlicuotasIVA', 'arrayAlicuotasIVA', auth, opts);
277
295
  }
278
- async consultarMonedas(auth) {
279
- const result = await this.call('consultarMonedas', {
280
- authRequest: {
281
- token: auth.Token,
282
- sign: auth.Sign,
283
- cuitRepresentada: auth.Cuit,
284
- },
285
- });
286
- const container = (result['arrayMonedas'] ?? result);
287
- const arr = ensureArray(container['codigoDescripcion']);
288
- return arr.map((m) => ({
289
- Id: String(m['codigo'] ?? ''),
290
- Desc: String(m['descripcion'] ?? ''),
291
- FchDesde: m['fechaDesde'] != null ? String(m['fechaDesde']) : undefined,
292
- FchHasta: m['fechaHasta'] != null ? String(m['fechaHasta']) : undefined,
293
- }));
296
+ consultarCondicionesIVA(auth, opts) {
297
+ return this.codigos('consultarCondicionesIVA', 'arrayCondicionesIVA', auth, opts);
294
298
  }
295
- async consultarUnidadesMedida(auth) {
296
- const result = await this.call('consultarUnidadesMedida', {
297
- authRequest: {
298
- token: auth.Token,
299
- sign: auth.Sign,
300
- cuitRepresentada: auth.Cuit,
301
- },
302
- });
303
- const container = (result['arrayUnidadesMedida'] ?? result);
304
- const arr = ensureArray(container['codigoDescripcion']);
305
- return arr.map((u) => ({
306
- Id: Number(u['codigo'] ?? 0),
307
- Desc: String(u['descripcion'] ?? ''),
308
- FchDesde: u['fechaDesde'] != null ? String(u['fechaDesde']) : undefined,
309
- FchHasta: u['fechaHasta'] != null ? String(u['fechaHasta']) : undefined,
310
- }));
299
+ /** Condiciones de IVA del receptor admitidas para un tipo de comprobante (RG 5616). */
300
+ async consultarCondicionesIVAReceptor(auth, cbteTipo, opts) {
301
+ const consultaCondicionesIVAReceptorRequest = validated((f) => ({ codigoTipoComprobante: f.int('cbteTipo', cbteTipo, true) }));
302
+ const result = await this.consulta('consultarCondicionesIVAReceptor', auth, { consultaCondicionesIVAReceptorRequest }, opts);
303
+ const r = new ResponseReader('consultarCondicionesIVAReceptor', 'consulta');
304
+ return r.codigosDescripciones(result['arrayCondicionesIVAReceptor'], 'arrayCondicionesIVAReceptor');
311
305
  }
312
- async consultarPuntosVenta(auth) {
313
- return this.getPtosVenta(auth, 'consultarPuntosVenta', 'arrayPuntosVenta');
306
+ consultarUnidadesMedida(auth, opts) {
307
+ return this.codigos('consultarUnidadesMedida', 'arrayUnidadesMedida', auth, opts);
314
308
  }
315
- async consultarPuntosVentaCAE(auth) {
316
- return this.getPtosVenta(auth, 'consultarPuntosVentaCAE', 'arrayPuntosVenta');
309
+ consultarTiposTributo(auth, opts) {
310
+ return this.codigos('consultarTiposTributo', 'arrayTiposTributo', auth, opts);
317
311
  }
318
- async consultarPuntosVentaCAEA(auth) {
319
- return this.getPtosVenta(auth, 'consultarPuntosVentaCAEA', 'arrayPuntosVenta');
312
+ consultarTiposDatosAdicionales(auth, opts) {
313
+ return this.codigos('consultarTiposDatosAdicionales', 'arrayTiposDatosAdicionales', auth, opts);
320
314
  }
321
- async consultarCotizacionMoneda(auth, monedaId) {
322
- const result = await this.call('consultarCotizacionMoneda', {
323
- authRequest: {
324
- token: auth.Token,
325
- sign: auth.Sign,
326
- cuitRepresentada: auth.Cuit,
327
- },
328
- codigoMoneda: monedaId,
329
- });
315
+ async consultarMonedas(auth, opts) {
316
+ const result = await this.consulta('consultarMonedas', auth, {}, opts);
317
+ const r = new ResponseReader('consultarMonedas', 'consulta');
318
+ return r.requiredList(result, 'arrayMonedas', 'codigoDescripcion').map((m, i) => ({
319
+ codigo: r.str(m, 'codigo', `arrayMonedas[${i}].codigo`),
320
+ descripcion: r.str(m, 'descripcion', `arrayMonedas[${i}].descripcion`),
321
+ }));
322
+ }
323
+ /**
324
+ * `consultarCotizacionMoneda`. `cotizacion` es `null` si ARCA no tiene
325
+ * cotización para la moneda en esa fecha (responde sin valor y sin error).
326
+ */
327
+ async consultarCotizacionMoneda(auth, codigoMoneda, fechaCotizacion, opts) {
328
+ const params = validated((f) => ({
329
+ codigoMoneda: f.text('codigoMoneda', codigoMoneda, true, 3),
330
+ fechaCotizacion: f.date('fechaCotizacion', fechaCotizacion, true),
331
+ }));
332
+ const result = await this.consulta('consultarCotizacionMoneda', auth, params, opts);
333
+ const r = new ResponseReader('consultarCotizacionMoneda', 'consulta');
330
334
  return {
331
- monedaId: String(result['codigoMoneda'] ?? monedaId),
332
- cotizacion: Number(result['cotizacionMoneda'] ?? 0),
333
- fechaCotizacion: String(result['fechaCotizacion'] ?? ''),
335
+ codigoMoneda: params.codigoMoneda,
336
+ fechaCotizacion: params.fechaCotizacion,
337
+ cotizacion: r.optNum(result, 'cotizacionMoneda') ?? null,
334
338
  };
335
339
  }
336
- // =========================================================================
337
- // Private helpers
338
- // =========================================================================
339
- async getParam(auth, method, containerKey, itemKey) {
340
- const result = await this.call(method, {
341
- authRequest: {
342
- token: auth.Token,
343
- sign: auth.Sign,
344
- cuitRepresentada: auth.Cuit,
345
- },
346
- });
347
- const container = (result[containerKey] ?? result);
348
- const arr = ensureArray(container[itemKey]);
349
- return arr.map((item) => ({
350
- Id: Number(item['codigo'] ?? 0),
351
- Desc: String(item['descripcion'] ?? ''),
352
- FchDesde: item['fechaDesde'] != null ? String(item['fechaDesde']) : undefined,
353
- FchHasta: item['fechaHasta'] != null ? String(item['fechaHasta']) : undefined,
340
+ async puntosVenta(method, auth, opts) {
341
+ const result = await this.consulta(method, auth, {}, opts);
342
+ const r = new ResponseReader(method, 'consulta');
343
+ return this.parsePuntosVenta(r, r.requiredList(result, 'arrayPuntosVenta', 'puntoVenta'));
344
+ }
345
+ parsePuntosVenta(r, list) {
346
+ return list.map((pv, i) => ({
347
+ numeroPuntoVenta: r.num(pv, 'numeroPuntoVenta', `puntoVenta[${i}].numeroPuntoVenta`),
348
+ bloqueado: r.str(pv, 'bloqueado', `puntoVenta[${i}].bloqueado`),
349
+ fechaBaja: r.optStr(pv, 'fechaBaja'),
354
350
  }));
355
351
  }
356
- async getPtosVenta(auth, method, containerKey) {
357
- const result = await this.call(method, {
358
- authRequest: {
359
- token: auth.Token,
360
- sign: auth.Sign,
361
- cuitRepresentada: auth.Cuit,
362
- },
363
- });
364
- const container = (result[containerKey] ?? result);
365
- const arr = ensureArray(container['puntoVenta']);
366
- return arr.map((pv) => ({
367
- Nro: Number(pv['numeroPuntoVenta'] ?? 0),
368
- EmisionTipo: String(pv['tipoEmision'] ?? ''),
369
- Bloqueado: String(pv['bloqueado'] ?? ''),
370
- FchBaja: String(pv['fechaBaja'] ?? ''),
352
+ consultarPuntosVenta(auth, opts) {
353
+ return this.puntosVenta('consultarPuntosVenta', auth, opts);
354
+ }
355
+ consultarPuntosVentaCAE(auth, opts) {
356
+ return this.puntosVenta('consultarPuntosVentaCAE', auth, opts);
357
+ }
358
+ consultarPuntosVentaCAEA(auth, opts) {
359
+ return this.puntosVenta('consultarPuntosVentaCAEA', auth, opts);
360
+ }
361
+ /** `consultarActividadesVigentes`: actividades vigentes del emisor. */
362
+ async consultarActividadesVigentes(auth, opts) {
363
+ const result = await this.consulta('consultarActividadesVigentes', auth, {}, opts);
364
+ const r = new ResponseReader('consultarActividadesVigentes', 'consulta');
365
+ return r.list(result['arrayActividades'], 'actividad').map((a, i) => ({
366
+ codigo: r.num(a, 'codigo', `arrayActividades[${i}].codigo`),
367
+ orden: r.num(a, 'orden', `arrayActividades[${i}].orden`),
368
+ descripcion: r.str(a, 'descripcion', `arrayActividades[${i}].descripcion`),
371
369
  }));
372
370
  }
373
371
  }
372
+ /** Parsea un `ComprobanteType` de `consultarComprobante`. */
373
+ function parseComprobante(r, c) {
374
+ const periodo = r.optObj(c, 'periodoComprobantesAsociados');
375
+ return {
376
+ codigoTipoComprobante: r.num(c, 'codigoTipoComprobante', 'comprobante.codigoTipoComprobante'),
377
+ numeroPuntoVenta: r.num(c, 'numeroPuntoVenta', 'comprobante.numeroPuntoVenta'),
378
+ numeroComprobante: r.num(c, 'numeroComprobante', 'comprobante.numeroComprobante'),
379
+ fechaEmision: r.optStr(c, 'fechaEmision'),
380
+ codigoTipoAutorizacion: r.optStr(c, 'codigoTipoAutorizacion'),
381
+ codigoAutorizacion: r.optStr(c, 'codigoAutorizacion'),
382
+ fechaVencimiento: r.optStr(c, 'fechaVencimiento'),
383
+ codigoTipoDocumento: r.optNum(c, 'codigoTipoDocumento'),
384
+ numeroDocumento: r.optStr(c, 'numeroDocumento'),
385
+ condicionIVAReceptor: r.optNum(c, 'condicionIVAReceptor'),
386
+ importeGravado: r.optNum(c, 'importeGravado'),
387
+ importeNoGravado: r.optNum(c, 'importeNoGravado'),
388
+ importeExento: r.optNum(c, 'importeExento'),
389
+ importeSubtotal: r.num(c, 'importeSubtotal', 'comprobante.importeSubtotal'),
390
+ importeOtrosTributos: r.optNum(c, 'importeOtrosTributos'),
391
+ importeTotal: r.num(c, 'importeTotal', 'comprobante.importeTotal'),
392
+ codigoMoneda: r.str(c, 'codigoMoneda', 'comprobante.codigoMoneda'),
393
+ cotizacionMoneda: r.optNum(c, 'cotizacionMoneda'),
394
+ cancelaEnMismaMonedaExtranjera: r.optStr(c, 'cancelaEnMismaMonedaExtranjera'),
395
+ observaciones: r.optStr(c, 'observaciones'),
396
+ codigoConcepto: r.num(c, 'codigoConcepto', 'comprobante.codigoConcepto'),
397
+ fechaServicioDesde: r.optStr(c, 'fechaServicioDesde'),
398
+ fechaServicioHasta: r.optStr(c, 'fechaServicioHasta'),
399
+ fechaVencimientoPago: r.optStr(c, 'fechaVencimientoPago'),
400
+ fechaHoraGen: r.optStr(c, 'fechaHoraGen'),
401
+ comprobantesAsociados: r.list(c['arrayComprobantesAsociados'], 'comprobanteAsociado').map((a, i) => {
402
+ const p = `comprobante.arrayComprobantesAsociados[${i}]`;
403
+ return {
404
+ codigoTipoComprobante: r.num(a, 'codigoTipoComprobante', `${p}.codigoTipoComprobante`),
405
+ numeroPuntoVenta: r.num(a, 'numeroPuntoVenta', `${p}.numeroPuntoVenta`),
406
+ numeroComprobante: r.num(a, 'numeroComprobante', `${p}.numeroComprobante`),
407
+ cuit: r.optStr(a, 'cuit'),
408
+ fechaEmision: r.optStr(a, 'fechaEmision'),
409
+ };
410
+ }),
411
+ periodoComprobantesAsociados: periodo
412
+ ? {
413
+ fechaDesde: r.str(periodo, 'fechaDesde', 'comprobante.periodoComprobantesAsociados.fechaDesde'),
414
+ fechaHasta: r.str(periodo, 'fechaHasta', 'comprobante.periodoComprobantesAsociados.fechaHasta'),
415
+ }
416
+ : undefined,
417
+ otrosTributos: r.list(c['arrayOtrosTributos'], 'otroTributo').map((t, i) => {
418
+ const p = `comprobante.arrayOtrosTributos[${i}]`;
419
+ return {
420
+ codigo: r.num(t, 'codigo', `${p}.codigo`),
421
+ descripcion: r.optStr(t, 'descripcion'),
422
+ baseImponible: r.num(t, 'baseImponible', `${p}.baseImponible`),
423
+ importe: r.num(t, 'importe', `${p}.importe`),
424
+ };
425
+ }),
426
+ items: r.requiredList(c, 'arrayItems', 'item').map((it, i) => {
427
+ const p = `comprobante.arrayItems[${i}]`;
428
+ return {
429
+ unidadesMtx: r.optNum(it, 'unidadesMtx'),
430
+ codigoMtx: r.optStr(it, 'codigoMtx'),
431
+ codigo: r.optStr(it, 'codigo'),
432
+ descripcion: r.str(it, 'descripcion', `${p}.descripcion`),
433
+ cantidad: r.optNum(it, 'cantidad'),
434
+ codigoUnidadMedida: r.num(it, 'codigoUnidadMedida', `${p}.codigoUnidadMedida`),
435
+ precioUnitario: r.optNum(it, 'precioUnitario'),
436
+ importeBonificacion: r.optNum(it, 'importeBonificacion'),
437
+ codigoCondicionIVA: r.num(it, 'codigoCondicionIVA', `${p}.codigoCondicionIVA`),
438
+ importeIVA: r.optNum(it, 'importeIVA'),
439
+ importeItem: r.num(it, 'importeItem', `${p}.importeItem`),
440
+ };
441
+ }),
442
+ subtotalesIVA: r.list(c['arraySubtotalesIVA'], 'subtotalIVA').map((s, i) => ({
443
+ codigo: r.num(s, 'codigo', `comprobante.arraySubtotalesIVA[${i}].codigo`),
444
+ importe: r.num(s, 'importe', `comprobante.arraySubtotalesIVA[${i}].importe`),
445
+ })),
446
+ datosAdicionales: r.list(c['arrayDatosAdicionales'], 'datoAdicional').map((d, i) => ({
447
+ t: r.num(d, 't', `comprobante.arrayDatosAdicionales[${i}].t`),
448
+ c1: r.optStr(d, 'c1'),
449
+ c2: r.optStr(d, 'c2'),
450
+ c3: r.optStr(d, 'c3'),
451
+ c4: r.optStr(d, 'c4'),
452
+ c5: r.optStr(d, 'c5'),
453
+ c6: r.optStr(d, 'c6'),
454
+ })),
455
+ compradores: r.list(c['arrayCompradores'], 'comprador').map((x, i) => {
456
+ const p = `comprobante.arrayCompradores[${i}]`;
457
+ return {
458
+ codigoTipoDocumento: r.num(x, 'codigoTipoDocumento', `${p}.codigoTipoDocumento`),
459
+ numeroDocumento: r.str(x, 'numeroDocumento', `${p}.numeroDocumento`),
460
+ porcentaje: r.num(x, 'porcentaje', `${p}.porcentaje`),
461
+ };
462
+ }),
463
+ actividades: r.list(c['arrayActividades'], 'actividad').map((a, i) => r.num(a, 'codigo', `comprobante.arrayActividades[${i}].codigo`)),
464
+ };
465
+ }
374
466
  //# sourceMappingURL=mtxca-client.js.map