pixelon 0.3.27 → 0.3.28

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/dist/index.esm.js CHANGED
@@ -1811,6 +1811,114 @@ function useComponentInteraction({
1811
1811
  return { emitInteraction, isConfirmed };
1812
1812
  }
1813
1813
 
1814
+ const FETCH_TIMEOUT_MS = 12e4;
1815
+ function fetchViaEventBus(eventBus, service, url, method, body) {
1816
+ const requestId = globalThis.crypto?.randomUUID?.() ?? "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
1817
+ const r = Math.random() * 16 | 0;
1818
+ return (c === "x" ? r : r & 3 | 8).toString(16);
1819
+ });
1820
+ return new Promise((resolve, reject) => {
1821
+ const timeoutId = setTimeout(() => {
1822
+ unsubscribe();
1823
+ reject(new Error(`Timeout: no response for ${url} after ${FETCH_TIMEOUT_MS}ms`));
1824
+ }, FETCH_TIMEOUT_MS);
1825
+ const unsubscribe = eventBus.on(CHAT_EVENT.FETCH_RESPONSE, (response) => {
1826
+ if (response.requestId !== requestId) return;
1827
+ clearTimeout(timeoutId);
1828
+ unsubscribe();
1829
+ if (response.httpError) {
1830
+ reject(new Error(response.httpError));
1831
+ } else {
1832
+ resolve(response.data);
1833
+ }
1834
+ });
1835
+ eventBus.emit(CHAT_EVENT.FETCH_REQUEST, {
1836
+ requestId,
1837
+ service,
1838
+ url,
1839
+ method,
1840
+ ...body ? { body } : {}
1841
+ });
1842
+ });
1843
+ }
1844
+
1845
+ function mapResponseToOptions(data, endpoint) {
1846
+ let items = [];
1847
+ if (endpoint.response_path) {
1848
+ const parts = endpoint.response_path.split(".");
1849
+ let current = data;
1850
+ for (const part of parts) {
1851
+ if (current != null && typeof current === "object") {
1852
+ current = current[part];
1853
+ } else {
1854
+ return [];
1855
+ }
1856
+ }
1857
+ items = Array.isArray(current) ? current : [];
1858
+ } else if (Array.isArray(data)) {
1859
+ items = data;
1860
+ } else {
1861
+ return [];
1862
+ }
1863
+ return items.filter((item) => item != null && typeof item === "object").map((item) => ({
1864
+ value: String(item[endpoint.value_field] ?? ""),
1865
+ label: String(item[endpoint.label_field] ?? "")
1866
+ }));
1867
+ }
1868
+
1869
+ function normalizeBody(body) {
1870
+ if (!body) return void 0;
1871
+ const normalized = { ...body };
1872
+ for (const [key, val] of Object.entries(normalized)) {
1873
+ if (/s_id$/.test(key) && typeof val === "string") {
1874
+ normalized[key] = [val];
1875
+ }
1876
+ }
1877
+ return normalized;
1878
+ }
1879
+ function useSelectEndpoint(endpoint, eventBus) {
1880
+ const [options, setOptions] = useState([]);
1881
+ const [status, setStatus] = useState(endpoint ? "loading" : "idle");
1882
+ const [errorMessage, setErrorMessage] = useState("");
1883
+ const fetchedRef = useRef(false);
1884
+ const endpointRef = useRef(endpoint);
1885
+ endpointRef.current = endpoint;
1886
+ const doFetch = useCallback(async () => {
1887
+ const ep = endpointRef.current;
1888
+ if (!ep || fetchedRef.current) return;
1889
+ fetchedRef.current = true;
1890
+ if (!eventBus) {
1891
+ setOptions([
1892
+ { value: "mock-1", label: "Mock Option 1" },
1893
+ { value: "mock-2", label: "Mock Option 2" }
1894
+ ]);
1895
+ setStatus("idle");
1896
+ return;
1897
+ }
1898
+ try {
1899
+ const body = normalizeBody(ep.body);
1900
+ if (ep.method.toUpperCase() === "POST" && !body) {
1901
+ setErrorMessage("Faltan par\xE1metros para cargar opciones");
1902
+ setStatus("error");
1903
+ return;
1904
+ }
1905
+ const data = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, body);
1906
+ const mapped = mapResponseToOptions(data, ep);
1907
+ setOptions(mapped);
1908
+ setStatus("idle");
1909
+ } catch (err) {
1910
+ setErrorMessage(err instanceof Error ? err.message : "Error al cargar opciones");
1911
+ setStatus("error");
1912
+ }
1913
+ }, [eventBus]);
1914
+ useEffect(() => {
1915
+ if (endpoint && !fetchedRef.current) {
1916
+ doFetch();
1917
+ }
1918
+ }, [endpoint, doFetch]);
1919
+ return { options, status, errorMessage };
1920
+ }
1921
+
1814
1922
  const MOBILE_BREAKPOINT$4 = 640;
