@timbra-ec/pdf 0.1.0-dev.20260403050717

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 (64) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/dist/generate-ride-pdf.d.ts +3 -0
  3. package/dist/generate-ride-pdf.d.ts.map +1 -0
  4. package/dist/generate-ride-pdf.js +103 -0
  5. package/dist/generate-ride-pdf.js.map +1 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +3 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/layout/additional-info.d.ts +6 -0
  11. package/dist/layout/additional-info.d.ts.map +1 -0
  12. package/dist/layout/additional-info.js +35 -0
  13. package/dist/layout/additional-info.js.map +1 -0
  14. package/dist/layout/buyer-info.d.ts +4 -0
  15. package/dist/layout/buyer-info.d.ts.map +1 -0
  16. package/dist/layout/buyer-info.js +41 -0
  17. package/dist/layout/buyer-info.js.map +1 -0
  18. package/dist/layout/footer.d.ts +3 -0
  19. package/dist/layout/footer.d.ts.map +1 -0
  20. package/dist/layout/footer.js +30 -0
  21. package/dist/layout/footer.js.map +1 -0
  22. package/dist/layout/header.d.ts +4 -0
  23. package/dist/layout/header.d.ts.map +1 -0
  24. package/dist/layout/header.js +110 -0
  25. package/dist/layout/header.js.map +1 -0
  26. package/dist/layout/items-table.d.ts +4 -0
  27. package/dist/layout/items-table.d.ts.map +1 -0
  28. package/dist/layout/items-table.js +53 -0
  29. package/dist/layout/items-table.js.map +1 -0
  30. package/dist/layout/payment-info.d.ts +4 -0
  31. package/dist/layout/payment-info.d.ts.map +1 -0
  32. package/dist/layout/payment-info.js +50 -0
  33. package/dist/layout/payment-info.js.map +1 -0
  34. package/dist/layout/tax-summary.d.ts +4 -0
  35. package/dist/layout/tax-summary.d.ts.map +1 -0
  36. package/dist/layout/tax-summary.js +65 -0
  37. package/dist/layout/tax-summary.js.map +1 -0
  38. package/dist/parse-authorized-xml.d.ts +7 -0
  39. package/dist/parse-authorized-xml.d.ts.map +1 -0
  40. package/dist/parse-authorized-xml.js +96 -0
  41. package/dist/parse-authorized-xml.js.map +1 -0
  42. package/dist/qr.d.ts +6 -0
  43. package/dist/qr.d.ts.map +1 -0
  44. package/dist/qr.js +14 -0
  45. package/dist/qr.js.map +1 -0
  46. package/dist/types.d.ts +74 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +2 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +37 -0
  51. package/scripts/test-ride.mjs +161 -0
  52. package/src/generate-ride-pdf.ts +119 -0
  53. package/src/index.ts +10 -0
  54. package/src/layout/additional-info.ts +37 -0
  55. package/src/layout/buyer-info.ts +46 -0
  56. package/src/layout/footer.ts +31 -0
  57. package/src/layout/header.ts +125 -0
  58. package/src/layout/items-table.ts +58 -0
  59. package/src/layout/payment-info.ts +55 -0
  60. package/src/layout/tax-summary.ts +75 -0
  61. package/src/parse-authorized-xml.ts +115 -0
  62. package/src/qr.ts +18 -0
  63. package/src/types.ts +90 -0
  64. package/tsconfig.json +8 -0
