sunat-engine-nest 2.0.0 → 2.0.2

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.
Files changed (33) hide show
  1. package/README.md +57 -134
  2. package/dist/index.d.ts +3 -4
  3. package/dist/index.js +5 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/pdf/document-pdf.service.d.ts +4 -33
  6. package/dist/pdf/document-pdf.service.js +113 -369
  7. package/dist/pdf/document-pdf.service.js.map +1 -1
  8. package/dist/soap/fake-sunat-soap.client.js +12 -12
  9. package/dist/soap/sunat-soap.client.js +34 -34
  10. package/dist/sunat-engine.module.d.ts +0 -4
  11. package/dist/sunat-engine.module.js +0 -2
  12. package/dist/sunat-engine.module.js.map +1 -1
  13. package/dist/sunat-engine.service.js +3 -0
  14. package/dist/sunat-engine.service.js.map +1 -1
  15. package/dist/tsconfig.build.tsbuildinfo +1 -0
  16. package/dist/tsconfig.tsbuildinfo +1 -0
  17. package/dist/types/sunat.types.d.ts +12 -0
  18. package/dist/validation/invoice.validator.d.ts +7 -0
  19. package/dist/validation/invoice.validator.js +249 -0
  20. package/dist/validation/invoice.validator.js.map +1 -0
  21. package/dist/xml/despatch.builder.js +26 -8
  22. package/dist/xml/despatch.builder.js.map +1 -1
  23. package/dist/xml/summary.builder.js +1 -0
  24. package/dist/xml/summary.builder.js.map +1 -1
  25. package/dist/xml/voided.builder.js +4 -2
  26. package/dist/xml/voided.builder.js.map +1 -1
  27. package/package.json +50 -50
  28. package/dist/licensing/pdf-license.service.d.ts +0 -11
  29. package/dist/licensing/pdf-license.service.js +0 -51
  30. package/dist/licensing/pdf-license.service.js.map +0 -1
  31. package/dist/licensing/public-key.d.ts +0 -1
  32. package/dist/licensing/public-key.js +0 -8
  33. package/dist/licensing/public-key.js.map +0 -1
@@ -5,428 +5,186 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  };
8
- var __metadata = (this && this.__metadata) || function (k, v) {
9
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
- };
11
- var __param = (this && this.__param) || function (paramIndex, decorator) {
12
- return function (target, key) { decorator(target, key, paramIndex); }
13
- };
14
8
  Object.defineProperty(exports, "__esModule", { value: true });
15
9
  exports.DocumentPdfService = void 0;
16
10
  const common_1 = require("@nestjs/common");
17
11
  const PdfMake = require("pdfmake/build/pdfmake");
18
12
  const PdfFonts = require("pdfmake/build/vfs_fonts");
19
13
  const QRCode = require("qrcode");
20
- const constants_1 = require("../constants");
21
- const pdf_license_service_1 = require("../licensing/pdf-license.service");
22
14
  PdfMake.vfs = PdfFonts.pdfMake?.vfs ?? PdfFonts.vfs;
23
- const DEFAULT_BRAND_COLOR = '#1e40af';
24
15
  const PAGE_SIZES = {
25
16
  A4: [595.28, 841.89],
26
17
  TICKET_80MM: [226.77, 650],
27
18
  STICKER_A6: [419.53, 595.28],
28
19
  };