1815
1923
  const useIsMobile$5 = () => {
1816
1924
  const [isMobile, setIsMobile] = useState(
@@ -1834,10 +1942,15 @@ const SelectWithConfirm = ({
1834
1942
  const [searchValue, setSearchValue] = useState("");
1835
1943
  const searchInputRef = useRef(null);
1836
1944
  const isMobile = useIsMobile$5();
1837
- const { field_name, value, ...componentProps } = component.props;
1838
- const options = componentProps.options || [];
1945
+ const { field_name, value, endpoint: endpointProp, ...componentProps } = component.props;
1946
+ const endpoint = endpointProp;
1947
+ const { options: endpointOptions, status: endpointStatus, errorMessage } = useSelectEndpoint(endpoint, eventBus);
1948
+ const staticOptions = componentProps.options || [];
1949
+ const options = endpoint ? endpointOptions : staticOptions;
1839
1950
  const placeholder = componentProps.placeholder || "Seleccionar...";
1840
1951
  const label = componentProps.label || "";
1952
+ const isLoading = endpointStatus === "loading";
1953
+ const isError = endpointStatus === "error";
1841
1954
  const { emitInteraction, isConfirmed } = useComponentInteraction({
1842
1955
  eventBus,
1843
1956
  fieldName: component.props.field_name,
@@ -1857,6 +1970,18 @@ const SelectWithConfirm = ({
1857
1970
  }
1858
1971
  }, [isDrawerOpen]);
1859
1972
  if (isConfirmed) return null;
1973
+ if (isLoading) {
1974
+ return /* @__PURE__ */ jsxs("div", { className: "px-chat-select-with-confirm", children: [
1975
+ label && /* @__PURE__ */ jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
1976
+ /* @__PURE__ */ jsx("div", { className: "px-chat-select-with-confirm__loading", children: "Cargando opciones..." })
1977
+ ] });
1978
+ }
1979
+ if (isError) {
1980
+ return /* @__PURE__ */ jsxs("div", { className: "px-chat-select-with-confirm", children: [
1981
+ label && /* @__PURE__ */ jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
1982
+ /* @__PURE__ */ jsx("div", { className: "px-chat-select-with-confirm__error", children: errorMessage })
1983
+ ] });
1984
+ }
1860
1985
  if (isMobile) {
1861
1986
  return /* @__PURE__ */ jsxs("div", { className: "px-chat-select-with-confirm", children: [
1862
1987
  label && /* @__PURE__ */ jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
@@ -1976,8 +2101,8 @@ const SelectWithConfirm = ({
1976
2101
  /* @__PURE__ */ jsx(
1977
2102
  Select$2,
1978
2103
  {
1979
- options: [],
1980
2104
  ...componentProps,
2105
+ options,
1981
2106
  variant: "chat",
1982
2107
  mode: "single",
1983
2108
  showSearch: true,
@@ -3137,7 +3262,7 @@ const SelectField$1 = ({ field, value, disabled, loading, options, onChange, dra
3137
3262
  ),
3138
3263
  searchValue && /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-select-drawer__search-clear", onClick: () => setSearchValue(""), children: /* @__PURE__ */ jsx(Icon$1, { type: ICON_TYPES.CLOSE, size: "small" }) })
3139
3264
  ] }),
3140
- /* @__PURE__ */ jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-chat-select-drawer__empty", children: "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
3265
+ /* @__PURE__ */ jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-chat-select-drawer__empty", children: field.empty_message || "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
3141
3266
  const isSelected = opt.value === value;
3142
3267
  return /* @__PURE__ */ jsxs(
3143
3268
  "li",
@@ -3258,7 +3383,7 @@ const SelectMultipleField = ({ field, value, disabled, loading, options, onChang
3258
3383
  ),
3259
3384
  searchValue && /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-select-drawer__search-clear", onClick: () => setSearchValue(""), children: /* @__PURE__ */ jsx(Icon$1, { type: ICON_TYPES.CLOSE, size: "small" }) })
3260
3385
  ] }),
3261
- /* @__PURE__ */ jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", "aria-multiselectable": "true", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-chat-select-drawer__empty", children: "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
3386
+ /* @__PURE__ */ jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", "aria-multiselectable": "true", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-chat-select-drawer__empty", children: field.empty_message || "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
3262
3387
  const isChecked = selected.includes(opt.value);
3263
3388
  return /* @__PURE__ */ jsxs(
3264
3389
  "li",
@@ -3356,6 +3481,200 @@ const DateField = ({ field, value, disabled, onChange, drawerPlacement }) => {
3356
3481
  ] });
3357
3482
  };
3358
3483
 
3484
+ const PREFIX$3 = "px-chat-currency";
3485
+ const ChatCurrencyDisplay = ({
3486
+ value,
3487
+ currencyCode,
3488
+ currencySymbol,
3489
+ showCode = true,
3490
+ size = "md",
3491
+ className
3492
+ }) => {
3493
+ const { locale, currency: configCurrency } = useChatConfig();
3494
+ const code = currencyCode || configCurrency?.code || "COP";
3495
+ const symbol = currencySymbol || configCurrency?.symbol || "$";
3496
+ const formatted = useMemo(() => {
3497
+ if (value === null || value === void 0 || value === "") return null;
3498
+ const num = typeof value === "string" ? parseFloat(value) : value;
3499
+ if (isNaN(num)) return null;
3500
+ try {
3501
+ return new Intl.NumberFormat(locale || "es-CO", {
3502
+ minimumFractionDigits: 2,
3503
+ maximumFractionDigits: 2
3504
+ }).format(num);
3505
+ } catch {
3506
+ return num.toFixed(2);
3507
+ }
3508
+ }, [value, locale]);
3509
+ if (!formatted) return /* @__PURE__ */ jsx("span", { className: `${PREFIX$3} ${PREFIX$3}--empty`, children: "-" });
3510
+ const classes = [PREFIX$3, `${PREFIX$3}--${size}`, className].filter(Boolean).join(" ");
3511
+ return /* @__PURE__ */ jsxs("span", { className: classes, children: [
3512
+ /* @__PURE__ */ jsx("span", { className: `${PREFIX$3}__symbol`, children: symbol }),
3513
+ " ",
3514
+ /* @__PURE__ */ jsx("span", { className: `${PREFIX$3}__value`, children: formatted }),
3515
+ showCode && /* @__PURE__ */ jsxs(Fragment, { children: [
3516
+ " ",
3517
+ /* @__PURE__ */ jsx("span", { className: `${PREFIX$3}__code`, children: code })
3518
+ ] })
3519
+ ] });
3520
+ };
3521
+ ChatCurrencyDisplay.displayName = "ChatCurrencyDisplay";
3522
+
3523
+ function resolveVariable(template, formValues, fieldStates) {
3524
+ if (!template.startsWith("$")) return template;
3525
+ const withoutDollar = template.slice(1);
3526
+ const dotIndex = withoutDollar.indexOf(".");
3527
+ const fieldName = dotIndex >= 0 ? withoutDollar.slice(0, dotIndex) : withoutDollar;
3528
+ const prop = dotIndex >= 0 ? withoutDollar.slice(dotIndex + 1) : null;
3529
+ const value = formValues[fieldName];
3530
+ const state = fieldStates[fieldName];
3531
+ if (!prop) return value;
3532
+ if (prop === "label" && state) {
3533
+ return state.label || "";
3534
+ }
3535
+ if (prop === "labels" && state && Array.isArray(value)) {
3536
+ return value.map((v) => {
3537
+ const opt = state.options.find((o) => o.value === v);
3538
+ return opt?.label || v;
3539
+ }).join(", ");
3540
+ }
3541
+ if (state) {
3542
+ const selectedOpt = state.options.find((o) => o.value === value);
3543
+ if (selectedOpt && prop in selectedOpt) {
3544
+ return selectedOpt[prop];
3545
+ }
3546
+ }
3547
+ return "";
3548
+ }
3549
+ function resolveBody(obj, formValues, fieldStates) {
3550
+ if (typeof obj === "string") return resolveVariable(obj, formValues, fieldStates);
3551
+ if (Array.isArray(obj)) return obj.map((item) => resolveBody(item, formValues, fieldStates));
3552
+ if (obj !== null && typeof obj === "object") {
3553
+ const resolved = {};
3554
+ for (const [key, value] of Object.entries(obj)) {
3555
+ resolved[key] = resolveBody(value, formValues, fieldStates);
3556
+ }
3557
+ return resolved;
3558
+ }
3559
+ return obj;
3560
+ }
3561
+ function resolveParams(params, formValues, fieldStates) {
3562
+ const resolved = {};
3563
+ for (const [key, template] of Object.entries(params)) {
3564
+ resolved[key] = resolveVariable(template, formValues, fieldStates);
3565
+ }
3566
+ return resolved;
3567
+ }
3568
+ function resolveSummaryRows(rows, formValues, fieldStates) {
3569
+ return rows.map((row) => ({
3570
+ label: row.label,
3571
+ value: String(resolveVariable(row.value, formValues, fieldStates) ?? "")
3572
+ }));
3573
+ }
3574
+
3575
+ const CostSummaryField = ({
3576
+ field,
3577
+ values,
3578
+ fieldStates,
3579
+ onCostResolved,
3580
+ eventBus,
3581
+ onCancel
3582
+ }) => {
3583
+ const [totalCost, setTotalCost] = useState(null);
3584
+ const [loading, setLoading] = useState(true);
3585
+ const [error, setError] = useState("");
3586
+ const selectedItems = useMemo(() => {
3587
+ if (!field.items_from) return [];
3588
+ const { source_field, label_key, value_key } = field.items_from;
3589
+ const sourceState = fieldStates[source_field];
3590
+ const selectedValues = values[source_field];
3591
+ if (!sourceState?.options || !selectedValues) return [];
3592
+ const selectedArr = Array.isArray(selectedValues) ? selectedValues : [selectedValues];
3593
+ return sourceState.options.filter((opt) => selectedArr.includes(opt.value)).map((opt) => ({
3594
+ label: String(opt[label_key] || opt.label || ""),
3595
+ value: Number(opt[value_key] || 0)
3596
+ }));
3597
+ }, [field.items_from, fieldStates, values]);
3598
+ const fetchedRef = useRef(false);
3599
+ useEffect(() => {
3600
+ if (!field.endpoint || !eventBus || fetchedRef.current) {
3601
+ if (!field.endpoint || !eventBus) setLoading(false);
3602
+ return;
3603
+ }
3604
+ fetchedRef.current = true;
3605
+ const body = resolveBody(field.endpoint.body || {}, values, fieldStates);
3606
+ setLoading(true);
3607
+ setError("");
3608
+ fetchViaEventBus(eventBus, field.endpoint.service, field.endpoint.url, field.endpoint.method, body).then((data) => {
3609
+ const resp = data;
3610
+ if (resp?.notification_type && String(resp.notification_type).toUpperCase() !== "SUCCESS") {
3611
+ setError(String(resp.message || "Error al calcular costo"));
3612
+ return;
3613
+ }
3614
+ const responseData = resp?.response;
3615
+ const totalField = field.endpoint.total_field || "total_cost";
3616
+ const cost = Number(responseData?.[totalField] ?? resp?.[totalField] ?? 0);
3617
+ setTotalCost(cost);
3618
+ if (onCostResolved) {
3619
+ try {
3620
+ const formatted = new Intl.NumberFormat("es-CO", { style: "currency", currency: "COP" }).format(cost);
3621
+ onCostResolved(field.name, formatted);
3622
+ } catch {
3623
+ onCostResolved(field.name, `$${cost}`);
3624
+ }
3625
+ }
3626
+ }).catch((err) => {
3627
+ setError(err instanceof Error ? err.message : "Error al calcular costo");
3628
+ }).finally(() => setLoading(false));
3629
+ }, [field.endpoint, eventBus]);
3630
+ const handleCancel = () => {
3631
+ if (field.on_cancel && eventBus) {
3632
+ if (field.on_cancel.type === "system_message") {
3633
+ eventBus.emit(CHAT_EVENT.FORM_TRACE, {
3634
+ chatId: "",
3635
+ formTitle: "",
3636
+ source: "reactive_form",
3637
+ phase: "cancel",
3638
+ data: { message: field.on_cancel.message },
3639
+ service: "",
3640
+ url: "",
3641
+ method: ""
3642
+ });
3643
+ }
3644
+ }
3645
+ onCancel();
3646
+ };
3647
+ if (loading) {
3648
+ return /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary px-chat-rf__cost-summary--loading", children: [
3649
+ /* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
3650
+ /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary__loader", children: [
3651
+ /* @__PURE__ */ jsx("span", { className: "px-chat-rf__spinner" }),
3652
+ /* @__PURE__ */ jsx("span", { children: "Calculando costo..." })
3653
+ ] })
3654
+ ] });
3655
+ }
3656
+ if (error) {
3657
+ return /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary px-chat-rf__cost-summary--error", children: [
3658
+ /* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
3659
+ /* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__error", children: error }),
3660
+ /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--secondary", onClick: handleCancel, children: "Cancelar" })
3661
+ ] });
3662
+ }
3663
+ return /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary", children: [
3664
+ /* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
3665
+ /* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__items", children: selectedItems.map((item, i) => /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary__item", children: [
3666
+ /* @__PURE__ */ jsx("span", { className: "px-chat-rf__cost-summary__item-label", children: item.label }),
3667
+ /* @__PURE__ */ jsx(ChatCurrencyDisplay, { value: item.value, size: "sm" })
3668
+ ] }, i)) }),
3669
+ /* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__divider" }),
3670
+ /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary__total", children: [
3671
+ /* @__PURE__ */ jsx("span", { className: "px-chat-rf__cost-summary__total-label", children: "Total" }),
3672
+ /* @__PURE__ */ jsx(ChatCurrencyDisplay, { value: totalCost, size: "lg" })
3673
+ ] }),
3674
+ /* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--secondary", onClick: handleCancel, children: "Cancelar" }) })
3675
+ ] });
3676
+ };
3677
+
3359
3678
  const checkIcon = /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M2.5 6L5 8.5L9.5 3.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
3360
3679
  const calendarIcon = /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
3361
3680
  /* @__PURE__ */ jsx("rect", { x: "2", y: "3", width: "12", height: "11", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
@@ -3371,7 +3690,7 @@ const formIcon = /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBo
3371
3690
  ] });
