arca-sdk 1.0.3 → 1.1.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@ Todos los cambios notables de este proyecto se documentan en este archivo.
4
4
 
5
5
  ---
6
6
 
7
+ ## [1.1.0] — 2026-02-28
8
+
9
+ ### ✨ Nuevos Comprobantes (Vouchers)
10
+
11
+ Se expandió la funcionalidad del servicio de facturación (`WsfeService`) para cubrir el espectro completo de comprobantes básicos:
12
+
13
+ - **Notas de Crédito**: Agregados métodos `issueCreditNoteA()`, `issueCreditNoteB()` y `issueCreditNoteC()`.
14
+ - **Notas de Débito**: Agregados métodos `issueDebitNoteA()`, `issueDebitNoteB()` y `issueDebitNoteC()`.
15
+ - **Recibos**: Agregados métodos `issueReceiptA()`, `issueReceiptB()` y `issueReceiptC()`.
16
+ - **Comprobantes Asociados**: El SDK ahora genera correctamente el nodo `<ar:CbtesAsoc>` de forma obligatoria para emitir NC/ND, asegurando que la operación de contingencia (anulación total o parcial de una factura) respete el estándar del ente recaudador.
17
+
18
+ ---
19
+
20
+ ## [1.0.4] — 2026-02-27
21
+
22
+ ### 🐛 Bugfix — Timezone Handling
23
+
24
+ - Se ajustó la generación de fechas para forzar la zona horaria UTC-3 (Argentina) independientemente de la zona horaria del servidor (ej: AWS, Vercel).
25
+ - Se restan 10 minutos al tiempo de generación en los TRA para evitar errores de desincronización con los servidores de ARCA.
26
+
27
+ ---
28
+
7
29
  ## [1.0.1] — 2026-02-23
8
30
 
9
31
  ### 🐛 Bugfix crítico — QR URL
package/README.md CHANGED
@@ -46,6 +46,34 @@ pnpm add arca-sdk
46
46
 
47
47
  ---
48
48
 
49
+ ## 🔐 Security & Responsibility / Seguridad y Responsabilidad
50
+
51
+ ### 🇺🇸 English
52
+
53
+ **arca-sdk** is designed to be **stateless and cloud-native**. It does **NOT** persist certificates or private keys to the filesystem.
54
+
55
+ It is the responsibility of the implementing application to:
56
+ 1. **Securely store** the Private Key (`.key`) and Certificate (`.crt`). (Recommended: Encrypted Database, AWS KMS, HashiCorp Vault).
57
+ 2. **Decrypt** credentials only at runtime.
58
+ 3. Pass the raw strings/buffers to the SDK constructors.
59
+
60
+ > [!WARNING]
61
+ > Never commit your `.key` files to Git or expose them in public folders. The SDK operates in-memory to ensure maximum security in Serverless environments (Vercel, AWS Lambda).
62
+
63
+ ### 🇦🇷 Español
64
+
65
+ **arca-sdk** está diseñado para ser **stateless** (sin estado) y **cloud-native**. **NO** guarda certificados ni claves privadas en el sistema de archivos.
66
+
67
+ Es responsabilidad de la aplicación que implementa el SDK:
68
+ 1. **Almacenar de forma segura** la Clave Privada (`.key`) y el Certificado (`.crt`). (Recomendado: Base de Datos encriptada, AWS KMS, HashiCorp Vault).
69
+ 2. **Desencriptar** las credenciales solo en tiempo de ejecución.
70
+ 3. Pasar los strings o buffers crudos a los constructores del SDK.
71
+
72
+ > [!CAUTION]
73
+ > Nunca subas tus archivos `.key` a Git ni los expongas en carpetas públicas. El SDK opera en memoria para garantizar la máxima seguridad en entornos Serverless (Vercel, AWS Lambda).
74
+
75
+ ---
76
+
49
77
  ## Quick Start — 5 minutos y estás facturando
50
78
 
