pixelon 0.3.76 → 0.3.78

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 (26) hide show
  1. package/dist/components/atoms/lumi/FieldValue/FieldValue.d.ts +15 -0
  2. package/dist/components/atoms/lumi/FieldValue/index.d.ts +2 -0
  3. package/dist/components/atoms/lumi/FieldValue/types.d.ts +10 -0
  4. package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/ChatLinkCell.d.ts +20 -0
  5. package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/optionsLoader.d.ts +2 -1
  6. package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/types.d.ts +6 -0
  7. package/dist/components/molecules/chat/StructuredContent/helpers/errorMessage.d.ts +5 -0
  8. package/dist/components/molecules/lumi/Drawer/Drawer.d.ts +8 -0
  9. package/dist/components/molecules/lumi/DropdownMenu/types.d.ts +6 -0
  10. package/dist/components/molecules/lumi/RecordCard/RecordCard.d.ts +14 -0
  11. package/dist/components/molecules/lumi/RecordCard/index.d.ts +2 -0
  12. package/dist/components/molecules/lumi/RecordCard/types.d.ts +16 -0
  13. package/dist/components/molecules/lumi/ScrollToEnd/ScrollToEnd.d.ts +21 -0
  14. package/dist/components/molecules/lumi/ScrollToEnd/index.d.ts +2 -0
  15. package/dist/components/templates/lumi/Crud/CardList.d.ts +34 -0
  16. package/dist/components/templates/lumi/Crud/Crud.d.ts +1 -1
  17. package/dist/components/templates/lumi/Crud/ScrollToEnd.d.ts +18 -0
  18. package/dist/components/templates/lumi/Crud/helpers/fieldValue.d.ts +12 -0
  19. package/dist/components/templates/lumi/Crud/types.d.ts +27 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.esm.js +583 -279
  22. package/dist/index.js +584 -278
  23. package/dist/styles.css +1 -1
  24. package/dist/styles.css.map +1 -1
  25. package/package.json +1 -1
  26. package/src/styles/index.scss +4 -0
package/dist/index.js CHANGED
@@ -4008,13 +4008,13 @@ function resolveVariable(template, formValues, fieldStates) {
4008
4008
  }
4009
4009
  return "";
4010
4010
  }