29
20
  let DocumentPdfService = class DocumentPdfService {
30
- constructor(options = {}, license = new pdf_license_service_1.PdfLicenseService()) {
31
- this.options = options;
32
- this.license = license;
33
- }
34
- async generateInvoice(payload, format = 'A4', opts) {
35
- this.license.assertValid(opts?.licenseKey ?? this.options?.pdf?.licenseKey);
36
- const branding = this.resolveBranding(opts?.branding);
37
- const qrDataUrl = await this.qr(this.buildQrContent(payload));
21
+ async generateInvoice(payload, format = 'A4') {
22
+ const qrContent = this.buildQrContent(payload);
23
+ const qrDataUrl = await QRCode.toDataURL(qrContent, { width: 120, margin: 1 });
38
24
  const docType = payload.tipoDoc === '01' ? 'FACTURA ELECTRÓNICA' : 'BOLETA DE VENTA ELECTRÓNICA';
39
25
  const docId = `${payload.serie}-${payload.correlativo}`;
40
26
  if (format === 'A4') {
41
- return this.generateInvoiceA4(payload, docType, docId, qrDataUrl, branding);
27
+ return this.generateA4(payload, docType, docId, qrDataUrl);
42
28
  }
43
- return this.generateInvoiceTicket(payload, docType, docId, qrDataUrl, format);
44
- }
45
- async generateInvoiceA4(payload, docType, docId, qrDataUrl, branding) {
46
- const docDefinition = {
47
- pageSize: 'A4',
48
- pageMargins: [40, 40, 40, 40],
49
- content: [
50
- ...this.buildHeader(payload.company, docType, docId, branding),
51
- {
52
- table: {
53
- widths: ['auto', '*'],
54
- body: [
55
- [{ text: 'Señor(es):', bold: true }, payload.client.rznSocial],
56
- [{ text: 'Tipo / N° Doc:', bold: true }, `${payload.client.tipoDoc} / ${payload.client.numDoc}`],
57
- [{ text: 'Fecha Emisión:', bold: true }, payload.fechaEmision.slice(0, 10)],
58
- [{ text: 'Moneda:', bold: true }, payload.tipoMoneda],
59
- ],
60
- },
61
- layout: this.cardLayout(),
62
- margin: [0, 0, 0, 12],
63
- },
64
- this.buildItemsTable(payload.details, branding),
65
- this.buildTotalsQrBlock(payload, qrDataUrl, branding, 'TOTAL A PAGAR'),
66
- ...(payload.legends ?? []).map((l) => ({ text: l.value, style: 'legend', margin: [0, 8, 0, 0] })),
67
- ],
68
- styles: this.baseStyles(),
69
- defaultStyle: { fontSize: 9 },
70
- };
71
- return this.renderPdf(docDefinition);
72
- }
73
- async generateInvoiceTicket(payload, docType, docId, qrDataUrl, format) {
74
- const moneda = payload.tipoMoneda;
75
- const isSticker = format === 'STICKER_A6';
76
- const docDefinition = {
77
- pageSize: { width: PAGE_SIZES[format][0], height: 'auto' },
78
- pageMargins: isSticker ? [20, 20, 20, 20] : [10, 10, 10, 10],
79
- content: [
80
- ...this.ticketHeader(payload.company, docType, docId, isSticker),
81
- { text: `Cliente: ${payload.client.rznSocial}`, fontSize: 7 },
82
- { text: `Doc: ${payload.client.numDoc}`, fontSize: 7 },
83
- { text: `Fecha: ${payload.fechaEmision.slice(0, 10)}`, fontSize: 7 },
84
- this.ticketDivider(),
85
- ...payload.details.map((d) => this.ticketDetailRow(d)),
86
- this.ticketDivider(),
87
- ...(payload.mtoOperGravadas ? [this.totRow('OP. GRAVADAS', `${moneda} ${this.fmt(payload.mtoOperGravadas)}`)] : []),
88
- this.totRow('IGV 18%', `${moneda} ${this.fmt(payload.mtoIGV)}`),
89
- this.totRow('TOTAL', `${moneda} ${this.fmt(payload.mtoImpVenta)}`, true),
90
- ...(payload.legends ?? []).map((l) => ({ text: l.value, fontSize: 7, italics: true, margin: [0, 1, 0, 0] })),
91
- { image: qrDataUrl, width: isSticker ? 80 : 60, alignment: 'center', margin: [0, 6, 0, 4] },
92
- { text: 'Comprobante Electrónico - SUNAT', fontSize: 6, alignment: 'center', color: '#888' },
93
- ],
94
- defaultStyle: { fontSize: 8 },
95
- };
96
- return this.renderPdf(docDefinition);
97
- }
98
- async generateNote(payload, format = 'A4', opts) {
99
- this.license.assertValid(opts?.licenseKey ?? this.options?.pdf?.licenseKey);
100
- const branding = this.resolveBranding(opts?.branding);
101
- const qrDataUrl = await this.qr(this.buildQrContent(payload));
102
- const docType = payload.tipoDoc === '07' ? 'NOTA DE CRÉDITO ELECTRÓNICA' : 'NOTA DE DÉBITO ELECTRÓNICA';
103
- const docId = `${payload.serie}-${payload.correlativo}`;
104
- if (format === 'A4') {
105
- return this.generateNoteA4(payload, docType, docId, qrDataUrl, branding);
29
+ else {
30
+ return this.generateTicket(payload, docType, docId, qrDataUrl, format);
106
31
  }
107
- return this.generateNoteTicket(payload, docType, docId, qrDataUrl, format);
108
32
  }
109
- async generateNoteA4(payload, docType, docId, qrDataUrl, branding) {
110
- const motivoLabel = payload.tipoDoc === '07' ? 'Motivo (N. Crédito):' : 'Motivo (N. Débito):';
111
- const docDefinition = {
112
- pageSize: 'A4',
113
- pageMargins: [40, 40, 40, 40],
114
- content: [
115
- ...this.buildHeader(payload.company, docType, docId, branding),
116
- {
117
- table: {
118
- widths: ['auto', '*'],
119
- body: [
120
- [{ text: 'Señor(es):', bold: true }, payload.client.rznSocial],
121
- [{ text: 'Tipo / N° Doc:', bold: true }, `${payload.client.tipoDoc} / ${payload.client.numDoc}`],
122
- [{ text: 'Fecha Emisión:', bold: true }, payload.fechaEmision.slice(0, 10)],
123
- [{ text: 'Moneda:', bold: true }, payload.tipoMoneda],
124
- [{ text: 'Doc. Afectado:', bold: true }, `${payload.tipDocAfectado} ${payload.numDocAfectado}`],
125
- [{ text: motivoLabel, bold: true }, payload.desMotivo],
126
- ],
127
- },
128
- layout: this.cardLayout(),
129
- margin: [0, 0, 0, 12],
130
- },
131
- this.buildItemsTable(payload.details, branding),
132
- this.buildTotalsQrBlock(payload, qrDataUrl, branding, 'TOTAL'),
133
- ...(payload.legends ?? []).map((l) => ({ text: l.value, style: 'legend', margin: [0, 8, 0, 0] })),
134
- ],
135
- styles: this.baseStyles(),
136
- defaultStyle: { fontSize: 9 },
137
- };
138
- return this.renderPdf(docDefinition);
139
- }
140
- async generateNoteTicket(payload, docType, docId, qrDataUrl, format) {
33
+ async generateA4(payload, docType, docId, qrDataUrl) {
141
34
  const moneda = payload.tipoMoneda;
142
- const isSticker = format === 'STICKER_A6';
143
- const docDefinition = {
144
- pageSize: { width: PAGE_SIZES[format][0], height: 'auto' },
145
- pageMargins: isSticker ? [20, 20, 20, 20] : [10, 10, 10, 10],
146
- content: [
147
- ...this.ticketHeader(payload.company, docType, docId, isSticker),
148
- { text: `Afecta a: ${payload.tipDocAfectado} ${payload.numDocAfectado}`, alignment: 'center', fontSize: 7 },
149
- this.ticketDivider(),
150
- { text: `Cliente: ${payload.client.rznSocial}`, fontSize: 7 },
151
- { text: `Doc: ${payload.client.numDoc}`, fontSize: 7 },
152
- { text: `Fecha: ${payload.fechaEmision.slice(0, 10)}`, fontSize: 7 },
153
- { text: `Motivo: ${payload.desMotivo}`, fontSize: 7 },
154
- this.ticketDivider(),
155
- ...payload.details.map((d) => this.ticketDetailRow(d)),
156
- this.ticketDivider(),
157
- ...(payload.mtoOperGravadas ? [this.totRow('OP. GRAVADAS', `${moneda} ${this.fmt(payload.mtoOperGravadas)}`)] : []),
158
- this.totRow('IGV 18%', `${moneda} ${this.fmt(payload.mtoIGV)}`),
159
- this.totRow('TOTAL', `${moneda} ${this.fmt(payload.mtoImpVenta)}`, true),
160
- ...(payload.legends ?? []).map((l) => ({ text: l.value, fontSize: 7, italics: true, margin: [0, 1, 0, 0] })),
161
- { image: qrDataUrl, width: isSticker ? 80 : 60, alignment: 'center', margin: [0, 6, 0, 4] },
162
- { text: 'Comprobante Electrónico - SUNAT', fontSize: 6, alignment: 'center', color: '#888' },
163
- ],
164
- defaultStyle: { fontSize: 8 },
165
- };
166
- return this.renderPdf(docDefinition);
167
- }
168
- async generateDespatch(payload, opts) {
169
- this.license.assertValid(opts?.licenseKey ?? this.options?.pdf?.licenseKey);
170
- const branding = this.resolveBranding(opts?.branding);
171
- const docId = `${payload.serie}-${payload.correlativo}`;
172
- const qrDataUrl = await this.qr([payload.company.ruc, payload.tipoDoc, payload.serie, payload.correlativo].join('|'));
173
- const envio = payload.envio;
174
- const modalidad = envio.modTraslado === '01' ? 'Transporte Público' : 'Transporte Privado';
175
- const chofer = envio.choferes?.[0];
176
- const detailRows = payload.details.map((d, i) => [
35
+ const details = payload.details.map((d, i) => [
177
36
  { text: String(i + 1), alignment: 'center' },
178
37
  { text: d.unidad },
179
38
  { text: String(d.cantidad), alignment: 'right' },
180
39
  { text: d.descripcion },
40
+ { text: this.fmt(d.mtoValorUnitario), alignment: 'right' },
41
+ { text: this.fmt(d.mtoValorVenta), alignment: 'right' },
181
42
  ]);
182
43
  const docDefinition = {
183
44
  pageSize: 'A4',
184
45
  pageMargins: [40, 40, 40, 40],
185
46
  content: [
186
- ...this.buildHeader(payload.company, 'GUÍA DE REMISIÓN ELECTRÓNICA', docId, branding),
187
- {
188
- table: {
189
- widths: ['auto', '*'],
190
- body: [
191
- [{ text: 'Destinatario:', bold: true }, payload.destinatario.rznSocial],
192
- [{ text: 'Tipo / N° Doc:', bold: true }, `${payload.destinatario.tipoDoc} / ${payload.destinatario.numDoc}`],
193
- [{ text: 'Fecha Emisión:', bold: true }, payload.fechaEmision.slice(0, 10)],
194
- ],
195
- },
196
- layout: this.cardLayout(),
197
- margin: [0, 0, 0, 10],
198
- },
199
- {
200
- table: {
201
- widths: ['auto', '*', 'auto', '*'],
202
- body: [
203
- [{ text: 'Motivo:', bold: true }, envio.desTraslado, { text: 'Modalidad:', bold: true }, modalidad],
204
- [{ text: 'F. Traslado:', bold: true }, (envio.fecTraslado ?? '').slice(0, 10), { text: 'Peso Total:', bold: true }, `${envio.pesoTotal} ${envio.undPesoTotal}`],
205
- [{ text: 'N° Bultos:', bold: true }, String(envio.numBultos ?? '-'), '', ''],
206
- ],
207
- },
208
- layout: this.cardLayout(),
209
- margin: [0, 0, 0, 10],
210
- },
211
47
  {
212
48
  columns: [
213
49
  {
214
- width: '50%',
50
+ width: '60%',
215
51
  stack: [
216
- { text: 'PUNTO DE PARTIDA', style: 'blockLabel', color: branding.color },
217
- { text: envio.partida.direccion, style: 'small' },
52
+ { text: payload.company.razonSocial, style: 'companyName' },
53
+ { text: payload.company.nombreComercial ?? '', style: 'companyComercial' },
54
+ { text: `RUC: ${payload.company.ruc}`, style: 'small' },
55
+ { text: payload.company.address?.direccion ?? '', style: 'small' },
218
56
  ],
219
57
  },
220
58
  {
221
- width: '50%',
59
+ width: '40%',
60
+ alignment: 'center',
222
61
  stack: [
223
- { text: 'PUNTO DE LLEGADA', style: 'blockLabel', color: branding.color },
224
- { text: envio.llegada.direccion, style: 'small' },
62
+ { text: 'R.U.C. ' + payload.company.ruc, bold: true, margin: [0, 0, 0, 4] },
63
+ { text: docType, style: 'docType' },
64
+ { text: docId, style: 'docId' },
225
65
  ],
66
+ border: [true, true, true, true],
67
+ fillColor: '#f8f8f8',
68
+ margin: [4, 0, 0, 0],
226
69
  },
227
70
  ],
71
+ },
72
+ { canvas: [{ type: 'line', x1: 0, y1: 5, x2: 515, y2: 5, lineWidth: 1 }], margin: [0, 8, 0, 8] },
73
+ {
74
+ table: {
75
+ widths: ['auto', '*'],
76
+ body: [
77
+ [{ text: 'Señor(es):', bold: true }, payload.client.rznSocial],
78
+ [{ text: 'Tipo / N° Doc:', bold: true }, `${payload.client.tipoDoc} / ${payload.client.numDoc}`],
79
+ [{ text: 'Fecha Emisión:', bold: true }, payload.fechaEmision.slice(0, 10)],
80
+ [{ text: 'Moneda:', bold: true }, moneda],
81
+ ],
82
+ },
83
+ layout: 'noBorders',
228
84
  margin: [0, 0, 0, 10],
229
85
  },
230
- ...(envio.transportista || envio.vehiculo || chofer ? [{
231
- table: {
232
- widths: ['auto', '*'],
233
- body: [
234
- ...(envio.transportista ? [[{ text: 'Transportista:', bold: true }, `${envio.transportista.rznSocial} (${envio.transportista.numDoc})`]] : []),
235
- ...(envio.vehiculo ? [[{ text: 'Vehículo:', bold: true }, envio.vehiculo.placa]] : []),
236
- ...(chofer ? [[{ text: 'Conductor:', bold: true }, `${chofer.nombres ?? ''} ${chofer.apellidos ?? ''} - Lic. ${chofer.licencia}`]] : []),
237
- ],
238
- },
239
- layout: this.cardLayout(),
240
- margin: [0, 0, 0, 10],
241
- }] : []),
242
86
  {
243
87
  table: {
244
88
  headerRows: 1,
245
- widths: [20, 40, 40, '*'],
89
+ widths: [20, 35, 40, '*', 60, 60],
246
90
  body: [
247
91
  [
248
92
  { text: '#', style: 'tableHeader' },
249
93
  { text: 'U.M.', style: 'tableHeader' },
250
94
  { text: 'Cant.', style: 'tableHeader', alignment: 'right' },
251
95
  { text: 'Descripción', style: 'tableHeader' },
96
+ { text: 'P.Unit.', style: 'tableHeader', alignment: 'right' },
97
+ { text: 'Total', style: 'tableHeader', alignment: 'right' },
252
98
  ],
253
- ...detailRows,
99
+ ...details,
254
100
  ],
255
101
  },
256
- layout: this.zebraTableLayout(branding),
257
- margin: [0, 0, 0, 12],
102
+ margin: [0, 0, 0, 10],
258
103
  },
259
104
  {
260
- stack: [
261
- { image: qrDataUrl, width: 85, alignment: 'center', margin: [0, 4, 0, 4] },
262
- { text: 'Representación impresa de la Guía de Remisión Electrónica', style: 'tiny', italics: true, alignment: 'center' },
105
+ columns: [
106
+ {
107
+ width: '50%',
108
+ stack: [
109
+ { image: qrDataUrl, width: 100, margin: [0, 10, 0, 4] },
110
+ { text: 'Representación impresa del comprobante electrónico', style: 'tiny', italics: true },
111
+ ],
112
+ },
113
+ {
114
+ width: '50%',
115
+ table: {
116
+ widths: ['*', 70],
117
+ body: [
118
+ ...(payload.mtoOperGravadas ? [
119
+ [{ text: 'OP. GRAVADAS', alignment: 'right' }, { text: `${moneda} ${this.fmt(payload.mtoOperGravadas)}`, alignment: 'right' }],
120
+ ] : []),
121
+ ...(payload.mtoOperExoneradas ? [
122
+ [{ text: 'OP. EXONERADAS', alignment: 'right' }, { text: `${moneda} ${this.fmt(payload.mtoOperExoneradas)}`, alignment: 'right' }],
123
+ ] : []),
124
+ ...(payload.mtoOperInafectas ? [
125
+ [{ text: 'OP. INAFECTAS', alignment: 'right' }, { text: `${moneda} ${this.fmt(payload.mtoOperInafectas)}`, alignment: 'right' }],
126
+ ] : []),
127
+ [{ text: 'IGV (18%)', alignment: 'right' }, { text: `${moneda} ${this.fmt(payload.mtoIGV)}`, alignment: 'right' }],
128
+ [{ text: 'TOTAL A PAGAR', bold: true, alignment: 'right' }, { text: `${moneda} ${this.fmt(payload.mtoImpVenta)}`, bold: true, alignment: 'right' }],
129
+ ],
130
+ },
131
+ layout: 'noBorders',
132
+ margin: [0, 10, 0, 0],
133
+ },
263
134
  ],
264
135
  },
136
+ ...(payload.legends ?? []).map((l) => ({ text: l.value, style: 'legend' })),
265
137
  ],
266
138
  styles: {
267
- ...this.baseStyles(),
268
- blockLabel: { bold: true, fontSize: 8, margin: [0, 0, 0, 3] },
139
+ companyName: { fontSize: 14, bold: true, margin: [0, 0, 0, 2] },
140
+ companyComercial: { fontSize: 10, color: '#555', margin: [0, 0, 0, 2] },
141
+ docType: { fontSize: 10, bold: true, margin: [0, 8, 0, 4] },
142
+ docId: { fontSize: 14, bold: true, color: '#c00' },
143
+ tableHeader: { bold: true, fillColor: '#eeeeee', fontSize: 9 },
144
+ small: { fontSize: 9, color: '#444' },
145
+ tiny: { fontSize: 7, color: '#888' },
146
+ legend: { fontSize: 9, italics: true, margin: [0, 2, 0, 0] },
269
147
  },
270
148
  defaultStyle: { fontSize: 9 },
271
149
  };
272
150
  return this.renderPdf(docDefinition);
273
151
  }
274
- buildHeader(company, docType, docId, branding) {
275
- const brandTint = this.lighten(branding.color, 0.92);
276
- return [
277
- {
278
- columns: [
279
- ...(branding.logoBase64 ? [{ image: branding.logoBase64, width: 64, height: 64, fit: [64, 64] }] : []),
280
- {
281
- width: branding.logoBase64 ? '48%' : '60%',
282
- stack: [
283
- { text: company.razonSocial, style: 'companyName' },
284
- { text: company.nombreComercial ?? '', style: 'companyComercial' },
285
- { text: `RUC: ${company.ruc}`, style: 'small' },
286
- { text: company.address?.direccion ?? '', style: 'small' },
287
- ],
288
- margin: branding.logoBase64 ? [10, 0, 0, 0] : [0, 0, 0, 0],
289
- },
290
- {
291
- width: '40%',
292
- alignment: 'center',
293
- stack: [
294
- { text: 'R.U.C. ' + company.ruc, bold: true, margin: [0, 0, 0, 4] },
295
- { text: docType, style: 'docType' },
296
- { text: docId, bold: true, fontSize: 14, color: branding.color },
297
- ],
298
- border: [true, true, true, true],
299
- borderColor: [branding.color, branding.color, branding.color, branding.color],
300
- fillColor: brandTint,
301
- margin: [4, 6, 0, 6],
302
- },
303
- ],
304
- },
305
- { canvas: [{ type: 'line', x1: 0, y1: 4, x2: 515, y2: 4, lineWidth: 2, lineColor: branding.color }], margin: [0, 10, 0, 10] },
306
- ];
307
- }
308
- buildItemsTable(details, branding) {
309
- const rows = details.map((d, i) => [
310
- { text: String(i + 1), alignment: 'center' },
311
- { text: d.unidad },
312
- { text: String(d.cantidad), alignment: 'right' },
313
- { text: d.descripcion },
314
- { text: this.fmt(d.mtoValorUnitario), alignment: 'right' },
315
- { text: this.fmt(d.mtoValorVenta), alignment: 'right' },
316
- ]);
317
- return {
318
- table: {
319
- headerRows: 1,
320
- widths: [20, 35, 40, '*', 60, 60],
321
- body: [
322
- [
323
- { text: '#', style: 'tableHeader' },
324
- { text: 'U.M.', style: 'tableHeader' },
325
- { text: 'Cant.', style: 'tableHeader', alignment: 'right' },
326
- { text: 'Descripción', style: 'tableHeader' },
327
- { text: 'P.Unit.', style: 'tableHeader', alignment: 'right' },
328
- { text: 'Total', style: 'tableHeader', alignment: 'right' },
329
- ],
330
- ...rows,
331
- ],
332
- },
333
- layout: this.zebraTableLayout(branding),
334
- margin: [0, 0, 0, 12],
335
- };
336
- }
337
- buildTotalsQrBlock(totals, qrDataUrl, branding, totalLabel) {
338
- const moneda = totals.tipoMoneda;
339
- const brandTint = this.lighten(branding.color, 0.92);
340
- const totalRows = [
341
- ...(totals.mtoOperGravadas ? [[{ text: 'OP. GRAVADAS', alignment: 'right' }, { text: `${moneda} ${this.fmt(totals.mtoOperGravadas)}`, alignment: 'right' }]] : []),
342
- ...(totals.mtoOperExoneradas ? [[{ text: 'OP. EXONERADAS', alignment: 'right' }, { text: `${moneda} ${this.fmt(totals.mtoOperExoneradas)}`, alignment: 'right' }]] : []),
343
- ...(totals.mtoOperInafectas ? [[{ text: 'OP. INAFECTAS', alignment: 'right' }, { text: `${moneda} ${this.fmt(totals.mtoOperInafectas)}`, alignment: 'right' }]] : []),
344
- [{ text: 'IGV (18%)', alignment: 'right' }, { text: `${moneda} ${this.fmt(totals.mtoIGV)}`, alignment: 'right' }],
345
- [
346
- { text: totalLabel, bold: true, alignment: 'right', fontSize: 10, fillColor: brandTint, margin: [0, 3, 4, 3] },
347
- { text: `${moneda} ${this.fmt(totals.mtoImpVenta)}`, bold: true, alignment: 'right', fontSize: 10, color: branding.color, fillColor: brandTint, margin: [0, 3, 4, 3] },
348
- ],
349
- ];
350
- return {
351
- columns: [
352
- {
353
- width: '38%',
354
- stack: [
355
- { image: qrDataUrl, width: 85, margin: [0, 4, 0, 4] },
356
- { text: 'Representación impresa del comprobante electrónico', style: 'tiny', italics: true },
152
+ async generateTicket(payload, docType, docId, qrDataUrl, format) {
153
+ const moneda = payload.tipoMoneda;
154
+ const isSticker = format === 'STICKER_A6';
155
+ const docDefinition = {
156
+ pageSize: { width: PAGE_SIZES[format][0], height: 'auto' },
157
+ pageMargins: isSticker ? [20, 20, 20, 20] : [10, 10, 10, 10],
158
+ content: [
159
+ { text: payload.company.razonSocial, bold: true, alignment: 'center', fontSize: isSticker ? 11 : 9 },
160
+ { text: `RUC: ${payload.company.ruc}`, alignment: 'center', fontSize: 8 },
161
+ { text: payload.company.address?.direccion ?? '', alignment: 'center', fontSize: 7 },
162
+ { canvas: [{ type: 'line', x1: 0, y1: 2, x2: 200, y2: 2, lineWidth: 0.5, dash: { length: 3 } }], margin: [0, 3, 0, 3] },
163
+ { text: docType, alignment: 'center', bold: true, fontSize: 9 },
164
+ { text: docId, alignment: 'center', bold: true, fontSize: 11, color: '#c00' },
165
+ { canvas: [{ type: 'line', x1: 0, y1: 2, x2: 200, y2: 2, lineWidth: 0.5, dash: { length: 3 } }], margin: [0, 3, 0, 3] },
166
+ { text: `Cliente: ${payload.client.rznSocial}`, fontSize: 7 },
167
+ { text: `Doc: ${payload.client.numDoc}`, fontSize: 7 },
168
+ { text: `Fecha: ${payload.fechaEmision.slice(0, 10)}`, fontSize: 7 },
169
+ { canvas: [{ type: 'line', x1: 0, y1: 2, x2: 200, y2: 2, lineWidth: 0.5 }], margin: [0, 3, 0, 3] },
170
+ ...payload.details.map((d) => ({
171
+ columns: [
172
+ { text: `${d.descripcion}\n${d.cantidad} ${d.unidad} x ${this.fmt(d.mtoValorUnitario)}`, width: '*', fontSize: 7 },
173
+ { text: this.fmt(d.mtoValorVenta), width: 45, alignment: 'right', fontSize: 7 },
357
174
  ],
358
- },
359
- {
360
- width: '62%',
361
- table: { widths: ['*', 80], body: totalRows },
362
- layout: 'noBorders',
363
- },
364
- ],
365
- };
366
- }
367
- cardLayout() {
368
- return {
369
- hLineWidth: () => 0,
370
- vLineWidth: () => 0,
371
- paddingLeft: () => 6,
372
- paddingRight: () => 6,
373
- paddingTop: () => 3,
374
- paddingBottom: () => 3,
375
- fillColor: () => '#f7f8fa',
376
- };
377
- }
378
- zebraTableLayout(branding) {
379
- return {
380
- hLineWidth: (i, node) => (i === 0 || i === node.table.body.length) ? 0 : 0.5,
381
- vLineWidth: () => 0,
382
- hLineColor: () => '#e5e7eb',
383
- paddingTop: () => 4,
384
- paddingBottom: () => 4,
385
- fillColor: (rowIndex) => rowIndex === 0 ? branding.color : (rowIndex % 2 === 0 ? '#f7f8fa' : null),
386
- };
387
- }
388
- baseStyles() {
389
- return {
390
- companyName: { fontSize: 14, bold: true, color: '#1a1a1a', margin: [0, 0, 0, 2] },
391
- companyComercial: { fontSize: 10, color: '#555', margin: [0, 0, 0, 2] },
392
- docType: { fontSize: 10, bold: true, margin: [0, 8, 0, 4] },
393
- tableHeader: { bold: true, color: '#ffffff', fontSize: 9 },
394
- small: { fontSize: 9, color: '#444' },
395
- tiny: { fontSize: 7, color: '#888' },
396
- legend: { fontSize: 9, italics: true },
397
- };
398
- }
399
- resolveBranding(branding) {
400
- return {
401
- logoBase64: branding?.logoBase64 ?? '',
402
- color: branding?.color ?? DEFAULT_BRAND_COLOR,
403
- };
404
- }
405
- ticketHeader(company, docType, docId, isSticker) {
406
- return [
407
- { text: company.razonSocial, bold: true, alignment: 'center', fontSize: isSticker ? 11 : 9 },
408
- { text: `RUC: ${company.ruc}`, alignment: 'center', fontSize: 8 },
409
- { text: company.address?.direccion ?? '', alignment: 'center', fontSize: 7 },
410
- this.ticketDivider(true),
411
- { text: docType, alignment: 'center', bold: true, fontSize: 9 },
412
- { text: docId, alignment: 'center', bold: true, fontSize: 11, color: '#c00' },
413
- this.ticketDivider(true),
414
- ];
415
- }
416
- ticketDivider(dashed = false) {
417
- return {
418
- canvas: [{ type: 'line', x1: 0, y1: 2, x2: 200, y2: 2, lineWidth: 0.5, ...(dashed ? { dash: { length: 3 } } : {}) }],
419
- margin: [0, 3, 0, 3],
420
- };
421
- }
422
- ticketDetailRow(d) {
423
- return {
424
- columns: [
425
- { text: `${d.descripcion}\n${d.cantidad} ${d.unidad} x ${this.fmt(d.mtoValorUnitario)}`, width: '*', fontSize: 7 },
426
- { text: this.fmt(d.mtoValorVenta), width: 45, alignment: 'right', fontSize: 7 },
175
+ margin: [0, 1, 0, 1],
176
+ })),
177
+ { canvas: [{ type: 'line', x1: 0, y1: 2, x2: 200, y2: 2, lineWidth: 0.5 }], margin: [0, 3, 0, 3] },
178
+ ...(payload.mtoOperGravadas ? [this.totRow('OP. GRAVADAS', `${moneda} ${this.fmt(payload.mtoOperGravadas)}`)] : []),
179
+ this.totRow('IGV 18%', `${moneda} ${this.fmt(payload.mtoIGV)}`),
180
+ this.totRow('TOTAL', `${moneda} ${this.fmt(payload.mtoImpVenta)}`, true),
181
+ ...(payload.legends ?? []).map((l) => ({ text: l.value, fontSize: 7, italics: true, margin: [0, 1, 0, 0] })),
182
+ { image: qrDataUrl, width: isSticker ? 80 : 60, alignment: 'center', margin: [0, 6, 0, 4] },
183
+ { text: 'Comprobante Electrónico - SUNAT', fontSize: 6, alignment: 'center', color: '#888' },
427
184
  ],
428
- margin: [0, 1, 0, 1],
185
+ defaultStyle: { fontSize: 8 },
429
186
  };
187
+ return this.renderPdf(docDefinition);
430
188
  }
431
189
  totRow(label, value, bold = false) {
432
190
  return {
@@ -451,20 +209,9 @@ let DocumentPdfService = class DocumentPdfService {
451
209
  '',
452
210
  ].join('|');
453
211
  }
454
- async qr(content) {
455
- return QRCode.toDataURL(content, { width: 120, margin: 1 });
456
- }
457
212
  fmt(n) {
458
213
  return (n ?? 0).toFixed(2);
459
214
  }
460
- lighten(hex, amount) {
461
- const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
462
- if (!m)
463
- return hex;
464
- const mix = (c) => Math.round(c + (255 - c) * amount);
465
- const [r, g, b] = [m[1], m[2], m[3]].map((h) => mix(parseInt(h, 16)));
466
- return `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;
467
- }
468
215
  renderPdf(docDefinition) {
469
216
  return new Promise((resolve, reject) => {
470
217
  const pdfDoc = PdfMake.createPdf(docDefinition);
@@ -478,9 +225,6 @@ let DocumentPdfService = class DocumentPdfService {
478
225
  };
479
226
  exports.DocumentPdfService = DocumentPdfService;
480
227
  exports.DocumentPdfService = DocumentPdfService = __decorate([
481
- (0, common_1.Injectable)(),
482
- __param(0, (0, common_1.Optional)()),
483
- __param(0, (0, common_1.Inject)(constants_1.SUNAT_ENGINE_OPTIONS)),
484
- __metadata("design:paramtypes", [Object, pdf_license_service_1.PdfLicenseService])
228
+ (0, common_1.Injectable)()
485
229
  ], DocumentPdfService);
486
230
  //# sourceMappingURL=document-pdf.service.js.map