3372
3691
  const chevronDown = /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }) });
3373
3692
  const chevronRight = /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M4.5 3L7.5 6L4.5 9", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }) });
3374
- const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form, drawerPlacement = "bottom" }) => {
3693
+ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form, drawerPlacement = "bottom", eventBus }) => {
3375
3694
  const {
3376
3695
  formStatus,
3377
3696
  setFormStatus,
@@ -3387,6 +3706,7 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
3387
3706
  handleNext,
3388
3707
  handleBack,
3389
3708
  handleSubmit,
3709
+ isSubmitting,
3390
3710
  getDependencies,
3391
3711
  allDepsResolved,
3392
3712
  resolveSummaryRows
@@ -3446,11 +3766,25 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
3446
3766
  const renderSummaryActions = () => {
3447
3767
  const actions = summary.actions;
3448
3768
  if (!actions || actions.length === 0) {
3449
- return /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--primary px-chat-rf__btn--full", onClick: handleSubmit, children: submit.label });
3769
+ return /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--primary px-chat-rf__btn--full", onClick: handleSubmit, disabled: isSubmitting, children: isSubmitting ? /* @__PURE__ */ jsx("span", { className: "px-chat-rf__spinner" }) : submit.label });
3450
3770
  }
3451
3771
  const actionHandlers = {
3452
3772
  submit: handleSubmit,
3453
- close: () => setFormStatus("closed"),
3773
+ close: () => {
3774
+ if (eventBus) {
3775
+ eventBus.emit(CHAT_EVENT.FORM_TRACE, {
3776
+ chatId: "",
3777
+ formTitle: "",
3778
+ source: "reactive_form",
3779
+ phase: "cancel",
3780
+ data: { message: "El usuario cancel\xF3 el agendamiento de la cita." },
3781
+ service: "",
3782
+ url: "",
3783
+ method: ""
3784
+ });
3785
+ }
3786
+ setFormStatus("closed");
3787
+ },
3454
3788
  back: handleBack
3455
3789
  };
3456
3790
  return /* @__PURE__ */ jsx("div", { className: "px-chat-rf__nav", children: actions.map((act, i) => /* @__PURE__ */ jsx(
@@ -3460,7 +3794,8 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
3460
3794
  className: `px-chat-rf__btn px-chat-rf__btn--${act.variant || "primary"}`,
3461
3795
  onClick: actionHandlers[act.action] || (() => {
3462
3796
  }),
3463
- children: act.label
3797
+ disabled: act.action === "submit" && isSubmitting,
3798
+ children: act.action === "submit" && isSubmitting ? /* @__PURE__ */ jsx("span", { className: "px-chat-rf__spinner" }) : act.label
3464
3799
  },
3465
3800
  i
3466
3801
  )) });
@@ -3493,8 +3828,15 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
3493
3828
  )
3494
3829
  ] }),