51
79
  ```typescript
@@ -99,9 +127,12 @@ console.log('QR:', result.qrUrl); // 'https://www.afip.gob.ar/fe/qr/
99
127
  |--------|-------------|---------------|
100
128
  | `issueSimpleReceipt()` | Ticket C | Monto total, sin detalle. Ideal para POS simple |
101
129
  | `issueReceipt()` | Ticket C + items | Con detalle de productos guardado localmente |
102
- | `issueInvoiceC()` | Factura C | Monotributistas a consumidor final |
130
+ | `issueInvoiceC()` | Factura C | Monotributistas a consumidor final / Empresas |
103
131
  | `issueInvoiceB()` | Factura B | Responsable Inscripto a consumidor final / Monotributo |
104
132
  | `issueInvoiceA()` | Factura A | Responsable Inscripto a Responsable Inscripto |
133
+ | `issueCreditNoteA/B/C()` | Nota de Crédito | Anulación/Devolución (Requiere asociar la factura original) |
134
+ | `issueDebitNoteA/B/C()` | Nota de Débito | Cobro extra/Penalidad (Requiere asociar la factura original) |
135
+ | `issueReceiptA/B/C()` | Recibo | Comprobante de pago (misma emisión que una factura) |
105
136
 
106
137
  ### ✅ Consultas disponibles
107
138
 
@@ -152,6 +183,26 @@ result.vat?.forEach(v => {
152
183
  });
153
184
  ```
154
185
 
186
+ ### Nota de Crédito (Anulando factura previa)
187
+
188
+ ```typescript
189
+ import { InvoiceType } from 'arca-sdk';
190
+
191
+ const result = await wsfe.issueCreditNoteC({
192
+ items: [
193
+ { description: 'Anulación de equipo defectuoso', quantity: 1, unitPrice: 45000 },
194
+ ],
195
+ // ⚠️ Obligatorio en NC/ND: especificar el comprobante original afectado
196
+ associatedInvoices: [{
197
+ type: InvoiceType.FACTURA_C, // La factura que estoy anulando
198
+ pointOfSale: 4,
199
+ invoiceNumber: 15302,
200
+ }],
201
+ });
202
+
203
+ console.log('CAE de anulación:', result.cae);
204
+ ```
205
+
155
206
  ### Consulta de Padrón A13
156
207
 
157
208
  ```typescript
@@ -242,6 +293,9 @@ try {
242
293
  }
243
294
  ```
244
295
 
296
+ ### 🚚 Acerca de los Remitos
297
+ > **¡Atención!** Este SDK implementa nativamente el servicio `WSFE` (Facturación Electrónica). Si tu negocio necesita emitir **Remitos Electrónicos Oficiales** para el traslado físico de mercaderías (Remitos Cárnicos, Azucareros, Harineros, etc.), tené en cuenta que la AFIP exige usar un webservice totalmente distinto llamado `WSREM` o similares. Estos servicios aún no están cubiertos por esta versión del SDK.
298
+
245
299
  ---
246
300
 
247
301
  ## Compatibilidad