4011
- function resolveBody(obj, formValues, fieldStates) {
4011
+ function resolveBody$1(obj, formValues, fieldStates) {
4012
4012
  if (typeof obj === "string") return resolveVariable(obj, formValues, fieldStates);
4013
- if (Array.isArray(obj)) return obj.map((item) => resolveBody(item, formValues, fieldStates));
4013
+ if (Array.isArray(obj)) return obj.map((item) => resolveBody$1(item, formValues, fieldStates));
4014
4014
  if (obj !== null && typeof obj === "object") {
4015
4015
  const resolved = {};
4016
4016
  for (const [key, value] of Object.entries(obj)) {
4017
- resolved[key] = resolveBody(value, formValues, fieldStates);
4017
+ resolved[key] = resolveBody$1(value, formValues, fieldStates);
4018
4018
  }
4019
4019
  return resolved;
4020
4020
  }
@@ -4064,7 +4064,7 @@ const CostSummaryField = ({
4064
4064
  return;
4065
4065
  }
4066
4066
  fetchedRef.current = true;
4067
- const body = resolveBody(field.endpoint.body || {}, values, fieldStates);
4067
+ const body = resolveBody$1(field.endpoint.body || {}, values, fieldStates);
4068
4068
  setLoading(true);
4069
4069
  setError("");
4070
4070
  fetchViaEventBus(eventBus, field.endpoint.service, field.endpoint.url, field.endpoint.method, body).then((data) => {
@@ -4518,7 +4518,7 @@ function useReactiveEndpoints({ fields, fieldStates, setFieldState, eventBus, ti
4518
4518
  setFieldState(field.name, { loading: true, error: null });
4519
4519
  const startTime = Date.now();
4520
4520
  try {
4521
- const resolvedBody = field.endpoint.body ? resolveBody(field.endpoint.body, formValues, fieldStates) : void 0;
4521
+ const resolvedBody = field.endpoint.body ? resolveBody$1(field.endpoint.body, formValues, fieldStates) : void 0;
4522
4522
  const params = field.endpoint.params ? resolveParams(field.endpoint.params, formValues, fieldStates) : {};
4523
4523
  const requestBody = resolvedBody ?? (Object.keys(params).length > 0 ? params : void 0);
4524
4524
  if (eventBus) {
@@ -4793,7 +4793,7 @@ function useReactiveForm(props) {
4793
4793
  const handleSubmit = async () => {
4794
4794
  if (isSubmitting) return;
4795
4795
  setIsSubmitting(true);
4796
- const resolvedPayload = resolveBody(submit.payload_map, values, fieldStates);
4796
+ const resolvedPayload = resolveBody$1(submit.payload_map, values, fieldStates);
4797
4797
  if (eventBus) {
4798
4798
  try {
4799
4799
  const data = await fetchViaEventBus(
@@ -10886,6 +10886,7 @@ const Drawer = ({
10886
10886
  size = DRAWER_SIZES.RESPONSIVE,
10887
10887
  fixedSize = 400,
10888
10888
  responsivePercentage = 80,
10889
+ mobileSheet = false,
10889
10890
  title,
10890
10891
  headerExtra,
10891
10892
  showCloseButton = true,
@@ -10968,11 +10969,13 @@ const Drawer = ({
10968
10969
  `${COMPONENT_PREFIX}drawer--${size}`,
10969
10970
  isOpen && `${COMPONENT_PREFIX}drawer--open`,
10970
10971
  isMobile && `${COMPONENT_PREFIX}drawer--mobile`,
10972
+ mobileSheet && `${COMPONENT_PREFIX}drawer--sheet`,
10971
10973
  className
10972
10974
  ].filter(Boolean).join(" ");
10973
10975
  const overlayClasses = [
10974
10976
  `${COMPONENT_PREFIX}drawer-overlay`,
10975
- isOpen && showOverlay && `${COMPONENT_PREFIX}drawer-overlay--visible`
10977
+ isOpen && showOverlay && `${COMPONENT_PREFIX}drawer-overlay--visible`,
10978
+ mobileSheet && `${COMPONENT_PREFIX}drawer-overlay--sheet`
10976
10979
  ].filter(Boolean).join(" ");
10977
10980
  const getDrawerStyles = () => {
10978
10981
  if (isMobile) {
@@ -11707,11 +11710,12 @@ const DropdownMenu = ({
11707
11710
  children: options.map((option, index) => /* @__PURE__ */ jsxRuntime.jsxs(
11708
11711
  "button",
11709
11712
  {
11710
- className: `${baseClass}__option ${option.disabled ? `${baseClass}__option--disabled` : ""}`,
11713
+ className: `${baseClass}__option ${option.disabled ? `${baseClass}__option--disabled` : ""} ${option.disabled && option.disabledReason ? `${baseClass}__option--con-motivo` : ""}`,
11711
11714
  onClick: () => !option.disabled && handleOptionClick(option.onClick),
11712
11715
  disabled: option.disabled,
11713
11716
  role: "menuitem",
11714
11717
  type: "button",
11718
+ title: option.disabled && option.disabledReason ? option.disabledReason : void 0,
11715
11719
  children: [
11716
11720
  option.icon && /* @__PURE__ */ jsxRuntime.jsx(
11717
11721
  Icon,
@@ -11721,7 +11725,10 @@ const DropdownMenu = ({
11721
11725
  className: `${baseClass}__option-icon`
11722
11726
  }
11723
11727
  ),
11724
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${baseClass}__option-label`, children: option.label })
11728
+ option.disabled && option.disabledReason ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `${baseClass}__option-texts`, children: [
11729
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${baseClass}__option-label`, children: option.label }),
11730
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${baseClass}__option-reason`, children: option.disabledReason })
11731
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${baseClass}__option-label`, children: option.label })
11725
11732
  ]
11726
11733
  },
11727
11734
  index
@@ -13075,36 +13082,45 @@ const Badge = ({
13075
13082
  };
13076
13083
  Badge.displayName = "LumiBadge";
13077
13084
 
13085
+ function fieldOptions(field) {
13086
+ return field.uiProps?.options;
13087
+ }
13088
+ function renderFieldValue(field, row) {
13089
+ const value = row[field.key];
13090
+ const options = fieldOptions(field);
13091
+ const opt = options?.find((o) => String(o.value) === String(value));
13092
+ if (field.moleculeType === "BADGE") {
13093
+ if (value == null || value === "") return null;
13094
+ return React.createElement(Badge, { color: opt?.color, children: opt?.label ?? String(value) });
13095
+ }
13096
+ if (field.moleculeType === "INPUT_CURRENCY") {
13097
+ if (value == null || value === "") return null;
13098
+ const cp = field.uiProps ?? {};
13099
+ return React.createElement(CurrencyDisplay, {
13100
+ value,
13101
+ showCode: cp.showCode ?? true,
13102
+ showSymbol: cp.showSymbol ?? true,
13103
+ currency: cp.currency ?? { decimalPlaces: 0 },
13104
+ align: field.align ?? "right",
13105
+ size: cp.size ?? "md"
13106
+ });
13107
+ }
13108
+ if (field.formatValue) return field.formatValue(value, row);
13109
+ if (opt) return opt.label;
13110
+ return value;
13111
+ }
13112
+ function hasFieldValue(field, row) {
13113
+ const value = row[field.key];
13114
+ return value != null && value !== "";
13115
+ }
13116
+
13078
13117
  function mapFieldsToTableColumns(fields) {
13079
13118
  return fields.filter((field) => field.showInTable !== false).map((field) => {
13080
13119
  let width = field.columnWidth;
13081
13120
  if (field.fixed && typeof field.columnWidth === "number") {
13082
13121
  width = `${field.columnWidth}px`;
13083
13122
  }
13084
- const options = field.uiProps?.options;
13085
- const build = (row) => {
13086
- const value = row[field.key];
13087
- const opt = options?.find((o) => String(o.value) === String(value));
13088
- if (field.moleculeType === "BADGE") {
13089
- if (value == null || value === "") return null;
13090
- return React.createElement(Badge, { color: opt?.color, children: opt?.label ?? String(value) });
13091
- }
13092
- if (field.moleculeType === "INPUT_CURRENCY") {
13093
- if (value == null || value === "") return null;
13094
- const cp = field.uiProps ?? {};
13095
- return React.createElement(CurrencyDisplay, {
13096
- value,
13097
- showCode: cp.showCode ?? true,
13098
- showSymbol: cp.showSymbol ?? true,
13099
- currency: cp.currency ?? { decimalPlaces: 0 },
13100
- align: field.align ?? "right",
13101
- size: cp.size ?? "md"
13102
- });
13103
- }
13104
- if (field.formatValue) return field.formatValue(value, row);
13105
- if (opt) return opt.label;
13106
- return value;
13107
- };
13123
+ const build = (row) => renderFieldValue(field, row);
13108
13124
  return {
13109
13125
  key: field.key,
13110
13126
  title: field.label,
@@ -13135,10 +13151,201 @@ function mapFieldsToFormFields(fields, mode) {
13135
13151
  }));
13136
13152
  }
13137
13153
 
13154
+ const CLASS$e = `${COMPONENT_PREFIX}field-value`;
13155
+ const FieldValue = ({
13156
+ label,
13157
+ children,
13158
+ layout = "inline",
13159
+ className
13160
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: [CLASS$e, `${CLASS$e}--${layout}`, className].filter(Boolean).join(" "), children: [
13161
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${CLASS$e}__label`, children: label }),
13162
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${CLASS$e}__value`, children })
13163
+ ] });
13164
+ FieldValue.displayName = "FieldValue";
13165
+
13166
+ const CLASS$d = `${COMPONENT_PREFIX}record-card`;
13167
+ const RecordCard = ({
13168
+ title,
13169
+ summary,
13170
+ children,
13171
+ actions,
13172
+ open = false,
13173
+ onToggle,
13174
+ ariaLabel,
13175
+ className
13176
+ }) => {
13177
+ const cuerpoId = React.useId();
13178
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: [CLASS$d, open ? `${CLASS$d}--open` : "", className].filter(Boolean).join(" "), children: [
13179
+ /* @__PURE__ */ jsxRuntime.jsxs(
13180
+ "button",
13181
+ {
13182
+ type: "button",
13183
+ className: `${CLASS$d}__header`,
13184
+ "aria-expanded": open,
13185
+ "aria-controls": cuerpoId,
13186
+ "aria-label": ariaLabel,
13187
+ onClick: onToggle,
13188
+ children: [
13189
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `${CLASS$d}__heading`, children: [
13190
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${CLASS$d}__title`, children: title }),
13191
+ summary && /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${CLASS$d}__summary`, children: summary })
13192
+ ] }),
13193
+ /* @__PURE__ */ jsxRuntime.jsx(
13194
+ Icon,
13195
+ {
13196
+ type: open ? "chevron-up" : "chevron-down",
13197
+ width: 18,
13198
+ height: 18,
13199
+ className: `${CLASS$d}__chevron`,
13200
+ "aria-hidden": true
13201
+ }
13202
+ )
13203
+ ]
13204
+ }
13205
+ ),
13206
+ open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${CLASS$d}__body`, id: cuerpoId, children: [
13207
+ children,
13208
+ actions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${CLASS$d}__actions`, children: actions })
13209
+ ] })
13210
+ ] });
13211
+ };
13212
+ RecordCard.displayName = "RecordCard";
13213
+
13214
+ const CLASS$c = `${COMPONENT_PREFIX}crud-cards`;
13215
+ function summaryFields(fields) {
13216
+ const visibles = fields.filter((f) => f.showInTable !== false);
13217
+ const marcados = visibles.filter((f) => f.summary);
13218
+ return marcados.length > 0 ? marcados : visibles.slice(0, 3);
13219
+ }
13220
+ function CardList({
13221
+ fields,
13222
+ rows,
13223
+ rowKey,
13224
+ buildOptions,
13225
+ loading = false,
13226
+ emptyText = "No hay registros"
13227
+ }) {
13228
+ const [abierta, setAbierta] = React.useState(null);
13229
+ const resumen = summaryFields(fields);
13230
+ const claveResumen = new Set(resumen.map((f) => f.key));
13231
+ const cuerpo = fields.filter((f) => f.showInTable !== false && !claveResumen.has(f.key));
13232
+ if (loading && rows.length === 0) {
13233
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${CLASS$c}__estado`, children: "Cargando..." });
13234
+ }
13235
+ if (rows.length === 0) {
13236
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${CLASS$c}__estado`, children: emptyText });
13237
+ }
13238
+ const [principal, ...secundarios] = resumen;
13239
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: CLASS$c, children: rows.map((row) => {
13240
+ const key = rowKey(row);
13241
+ const opciones = buildOptions?.(row) ?? [];
13242
+ const titulo = principal ? renderFieldValue(principal, row) : key;
13243
+ return /* @__PURE__ */ jsxRuntime.jsx(
13244
+ RecordCard,
13245
+ {
13246
+ open: abierta === key,
13247
+ onToggle: () => setAbierta((actual) => actual === key ? null : key),
13248
+ ariaLabel: principal ? `${principal.label}: ${String(row[principal.key] ?? "")}` : void 0,
13249
+ title: titulo,
13250
+ summary: secundarios.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: secundarios.map((f) => /* @__PURE__ */ jsxRuntime.jsx(React.Fragment, { children: renderFieldValue(f, row) }, f.key)) }) : void 0,
13251
+ actions: opciones.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: opciones.map((o) => /* @__PURE__ */ jsxRuntime.jsxs(
13252
+ "button",
13253
+ {
13254
+ type: "button",
13255
+ className: [
13256
+ `${CLASS$c}__accion`,
13257
+ o.disabled && o.disabledReason ? "is-con-motivo" : ""
13258
+ ].filter(Boolean).join(" "),
13259
+ disabled: o.disabled,
13260
+ onClick: () => void o.onClick(),
13261
+ children: [
13262
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: o.label }),
13263
+ o.disabled && o.disabledReason && /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${CLASS$c}__motivo`, children: o.disabledReason })
13264
+ ]
13265
+ },
13266
+ o.label
13267
+ )) }) : void 0,
13268
+ children: cuerpo.map(
13269
+ (f) => hasFieldValue(f, row) ? /* @__PURE__ */ jsxRuntime.jsx(FieldValue, { label: f.label, children: renderFieldValue(f, row) }, f.key) : null
13270
+ )
13271
+ },
13272
+ key
13273
+ );
13274
+ }) });
13275
+ }
13276
+ CardList.displayName = "CardList";
13277
+
13278
+ const CLASS$b = `${COMPONENT_PREFIX}crud-scroll-end`;
13279
+ const MARGEN = 24;
13280
+ const SELECTOR_CRUD = `.${COMPONENT_PREFIX}table-container, .${COMPONENT_PREFIX}crud-cards`;
13281
+ const ScrollToEnd = ({
13282
+ hostRef,
13283
+ selector = SELECTOR_CRUD,
13284
+ dep,
13285
+ label = "Ir al final de la lista"
13286
+ }) => {
13287
+ const [visible, setVisible] = React.useState(false);
13288
+ const scrollerRef = React.useRef(null);
13289
+ React.useEffect(() => {
13290
+ const host = hostRef.current;
13291
+ if (!host) return;
13292
+ const scroller = host.querySelector(selector) ?? host;
13293
+ scrollerRef.current = scroller;
13294
+ if (!scroller) {
13295
+ setVisible(false);
13296
+ return;
13297
+ }
13298
+ const revisar = () => {
13299
+ const s = scrollerRef.current;
13300
+ if (!s) return;
13301
+ setVisible(s.scrollHeight - s.clientHeight - s.scrollTop > MARGEN);
13302
+ };
13303
+ revisar();
13304
+ scroller.addEventListener("scroll", revisar, { passive: true });
13305
+ let observador;
13306
+ if (typeof ResizeObserver !== "undefined") {
13307
+ observador = new ResizeObserver(revisar);
13308
+ observador.observe(scroller);
13309
+ if (scroller.firstElementChild) observador.observe(scroller.firstElementChild);
13310
+ }
13311
+ return () => {
13312
+ scroller.removeEventListener("scroll", revisar);
13313
+ observador?.disconnect();
13314
+ };
13315
+ }, [hostRef, selector, dep]);
13316
+ const alFinal = React.useCallback(() => {
13317
+ const s = scrollerRef.current;
13318
+ if (!s) return;
13319
+ s.scrollTo({ top: s.scrollHeight, behavior: "smooth" });
13320
+ }, []);
13321
+ if (!visible) return null;
13322
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: CLASS$b, onClick: alFinal, "aria-label": label, title: label, children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { type: "chevron-down", width: 18, height: 18, "aria-hidden": true }) });
13323
+ };
13324
+ ScrollToEnd.displayName = "ScrollToEnd";
13325
+
13138
13326
  function calculateTotalPages(totalRecords, limit) {
13139
13327
  return Math.ceil(totalRecords / limit);
13140
13328
  }
13141
13329
 
13330
+ const useAnchoPropio = () => {
13331
+ const ref = React.useRef(null);
13332
+ const [ancho, setAncho] = React.useState(null);
13333
+ React.useEffect(() => {
13334
+ const nodo = ref.current;
13335
+ if (!nodo) return;
13336
+ if (typeof ResizeObserver === "undefined") {
13337
+ setAncho(nodo.getBoundingClientRect().width);
13338
+ return;
13339
+ }
13340
+ const observador = new ResizeObserver((entradas) => {
13341
+ const w = entradas[0]?.contentRect.width;
13342
+ if (typeof w === "number") setAncho(w);
13343
+ });
13344
+ observador.observe(nodo);
13345
+ return () => observador.disconnect();
13346
+ }, []);
13347
+ return [ref, ancho];
13348
+ };
13142
13349
  const useIsMobile = () => {
13143
13350
  const [isMobile, setIsMobile] = React.useState(false);
13144
13351
  React.useEffect(() => {
@@ -13178,6 +13385,8 @@ function Crud({
13178
13385
  defaultFilters,
13179
13386
  tableHeight,
13180
13387
  tableScroll,
13388
+ layout = "auto",
13389
+ cardsBelow = 768,
13181
13390
  hidePagination = false,
13182
13391
  formDrawerWidth = "80%",
13183
13392
  formColumns = 2,
@@ -13282,6 +13491,69 @@ function Crud({
13282
13491
  }
13283
13492
  return columns;
13284
13493
  }, [renderByFields, isMobile, tableScroll?.x]);
13494
+ const buildRowOptions = React.useCallback((row) => {
13495
+ const menuOptions = [];
13496
+ if (rowActionTypes.includes("edit") && !disabledActions?.edit && onEdit) {
13497
+ menuOptions.push({
13498
+ label: labels?.editButton || "Editar",
13499
+ icon: "edit",
13500
+ onClick: () => {
13501
+ openFormDrawer("edit", row);
13502
+ }
13503
+ });
13504
+ }
13505
+ if (rowActionTypes.includes("delete") && !disabledActions?.delete && onDelete) {
13506
+ menuOptions.push({
13507
+ label: labels?.deleteButton || "Eliminar",
13508
+ icon: "trash",
13509
+ onClick: () => {
13510
+ openConfirmDrawer("delete", void 0, [row]);
13511
+ }
13512
+ });
13513
+ }
13514
+ if (rowActionTypes.includes("view") && !disabledActions?.view) {
13515
+ menuOptions.push({
13516
+ label: labels?.viewButton || "Ver",
13517
+ icon: "eye",
13518
+ onClick: async () => {
13519
+ if (onView) {
13520
+ await onView([row]);
13521
+ }
13522
+ openFormDrawer("view", row);
13523
+ }
13524
+ });
13525
+ }
13526
+ rowActions?.forEach((action) => {
13527
+ if (action.show && !action.show(row)) return;
13528
+ menuOptions.push({
13529
+ label: action.label,
13530
+ icon: action.icon,
13531
+ onClick: () => {
13532
+ if (action.drawerId) {
13533
+ openCustomDrawer(action.drawerId, row);
13534
+ }
13535
+ if (action.onClick) {
13536
+ action.onClick(row);
13537
+ }
13538
+ },
13539
+ disabled: action.disabled ? action.disabled(row) : false,
13540
+ // SPEC-090 T3 — una acción apagada sin motivo es indistinguible de una rota.
13541
+ disabledReason: action.disabledReason?.(row)
13542
+ });
13543
+ });
13544
+ return menuOptions;
13545
+ }, [
13546
+ rowActionTypes,
13547
+ disabledActions,
13548
+ onEdit,
13549
+ onDelete,
13550
+ onView,
13551
+ labels,
13552
+ openFormDrawer,
13553
+ openConfirmDrawer,
13554
+ openCustomDrawer,
13555
+ rowActions
13556
+ ]);
13285
13557
  const tableColumns = React.useMemo(() => {
13286
13558
  if (!showRowActions) return baseTableColumns;
13287
13559
  const actionsColumn = {
@@ -13293,53 +13565,7 @@ function Crud({
13293
13565
  cellType: "CHILDREN",
13294
13566
  dataSource: {
13295
13567
  build: (row) => {
13296
- const menuOptions = [];
13297
- if (rowActionTypes.includes("edit") && !disabledActions?.edit && onEdit) {
13298
- menuOptions.push({
13299
- label: labels?.editButton || "Editar",
13300
- icon: "edit",
13301
- onClick: () => {
13302
- openFormDrawer("edit", row);
13303
- }
13304
- });
13305
- }
13306
- if (rowActionTypes.includes("delete") && !disabledActions?.delete && onDelete) {
13307
- menuOptions.push({
13308
- label: labels?.deleteButton || "Eliminar",
13309
- icon: "trash",
13310
- onClick: () => {
13311
- openConfirmDrawer("delete", void 0, [row]);
13312
- }
13313
- });
13314
- }
13315
- if (rowActionTypes.includes("view") && !disabledActions?.view) {
13316
- menuOptions.push({
13317
- label: labels?.viewButton || "Ver",
13318
- icon: "eye",
13319
- onClick: async () => {
13320
- if (onView) {
13321
- await onView([row]);
13322
- }
13323
- openFormDrawer("view", row);
13324
- }
13325
- });
13326
- }
13327
- rowActions?.forEach((action) => {
13328
- if (action.show && !action.show(row)) return;
13329
- menuOptions.push({
13330
- label: action.label,
13331
- icon: action.icon,
13332
- onClick: () => {
13333
- if (action.drawerId) {
13334
- openCustomDrawer(action.drawerId, row);
13335
- }
13336
- if (action.onClick) {
13337
- action.onClick(row);
13338
- }
13339
- },
13340
- disabled: action.disabled ? action.disabled(row) : false
13341
- });
13342
- });
13568
+ const menuOptions = buildRowOptions(row);
13343
13569
  if (menuOptions.length === 0) return null;
13344
13570
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", width: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
13345
13571
  DropdownMenu,
@@ -13362,20 +13588,7 @@ function Crud({
13362
13588
  }
13363
13589
  };
13364
13590
  return [...baseTableColumns, actionsColumn];
13365
- }, [
13366
- baseTableColumns,
13367
- showRowActions,
13368
- rowActionTypes,
13369
- disabledActions,
13370
- labels,
13371
- onEdit,
13372
- onDelete,
13373
- onView,
13374
- rowActions,
13375
- openFormDrawer,
13376
- openConfirmDrawer,
13377
- openCustomDrawer
13378
- ]);
13591
+ }, [baseTableColumns, showRowActions, buildRowOptions]);
13379
13592
  const formFields = React.useMemo(() => {
13380
13593
  return mapFieldsToFormFields(renderByFields, formMode);
13381
13594
  }, [renderByFields, formMode]);
@@ -13572,6 +13785,10 @@ function Crud({
13572
13785
  return "\xBFEst\xE1 seguro que desea realizar esta acci\xF3n?";
13573
13786
  }
13574
13787
  };
13788
+ const [refContenedor, anchoPropio] = useAnchoPropio();
13789
+ const enTarjetas = layout === "cards" || // Mientras no se ha medido (`null`) se mantiene la tabla: pintar tarjetas y saltar a tabla un
13790
+ // instante después es el parpadeo que se acaba de quitar en otro sitio.
13791
+ layout === "auto" && anchoPropio !== null && anchoPropio < cardsBelow;
13575
13792
  const crudClasses = [
13576
13793
  `${COMPONENT_PREFIX}crud`,
13577
13794
  loading && `${COMPONENT_PREFIX}crud--loading`,
@@ -13588,24 +13805,43 @@ function Crud({
13588
13805
  showLabels: false
13589
13806
  }
13590
13807
  ) }),
13591
- !formOnly && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${COMPONENT_PREFIX}crud__table`, children: /* @__PURE__ */ jsxRuntime.jsx(
13592
- Table$1,
13808
+ !formOnly && /* @__PURE__ */ jsxRuntime.jsxs(
13809
+ "div",
13593
13810
  {
13594
- dataSource,
13595
- columns: tableColumns,
13596
- selectable: showActionBar && actionBarMode === "full",
13597
- selectedRowKeys: selectedKeys,
13598
- onSelectionChange: handleSelectionChange,
13599
- rowKey: getRowKey,
13600
- loading,
13601
- height: tableHeight,
13602
- scroll: tableScroll,
13603
- compact: size === "small",
13604
- bordered: true,
13605
- hoverable: true,
13606
- emptyText: labels?.emptyText
13811
+ className: `${COMPONENT_PREFIX}crud__data${enTarjetas ? ` ${COMPONENT_PREFIX}crud__data--cards` : ""}`,
13812
+ children: [
13813
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${COMPONENT_PREFIX}crud__table`, ref: refContenedor, children: enTarjetas ? /* @__PURE__ */ jsxRuntime.jsx(
13814
+ CardList,
13815
+ {
13816
+ fields: renderByFields,
13817
+ rows: dataSource,
13818
+ rowKey: getRowKey,
13819
+ buildOptions: showRowActions ? buildRowOptions : void 0,
13820
+ loading,
13821
+ emptyText: labels?.emptyText
13822
+ }
13823
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
13824
+ Table$1,
13825
+ {
13826
+ dataSource,
13827
+ columns: tableColumns,
13828
+ selectable: showActionBar && actionBarMode === "full",
13829
+ selectedRowKeys: selectedKeys,
13830
+ onSelectionChange: handleSelectionChange,
13831
+ rowKey: getRowKey,
13832
+ loading,
13833
+ height: tableHeight,
13834
+ scroll: tableScroll,
13835
+ compact: size === "small",
13836
+ bordered: true,
13837
+ hoverable: true,
13838
+ emptyText: labels?.emptyText
13839
+ }
13840
+ ) }),
13841
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollToEnd, { hostRef: refContenedor, dep: `${enTarjetas}:${dataSource.length}:${loading}` })
13842
+ ]
13607
13843
  }
13608
- ) }),
13844
+ ),
13609
13845
  !formOnly && !filters.all_data && !hidePagination && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${COMPONENT_PREFIX}crud__pagination`, children: /* @__PURE__ */ jsxRuntime.jsx(
13610
13846
  Pagination,
13611
13847
  {
@@ -13670,6 +13906,7 @@ function Crud({
13670
13906
  position: "right",
13671
13907
  size: "responsive",
13672
13908
  responsivePercentage: 30,
13909
+ mobileSheet: true,
13673
13910
  className: `${COMPONENT_PREFIX}crud__confirm-drawer`,
13674
13911
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${COMPONENT_PREFIX}crud__confirm-content`, children: [
13675
13912
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: `${COMPONENT_PREFIX}crud__confirm-message`, children: getConfirmMessage() }),
@@ -13718,6 +13955,60 @@ function Crud({
13718
13955
  }
13719
13956
  Crud.displayName = "Crud";
13720
13957
 
13958
+ const CLASS$a = "px-chat-reactive-crud__chat-link";
13959
+ function Burbuja() {
13960
+ return /* @__PURE__ */ jsxRuntime.jsx(
13961
+ "svg",
13962
+ {
13963
+ width: 18,
13964
+ height: 18,
13965
+ viewBox: "0 0 24 24",
13966
+ fill: "none",
13967
+ stroke: "currentColor",
13968
+ strokeWidth: 2,
13969
+ strokeLinecap: "round",
13970
+ strokeLinejoin: "round",
13971
+ "aria-hidden": true,
13972
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
13973
+ }
13974
+ );
13975
+ }
13976
+ const ChatLinkCell = ({
13977
+ targets,
13978
+ eventBus
13979
+ }) => {
13980
+ if (targets.length === 0) return null;
13981
+ const abrir = (id) => eventBus?.emit(CHAT_EVENT.OPEN_CHAT, { chatId: id, closed: false });
13982
+ if (targets.length === 1) {
13983
+ const t = targets[0];
13984
+ const texto = t.label ? `Abrir chat ${t.label}` : "Abrir chat";
13985
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: CLASS$a, "aria-label": texto, title: texto, onClick: () => abrir(t.id), children: /* @__PURE__ */ jsxRuntime.jsx(Burbuja, {}) });
13986
+ }
13987
+ return /* @__PURE__ */ jsxRuntime.jsx(
13988
+ DropdownMenu,
13989
+ {
13990
+ options: targets.map((t) => ({
13991
+ label: t.label ?? "Chat",
13992
+ icon: "message-square",
13993
+ onClick: () => abrir(t.id)
13994
+ })),
13995
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(
13996
+ "button",
13997
+ {
13998
+ type: "button",
13999
+ className: CLASS$a,
14000
+ "aria-label": `Abrir chat (${targets.length} conversaciones)`,
14001
+ title: "Abrir chat",
14002
+ children: /* @__PURE__ */ jsxRuntime.jsx(Burbuja, {})
14003
+ }
14004
+ ),
14005
+ position: "bottom-left",
14006
+ usePortal: true
14007
+ }
14008
+ );
14009
+ };
14010
+ ChatLinkCell.displayName = "ChatLinkCell";
14011
+
13721
14012
  function buildCrudYupSchema(fields) {
13722
14013
  const shape = {};
13723
14014
  for (const f of fields) {
@@ -13798,8 +14089,21 @@ function unwrap$3(raw) {
13798
14089
  }
13799
14090
  return raw;
13800
14091
  }
13801
- async function loadOptions(eventBus, ep) {
13802
- const raw = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, ep.body ?? {});
14092
+ function resolveBody(body, ctx) {
14093
+ if (!body) return {};
14094
+ if (!ctx) return body;
14095
+ return Object.fromEntries(
14096
+ Object.entries(body).map(([k, v]) => {
14097
+ if (typeof v === "string" && v.startsWith("$")) {
14098
+ const key = v.slice(1);
14099
+ return [k, key in ctx ? ctx[key] : v];
14100
+ }
14101
+ return [k, v];
14102
+ })
14103
+ );
14104
+ }
14105
+ async function loadOptions(eventBus, ep, ctx) {
14106
+ const raw = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, resolveBody(ep.body, ctx));
13803
14107
  let data = unwrap$3(raw);
13804
14108
  if (ep.response_path && data && typeof data === "object" && !Array.isArray(data)) {
13805
14109
  data = data[ep.response_path];
@@ -13811,6 +14115,30 @@ async function loadOptions(eventBus, ep) {
13811
14115
  }));
13812
14116
  }
13813
14117
 
14118
+ const esTextoUtil = (v) => typeof v === "string" && v.trim().length > 0;
14119
+ const buscarEnCuerpo = (cuerpo) => {
14120
+ if (!cuerpo || typeof cuerpo !== "object") return null;
14121
+ const c = cuerpo;
14122
+ const detalle = c.detail;
14123
+ if (esTextoUtil(detalle)) return detalle;
14124
+ if (detalle && typeof detalle === "object") {
14125
+ const d = detalle;
14126
+ if (esTextoUtil(d.message)) return d.message;
14127
+ }
14128
+ if (esTextoUtil(c.message)) return c.message;
14129
+ return null;
14130
+ };
14131
+ const ES_JERGA_HTTP = /^Request failed with status code \d+$/i;
14132
+ const mensajeDeError = (e, respaldo) => {
14133
+ const err = e;
14134
+ const delServidor = buscarEnCuerpo(err?.response?.data);
14135
+ if (delServidor) return delServidor;
14136
+ if (esTextoUtil(err?.message) && !ES_JERGA_HTTP.test(err.message.trim())) {
14137
+ return err.message;
14138
+ }
14139
+ return respaldo;
14140
+ };
14141
+
13814
14142
  const REFRESH_INTERVAL_MS = 2e4;
13815
14143
  function unwrap$2(raw) {
13816
14144
  if (raw && typeof raw === "object" && "response" in raw) {
@@ -13851,7 +14179,7 @@ function useReactiveCrud(config, eventBus) {
13851
14179
  const loadList = React.useCallback(async () => {
13852
14180
  if (!eventBus) return;
13853
14181
  const myReq = ++reqIdRef.current;
13854
- setState((s) => ({ ...s, loading: true, error: null }));
14182
+ setState((s) => s.rows.length === 0 ? { ...s, loading: true, error: null } : { ...s, error: null });
13855
14183
  try {
13856
14184
  const { service, url, method } = config.data.list;
13857
14185
  const raw = await fetchViaEventBus(eventBus, service, url, method, {
@@ -13875,7 +14203,7 @@ function useReactiveCrud(config, eventBus) {
13875
14203
  if (myReq === reqIdRef.current) setState({ rows, total, loading: false, error: null });
13876
14204
  } catch (e) {
13877
14205
  if (myReq === reqIdRef.current) {
13878
- setState((s) => ({ ...s, loading: false, error: e instanceof Error ? e.message : "Error al cargar" }));
14206
+ setState((s) => ({ ...s, loading: false, error: mensajeDeError(e, "Error al cargar") }));
13879
14207
  }
13880
14208
  }
13881
14209
  }, [eventBus, config.data.list, skip, pageSize, filters]);
@@ -13919,7 +14247,7 @@ function useReactiveCrud(config, eventBus) {
13919
14247
  }
13920
14248
  await loadList();
13921
14249
  } catch (e) {
13922
- setActionError(e instanceof Error ? e.message : "La acci\xF3n no se pudo completar");
14250
+ setActionError(mensajeDeError(e, "La acci\xF3n no se pudo completar"));
13923
14251
  throw e;
13924
14252
  }
13925
14253
  },
@@ -14102,7 +14430,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
14102
14430
  void Promise.all(
14103
14431
  toLoad.map(async (f) => {
14104
14432
  try {
14105
- const opts = await loadOptions(eventBus, f.optionsEndpoint);
14433
+ const opts = await loadOptions(eventBus, f.optionsEndpoint, { ...row, id: row[idField] });
14106
14434
  return [f.name, opts];
14107
14435
  } catch {
14108
14436
  return [f.name, []];
@@ -14180,7 +14508,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
14180
14508
  await onExecute(ctx);
14181
14509
  onClose();
14182
14510
  } catch (e) {
14183
- setFormError(e instanceof Error ? e.message : "La acci\xF3n no se pudo completar");
14511
+ setFormError(mensajeDeError(e, "La acci\xF3n no se pudo completar"));
14184
14512
  } finally {
14185
14513
  setSubmitting(false);
14186
14514
  }
@@ -14791,6 +15119,7 @@ function unwrap$1(raw) {
14791
15119
  }
14792
15120
  return raw;
14793
15121
  }
15122
+ const DETALLE_OCULTOS = /* @__PURE__ */ new Set(["LOCATION", "CHAT_LINK", "FILE"]);
14794
15123
  function DetailDrawer({ row, config, eventBus, onClose, loadedOptions }) {
14795
15124
  const detail = config.detail;
14796
15125
  const [files, setFiles] = React.useState([]);
@@ -14889,7 +15218,7 @@ function DetailDrawer({ row, config, eventBus, onClose, loadedOptions }) {
14889
15218
  }
14890
15219
  };
14891
15220
  return /* @__PURE__ */ jsxRuntime.jsx(Drawer$1, { isOpen: true, onClose, title, placement: "right", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-reactive-crud-detail", children: [
14892
- /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "px-chat-reactive-crud-detail__fields", children: config.fields.map((f) => {
15221
+ /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "px-chat-reactive-crud-detail__fields", children: config.fields.filter((f) => !DETALLE_OCULTOS.has(f.moleculeType)).map((f) => {
14893
15222
  const { node, copyText } = renderField(f);
14894
15223
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-reactive-crud-detail__row", children: [
14895
15224
  /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "px-chat-reactive-crud-detail__label", children: f.label }),
@@ -15551,35 +15880,13 @@ const LocationField = ({
15551
15880
  };
15552
15881
  LocationField.displayName = "LocationField";
15553
15882
 
15554
- function chatLinkButton(id, eventBus, label) {
15555
- return React.createElement(
15556
- "button",
15557
- {
15558
- key: label ?? id,
15559
- type: "button",
15560
- className: "px-chat-reactive-crud__chat-link",
15561
- "aria-label": label ? `Abrir chat ${label}` : "Abrir chat",
15562
- title: label ?? "Abrir chat",
15563
- onClick: () => eventBus?.emit(CHAT_EVENT.OPEN_CHAT, { chatId: id, closed: false })
15564
- },
15565
- React.createElement(
15566
- "svg",
15567
- { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
15568
- React.createElement("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
15569
- ),
15570
- label ? React.createElement("span", { className: "px-chat-reactive-crud__chat-link-label" }, label) : null
15571
- );
15572
- }
15573
15883
  function chatLinkFormatter(field, eventBus) {
15574
15884
  const sources = field.chats;
15575
15885
  return (value, row) => {
15576
15886
  const r = row;
15577
- if (sources && sources.length) {
15578
- const btns = sources.map((s) => ({ id: r ? r[s.field] : void 0, label: s.label })).filter((s) => s.id != null && s.id !== "").map((s) => chatLinkButton(String(s.id), eventBus, s.label));
15579
- return btns.length ? React.createElement("div", { className: "px-chat-reactive-crud__chat-links" }, btns) : null;
15580
- }
15581
- const id = value != null && value !== "" ? String(value) : "";
15582
- return id ? chatLinkButton(id, eventBus) : null;
15887
+ const targets = sources?.length ? sources.map((s) => ({ id: r ? r[s.field] : void 0, label: s.label })).filter((s) => s.id != null && s.id !== "").map((s) => ({ id: String(s.id), label: s.label })) : value != null && value !== "" ? [{ id: String(value) }] : [];
15888
+ if (!targets.length) return null;
15889
+ return React.createElement(ChatLinkCell, { targets, eventBus });
15583
15890
  };
15584
15891
  }
15585
15892
  function toRenderField(f, loaded, eventBus, mapsApiKey) {
@@ -15618,6 +15925,8 @@ function toRenderField(f, loaded, eventBus, mapsApiKey) {
15618
15925
  // FILE/CHAT_LINK solo son columnas → INPUT (el render lo define formatValue/build).
15619
15926
  moleculeType: f.moleculeType === "FILE" || isChatLink ? "INPUT" : f.moleculeType,
15620
15927
  showInTable: f.showInTable,
15928
+ summary: f.summary,
15929
+ // SPEC-093
15621
15930
  showInForm: f.showInForm,
15622
15931
  showInModes: f.showInModes,
15623
15932
  required: f.required,
@@ -15702,6 +16011,10 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
15702
16011
  icon: a.icon,
15703
16012
  show: (row) => matchesVisibleWhen(a, row),
15704
16013
  disabled: (row) => matchesDisabledWhen(a, row),
16014
+ // SPEC-090 T3 — el `disabledReason` de la config ya existía, pero solo lo pintaba el panel
16015
+ // del chat; en la tabla el botón se apagaba mudo. Afecta también al de la inspección
16016
+ // («Falta el comprobante de pago del cliente», SPEC-066 T7), que tampoco se veía aquí.
16017
+ disabledReason: (row) => matchesDisabledWhen(a, row) ? a.disabledReason : void 0,
15705
16018
  onClick: (row) => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] })
15706
16019
  }));
15707
16020
  if (config.detail) {
@@ -15710,6 +16023,7 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
15710
16023
  icon: "eye",
15711
16024
  show: () => true,
15712
16025
  disabled: () => false,
16026
+ disabledReason: () => void 0,
15713
16027
  onClick: (row) => setDetailRow(row)
15714
16028
  });
15715
16029
  }
@@ -15742,6 +16056,8 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
15742
16056
  loading: crud.loading,
15743
16057
  filters: { skip: crud.skip, limit: crud.pageSize, all_data: false, filters: crud.filters },
15744
16058
  tableScroll: config.tableScroll,
16059
+ layout: config.layout,
16060
+ cardsBelow: config.cardsBelow,
15745
16061
  validationSchema,
15746
16062
  getRowKey: (row) => String(row[config.idField]),
15747
16063
  onCreate: config.data.create ? async (rows) => {
@@ -16011,12 +16327,18 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
16011
16327
  "button",
16012
16328
  {
16013
16329
  type: "button",
16014
- className: `px-chat-crud-item__action${a.intent === "danger" ? " is-danger" : ""}`,
16330
+ className: [
16331
+ "px-chat-crud-item__action",
16332
+ a.intent === "danger" ? "is-danger" : "",
16333
+ disabled && a.disabledReason ? "is-con-motivo" : ""
16334
+ ].filter(Boolean).join(" "),
16015
16335
  disabled,
16016
- title: disabled ? a.disabledReason : void 0,
16017
16336
  onClick: () => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] }),
16018
16337
  children: [
16019
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: a.label }),
16338
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-crud-item__action-texts", children: [
16339
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: a.label }),
16340
+ disabled && a.disabledReason && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-crud-item__action-reason", children: a.disabledReason })
16341
+ ] }),
16020
16342
  done && /* @__PURE__ */ jsxRuntime.jsx(Icon$1, { type: "check-circle", width: 18, height: 18, className: "px-chat-crud-item__done", "aria-label": "Hecho" })
16021
16343
  ]
16022
16344
  },
@@ -16651,6 +16973,7 @@ function ChatNavigator({ config, eventBus }) {
16651
16973
  React.useEffect(() => {
16652
16974
  void load();
16653
16975
  }, [load]);
16976
+ const listaRef = React.useRef(null);
16654
16977
  const loadRef = React.useRef(load);
16655
16978
  loadRef.current = load;
16656
16979
  React.useEffect(() => {
@@ -16698,8 +17021,6 @@ function ChatNavigator({ config, eventBus }) {
16698
17021
  };
16699
17022
  const page = Math.floor(skip / pageSize) + 1;
16700
17023
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
16701
- const canPrev = skip > 0;
16702
- const canNext = skip + pageSize < total;
16703
17024
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-chat-navigator", children: [
16704
17025
  config.search && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-chat-navigator__search", children: [
16705
17026
  /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "px-chat-chat-navigator__search-icon", children: [
@@ -16779,143 +17100,126 @@ function ChatNavigator({ config, eventBus }) {
16779
17100
  loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-chat-navigator__state", children: "Cargando conversaciones\u2026" }),
16780
17101
  error && !loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-chat-navigator__state px-chat-chat-navigator__state--error", children: error }),
16781
17102
  !loading && !error && items.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-chat-navigator__state", children: labels.emptyText ?? "Sin conversaciones" }),
16782
- /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "px-chat-chat-navigator__list", children: items.map((item) => {
16783
- const isRenaming = renamingId === item.id;
16784
- const displayTitle = item.title?.trim() || fallbackTitle(item);
16785
- return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "px-chat-chat-navigator__item", children: [
16786
- isRenaming ? /* @__PURE__ */ jsxRuntime.jsx(
16787
- "form",
16788
- {
16789
- className: "px-chat-chat-navigator__rename",
16790
- onSubmit: (e) => {
16791
- e.preventDefault();
16792
- void submitRename(item.id);
16793
- },
16794
- children: /* @__PURE__ */ jsxRuntime.jsx(
16795
- "input",
16796
- {
16797
- autoFocus: true,
16798
- className: "px-chat-chat-navigator__rename-input",
16799
- value: renameValue,
16800
- onChange: (e) => setRenameValue(e.target.value),
16801
- onBlur: () => void submitRename(item.id),
16802
- "aria-label": labels.renameTitle ?? "Nuevo t\xEDtulo"
16803
- }
16804
- )
16805
- }
16806
- ) : /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "px-chat-chat-navigator__open", onClick: () => open(item), children: [
16807
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-chat-navigator__title", children: [
16808
- displayTitle,
16809
- item.unread_proactive_count && item.unread_proactive_count >= 1 ? /* @__PURE__ */ jsxRuntime.jsx(
16810
- "span",
16811
- {
16812
- className: "px-chat-chat-navigator__badge-count",
16813
- "aria-label": `${item.unread_proactive_count} mensajes nuevos sin leer`,
16814
- title: `${item.unread_proactive_count} sin leer`,
16815
- children: item.unread_proactive_count >= 100 ? "100+" : item.unread_proactive_count
16816
- }
16817
- ) : null
16818
- ] }),
16819
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-chat-navigator__meta", children: [
16820
- item.agent_name ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__agent", children: item.agent_name }) : null,
16821
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__time", children: relativeTime(item.updated_date) }),
16822
- (() => {
16823
- const opt = item.rol_code ? (config.originActorOptions ?? []).find((o) => o.value === item.rol_code) : void 0;
16824
- if (!opt) return null;
16825
- const c = opt.color;
16826
- const nameField = config.originActorNameField;
16827
- const allyName = nameField ? item[nameField] : void 0;
16828
- const label = allyName ? String(allyName) : opt.label;
16829
- return /* @__PURE__ */ jsxRuntime.jsx(
16830
- "span",
16831
- {
16832
- className: "px-chat-chat-navigator__origin-badge",
16833
- style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
16834
- title: label,
16835
- children: label
16836
- }
16837
- );
16838
- })(),
16839
- (() => {
16840
- const field = config.tagField;
16841
- if (!field) return null;
16842
- const value = item[field];
16843
- if (!value) return null;
16844
- const opt = (config.tagOptions ?? []).find((o) => o.value === String(value));
16845
- if (!opt) return null;
16846
- const c = opt.color;
16847
- return /* @__PURE__ */ jsxRuntime.jsx(
16848
- "span",
17103
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-chat-navigator__list-wrap", ref: listaRef, children: [
17104
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "px-chat-chat-navigator__list", children: items.map((item) => {
17105
+ const isRenaming = renamingId === item.id;
17106
+ const displayTitle = item.title?.trim() || fallbackTitle(item);
17107
+ return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "px-chat-chat-navigator__item", children: [
17108
+ isRenaming ? /* @__PURE__ */ jsxRuntime.jsx(
17109
+ "form",
17110
+ {
17111
+ className: "px-chat-chat-navigator__rename",
17112
+ onSubmit: (e) => {
17113
+ e.preventDefault();
17114
+ void submitRename(item.id);
17115
+ },
17116
+ children: /* @__PURE__ */ jsxRuntime.jsx(
17117
+ "input",
16849
17118
  {
16850
- className: "px-chat-chat-navigator__origin-badge",
16851
- style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
16852
- title: opt.label,
16853
- children: opt.label
17119
+ autoFocus: true,
17120
+ className: "px-chat-chat-navigator__rename-input",
17121
+ value: renameValue,
17122
+ onChange: (e) => setRenameValue(e.target.value),
17123
+ onBlur: () => void submitRename(item.id),
17124
+ "aria-label": labels.renameTitle ?? "Nuevo t\xEDtulo"
16854
17125
  }
16855
- );
16856
- })(),
16857
- item.service_status ? (() => {
16858
- const opt = (config.serviceStatusOptions ?? []).find(
16859
- (o) => o.value === item.service_status
16860
- );
16861
- const c = opt?.color;
16862
- return /* @__PURE__ */ jsxRuntime.jsx(
17126
+ )
17127
+ }
17128
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "px-chat-chat-navigator__open", onClick: () => open(item), children: [
17129
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-chat-navigator__title", children: [
17130
+ displayTitle,
17131
+ item.unread_proactive_count && item.unread_proactive_count >= 1 ? /* @__PURE__ */ jsxRuntime.jsx(
16863
17132
  "span",
16864
17133
  {
16865
- className: "px-chat-chat-navigator__service-badge",
16866
- style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
16867
- children: opt?.label ?? item.service_status
17134
+ className: "px-chat-chat-navigator__badge-count",
17135
+ "aria-label": `${item.unread_proactive_count} mensajes nuevos sin leer`,
17136
+ title: `${item.unread_proactive_count} sin leer`,
17137
+ children: item.unread_proactive_count >= 100 ? "100+" : item.unread_proactive_count
16868
17138
  }
16869
- );
16870
- })() : null,
16871
- item.closed ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__badge", children: labels.closedBadge ?? "Cerrado" }) : null
16872
- ] })
16873
- ] }),
16874
- config.data.rename && !isRenaming && /* @__PURE__ */ jsxRuntime.jsx(
16875
- "button",
16876
- {
16877
- type: "button",
16878
- className: "px-chat-chat-navigator__rename-btn",
16879
- "aria-label": "Renombrar",
16880
- title: "Renombrar",
16881
- onClick: () => startRename(item),
16882
- children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
16883
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 20h9" }),
16884
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" })
17139
+ ) : null
17140
+ ] }),
17141
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-chat-navigator__meta", children: [
17142
+ item.agent_name ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__agent", children: item.agent_name }) : null,
17143
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__time", children: relativeTime(item.updated_date) }),
17144
+ (() => {
17145
+ const opt = item.rol_code ? (config.originActorOptions ?? []).find((o) => o.value === item.rol_code) : void 0;
17146
+ if (!opt) return null;
17147
+ const c = opt.color;
17148
+ const nameField = config.originActorNameField;
17149
+ const allyName = nameField ? item[nameField] : void 0;
17150
+ const label = allyName ? String(allyName) : opt.label;
17151
+ return /* @__PURE__ */ jsxRuntime.jsx(
17152
+ "span",
17153
+ {
17154
+ className: "px-chat-chat-navigator__origin-badge",
17155
+ style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
17156
+ title: label,
17157
+ children: label
17158
+ }
17159
+ );
17160
+ })(),
17161
+ (() => {
17162
+ const field = config.tagField;
17163
+ if (!field) return null;
17164
+ const value = item[field];
17165
+ if (!value) return null;
17166
+ const opt = (config.tagOptions ?? []).find((o) => o.value === String(value));
17167
+ if (!opt) return null;
17168
+ const c = opt.color;
17169
+ return /* @__PURE__ */ jsxRuntime.jsx(
17170
+ "span",
17171
+ {
17172
+ className: "px-chat-chat-navigator__origin-badge",
17173
+ style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
17174
+ title: opt.label,
17175
+ children: opt.label
17176
+ }
17177
+ );
17178
+ })(),
17179
+ item.service_status ? (() => {
17180
+ const opt = (config.serviceStatusOptions ?? []).find(
17181
+ (o) => o.value === item.service_status
17182
+ );
17183
+ const c = opt?.color;
17184
+ return /* @__PURE__ */ jsxRuntime.jsx(
17185
+ "span",
17186
+ {
17187
+ className: "px-chat-chat-navigator__service-badge",
17188
+ style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
17189
+ children: opt?.label ?? item.service_status
17190
+ }
17191
+ );
17192
+ })() : null,
17193
+ item.closed ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__badge", children: labels.closedBadge ?? "Cerrado" }) : null
16885
17194
  ] })
16886
- }
16887
- )
16888
- ] }, item.id);
16889
- }) }),
16890
- total > pageSize && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-chat-navigator__pager", children: [
16891
- /* @__PURE__ */ jsxRuntime.jsx(
16892
- "button",
16893
- {
16894
- type: "button",
16895
- className: "px-chat-chat-navigator__pager-btn",
16896
- disabled: !canPrev,
16897
- onClick: () => setSkip(Math.max(0, skip - pageSize)),
16898
- "aria-label": "P\xE1gina anterior",
16899
- children: "\u2039"
16900
- }
16901
- ),
16902
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-chat-navigator__pager-info", children: [
16903
- page,
16904
- " / ",
16905
- totalPages
16906
- ] }),
16907
- /* @__PURE__ */ jsxRuntime.jsx(
16908
- "button",
16909
- {
16910
- type: "button",
16911
- className: "px-chat-chat-navigator__pager-btn",
16912
- disabled: !canNext,
16913
- onClick: () => setSkip(skip + pageSize),
16914
- "aria-label": "P\xE1gina siguiente",
16915
- children: "\u203A"
16916
- }
16917
- )
17195
+ ] }),
17196
+ config.data.rename && !isRenaming && /* @__PURE__ */ jsxRuntime.jsx(
17197
+ "button",
17198
+ {
17199
+ type: "button",
17200
+ className: "px-chat-chat-navigator__rename-btn",
17201
+ "aria-label": "Renombrar",
17202
+ title: "Renombrar",
17203
+ onClick: () => startRename(item),
17204
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
17205
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 20h9" }),
17206
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" })
17207
+ ] })
17208
+ }
17209
+ )
17210
+ ] }, item.id);
17211
+ }) }),
17212
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollToEnd, { hostRef: listaRef, selector: ".px-chat-chat-navigator__list", dep: `${items.length}:${loading}` })
16918
17213
  ] }),
17214
+ total > pageSize && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-chat-navigator__pager", children: /* @__PURE__ */ jsxRuntime.jsx(
17215
+ Pagination,
17216
+ {
17217
+ current: page,
17218
+ total: totalPages,
17219
+ onChange: (p) => setSkip((p - 1) * pageSize),
17220
+ showTotal: false
17221
+ }
17222
+ ) }),
16919
17223
  config.advancedFilters?.length ? /* @__PURE__ */ jsxRuntime.jsx(Drawer$1, { isOpen: advOpen, onClose: () => setAdvOpen(false), title: "Filtros avanzados", children: /* @__PURE__ */ jsxRuntime.jsx(
16920
17224
  Filter,
16921
17225
  {
@@ -24320,6 +24624,7 @@ exports.LumiDateInput = DateInput$1;
24320
24624
  exports.LumiDrawer = Drawer;
24321
24625
  exports.LumiDropdownMenu = DropdownMenu;
24322
24626
  exports.LumiDynamicForm = DynamicForm;
24627
+ exports.LumiFieldValue = FieldValue;
24323
24628
  exports.LumiFilter = Filter;
24324
24629
  exports.LumiFilterRow = FilterRow;
24325
24630
  exports.LumiFormCheckbox = Checkbox;
@@ -24338,6 +24643,7 @@ exports.LumiNotification = Notification;
24338
24643
  exports.LumiPagination = Pagination;
24339
24644
  exports.LumiPopover = Popover;
24340
24645
  exports.LumiQRCode = QRCode;
24646
+ exports.LumiRecordCard = RecordCard;
24341
24647
  exports.LumiSelect = Select$1;
24342
24648
  exports.LumiStepIndicator = StepIndicator;
24343
24649
  exports.LumiStepper = Stepper;