@@ -0,0 +1,65 @@
1
+ export function buildTaxSummary(data) {
2
+ const rows = [];
3
+ // Group by IVA rate
4
+ for (const imp of data.impuestos) {
5
+ const rateLabel = imp.tarifa > 0 ? `SUBTOTAL ${imp.tarifa}%` : 'SUBTOTAL 0%';
6
+ rows.push([
7
+ { text: rateLabel, style: 'summaryLabel' },
8
+ { text: fmt(imp.baseImponible), style: 'summaryValue', alignment: 'right' },
9
+ ]);
10
+ }
11
+ rows.push([
12
+ { text: 'SUBTOTAL SIN IMPUESTOS', style: 'summaryLabel' },
13
+ { text: fmt(data.totalSinImpuestos), style: 'summaryValue', alignment: 'right' },
14
+ ]);
15
+ rows.push([
16
+ { text: 'DESCUENTO', style: 'summaryLabel' },
17
+ { text: fmt(data.totalDescuento), style: 'summaryValue', alignment: 'right' },
18
+ ]);
19
+ // IVA totals
20
+ for (const imp of data.impuestos) {
21
+ if (imp.valor > 0) {
22
+ rows.push([
23
+ { text: `IVA ${imp.tarifa}%`, style: 'summaryLabel' },
24
+ { text: fmt(imp.valor), style: 'summaryValue', alignment: 'right' },
25
+ ]);
26
+ }
27
+ }
28
+ if (data.propina > 0) {
29
+ rows.push([
30
+ { text: 'PROPINA', style: 'summaryLabel' },
31
+ { text: fmt(data.propina), style: 'summaryValue', alignment: 'right' },
32
+ ]);
33
+ }
34
+ rows.push([
35
+ { text: 'VALOR TOTAL', style: 'summaryLabelBold' },
36
+ { text: fmt(data.importeTotal), style: 'summaryValueBold', alignment: 'right' },
37
+ ]);
38
+ return {
39
+ columns: [
40
+ { width: '*', text: '' },
41
+ {
42
+ width: 220,
43
+ table: {
44
+ widths: ['*', 70],
45
+ body: rows,
46
+ },
47
+ layout: {
48
+ hLineWidth: () => 0.5,
49
+ vLineWidth: () => 0.5,
50
+ hLineColor: () => '#d4d4d8',
51
+ vLineColor: () => '#d4d4d8',
52
+ paddingTop: () => 3,
53
+ paddingBottom: () => 3,
54
+ paddingLeft: () => 6,
55
+ paddingRight: () => 6,
56
+ },
57
+ },
58
+ ],
59
+ margin: [0, 0, 0, 16],
60
+ };
61
+ }
62
+ function fmt(n) {
63
+ return n.toFixed(2);
64
+ }
65
+ //# sourceMappingURL=tax-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tax-summary.js","sourceRoot":"","sources":["../../src/layout/tax-summary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { RideData } from './types';
2
+ export declare function parseComprobanteXml(xml: string, authData: {
3
+ numeroAutorizacion: string;
4
+ fechaAutorizacion: string;
5
+ ambiente: string;
6
+ }): RideData;
7
+ //# sourceMappingURL=parse-authorized-xml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-authorized-xml.d.ts","sourceRoot":"","sources":["../src/parse-authorized-xml.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAuC,MAAM,SAAS,CAAA;AAW5E,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE;IAAE,kBAAkB,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACpF,QAAQ,CAyFV"}
@@ -0,0 +1,96 @@
1
+ import { XMLParser } from 'fast-xml-parser';
2
+ const parser = new XMLParser({
3
+ ignoreAttributes: false,
4
+ attributeNamePrefix: '@_',
5
+ isArray: (name) => {
6
+ const arrayElements = ['detalle', 'totalImpuesto', 'impuesto', 'pago', 'campoAdicional'];
7
+ return arrayElements.includes(name);
8
+ },
9
+ });
10
+ export function parseComprobanteXml(xml, authData) {
11
+ const parsed = parser.parse(xml);
12
+ const factura = parsed.factura;
13
+ if (!factura) {
14
+ throw new Error('Invalid XML: missing <factura> root element');
15
+ }
16
+ const infoTrib = factura.infoTributaria;
17
+ const infoFact = factura.infoFactura;
18
+ const detalles = factura.detalles?.detalle ?? [];
19
+ const pagos = infoFact.pagos?.pago ?? [];
20
+ const camposAdicionales = factura.infoAdicional?.campoAdicional ?? [];
21
+ // Parse totalConImpuestos
22
+ const totalImpuestos = toArray(infoFact.totalConImpuestos?.totalImpuesto).map((imp) => ({
23
+ codigo: String(imp.codigo),
24
+ codigoPorcentaje: String(imp.codigoPorcentaje),
25
+ tarifa: toNumber(imp.tarifa),
26
+ baseImponible: toNumber(imp.baseImponible),
27
+ valor: toNumber(imp.valor),
28
+ }));
29
+ // Parse line items
30
+ const items = toArray(detalles).map((det) => ({
31
+ codigoPrincipal: String(det.codigoPrincipal),
32
+ descripcion: String(det.descripcion),
33
+ cantidad: toNumber(det.cantidad),
34
+ precioUnitario: toNumber(det.precioUnitario),
35
+ descuento: toNumber(det.descuento ?? 0),
36
+ precioTotalSinImpuesto: toNumber(det.precioTotalSinImpuesto),
37
+ }));
38
+ // Parse payments
39
+ const parsedPagos = toArray(pagos).map((p) => ({
40
+ formaPago: String(p.formaPago),
41
+ total: toNumber(p.total),
42
+ plazo: p.plazo != null ? toNumber(p.plazo) : null,
43
+ unidadTiempo: p.unidadTiempo != null ? String(p.unidadTiempo) : null,
44
+ }));
45
+ // Parse infoAdicional
46
+ const infoAdicional = toArray(camposAdicionales).map((campo) => ({
47
+ nombre: String(campo['@_nombre'] ?? ''),
48
+ valor: String(campo['#text'] ?? campo),
49
+ }));
50
+ return {
51
+ ruc: String(infoTrib.ruc),
52
+ razonSocial: String(infoTrib.razonSocial),
53
+ nombreComercial: infoTrib.nombreComercial ? String(infoTrib.nombreComercial) : null,
54
+ direccionMatriz: String(infoTrib.dirMatriz),
55
+ direccionEstablecimiento: infoTrib.dirEstablecimiento
56
+ ? String(infoTrib.dirEstablecimiento)
57
+ : null,
58
+ contribuyenteEspecial: infoTrib.contribuyenteEspecial
59
+ ? String(infoTrib.contribuyenteEspecial)
60
+ : null,
61
+ obligadoContabilidad: String(infoTrib.obligadoContabilidad).toUpperCase() === 'SI',
62
+ regimenRimpe: infoTrib.contribuyenteRimpe ? String(infoTrib.contribuyenteRimpe) : null,
63
+ codDoc: String(infoTrib.codDoc),
64
+ estab: String(infoTrib.estab),
65
+ ptoEmi: String(infoTrib.ptoEmi),
66
+ secuencial: String(infoTrib.secuencial),
67
+ claveAcceso: String(infoTrib.claveAcceso),
68
+ numeroAutorizacion: authData.numeroAutorizacion,
69
+ fechaAutorizacion: authData.fechaAutorizacion,
70
+ ambiente: authData.ambiente,
71
+ fechaEmision: String(infoFact.fechaEmision),
72
+ tipoIdentificacionComprador: String(infoFact.tipoIdentificacionComprador),
73
+ identificacionComprador: String(infoFact.identificacionComprador),
74
+ razonSocialComprador: String(infoFact.razonSocialComprador),
75
+ direccionComprador: infoFact.direccionComprador ? String(infoFact.direccionComprador) : null,
76
+ totalSinImpuestos: toNumber(infoFact.totalSinImpuestos),
77
+ totalDescuento: toNumber(infoFact.totalDescuento),
78
+ importeTotal: toNumber(infoFact.importeTotal),
79
+ moneda: String(infoFact.moneda ?? 'DOLAR'),
80
+ propina: toNumber(infoFact.propina ?? 0),
81
+ impuestos: totalImpuestos,
82
+ items,
83
+ pagos: parsedPagos,
84
+ infoAdicional,
85
+ };
86
+ }
87
+ function toNumber(value) {
88
+ const n = Number(value);
89
+ return Number.isNaN(n) ? 0 : n;
90
+ }
91
+ function toArray(value) {
92
+ if (value == null)
93
+ return [];
94
+ return Array.isArray(value) ? value : [value];
95
+ }
96
+ //# sourceMappingURL=parse-authorized-xml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-authorized-xml.js","sourceRoot":"","sources":["../src/parse-authorized-xml.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAG3C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,gBAAgB,EAAE,KAAK;IACvB,mBAAmB,EAAE,IAAI;IACzB,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;QAChB,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAA;QACxF,OAAO,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IACrC,CAAC;CACF,CAAC,CAAA;AAEF,MAAM,UAAU,mBAAmB,CACjC,GAAW,EACX,QAAqF;IAErF,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAChC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;IAE9B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAChE,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAA;IACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAA;IACpC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,OAAO,IAAI,EAAE,CAAA;IAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE,CAAA;IACxC,MAAM,iBAAiB,GAAG,OAAO,CAAC,aAAa,EAAE,cAAc,IAAI,EAAE,CAAA;IAErE,0BAA0B;IAC1B,MAAM,cAAc,GAAmB,OAAO,CAAC,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC,GAAG,CAC3F,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACR,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC;QAC9C,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;QAC5B,aAAa,EAAE,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC;QAC1C,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;KAC3B,CAAC,CACH,CAAA;IAED,mBAAmB;IACnB,MAAM,KAAK,GAAe,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACxD,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;QAC5C,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;QACpC,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;QAChC,cAAc,EAAE,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC;QAC5C,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC;QACvC,sBAAsB,EAAE,QAAQ,CAAC,GAAG,CAAC,sBAAsB,CAAC;KAC7D,CAAC,CAAC,CAAA;IAEH,iBAAiB;IACjB,MAAM,WAAW,GAAkB,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5D,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9B,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;QACxB,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;QACjD,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI;KACrE,CAAC,CAAC,CAAA;IAEH,sBAAsB;IACtB,MAAM,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC/D,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACvC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC;KACvC,CAAC,CAAC,CAAA;IAEH,OAAO;QACL,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;QACzC,eAAe,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,IAAI;QACnF,eAAe,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC3C,wBAAwB,EAAE,QAAQ,CAAC,kBAAkB;YACnD,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC;YACrC,CAAC,CAAC,IAAI;QACR,qBAAqB,EAAE,QAAQ,CAAC,qBAAqB;YACnD,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC;YACxC,CAAC,CAAC,IAAI;QACR,oBAAoB,EAAE,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,WAAW,EAAE,KAAK,IAAI;QAClF,YAAY,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI;QAEtF,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC/B,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC7B,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;QACvC,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;QAEzC,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;QAC/C,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB;QAC7C,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAE3B,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC3C,2BAA2B,EAAE,MAAM,CAAC,QAAQ,CAAC,2BAA2B,CAAC;QACzE,uBAAuB,EAAE,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QACjE,oBAAoB,EAAE,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QAC3D,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI;QAC5F,iBAAiB,EAAE,QAAQ,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QACvD,cAAc,EAAE,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC;QACjD,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC7C,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,OAAO,CAAC;QAC1C,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;QAExC,SAAS,EAAE,cAAc;QACzB,KAAK;QACL,KAAK,EAAE,WAAW;QAClB,aAAa;KACd,CAAA;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACvB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAChC,CAAC;AAED,SAAS,OAAO,CAAI,KAAiC;IACnD,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,EAAE,CAAA;IAC5B,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;AAC/C,CAAC"}
package/dist/qr.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Generates a QR code as a base64 PNG data URL.
3
+ * Content: claveAcceso|numeroAutorizacion|fechaAutorizacion
4
+ */
5
+ export declare function generateQrDataUrl(claveAcceso: string, numeroAutorizacion: string, fechaAutorizacion: string): Promise<string>;
6
+ //# sourceMappingURL=qr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.d.ts","sourceRoot":"","sources":["../src/qr.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,MAAM,CAAC,CAOjB"}
package/dist/qr.js ADDED
@@ -0,0 +1,14 @@
1
+ import QRCode from 'qrcode';
2
+ /**
3
+ * Generates a QR code as a base64 PNG data URL.
4
+ * Content: claveAcceso|numeroAutorizacion|fechaAutorizacion
5
+ */
6
+ export async function generateQrDataUrl(claveAcceso, numeroAutorizacion, fechaAutorizacion) {
7
+ const content = `${claveAcceso}|${numeroAutorizacion}|${fechaAutorizacion}`;
8
+ return QRCode.toDataURL(content, {
9
+ width: 150,
10
+ margin: 1,
11
+ errorCorrectionLevel: 'M',
12
+ });
13
+ }
14
+ //# sourceMappingURL=qr.js.map
package/dist/qr.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.js","sourceRoot":"","sources":["../src/qr.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAE3B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,WAAmB,EACnB,kBAA0B,EAC1B,iBAAyB;IAEzB,MAAM,OAAO,GAAG,GAAG,WAAW,IAAI,kBAAkB,IAAI,iBAAiB,EAAE,CAAA;IAC3E,OAAO,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE;QAC/B,KAAK,EAAE,GAAG;QACV,MAAM,EAAE,CAAC;QACT,oBAAoB,EAAE,GAAG;KAC1B,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,74 @@
1
+ /** Parameters for RIDE PDF generation */
2
+ export interface GenerateRidePdfParams {
3
+ /** Inner <factura> XML string from SRI authorization */
4
+ comprobante: string;
5
+ /** SRI authorization number */
6
+ numeroAutorizacion: string;
7
+ /** SRI authorization date/time string */
8
+ fechaAutorizacion: string;
9
+ /** SRI environment: '1' = pruebas, '2' = produccion */
10
+ ambiente: string;
11
+ }
12
+ /** Options for RIDE PDF generation */
13
+ export interface GenerateRidePdfOptions {
14
+ /** Organization logo as PNG or JPEG bytes */
15
+ orgLogo?: Uint8Array | null;
16
+ }
17
+ /** Parsed invoice data from SRI authorized XML */
18
+ export interface RideData {
19
+ ruc: string;
20
+ razonSocial: string;
21
+ nombreComercial: string | null;
22
+ direccionMatriz: string;
23
+ direccionEstablecimiento: string | null;
24
+ contribuyenteEspecial: string | null;
25
+ obligadoContabilidad: boolean;
26
+ regimenRimpe: string | null;
27
+ codDoc: string;
28
+ estab: string;
29
+ ptoEmi: string;
30
+ secuencial: string;
31
+ claveAcceso: string;
32
+ numeroAutorizacion: string;
33
+ fechaAutorizacion: string;
34
+ ambiente: string;
35
+ fechaEmision: string;
36
+ tipoIdentificacionComprador: string;
37
+ identificacionComprador: string;
38
+ razonSocialComprador: string;
39
+ direccionComprador: string | null;
40
+ totalSinImpuestos: number;
41
+ totalDescuento: number;
42
+ importeTotal: number;
43
+ moneda: string;
44
+ propina: number;
45
+ impuestos: RideTaxTotal[];
46
+ items: RideItem[];
47
+ pagos: RidePayment[];
48
+ infoAdicional: {
49
+ nombre: string;
50
+ valor: string;
51
+ }[];
52
+ }
53
+ export interface RideTaxTotal {
54
+ codigo: string;
55
+ codigoPorcentaje: string;
56
+ tarifa: number;
57
+ baseImponible: number;
58
+ valor: number;
59
+ }
60
+ export interface RideItem {
61
+ codigoPrincipal: string;
62
+ descripcion: string;
63
+ cantidad: number;
64
+ precioUnitario: number;
65
+ descuento: number;
66
+ precioTotalSinImpuesto: number;
67
+ }
68
+ export interface RidePayment {
69
+ formaPago: string;
70
+ total: number;
71
+ plazo: number | null;
72
+ unidadTiempo: string | null;
73
+ }
74
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,MAAM,WAAW,qBAAqB;IACpC,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAA;IACnB,+BAA+B;IAC/B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,yCAAyC;IACzC,iBAAiB,EAAE,MAAM,CAAA;IACzB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACrC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;CAC5B;AAED,kDAAkD;AAClD,MAAM,WAAW,QAAQ;IAEvB,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,eAAe,EAAE,MAAM,CAAA;IACvB,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,oBAAoB,EAAE,OAAO,CAAA;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAG3B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IAGnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,iBAAiB,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAGhB,YAAY,EAAE,MAAM,CAAA;IACpB,2BAA2B,EAAE,MAAM,CAAA;IACnC,uBAAuB,EAAE,MAAM,CAAA;IAC/B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IAGf,SAAS,EAAE,YAAY,EAAE,CAAA;IAGzB,KAAK,EAAE,QAAQ,EAAE,CAAA;IAGjB,KAAK,EAAE,WAAW,EAAE,CAAA;IAGpB,aAAa,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CACnD;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,EAAE,MAAM,CAAA;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,eAAe,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,sBAAsB,EAAE,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@timbra-ec/pdf",
3
+ "version": "0.1.0-dev.20260403050717",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/timbra-ec/timbra-app.git",
10
+ "directory": "packages/pdf"
11
+ },
12
+ "type": "module",
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ }
20
+ },
21
+ "dependencies": {
22
+ "fast-xml-parser": "^5.5.9",
23
+ "pdfmake": "^0.3.7",
24
+ "qrcode": "^1.5.4",
25
+ "@timbra-ec/types": "0.1.0-dev.20260403050717"
26
+ },
27
+ "devDependencies": {
28
+ "@types/pdfmake": "^0.3.2",
29
+ "@types/qrcode": "^1.5.6",
30
+ "typescript": "^5.8.2"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "typecheck": "tsc --noEmit",
35
+ "lint": "echo 'no lint configured yet'"
36
+ }
37
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Manual verification script for RIDE PDF generation.
3
+ * Run: node packages/pdf/scripts/test-ride.mjs
4
+ * Output: /tmp/timbra-test-ride.pdf
5
+ */
6
+ import { writeFileSync } from 'node:fs'
7
+ import { generateRidePdf } from '../src/generate-ride-pdf.ts'
8
+
9
+ const sampleFacturaXml = `<?xml version="1.0" encoding="UTF-8"?>
10
+ <factura id="comprobante" version="1.0.0">
11
+ <infoTributaria>
12
+ <ambiente>1</ambiente>
13
+ <tipoEmision>1</tipoEmision>
14
+ <razonSocial>ACME DISTRIBUCIONES S.A.</razonSocial>
15
+ <nombreComercial>ACME Ecuador</nombreComercial>
16
+ <ruc>1790016919001</ruc>
17
+ <claveAcceso>2803202601179001691900110010010000000421234567817</claveAcceso>
18
+ <codDoc>01</codDoc>
19
+ <estab>001</estab>
20
+ <ptoEmi>001</ptoEmi>
21
+ <secuencial>000000042</secuencial>
22
+ <dirMatriz>AV. AMAZONAS N37-29 Y VILLALENGUA</dirMatriz>
23
+ <dirEstablecimiento>AV. AMAZONAS N37-29 Y VILLALENGUA</dirEstablecimiento>
24
+ <contribuyenteEspecial>5368</contribuyenteEspecial>
25
+ <obligadoContabilidad>SI</obligadoContabilidad>
26
+ <contribuyenteRimpe>CONTRIBUYENTE RIMPE - EMPRENDEDOR</contribuyenteRimpe>
27
+ </infoTributaria>
28
+ <infoFactura>
29
+ <fechaEmision>28/03/2026</fechaEmision>
30
+ <dirEstablecimiento>AV. AMAZONAS N37-29 Y VILLALENGUA</dirEstablecimiento>
31
+ <obligadoContabilidad>SI</obligadoContabilidad>
32
+ <tipoIdentificacionComprador>04</tipoIdentificacionComprador>
33
+ <razonSocialComprador>JUAN PEREZ LOPEZ</razonSocialComprador>
34
+ <identificacionComprador>1712345678001</identificacionComprador>
35
+ <direccionComprador>CALLE FALSA 123, QUITO</direccionComprador>
36
+ <totalSinImpuestos>100.00</totalSinImpuestos>
37
+ <totalDescuento>5.00</totalDescuento>
38
+ <totalConImpuestos>
39
+ <totalImpuesto>
40
+ <codigo>2</codigo>
41
+ <codigoPorcentaje>4</codigoPorcentaje>
42
+ <baseImponible>95.00</baseImponible>
43
+ <tarifa>15</tarifa>
44
+ <valor>14.25</valor>
45
+ </totalImpuesto>
46
+ <totalImpuesto>
47
+ <codigo>2</codigo>
48
+ <codigoPorcentaje>0</codigoPorcentaje>
49
+ <baseImponible>5.00</baseImponible>
50
+ <tarifa>0</tarifa>
51
+ <valor>0.00</valor>
52
+ </totalImpuesto>
53
+ </totalConImpuestos>
54
+ <propina>0.00</propina>
55
+ <importeTotal>109.25</importeTotal>
56
+ <moneda>DOLAR</moneda>
57
+ <pagos>
58
+ <pago>
59
+ <formaPago>01</formaPago>
60
+ <total>50.00</total>
61
+ </pago>
62
+ <pago>
63
+ <formaPago>19</formaPago>
64
+ <total>59.25</total>
65
+ <plazo>30</plazo>
66
+ <unidadTiempo>dias</unidadTiempo>
67
+ </pago>
68
+ </pagos>
69
+ </infoFactura>
70
+ <detalles>
71
+ <detalle>
72
+ <codigoPrincipal>PROD001</codigoPrincipal>
73
+ <descripcion>Laptop HP ProBook 450 G10</descripcion>
74
+ <cantidad>1</cantidad>
75
+ <precioUnitario>80.00</precioUnitario>
76
+ <descuento>5.00</descuento>
77
+ <precioTotalSinImpuesto>75.00</precioTotalSinImpuesto>
78
+ <impuestos>
79
+ <impuesto>
80
+ <codigo>2</codigo>
81
+ <codigoPorcentaje>4</codigoPorcentaje>
82
+ <tarifa>15</tarifa>
83
+ <baseImponible>75.00</baseImponible>
84
+ <valor>11.25</valor>
85
+ </impuesto>
86
+ </impuestos>
87
+ </detalle>
88
+ <detalle>
89
+ <codigoPrincipal>PROD002</codigoPrincipal>
90
+ <descripcion>Mouse inalambrico Logitech MX Master 3</descripcion>
91
+ <cantidad>2</cantidad>
92
+ <precioUnitario>10.00</precioUnitario>
93
+ <descuento>0.00</descuento>
94
+ <precioTotalSinImpuesto>20.00</precioTotalSinImpuesto>
95
+ <impuestos>
96
+ <impuesto>
97
+ <codigo>2</codigo>
98
+ <codigoPorcentaje>4</codigoPorcentaje>
99
+ <tarifa>15</tarifa>
100
+ <baseImponible>20.00</baseImponible>
101
+ <valor>3.00</valor>
102
+ </impuesto>
103
+ </impuestos>
104
+ </detalle>
105
+ <detalle>
106
+ <codigoPrincipal>SRV001</codigoPrincipal>
107
+ <descripcion>Servicio de configuracion inicial</descripcion>
108
+ <cantidad>1</cantidad>
109
+ <precioUnitario>5.00</precioUnitario>
110
+ <descuento>0.00</descuento>
111
+ <precioTotalSinImpuesto>5.00</precioTotalSinImpuesto>
112
+ <impuestos>
113
+ <impuesto>
114
+ <codigo>2</codigo>
115
+ <codigoPorcentaje>0</codigoPorcentaje>
116
+ <tarifa>0</tarifa>
117
+ <baseImponible>5.00</baseImponible>
118
+ <valor>0.00</valor>
119
+ </impuesto>
120
+ </impuestos>
121
+ </detalle>
122
+ </detalles>
123
+ <infoAdicional>
124
+ <campoAdicional nombre="Email">juan.perez@ejemplo.com</campoAdicional>
125
+ <campoAdicional nombre="Telefono">0991234567</campoAdicional>
126
+ <campoAdicional nombre="Direccion">Calle Falsa 123, Quito, Ecuador</campoAdicional>
127
+ </infoAdicional>
128
+ </factura>`
129
+
130
+ async function main() {
131
+ console.log('Generating test RIDE PDF...')
132
+
133
+ const pdfBytes = await generateRidePdf({
134
+ comprobante: sampleFacturaXml,
135
+ numeroAutorizacion: '2803202601179001691900110010010000000420000000421',
136
+ fechaAutorizacion: '28/03/2026 14:30:45',
137
+ ambiente: '1', // test environment — should show watermark
138
+ })
139
+
140
+ const outPath = '/tmp/timbra-test-ride.pdf'
141
+ writeFileSync(outPath, pdfBytes)
142
+ console.log(`PDF generated: ${outPath} (${pdfBytes.length} bytes)`)
143
+ console.log('PDF starts with:', String.fromCharCode(...pdfBytes.slice(0, 5)))
144
+
145
+ // Verify it's a valid PDF (magic bytes)
146
+ const isPdf =
147
+ pdfBytes[0] === 0x25 && // %
148
+ pdfBytes[1] === 0x50 && // P
149
+ pdfBytes[2] === 0x44 && // D
150
+ pdfBytes[3] === 0x46 // F
151
+ console.log(`Valid PDF: ${isPdf}`)
152
+
153
+ if (!isPdf) {
154
+ process.exit(1)
155
+ }
156
+ }
157
+
158
+ main().catch((err) => {
159
+ console.error('RIDE generation failed:', err)
160
+ process.exit(1)
161
+ })
@@ -0,0 +1,119 @@
1
+ import type { TDocumentDefinitions } from 'pdfmake/interfaces'
2
+ import { buildAdditionalInfo } from './layout/additional-info'
3
+ import { buildBuyerInfo } from './layout/buyer-info'
4
+ import { buildFooter } from './layout/footer'
5
+ import { buildHeader } from './layout/header'
6
+ import { buildItemsTable } from './layout/items-table'
7
+ import { buildPaymentInfo } from './layout/payment-info'
8
+ import { buildTaxSummary } from './layout/tax-summary'
9
+ import { parseComprobanteXml } from './parse-authorized-xml'
10
+ import { generateQrDataUrl } from './qr'
11
+ import type { GenerateRidePdfOptions, GenerateRidePdfParams } from './types'
12
+
13
+ let pdfmakeInitialized = false
14
+
15
+ async function getPdfMake() {
16
+ // pdfmake is CJS — dynamic import for ESM compat
17
+ const pdfmake = (await import('pdfmake')).default ?? (await import('pdfmake'))
18
+ if (!pdfmakeInitialized) {
19
+ // Load fonts via virtualfs — works in Node.js, Deno, and Cloudflare Workers
20
+ // (no filesystem access needed, unlike the previous createRequire/fs approach)
21
+ const vfsFontsModule = await import('pdfmake/build/vfs_fonts.js')
22
+ const vfsFonts = (vfsFontsModule.default ?? vfsFontsModule) as Record<string, unknown>
23
+ const vfs = (pdfmake as unknown as { virtualfs: { storage: Record<string, Buffer> } }).virtualfs
24
+ for (const [filename, base64Data] of Object.entries(vfsFonts)) {
25
+ if (typeof base64Data === 'string') {
26
+ vfs.storage[filename] = Buffer.from(base64Data, 'base64')
27
+ }
28
+ }
29
+ pdfmake.addFonts({
30
+ Roboto: {
31
+ normal: 'Roboto-Regular.ttf',
32
+ bold: 'Roboto-Medium.ttf',
33
+ italics: 'Roboto-Italic.ttf',
34
+ bolditalics: 'Roboto-MediumItalic.ttf',
35
+ },
36
+ })
37
+ pdfmakeInitialized = true
38
+ }
39
+ return pdfmake
40
+ }
41
+
42
+ export async function generateRidePdf(
43
+ params: GenerateRidePdfParams,
44
+ options?: GenerateRidePdfOptions,
45
+ ): Promise<Uint8Array> {
46
+ const data = parseComprobanteXml(params.comprobante, {
47
+ numeroAutorizacion: params.numeroAutorizacion,
48
+ fechaAutorizacion: params.fechaAutorizacion,
49
+ ambiente: params.ambiente,
50
+ })
51
+
52
+ const qrDataUrl = await generateQrDataUrl(
53
+ data.claveAcceso,
54
+ data.numeroAutorizacion,
55
+ data.fechaAutorizacion,
56
+ )
57
+
58
+ let logoDataUrl: string | null = null
59
+ if (options?.orgLogo) {
60
+ const base64 = Buffer.from(options.orgLogo).toString('base64')
61
+ logoDataUrl = `data:image/png;base64,${base64}`
62
+ }
63
+
64
+ const isTestEnvironment = data.ambiente === '1'
65
+
66
+ const docDefinition: TDocumentDefinitions = {
67
+ pageSize: 'A4',
68
+ pageMargins: [40, 40, 40, 40],
69
+ ...(isTestEnvironment
70
+ ? {
71
+ watermark: {
72
+ text: 'SIN VALIDEZ TRIBUTARIA',
73
+ color: '#dc2626',
74
+ opacity: 0.15,
75
+ bold: true,
76
+ fontSize: 48,
77
+ angle: -45,
78
+ },
79
+ }
80
+ : {}),
81
+ content: [
82
+ buildHeader(data, logoDataUrl),
83
+ buildBuyerInfo(data),
84
+ buildItemsTable(data.items),
85
+ buildTaxSummary(data),
86
+ buildPaymentInfo(data.pagos),
87
+ buildAdditionalInfo(data.infoAdicional),
88
+ buildFooter(qrDataUrl),
89
+ ],
90
+ styles: {
91
+ companyName: { fontSize: 11, bold: true, color: '#18181b' },
92
+ companyTrade: { fontSize: 9, color: '#52525b' },
93
+ docType: { fontSize: 12, bold: true, color: '#18181b' },
94
+ docInfoBold: { fontSize: 9, bold: true, color: '#18181b' },
95
+ docInfoLabel: { fontSize: 7, color: '#71717a' },
96
+ docInfoSmall: { fontSize: 7, color: '#18181b' },
97
+ claveAcceso: { fontSize: 6, color: '#18181b', font: 'Roboto' },
98
+ sectionTitle: { fontSize: 9, bold: true, color: '#18181b' },
99
+ label: { fontSize: 8, color: '#52525b' },
100
+ value: { fontSize: 8, color: '#18181b' },
101
+ tableHeader: { fontSize: 7, bold: true, color: '#18181b', fillColor: '#f4f4f5' },
102
+ tableCell: { fontSize: 7, color: '#18181b' },
103
+ summaryLabel: { fontSize: 8, color: '#52525b' },
104
+ summaryValue: { fontSize: 8, color: '#18181b' },
105
+ summaryLabelBold: { fontSize: 8, bold: true, color: '#18181b' },
106
+ summaryValueBold: { fontSize: 9, bold: true, color: '#18181b' },
107
+ small: { fontSize: 7, color: '#52525b' },
108
+ },
109
+ defaultStyle: {
110
+ font: 'Roboto',
111
+ fontSize: 8,
112
+ },
113
+ }
114
+
115
+ const pdfmake = await getPdfMake()
116
+ const pdfDoc = pdfmake.createPdf(docDefinition)
117
+ const buffer = await pdfDoc.getBuffer()
118
+ return new Uint8Array(buffer)
119
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export { generateRidePdf } from './generate-ride-pdf'
2
+ export { parseComprobanteXml } from './parse-authorized-xml'
3
+ export type {
4
+ GenerateRidePdfParams,
5
+ GenerateRidePdfOptions,
6
+ RideData,
7
+ RideItem,
8
+ RidePayment,
9
+ RideTaxTotal,
10
+ } from './types'