package/dist/index.cjs CHANGED
@@ -98,16 +98,33 @@ var ArcaNetworkError = class extends ArcaError {
98
98
 
99
99
  // src/utils/xml.ts
100
100
  var import_fast_xml_parser = require("fast-xml-parser");
101
+
102
+ // src/utils/formatArcaDate.ts
103
+ function formatArcaDate(date) {
104
+ const pad = (n) => n.toString().padStart(2, "0");
105
+ const tzOffset = 3 * 60 * 60 * 1e3;
106
+ const baTime = new Date(date.getTime() - tzOffset);
107
+ const yyyy = baTime.getUTCFullYear();
108
+ const mm = pad(baTime.getUTCMonth() + 1);
109
+ const dd = pad(baTime.getUTCDate());
110
+ const hh = pad(baTime.getUTCHours());
111
+ const min = pad(baTime.getUTCMinutes());
112
+ const ss = pad(baTime.getUTCSeconds());
113
+ return `${yyyy}-${mm}-${dd}T${hh}:${min}:${ss}-03:00`;
114
+ }
115
+
116
+ // src/utils/xml.ts
101
117
  function buildTRA(service, cuit) {
102
118
  const now = /* @__PURE__ */ new Date();
103
- const expirationTime = new Date(now.getTime() + 12 * 60 * 60 * 1e3);
119
+ const genTime = new Date(now.getTime() - 10 * 60 * 1e3);
120
+ const expTime = new Date(now.getTime() + 12 * 60 * 60 * 1e3);
104
121
  const tra = {
105
122
  loginTicketRequest: {
106
123
  "@_version": "1.0",
107
124
  header: {
108
125
  uniqueId: Math.floor(now.getTime() / 1e3),
109
- generationTime: now.toISOString(),
110
- expirationTime: expirationTime.toISOString()
126
+ generationTime: formatArcaDate(genTime),
127
+ expirationTime: formatArcaDate(expTime)
111
128
  },
112
129
  service
113
130
  }
@@ -490,8 +507,17 @@ var WsaaService = class {
490
507
  // src/types/wsfe.ts
491
508
  var InvoiceType = /* @__PURE__ */ ((InvoiceType2) => {
492
509
  InvoiceType2[InvoiceType2["FACTURA_A"] = 1] = "FACTURA_A";
510
+ InvoiceType2[InvoiceType2["NOTA_DEBITO_A"] = 2] = "NOTA_DEBITO_A";
511
+ InvoiceType2[InvoiceType2["NOTA_CREDITO_A"] = 3] = "NOTA_CREDITO_A";
512
+ InvoiceType2[InvoiceType2["RECIBO_A"] = 4] = "RECIBO_A";
493
513
  InvoiceType2[InvoiceType2["FACTURA_B"] = 6] = "FACTURA_B";
514
+ InvoiceType2[InvoiceType2["NOTA_DEBITO_B"] = 7] = "NOTA_DEBITO_B";
515
+ InvoiceType2[InvoiceType2["NOTA_CREDITO_B"] = 8] = "NOTA_CREDITO_B";
516
+ InvoiceType2[InvoiceType2["RECIBO_B"] = 9] = "RECIBO_B";
494
517
  InvoiceType2[InvoiceType2["FACTURA_C"] = 11] = "FACTURA_C";
518
+ InvoiceType2[InvoiceType2["NOTA_DEBITO_C"] = 12] = "NOTA_DEBITO_C";
519
+ InvoiceType2[InvoiceType2["NOTA_CREDITO_C"] = 13] = "NOTA_CREDITO_C";
520
+ InvoiceType2[InvoiceType2["RECIBO_C"] = 15] = "RECIBO_C";
495
521
  InvoiceType2[InvoiceType2["TICKET_A"] = 81] = "TICKET_A";
496
522
  InvoiceType2[InvoiceType2["TICKET_B"] = 82] = "TICKET_B";
497
523
  InvoiceType2[InvoiceType2["TICKET_C"] = 83] = "TICKET_C";
@@ -720,57 +746,93 @@ var WsfeService = class _WsfeService {
720
746
  return { ...cae, items: params.items };
721
747
  }
722
748
  /**
723
- * Emite una Factura C (consumidor final, sin discriminación de IVA).
749
+ * Emite una Factura A (Responsable Inscripto a Responsable Inscripto, con IVA discriminado).
750
+ * REQUIERE `vatRate` en todos los items.
724
751
  */
725
- async issueInvoiceC(params) {
726
- const total = round(calculateTotal(params.items));
727
- return this.issueDocument({
728
- type: 11 /* FACTURA_C */,
729
- concept: params.concept || 1 /* PRODUCTS */,
730
- total,
731
- date: params.date,
732
- buyer: {
733
- docType: 99 /* FINAL_CONSUMER */,
734
- docNumber: "0"
735
- },
736
- items: params.items
737
- });
752
+ async issueInvoiceA(params) {
753
+ return this.issueInvoiceWithVAT(1 /* FACTURA_A */, params);
738
754
  }
739
755
  /**
740
756
  * Emite una Factura B (con IVA discriminado).
741
757
  * REQUIERE `vatRate` en todos los items.
742
758
  */
743
759
  async issueInvoiceB(params) {
744
- this.validateItemsWithVAT(params.items);
745
- const includesVAT = params.includesVAT || false;
746
- const vatData = this.calculateVATByRate(params.items, includesVAT);
747
- return this.issueDocument({
748
- type: 6 /* FACTURA_B */,
749
- concept: params.concept || 1 /* PRODUCTS */,
750
- items: params.items,
751
- buyer: params.buyer,
752
- date: params.date,
753
- vatData,
754
- includesVAT
755
- });
760
+ return this.issueInvoiceWithVAT(6 /* FACTURA_B */, params);
756
761
  }
757
762
  /**
758
- * Emite una Factura A (Responsable Inscripto a Responsable Inscripto, con IVA discriminado).
759
- * REQUIERE `vatRate` en todos los items.
763
+ * Emite una Factura C (consumidor final, sin discriminación de IVA).
760
764
  */
761
- async issueInvoiceA(params) {
762
- this.validateItemsWithVAT(params.items);
763
- const includesVAT = params.includesVAT || false;
764
- const vatData = this.calculateVATByRate(params.items, includesVAT);
765
- return this.issueDocument({
766
- type: 1 /* FACTURA_A */,
767
- concept: params.concept || 1 /* PRODUCTS */,
768
- items: params.items,
769
- buyer: params.buyer,
770
- date: params.date,
771
- vatData,
772
- includesVAT
773
- });
765
+ async issueInvoiceC(params) {
766
+ return this.issueInvoiceWithoutVAT(11 /* FACTURA_C */, params);
767
+ }
768
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
769
+ // Recibos
770
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
771
+ /**
772
+ * Emite un Recibo A (con IVA discriminado).
773
+ */
774
+ async issueReceiptA(params) {
775
+ return this.issueInvoiceWithVAT(4 /* RECIBO_A */, params);
776
+ }
777
+ /**
778
+ * Emite un Recibo B (con IVA discriminado).
779
+ */
780
+ async issueReceiptB(params) {
781
+ return this.issueInvoiceWithVAT(9 /* RECIBO_B */, params);
782
+ }
783
+ /**
784
+ * Emite un Recibo C (sin discriminación de IVA).
785
+ */
786
+ async issueReceiptC(params) {
787
+ return this.issueInvoiceWithoutVAT(15 /* RECIBO_C */, params);
788
+ }
789
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
790
+ // Notas de Crédito
791
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
792
+ /**
793
+ * Emite una Nota de Crédito A.
794
+ * REQUIERE especificar la Factura A original en `associatedInvoices`.
795
+ */
796
+ async issueCreditNoteA(params) {
797
+ return this.issueInvoiceWithVAT(3 /* NOTA_CREDITO_A */, params);
798
+ }
799
+ /**
800
+ * Emite una Nota de Crédito B.
801
+ * REQUIERE especificar la Factura B original en `associatedInvoices`.
802
+ */
803
+ async issueCreditNoteB(params) {
804
+ return this.issueInvoiceWithVAT(8 /* NOTA_CREDITO_B */, params);
805
+ }
806
+ /**
807
+ * Emite una Nota de Crédito C.
808
+ * REQUIERE especificar la Factura C original en `associatedInvoices`.
809
+ */
810
+ async issueCreditNoteC(params) {
811
+ return this.issueInvoiceWithoutVAT(13 /* NOTA_CREDITO_C */, params);
812
+ }
813
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
814
+ // Notas de Débito
815
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
816
+ /**
817
+ * Emite una Nota de Débito A.
818
+ * REQUIERE especificar la Factura A original en `associatedInvoices`.
819
+ */
820
+ async issueDebitNoteA(params) {
821
+ return this.issueInvoiceWithVAT(2 /* NOTA_DEBITO_A */, params);
822
+ }
823
+ /**
824
+ * Emite una Nota de Débito B.
825
+ * REQUIERE especificar la Factura B original en `associatedInvoices`.
826
+ */
827
+ async issueDebitNoteB(params) {
828
+ return this.issueInvoiceWithVAT(7 /* NOTA_DEBITO_B */, params);
829
+ }
830
+ /**
831
+ * Emite una Nota de Débito C.
832
+ * REQUIERE especificar la Factura C original en `associatedInvoices`.
833
+ */
834
+ async issueDebitNoteC(params) {
835
+ return this.issueInvoiceWithoutVAT(12 /* NOTA_DEBITO_C */, params);
774
836
  }
775
837
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
776
838
  // Consultas
@@ -901,6 +963,63 @@ var WsfeService = class _WsfeService {
901
963
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
902
964
  // Métodos internos
903
965
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
966
+ /**
967
+ * Helper para emitir comprobantes tipo A/B que requieren IVA
968
+ */
969
+ async issueInvoiceWithVAT(type, params) {
970
+ this.validateItemsWithVAT(params.items);
971
+ this.validateAssociatedInvoices(type, params.associatedInvoices);
972
+ const includesVAT = params.includesVAT || false;
973
+ const vatData = this.calculateVATByRate(params.items, includesVAT);
974
+ return this.issueDocument({
975
+ type,
976
+ concept: params.concept || 1 /* PRODUCTS */,
977
+ items: params.items,
978
+ buyer: params.buyer,
979
+ associatedInvoices: params.associatedInvoices,
980
+ date: params.date,
981
+ vatData,
982
+ includesVAT
983
+ });
984
+ }
985
+ /**
986
+ * Helper para emitir comprobantes tipo C que no discriminan IVA
987
+ */
988
+ async issueInvoiceWithoutVAT(type, params) {
989
+ this.validateAssociatedInvoices(type, params.associatedInvoices);
990
+ const total = round(calculateTotal(params.items));
991
+ return this.issueDocument({
992
+ type,
993
+ concept: params.concept || 1 /* PRODUCTS */,
994
+ total,
995
+ date: params.date,
996
+ buyer: params.buyer || {
997
+ docType: 99 /* FINAL_CONSUMER */,
998
+ docNumber: "0"
999
+ },
1000
+ items: params.items,
1001
+ associatedInvoices: params.associatedInvoices
1002
+ });
1003
+ }
1004
+ /**
1005
+ * Validación obligatoria para NC/ND
1006
+ */
1007
+ validateAssociatedInvoices(type, associatedInvoices) {
1008
+ const needsAssociation = [
1009
+ 3 /* NOTA_CREDITO_A */,
1010
+ 2 /* NOTA_DEBITO_A */,
1011
+ 8 /* NOTA_CREDITO_B */,
1012
+ 7 /* NOTA_DEBITO_B */,
1013
+ 13 /* NOTA_CREDITO_C */,
1014
+ 12 /* NOTA_DEBITO_C */
1015
+ ].includes(type);
1016
+ if (needsAssociation && (!associatedInvoices || associatedInvoices.length === 0)) {
1017
+ throw new ArcaValidationError(
1018
+ "Las Notas de Cr\xE9dito y D\xE9bito requieren al menos un comprobante asociado.",
1019
+ { hint: "Debes enviar el arreglo `associatedInvoices` con la factura original a la cual haces referencia" }
1020
+ );
1021
+ }
1022
+ }
904
1023
  /**
905
1024
  * Método genérico interno para emitir cualquier tipo de comprobante.
906
1025
  */
@@ -925,6 +1044,7 @@ var WsfeService = class _WsfeService {
925
1044
  concept: request.concept,
926
1045
  date: request.date || /* @__PURE__ */ new Date(),
927
1046
  buyer: request.buyer,
1047
+ associatedInvoices: request.associatedInvoices,
928
1048
  net,
929
1049
  vat,
930
1050
  total,
@@ -1070,6 +1190,21 @@ var WsfeService = class _WsfeService {
1070
1190
  });
1071
1191
  vatXml += "\n </ar:Iva>";
1072
1192
  }
1193
+ let asocXml = "";
1194
+ if (params.associatedInvoices && params.associatedInvoices.length > 0) {
1195
+ asocXml = "<ar:CbtesAsoc>";
1196
+ params.associatedInvoices.forEach((asoc) => {
1197
+ asocXml += `
1198
+ <ar:CbteAsoc>
1199
+ <ar:Tipo>${asoc.type}</ar:Tipo>
1200
+ <ar:PtoVta>${asoc.pointOfSale}</ar:PtoVta>
1201
+ <ar:Nro>${asoc.invoiceNumber}</ar:Nro>
1202
+ ${asoc.cuit ? `<ar:Cuit>${asoc.cuit}</ar:Cuit>` : ""}
1203
+ ${asoc.date ? `<ar:CbteFch>${asoc.date.toISOString().split("T")[0].replace(/-/g, "")}</ar:CbteFch>` : ""}
1204
+ </ar:CbteAsoc>`;
1205
+ });
1206
+ asocXml += "\n </ar:CbtesAsoc>";
1207
+ }
1073
1208
  return `<?xml version="1.0" encoding="UTF-8"?>
1074
1209
  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
1075
1210
  xmlns:ar="http://ar.gov.afip.dif.FEV1/">
@@ -1103,6 +1238,7 @@ var WsfeService = class _WsfeService {
1103
1238
  <ar:ImpTrib>0.00</ar:ImpTrib>
1104
1239
  <ar:MonId>PES</ar:MonId>
1105
1240
  <ar:MonCotiz>1</ar:MonCotiz>
1241
+ ${asocXml}
1106
1242
  ${vatXml}
1107
1243
  </ar:FECAEDetRequest>
1108
1244
  </ar:FeDetReq>