@qcobro/common 1.11.4 → 1.12.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.
@@ -5,6 +5,16 @@ import type { NumberSelector } from "../types/dispatch.js";
5
5
  * script / SMS), never HTML, so escaping is disabled. A missing `{{field}}`
6
6
  * renders as empty rather than throwing, so a sparse account never aborts a
7
7
  * dispatch mid-flight.
8
+ *
9
+ * A malformed template — most commonly a reference to an unregistered helper,
10
+ * e.g. `{{multiply amount rate}}` before that helper existed — throws a
11
+ * synchronous Handlebars compile/render error. That used to propagate
12
+ * uncaught, crashing the webapp's live template preview
13
+ * (`ReachOutModal.tsx`) and, in the dispatch pipeline, aborting the send
14
+ * attempt. Both are now caught here: the error surfaces as a visible
15
+ * `[Error de plantilla: ...]` marker in the rendered output instead of
16
+ * throwing, so a template bug is obvious without taking down a preview or a
17
+ * send.
8
18
  */
9
19
  export declare function renderTemplate(template: string, context: Record<string, unknown>): string;
10
20
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"outreach.d.ts","sourceRoot":"","sources":["../../src/utils/outreach.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAGzF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAQhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB;AAED,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,EAAE,cACsB,CAAC"}
1
+ {"version":3,"file":"outreach.d.ts","sourceRoot":"","sources":["../../src/utils/outreach.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAyD3D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAQzF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAQhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB;AAED,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,EAAE,cACsB,CAAC"}
@@ -1,13 +1,80 @@
1
1
  import Handlebars from "handlebars";
2
+ /**
3
+ * `{{multiply a b}}` — coerces both operands to numbers; either operand being
4
+ * non-numeric (missing field, bad data) yields 0 rather than NaN, so a
5
+ * malformed context never produces `NaN` in a customer-facing message.
6
+ */
7
+ Handlebars.registerHelper("multiply", (a, b) => {
8
+ const x = Number(a);
9
+ const y = Number(b);
10
+ return Number.isFinite(x) && Number.isFinite(y) ? x * y : 0;
11
+ });
12
+ /**
13
+ * `{{eq a b}}` — strict equality, for branching on exact values (e.g.
14
+ * `{{#if (eq customerSegment "variant_A")}}`).
15
+ */
16
+ Handlebars.registerHelper("eq", (a, b) => a === b);
17
+ /**
18
+ * `{{gt a b}}` / `{{gte a b}}` / `{{lt a b}}` / `{{lte a b}}` — numeric
19
+ * comparisons for use inside `{{#if}}`, e.g. `{{#if (gte daysPastDue 30)}}`.
20
+ * Operands are coerced with `Number()`; a non-numeric operand becomes `NaN`,
21
+ * and every JS comparison against `NaN` is `false` — so a malformed context
22
+ * makes the condition not match rather than throwing. `ge` is registered as
23
+ * an alias of `gte` (both names are in common use).
24
+ */
25
+ Handlebars.registerHelper("gt", (a, b) => Number(a) > Number(b));
26
+ Handlebars.registerHelper("gte", (a, b) => Number(a) >= Number(b));
27
+ Handlebars.registerHelper("ge", (a, b) => Number(a) >= Number(b));
28
+ Handlebars.registerHelper("lt", (a, b) => Number(a) < Number(b));
29
+ Handlebars.registerHelper("lte", (a, b) => Number(a) <= Number(b));
30
+ const DAY_MS = 24 * 60 * 60 * 1000;
31
+ function toDate(value) {
32
+ if (value instanceof Date)
33
+ return Number.isNaN(value.getTime()) ? null : value;
34
+ if (typeof value !== "string" && typeof value !== "number")
35
+ return null;
36
+ const parsed = new Date(value);
37
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
38
+ }
39
+ /**
40
+ * `{{daysSince date}}` / `{{daysUntil date}}` — whole days between `date` and
41
+ * now (rounded down), for copy like "han pasado {{daysSince lastPaymentDate}}
42
+ * días desde su último pago". An unparseable or missing date yields `0`
43
+ * rather than `NaN`, matching `multiply`'s malformed-context handling.
44
+ */
45
+ Handlebars.registerHelper("daysSince", (value) => {
46
+ const date = toDate(value);
47
+ return date ? Math.floor((Date.now() - date.getTime()) / DAY_MS) : 0;
48
+ });
49
+ Handlebars.registerHelper("daysUntil", (value) => {
50
+ const date = toDate(value);
51
+ return date ? Math.floor((date.getTime() - Date.now()) / DAY_MS) : 0;
52
+ });
2
53
  /**
3
54
  * Renders a Handlebars template against a context. Bodies are plain text (voice
4
55
  * script / SMS), never HTML, so escaping is disabled. A missing `{{field}}`
5
56
  * renders as empty rather than throwing, so a sparse account never aborts a
6
57
  * dispatch mid-flight.
58
+ *
59
+ * A malformed template — most commonly a reference to an unregistered helper,
60
+ * e.g. `{{multiply amount rate}}` before that helper existed — throws a
61
+ * synchronous Handlebars compile/render error. That used to propagate
62
+ * uncaught, crashing the webapp's live template preview
63
+ * (`ReachOutModal.tsx`) and, in the dispatch pipeline, aborting the send
64
+ * attempt. Both are now caught here: the error surfaces as a visible
65
+ * `[Error de plantilla: ...]` marker in the rendered output instead of
66
+ * throwing, so a template bug is obvious without taking down a preview or a
67
+ * send.
7
68
  */
8
69
  export function renderTemplate(template, context) {
9
- const compiled = Handlebars.compile(template, { noEscape: true });
10
- return compiled(context);
70
+ try {
71
+ const compiled = Handlebars.compile(template, { noEscape: true });
72
+ return compiled(context);
73
+ }
74
+ catch (err) {
75
+ const message = err instanceof Error ? err.message : String(err);
76
+ return `[Error de plantilla: ${message}]`;
77
+ }
11
78
  }
12
79
  /**
13
80
  * Extracts the distinct simple placeholder names from a template, in first-seen order
@@ -1 +1 @@
1
- {"version":3,"file":"outreach.js","sourceRoot":"","sources":["../../src/utils/outreach.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,YAAY,CAAC;AAIpC;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,OAAgC;IAC/E,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAClE,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,EAAE,GAAG,kCAAkC,CAAC;IAC9C,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAA+B,EAC/B,IAA0B;IAE1B,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAChE,OAAO;QACL,GAAG,OAAO;QACV,SAAS;QACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,KAAK,EAAE,OAAO,CAAC,WAAW,GAAG,CAAC;KAC/B,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,MAAM,gBAAgB,GAAmB,CAAC,OAAO,EAAE,EAAE,CAC1D,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"outreach.js","sourceRoot":"","sources":["../../src/utils/outreach.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qcobro/common",
3
- "version": "1.11.4",
3
+ "version": "1.12.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"