3495
3830
  prevStep.fields.map((fname) => {
3496
- const fs = fieldStates[fname];
3497
3831
  const fld = fields.find((f) => f.name === fname);
3832
+ if (fld?.type === "cost_summary") {
3833
+ const costLabel = fieldStates[fname]?.label;
3834
+ return costLabel ? /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__completed-field", children: [
3835
+ /* @__PURE__ */ jsx("span", { className: "px-chat-rf__completed-field-label", children: "Total:" }),
3836
+ /* @__PURE__ */ jsx("span", { className: "px-chat-rf__completed-field-value", children: costLabel })
3837
+ ] }, fname) : null;
3838
+ }
3839
+ const fs = fieldStates[fname];
3498
3840
  return /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__completed-field", children: [
3499
3841
  /* @__PURE__ */ jsxs("span", { className: "px-chat-rf__completed-field-label", children: [
3500
3842
  fld?.label,
@@ -3507,6 +3849,20 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
3507
3849
  /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__active-step", children: [
3508
3850
  /* @__PURE__ */ jsx("div", { className: "px-chat-rf__step-title", children: step.title }),
3509
3851
  stepFields.map((field) => {
3852
+ if (field.type === "cost_summary") {
3853
+ return /* @__PURE__ */ jsx(
3854
+ CostSummaryField,
3855
+ {
3856
+ field,
3857
+ values,
3858
+ fieldStates,
3859
+ eventBus,
3860
+ onCancel: () => setFormStatus("closed"),
3861
+ onCostResolved: (name, label) => handleFieldChange(name, label, label)
3862
+ },
3863
+ field.name
3864
+ );
3865
+ }
3510
3866
  const deps = getDependencies(field);
3511
3867
  const isDisabled = !allDepsResolved(deps, values);
3512
3868
  return /* @__PURE__ */ jsx(
@@ -3602,89 +3958,6 @@ function buildInitialValues(fields) {
3602
3958
  return values;
3603
3959
  }
3604
3960
 
3605
- function resolveVariable(template, formValues, fieldStates) {
3606
- if (!template.startsWith("$")) return template;
3607
- const withoutDollar = template.slice(1);
3608
- const dotIndex = withoutDollar.indexOf(".");
3609
- const fieldName = dotIndex >= 0 ? withoutDollar.slice(0, dotIndex) : withoutDollar;
3610
- const prop = dotIndex >= 0 ? withoutDollar.slice(dotIndex + 1) : null;
3611
- const value = formValues[fieldName];
3612
- const state = fieldStates[fieldName];
3613
- if (!prop) return value;
3614
- if (prop === "label" && state) {
3615
- return state.label || "";
3616
- }
3617
- if (prop === "labels" && state && Array.isArray(value)) {
3618
- return value.map((v) => {
3619
- const opt = state.options.find((o) => o.value === v);
3620
- return opt?.label || v;
3621
- }).join(", ");
3622
- }
3623
- if (state) {
3624
- const selectedOpt = state.options.find((o) => o.value === value);
3625
- if (selectedOpt && prop in selectedOpt) {
3626
- return selectedOpt[prop];
3627
- }
3628
- }
3629
- return "";
3630
- }
3631
- function resolveBody(obj, formValues, fieldStates) {
3632
- if (typeof obj === "string") return resolveVariable(obj, formValues, fieldStates);
3633
- if (Array.isArray(obj)) return obj.map((item) => resolveBody(item, formValues, fieldStates));
3634
- if (obj !== null && typeof obj === "object") {
3635
- const resolved = {};
3636
- for (const [key, value] of Object.entries(obj)) {
3637
- resolved[key] = resolveBody(value, formValues, fieldStates);
3638
- }
3639
- return resolved;
3640
- }
3641
- return obj;
3642
- }
3643
- function resolveParams(params, formValues, fieldStates) {
3644
- const resolved = {};
3645
- for (const [key, template] of Object.entries(params)) {
3646
- resolved[key] = resolveVariable(template, formValues, fieldStates);
3647
- }
3648
- return resolved;
3649
- }
3650
- function resolveSummaryRows(rows, formValues, fieldStates) {
3651
- return rows.map((row) => ({
3652
- label: row.label,
3653
- value: String(resolveVariable(row.value, formValues, fieldStates) ?? "")
3654
- }));
3655
- }
3656
-
3657
- const FETCH_TIMEOUT_MS = 12e4;
3658
- function fetchViaEventBus(eventBus, service, url, method, body) {
3659
- const requestId = globalThis.crypto?.randomUUID?.() ?? "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
3660
- const r = Math.random() * 16 | 0;
3661
- return (c === "x" ? r : r & 3 | 8).toString(16);
3662
- });
3663
- return new Promise((resolve, reject) => {
3664
- const timeoutId = setTimeout(() => {
3665
- unsubscribe();
3666
- reject(new Error(`Timeout: no response for ${url} after ${FETCH_TIMEOUT_MS}ms`));
3667
- }, FETCH_TIMEOUT_MS);
3668
- const unsubscribe = eventBus.on(CHAT_EVENT.FETCH_RESPONSE, (response) => {
3669
- if (response.requestId !== requestId) return;
3670
- clearTimeout(timeoutId);
3671
- unsubscribe();
3672
- if (response.httpError) {
3673
- reject(new Error(response.httpError));
3674
- } else {
3675
- resolve(response.data);
3676
- }
3677
- });
3678
- eventBus.emit(CHAT_EVENT.FETCH_REQUEST, {
3679
- requestId,
3680
- service,
3681
- url,
3682
- method,
3683
- ...body ? { body } : {}
3684
- });
3685
- });
3686
- }
3687
-
3688
3961
  function formatTime$1(value, timezone) {
3689
3962
  try {
3690
3963
  const d = new Date(value);
@@ -3692,7 +3965,7 @@ function formatTime$1(value, timezone) {
3692
3965
  return d.toLocaleTimeString([], {
3693
3966
  hour: "2-digit",
3694
3967
  minute: "2-digit",
3695
- hour12: false,
3968
+ hour12: true,
3696
3969
  ...timezone ? { timeZone: timezone } : {}
3697
3970
  });
3698
3971
  } catch {
@@ -4054,7 +4327,10 @@ function useReactiveForm(props) {
4054
4327
  };
4055
4328
  const handleBack = () => setCurrentStep((s) => s - 1);
4056
4329
  const [submitMessage, setSubmitMessage] = useState("");
4330
+ const [isSubmitting, setIsSubmitting] = useState(false);
4057
4331
  const handleSubmit = async () => {
4332
+ if (isSubmitting) return;
4333
+ setIsSubmitting(true);
4058
4334
  const resolvedPayload = resolveBody(submit.payload_map, values, fieldStates);
4059
4335
  if (eventBus) {
4060
4336
  try {
@@ -4077,7 +4353,8 @@ function useReactiveForm(props) {
4077
4353
  submitUrl: submit.url,
4078
4354
  result: data,
4079
4355
  formValues: values,
4080
- formLabels
4356
+ formLabels,
4357
+ onSuccess: submit.on_success
4081
4358
  });
4082
4359
  if (notificationType === "SUCCESS") {
4083
4360
  setSubmitMessage(message || "Completado");
@@ -4093,6 +4370,7 @@ function useReactiveForm(props) {
4093
4370
  const message = err instanceof Error ? err.message : "Error al enviar formulario";
4094
4371
  setSubmitMessage(message);
4095
4372
  setFormStatus("error");
4373
+ setIsSubmitting(false);
4096
4374
  }
4097
4375
  } else {
4098
4376
  console.log("[ReactiveForm] Submit:", values);
@@ -4121,6 +4399,7 @@ function useReactiveForm(props) {
4121
4399
  handleNext,
4122
4400
  handleBack,
4123
4401
  handleSubmit,
4402
+ isSubmitting,
4124
4403
  getDependencies,
4125
4404
  allDepsResolved,
4126
4405
  resolveSummaryRows: resolveRows
@@ -4152,7 +4431,8 @@ const ReactiveForm = ({ component, eventBus }) => {
4152
4431
  {
4153
4432
  ...props,
4154
4433
  form,
4155
- drawerPlacement: isMobile ? "bottom" : "right"
4434
+ drawerPlacement: isMobile ? "bottom" : "right",
4435
+ eventBus
4156
4436
  }
4157
4437
  );
4158
4438
  };