pixelon 0.3.77 → 0.3.79

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 (25) 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/atoms/lumi/Icon/Icon.d.ts +1 -0
  5. package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/ChatLinkCell.d.ts +20 -0
  6. package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/optionsLoader.d.ts +2 -1
  7. package/dist/components/molecules/chat/StructuredContent/components/ReactiveCrud/types.d.ts +6 -0
  8. package/dist/components/molecules/lumi/Drawer/Drawer.d.ts +8 -0
  9. package/dist/components/molecules/lumi/RecordCard/RecordCard.d.ts +14 -0
  10. package/dist/components/molecules/lumi/RecordCard/index.d.ts +2 -0
  11. package/dist/components/molecules/lumi/RecordCard/types.d.ts +16 -0
  12. package/dist/components/molecules/lumi/ScrollToEnd/ScrollToEnd.d.ts +21 -0
  13. package/dist/components/molecules/lumi/ScrollToEnd/index.d.ts +2 -0
  14. package/dist/components/templates/lumi/Crud/CardList.d.ts +34 -0
  15. package/dist/components/templates/lumi/Crud/Crud.d.ts +1 -1
  16. package/dist/components/templates/lumi/Crud/ScrollToEnd.d.ts +18 -0
  17. package/dist/components/templates/lumi/Crud/helpers/fieldValue.d.ts +12 -0
  18. package/dist/components/templates/lumi/Crud/types.d.ts +25 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.esm.js +566 -276
  21. package/dist/index.js +567 -275
  22. package/dist/styles.css +1 -1
  23. package/dist/styles.css.map +1 -1
  24. package/package.json +1 -1
  25. package/src/styles/index.scss +4 -0
package/dist/index.esm.js CHANGED
@@ -3987,13 +3987,13 @@ function resolveVariable(template, formValues, fieldStates) {
3987
3987
  }
3988
3988
  return "";
3989
3989
  }
3990
- function resolveBody(obj, formValues, fieldStates) {
3990
+ function resolveBody$1(obj, formValues, fieldStates) {
3991
3991
  if (typeof obj === "string") return resolveVariable(obj, formValues, fieldStates);
3992
- if (Array.isArray(obj)) return obj.map((item) => resolveBody(item, formValues, fieldStates));
3992
+ if (Array.isArray(obj)) return obj.map((item) => resolveBody$1(item, formValues, fieldStates));
3993
3993
  if (obj !== null && typeof obj === "object") {
3994
3994
  const resolved = {};
3995
3995
  for (const [key, value] of Object.entries(obj)) {
3996
- resolved[key] = resolveBody(value, formValues, fieldStates);
3996
+ resolved[key] = resolveBody$1(value, formValues, fieldStates);
3997
3997
  }
3998
3998
  return resolved;
3999
3999
  }
@@ -4043,7 +4043,7 @@ const CostSummaryField = ({
4043
4043
  return;
4044
4044
  }
4045
4045
  fetchedRef.current = true;
4046
- const body = resolveBody(field.endpoint.body || {}, values, fieldStates);
4046
+ const body = resolveBody$1(field.endpoint.body || {}, values, fieldStates);
4047
4047
  setLoading(true);
4048
4048
  setError("");
4049
4049
  fetchViaEventBus(eventBus, field.endpoint.service, field.endpoint.url, field.endpoint.method, body).then((data) => {
@@ -4497,7 +4497,7 @@ function useReactiveEndpoints({ fields, fieldStates, setFieldState, eventBus, ti
4497
4497
  setFieldState(field.name, { loading: true, error: null });
4498
4498
  const startTime = Date.now();
4499
4499
  try {
4500
- const resolvedBody = field.endpoint.body ? resolveBody(field.endpoint.body, formValues, fieldStates) : void 0;
4500
+ const resolvedBody = field.endpoint.body ? resolveBody$1(field.endpoint.body, formValues, fieldStates) : void 0;
4501
4501
  const params = field.endpoint.params ? resolveParams(field.endpoint.params, formValues, fieldStates) : {};
4502
4502
  const requestBody = resolvedBody ?? (Object.keys(params).length > 0 ? params : void 0);
4503
4503
  if (eventBus) {
@@ -4772,7 +4772,7 @@ function useReactiveForm(props) {
4772
4772
  const handleSubmit = async () => {
4773
4773
  if (isSubmitting) return;
4774
4774
  setIsSubmitting(true);
4775
- const resolvedPayload = resolveBody(submit.payload_map, values, fieldStates);
4775
+ const resolvedPayload = resolveBody$1(submit.payload_map, values, fieldStates);
4776
4776
  if (eventBus) {
4777
4777
  try {
4778
4778
  const data = await fetchViaEventBus(
@@ -5732,6 +5732,7 @@ const LUMI_ICON_TYPES = {
5732
5732
  MENU: "menu",
5733
5733
  ARROW_UP: "arrow-up",
5734
5734
  ARROW_RIGHT: "arrow-right",
5735
+ ARROW_LEFT: "arrow-left",
5735
5736
  // Iconos para Servicios
5736
5737
  BUILDING: "building",
5737
5738
  MAP_PIN: "map-pin",
@@ -6403,6 +6404,24 @@ const ArrowRightIcon = (props) => /* @__PURE__ */ jsxs(
6403
6404
  ]
6404
6405
  }
6405
6406
  );
6407
+ const ArrowLeftIcon = (props) => /* @__PURE__ */ jsxs(
6408
+ "svg",
6409
+ {
6410
+ width: "16",
6411
+ height: "16",
6412
+ viewBox: "0 0 24 24",
6413
+ fill: "none",
6414
+ stroke: "currentColor",
6415
+ strokeWidth: "2",
6416
+ strokeLinecap: "round",
6417
+ strokeLinejoin: "round",
6418
+ ...props,
6419
+ children: [
6420
+ /* @__PURE__ */ jsx("line", { x1: "19", y1: "12", x2: "5", y2: "12" }),
6421
+ /* @__PURE__ */ jsx("polyline", { points: "12 19 5 12 12 5" })
6422
+ ]
6423
+ }
6424
+ );
6406
6425
  const BuildingIcon = (props) => /* @__PURE__ */ jsxs(
6407
6426
  "svg",
6408
6427
  {
@@ -6544,6 +6563,7 @@ const lumiIconMap = {
6544
6563
  [LUMI_ICON_TYPES.MENU]: MenuIcon,
6545
6564
  [LUMI_ICON_TYPES.ARROW_UP]: ArrowUpIcon,
6546
6565
  [LUMI_ICON_TYPES.ARROW_RIGHT]: ArrowRightIcon,
6566
+ [LUMI_ICON_TYPES.ARROW_LEFT]: ArrowLeftIcon,
6547
6567
  // Iconos para Servicios
6548
6568
  [LUMI_ICON_TYPES.BUILDING]: BuildingIcon,
6549
6569
  [LUMI_ICON_TYPES.MAP_PIN]: MapPinIcon,
@@ -10865,6 +10885,7 @@ const Drawer = ({
10865
10885
  size = DRAWER_SIZES.RESPONSIVE,
10866
10886
  fixedSize = 400,
10867
10887
  responsivePercentage = 80,
10888
+ mobileSheet = false,
10868
10889
  title,
10869
10890
  headerExtra,
10870
10891
  showCloseButton = true,
@@ -10947,11 +10968,13 @@ const Drawer = ({
10947
10968
  `${COMPONENT_PREFIX}drawer--${size}`,
10948
10969
  isOpen && `${COMPONENT_PREFIX}drawer--open`,
10949
10970
  isMobile && `${COMPONENT_PREFIX}drawer--mobile`,
10971
+ mobileSheet && `${COMPONENT_PREFIX}drawer--sheet`,
10950
10972
  className
10951
10973
  ].filter(Boolean).join(" ");
10952
10974
  const overlayClasses = [
10953
10975
  `${COMPONENT_PREFIX}drawer-overlay`,
10954
- isOpen && showOverlay && `${COMPONENT_PREFIX}drawer-overlay--visible`
10976
+ isOpen && showOverlay && `${COMPONENT_PREFIX}drawer-overlay--visible`,
10977
+ mobileSheet && `${COMPONENT_PREFIX}drawer-overlay--sheet`
10955
10978
  ].filter(Boolean).join(" ");
10956
10979
  const getDrawerStyles = () => {
10957
10980
  if (isMobile) {
@@ -11015,6 +11038,7 @@ const Drawer = ({
11015
11038
  variant: "ghost",
11016
11039
  onClick: onClose,
11017
11040
  "aria-label": "Cerrar drawer",
11041
+ className: `${COMPONENT_PREFIX}drawer__close`,
11018
11042
  icon: /* @__PURE__ */ jsx(Icon, { type: LUMI_ICON_TYPES.CLOSE, size: "medium" })
11019
11043
  }
11020
11044
  )
@@ -13058,36 +13082,45 @@ const Badge = ({
13058
13082
  };
13059
13083
  Badge.displayName = "LumiBadge";
13060
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 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 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
+
13061
13117
  function mapFieldsToTableColumns(fields) {
13062
13118
  return fields.filter((field) => field.showInTable !== false).map((field) => {
13063
13119
  let width = field.columnWidth;
13064
13120
  if (field.fixed && typeof field.columnWidth === "number") {
13065
13121
  width = `${field.columnWidth}px`;
13066
13122
  }
13067
- const options = field.uiProps?.options;
13068
- const build = (row) => {
13069
- const value = row[field.key];
13070
- const opt = options?.find((o) => String(o.value) === String(value));
13071
- if (field.moleculeType === "BADGE") {
13072
- if (value == null || value === "") return null;
13073
- return createElement(Badge, { color: opt?.color, children: opt?.label ?? String(value) });
13074
- }
13075
- if (field.moleculeType === "INPUT_CURRENCY") {
13076
- if (value == null || value === "") return null;
13077
- const cp = field.uiProps ?? {};
13078
- return createElement(CurrencyDisplay, {
13079
- value,
13080
- showCode: cp.showCode ?? true,
13081
- showSymbol: cp.showSymbol ?? true,
13082
- currency: cp.currency ?? { decimalPlaces: 0 },
13083
- align: field.align ?? "right",
13084
- size: cp.size ?? "md"
13085
- });
13086
- }
13087
- if (field.formatValue) return field.formatValue(value, row);
13088
- if (opt) return opt.label;
13089
- return value;
13090
- };
13123
+ const build = (row) => renderFieldValue(field, row);
13091
13124
  return {
13092
13125
  key: field.key,
13093
13126
  title: field.label,
@@ -13118,10 +13151,201 @@ function mapFieldsToFormFields(fields, mode) {
13118
13151
  }));
13119
13152
  }
13120
13153
 
13154
+ const CLASS$e = `${COMPONENT_PREFIX}field-value`;
13155
+ const FieldValue = ({
13156
+ label,
13157
+ children,
13158
+ layout = "inline",
13159
+ className
13160
+ }) => /* @__PURE__ */ jsxs("div", { className: [CLASS$e, `${CLASS$e}--${layout}`, className].filter(Boolean).join(" "), children: [
13161
+ /* @__PURE__ */ jsx("span", { className: `${CLASS$e}__label`, children: label }),
13162
+ /* @__PURE__ */ 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 = useId();
13178
+ return /* @__PURE__ */ jsxs("div", { className: [CLASS$d, open ? `${CLASS$d}--open` : "", className].filter(Boolean).join(" "), children: [
13179
+ /* @__PURE__ */ 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__ */ jsxs("span", { className: `${CLASS$d}__heading`, children: [
13190
+ /* @__PURE__ */ jsx("span", { className: `${CLASS$d}__title`, children: title }),
13191
+ summary && /* @__PURE__ */ jsx("span", { className: `${CLASS$d}__summary`, children: summary })
13192
+ ] }),
13193
+ /* @__PURE__ */ 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__ */ jsxs("div", { className: `${CLASS$d}__body`, id: cuerpoId, children: [
13207
+ children,
13208
+ actions && /* @__PURE__ */ 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] = 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__ */ jsx("div", { className: `${CLASS$c}__estado`, children: "Cargando..." });
13234
+ }
13235
+ if (rows.length === 0) {
13236
+ return /* @__PURE__ */ jsx("div", { className: `${CLASS$c}__estado`, children: emptyText });
13237
+ }
13238
+ const [principal, ...secundarios] = resumen;
13239
+ return /* @__PURE__ */ 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__ */ 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__ */ jsx(Fragment, { children: secundarios.map((f) => /* @__PURE__ */ jsx(React.Fragment, { children: renderFieldValue(f, row) }, f.key)) }) : void 0,
13251
+ actions: opciones.length > 0 ? /* @__PURE__ */ jsx(Fragment, { children: opciones.map((o) => /* @__PURE__ */ 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__ */ jsx("span", { children: o.label }),
13263
+ o.disabled && o.disabledReason && /* @__PURE__ */ 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__ */ 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] = useState(false);
13288
+ const scrollerRef = useRef(null);
13289
+ 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 = 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__ */ jsx("button", { type: "button", className: CLASS$b, onClick: alFinal, "aria-label": label, title: label, children: /* @__PURE__ */ jsx(Icon, { type: "chevron-down", width: 18, height: 18, "aria-hidden": true }) });
13323
+ };
13324
+ ScrollToEnd.displayName = "ScrollToEnd";
13325
+
13121
13326
  function calculateTotalPages(totalRecords, limit) {
13122
13327
  return Math.ceil(totalRecords / limit);
13123
13328
  }
13124
13329
 
13330
+ const useAnchoPropio = () => {
13331
+ const ref = useRef(null);
13332
+ const [ancho, setAncho] = useState(null);
13333
+ 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
+ };
13125
13349
  const useIsMobile = () => {
13126
13350
  const [isMobile, setIsMobile] = useState(false);
13127
13351
  useEffect(() => {
@@ -13161,6 +13385,8 @@ function Crud({
13161
13385
  defaultFilters,
13162
13386
  tableHeight,
13163
13387
  tableScroll,
13388
+ layout = "auto",
13389
+ cardsBelow = 768,
13164
13390
  hidePagination = false,
13165
13391
  formDrawerWidth = "80%",
13166
13392
  formColumns = 2,
@@ -13265,6 +13491,69 @@ function Crud({
13265
13491
  }
13266
13492
  return columns;
13267
13493
  }, [renderByFields, isMobile, tableScroll?.x]);
13494
+ const buildRowOptions = 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
+ ]);
13268
13557
  const tableColumns = useMemo(() => {
13269
13558
  if (!showRowActions) return baseTableColumns;
13270
13559
  const actionsColumn = {
@@ -13276,55 +13565,7 @@ function Crud({
13276
13565
  cellType: "CHILDREN",
13277
13566
  dataSource: {
13278
13567
  build: (row) => {
13279
- const menuOptions = [];
13280
- if (rowActionTypes.includes("edit") && !disabledActions?.edit && onEdit) {
13281
- menuOptions.push({
13282
- label: labels?.editButton || "Editar",
13283
- icon: "edit",
13284
- onClick: () => {
13285
- openFormDrawer("edit", row);
13286
- }
13287
- });
13288
- }
13289
- if (rowActionTypes.includes("delete") && !disabledActions?.delete && onDelete) {
13290
- menuOptions.push({
13291
- label: labels?.deleteButton || "Eliminar",
13292
- icon: "trash",
13293
- onClick: () => {
13294
- openConfirmDrawer("delete", void 0, [row]);
13295
- }
13296
- });
13297
- }
13298
- if (rowActionTypes.includes("view") && !disabledActions?.view) {
13299
- menuOptions.push({
13300
- label: labels?.viewButton || "Ver",
13301
- icon: "eye",
13302
- onClick: async () => {
13303
- if (onView) {
13304
- await onView([row]);
13305
- }
13306
- openFormDrawer("view", row);
13307
- }
13308
- });
13309
- }
13310
- rowActions?.forEach((action) => {
13311
- if (action.show && !action.show(row)) return;
13312
- menuOptions.push({
13313
- label: action.label,
13314
- icon: action.icon,
13315
- onClick: () => {
13316
- if (action.drawerId) {
13317
- openCustomDrawer(action.drawerId, row);
13318
- }
13319
- if (action.onClick) {
13320
- action.onClick(row);
13321
- }
13322
- },
13323
- disabled: action.disabled ? action.disabled(row) : false,
13324
- // SPEC-090 T3 — una acción apagada sin motivo es indistinguible de una rota.
13325
- disabledReason: action.disabledReason?.(row)
13326
- });
13327
- });
13568
+ const menuOptions = buildRowOptions(row);
13328
13569
  if (menuOptions.length === 0) return null;
13329
13570
  return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", width: "100%" }, children: /* @__PURE__ */ jsx(
13330
13571
  DropdownMenu,
@@ -13347,20 +13588,7 @@ function Crud({
13347
13588
  }
13348
13589
  };
13349
13590
  return [...baseTableColumns, actionsColumn];
13350
- }, [
13351
- baseTableColumns,
13352
- showRowActions,
13353
- rowActionTypes,
13354
- disabledActions,
13355
- labels,
13356
- onEdit,
13357
- onDelete,
13358
- onView,
13359
- rowActions,
13360
- openFormDrawer,
13361
- openConfirmDrawer,
13362
- openCustomDrawer
13363
- ]);
13591
+ }, [baseTableColumns, showRowActions, buildRowOptions]);
13364
13592
  const formFields = useMemo(() => {
13365
13593
  return mapFieldsToFormFields(renderByFields, formMode);
13366
13594
  }, [renderByFields, formMode]);
@@ -13557,6 +13785,10 @@ function Crud({
13557
13785
  return "\xBFEst\xE1 seguro que desea realizar esta acci\xF3n?";
13558
13786
  }
13559
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;
13560
13792
  const crudClasses = [
13561
13793
  `${COMPONENT_PREFIX}crud`,
13562
13794
  loading && `${COMPONENT_PREFIX}crud--loading`,
@@ -13573,24 +13805,43 @@ function Crud({
13573
13805
  showLabels: false
13574
13806
  }
13575
13807
  ) }),
13576
- !formOnly && /* @__PURE__ */ jsx("div", { className: `${COMPONENT_PREFIX}crud__table`, children: /* @__PURE__ */ jsx(
13577
- Table,
13808
+ !formOnly && /* @__PURE__ */ jsxs(
13809
+ "div",
13578
13810
  {
13579
- dataSource,
13580
- columns: tableColumns,
13581
- selectable: showActionBar && actionBarMode === "full",
13582
- selectedRowKeys: selectedKeys,
13583
- onSelectionChange: handleSelectionChange,
13584
- rowKey: getRowKey,
13585
- loading,
13586
- height: tableHeight,
13587
- scroll: tableScroll,
13588
- compact: size === "small",
13589
- bordered: true,
13590
- hoverable: true,
13591
- emptyText: labels?.emptyText
13811
+ className: `${COMPONENT_PREFIX}crud__data${enTarjetas ? ` ${COMPONENT_PREFIX}crud__data--cards` : ""}`,
13812
+ children: [
13813
+ /* @__PURE__ */ jsx("div", { className: `${COMPONENT_PREFIX}crud__table`, ref: refContenedor, children: enTarjetas ? /* @__PURE__ */ 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__ */ jsx(
13824
+ Table,
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__ */ jsx(ScrollToEnd, { hostRef: refContenedor, dep: `${enTarjetas}:${dataSource.length}:${loading}` })
13842
+ ]
13592
13843
  }
13593
- ) }),
13844
+ ),
13594
13845
  !formOnly && !filters.all_data && !hidePagination && /* @__PURE__ */ jsx("div", { className: `${COMPONENT_PREFIX}crud__pagination`, children: /* @__PURE__ */ jsx(
13595
13846
  Pagination,
13596
13847
  {
@@ -13655,6 +13906,7 @@ function Crud({
13655
13906
  position: "right",
13656
13907
  size: "responsive",
13657
13908
  responsivePercentage: 30,
13909
+ mobileSheet: true,
13658
13910
  className: `${COMPONENT_PREFIX}crud__confirm-drawer`,
13659
13911
  children: /* @__PURE__ */ jsxs("div", { className: `${COMPONENT_PREFIX}crud__confirm-content`, children: [
13660
13912
  /* @__PURE__ */ jsx("p", { className: `${COMPONENT_PREFIX}crud__confirm-message`, children: getConfirmMessage() }),
@@ -13703,6 +13955,60 @@ function Crud({
13703
13955
  }
13704
13956
  Crud.displayName = "Crud";
13705
13957
 
13958
+ const CLASS$a = "px-chat-reactive-crud__chat-link";
13959
+ function Burbuja() {
13960
+ return /* @__PURE__ */ 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__ */ 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__ */ jsx("button", { type: "button", className: CLASS$a, "aria-label": texto, title: texto, onClick: () => abrir(t.id), children: /* @__PURE__ */ jsx(Burbuja, {}) });
13986
+ }
13987
+ return /* @__PURE__ */ 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__ */ 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__ */ jsx(Burbuja, {})
14003
+ }
14004
+ ),
14005
+ position: "bottom-left",
14006
+ usePortal: true
14007
+ }
14008
+ );
14009
+ };
14010
+ ChatLinkCell.displayName = "ChatLinkCell";
14011
+
13706
14012
  function buildCrudYupSchema(fields) {
13707
14013
  const shape = {};
13708
14014
  for (const f of fields) {
@@ -13783,8 +14089,21 @@ function unwrap$3(raw) {
13783
14089
  }
13784
14090
  return raw;
13785
14091
  }
13786
- async function loadOptions(eventBus, ep) {
13787
- 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));
13788
14107
  let data = unwrap$3(raw);
13789
14108
  if (ep.response_path && data && typeof data === "object" && !Array.isArray(data)) {
13790
14109
  data = data[ep.response_path];
@@ -13860,7 +14179,7 @@ function useReactiveCrud(config, eventBus) {
13860
14179
  const loadList = useCallback(async () => {
13861
14180
  if (!eventBus) return;
13862
14181
  const myReq = ++reqIdRef.current;
13863
- setState((s) => ({ ...s, loading: true, error: null }));
14182
+ setState((s) => s.rows.length === 0 ? { ...s, loading: true, error: null } : { ...s, error: null });
13864
14183
  try {
13865
14184
  const { service, url, method } = config.data.list;
13866
14185
  const raw = await fetchViaEventBus(eventBus, service, url, method, {
@@ -14111,7 +14430,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
14111
14430
  void Promise.all(
14112
14431
  toLoad.map(async (f) => {
14113
14432
  try {
14114
- const opts = await loadOptions(eventBus, f.optionsEndpoint);
14433
+ const opts = await loadOptions(eventBus, f.optionsEndpoint, { ...row, id: row[idField] });
14115
14434
  return [f.name, opts];
14116
14435
  } catch {
14117
14436
  return [f.name, []];
@@ -14800,6 +15119,7 @@ function unwrap$1(raw) {
14800
15119
  }
14801
15120
  return raw;
14802
15121
  }
15122
+ const DETALLE_OCULTOS = /* @__PURE__ */ new Set(["LOCATION", "CHAT_LINK", "FILE"]);
14803
15123
  function DetailDrawer({ row, config, eventBus, onClose, loadedOptions }) {
14804
15124
  const detail = config.detail;
14805
15125
  const [files, setFiles] = useState([]);
@@ -14898,7 +15218,7 @@ function DetailDrawer({ row, config, eventBus, onClose, loadedOptions }) {
14898
15218
  }
14899
15219
  };
14900
15220
  return /* @__PURE__ */ jsx(Drawer$1, { isOpen: true, onClose, title, placement: "right", children: /* @__PURE__ */ jsxs("div", { className: "px-chat-reactive-crud-detail", children: [
14901
- /* @__PURE__ */ jsx("dl", { className: "px-chat-reactive-crud-detail__fields", children: config.fields.map((f) => {
15221
+ /* @__PURE__ */ jsx("dl", { className: "px-chat-reactive-crud-detail__fields", children: config.fields.filter((f) => !DETALLE_OCULTOS.has(f.moleculeType)).map((f) => {
14902
15222
  const { node, copyText } = renderField(f);
14903
15223
  return /* @__PURE__ */ jsxs("div", { className: "px-chat-reactive-crud-detail__row", children: [
14904
15224
  /* @__PURE__ */ jsx("dt", { className: "px-chat-reactive-crud-detail__label", children: f.label }),
@@ -15560,35 +15880,13 @@ const LocationField = ({
15560
15880
  };
15561
15881
  LocationField.displayName = "LocationField";
15562
15882
 
15563
- function chatLinkButton(id, eventBus, label) {
15564
- return createElement(
15565
- "button",
15566
- {
15567
- key: label ?? id,
15568
- type: "button",
15569
- className: "px-chat-reactive-crud__chat-link",
15570
- "aria-label": label ? `Abrir chat ${label}` : "Abrir chat",
15571
- title: label ?? "Abrir chat",
15572
- onClick: () => eventBus?.emit(CHAT_EVENT.OPEN_CHAT, { chatId: id, closed: false })
15573
- },
15574
- createElement(
15575
- "svg",
15576
- { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
15577
- 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" })
15578
- ),
15579
- label ? createElement("span", { className: "px-chat-reactive-crud__chat-link-label" }, label) : null
15580
- );
15581
- }
15582
15883
  function chatLinkFormatter(field, eventBus) {
15583
15884
  const sources = field.chats;
15584
15885
  return (value, row) => {
15585
15886
  const r = row;
15586
- if (sources && sources.length) {
15587
- 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));
15588
- return btns.length ? createElement("div", { className: "px-chat-reactive-crud__chat-links" }, btns) : null;
15589
- }
15590
- const id = value != null && value !== "" ? String(value) : "";
15591
- 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 createElement(ChatLinkCell, { targets, eventBus });
15592
15890
  };
15593
15891
  }
15594
15892
  function toRenderField(f, loaded, eventBus, mapsApiKey) {
@@ -15627,6 +15925,8 @@ function toRenderField(f, loaded, eventBus, mapsApiKey) {
15627
15925
  // FILE/CHAT_LINK solo son columnas → INPUT (el render lo define formatValue/build).
15628
15926
  moleculeType: f.moleculeType === "FILE" || isChatLink ? "INPUT" : f.moleculeType,
15629
15927
  showInTable: f.showInTable,
15928
+ summary: f.summary,
15929
+ // SPEC-093
15630
15930
  showInForm: f.showInForm,
15631
15931
  showInModes: f.showInModes,
15632
15932
  required: f.required,
@@ -15756,6 +16056,8 @@ function ReactiveCrud({ config, eventBus, autoOpenCreate, prefill, onCreated, on
15756
16056
  loading: crud.loading,
15757
16057
  filters: { skip: crud.skip, limit: crud.pageSize, all_data: false, filters: crud.filters },
15758
16058
  tableScroll: config.tableScroll,
16059
+ layout: config.layout,
16060
+ cardsBelow: config.cardsBelow,
15759
16061
  validationSchema,
15760
16062
  getRowKey: (row) => String(row[config.idField]),
15761
16063
  onCreate: config.data.create ? async (rows) => {
@@ -16025,12 +16327,18 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
16025
16327
  "button",
16026
16328
  {
16027
16329
  type: "button",
16028
- 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(" "),
16029
16335
  disabled,
16030
- title: disabled ? a.disabledReason : void 0,
16031
16336
  onClick: () => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] }),
16032
16337
  children: [
16033
- /* @__PURE__ */ jsx("span", { children: a.label }),
16338
+ /* @__PURE__ */ jsxs("span", { className: "px-chat-crud-item__action-texts", children: [
16339
+ /* @__PURE__ */ jsx("span", { children: a.label }),
16340
+ disabled && a.disabledReason && /* @__PURE__ */ jsx("span", { className: "px-chat-crud-item__action-reason", children: a.disabledReason })
16341
+ ] }),
16034
16342
  done && /* @__PURE__ */ jsx(Icon$1, { type: "check-circle", width: 18, height: 18, className: "px-chat-crud-item__done", "aria-label": "Hecho" })
16035
16343
  ]
16036
16344
  },
@@ -16665,6 +16973,7 @@ function ChatNavigator({ config, eventBus }) {
16665
16973
  useEffect(() => {
16666
16974
  void load();
16667
16975
  }, [load]);
16976
+ const listaRef = useRef(null);
16668
16977
  const loadRef = useRef(load);
16669
16978
  loadRef.current = load;
16670
16979
  useEffect(() => {
@@ -16712,8 +17021,6 @@ function ChatNavigator({ config, eventBus }) {
16712
17021
  };
16713
17022
  const page = Math.floor(skip / pageSize) + 1;
16714
17023
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
16715
- const canPrev = skip > 0;
16716
- const canNext = skip + pageSize < total;
16717
17024
  return /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator", children: [
16718
17025
  config.search && /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator__search", children: [
16719
17026
  /* @__PURE__ */ 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: [
@@ -16793,143 +17100,126 @@ function ChatNavigator({ config, eventBus }) {
16793
17100
  loading && /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__state", children: "Cargando conversaciones\u2026" }),
16794
17101
  error && !loading && /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__state px-chat-chat-navigator__state--error", children: error }),
16795
17102
  !loading && !error && items.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__state", children: labels.emptyText ?? "Sin conversaciones" }),
16796
- /* @__PURE__ */ jsx("ul", { className: "px-chat-chat-navigator__list", children: items.map((item) => {
16797
- const isRenaming = renamingId === item.id;
16798
- const displayTitle = item.title?.trim() || fallbackTitle(item);
16799
- return /* @__PURE__ */ jsxs("li", { className: "px-chat-chat-navigator__item", children: [
16800
- isRenaming ? /* @__PURE__ */ jsx(
16801
- "form",
16802
- {
16803
- className: "px-chat-chat-navigator__rename",
16804
- onSubmit: (e) => {
16805
- e.preventDefault();
16806
- void submitRename(item.id);
16807
- },
16808
- children: /* @__PURE__ */ jsx(
16809
- "input",
16810
- {
16811
- autoFocus: true,
16812
- className: "px-chat-chat-navigator__rename-input",
16813
- value: renameValue,
16814
- onChange: (e) => setRenameValue(e.target.value),
16815
- onBlur: () => void submitRename(item.id),
16816
- "aria-label": labels.renameTitle ?? "Nuevo t\xEDtulo"
16817
- }
16818
- )
16819
- }
16820
- ) : /* @__PURE__ */ jsxs("button", { type: "button", className: "px-chat-chat-navigator__open", onClick: () => open(item), children: [
16821
- /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__title", children: [
16822
- displayTitle,
16823
- item.unread_proactive_count && item.unread_proactive_count >= 1 ? /* @__PURE__ */ jsx(
16824
- "span",
16825
- {
16826
- className: "px-chat-chat-navigator__badge-count",
16827
- "aria-label": `${item.unread_proactive_count} mensajes nuevos sin leer`,
16828
- title: `${item.unread_proactive_count} sin leer`,
16829
- children: item.unread_proactive_count >= 100 ? "100+" : item.unread_proactive_count
16830
- }
16831
- ) : null
16832
- ] }),
16833
- /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__meta", children: [
16834
- item.agent_name ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__agent", children: item.agent_name }) : null,
16835
- /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__time", children: relativeTime(item.updated_date) }),
16836
- (() => {
16837
- const opt = item.rol_code ? (config.originActorOptions ?? []).find((o) => o.value === item.rol_code) : void 0;
16838
- if (!opt) return null;
16839
- const c = opt.color;
16840
- const nameField = config.originActorNameField;
16841
- const allyName = nameField ? item[nameField] : void 0;
16842
- const label = allyName ? String(allyName) : opt.label;
16843
- return /* @__PURE__ */ jsx(
16844
- "span",
16845
- {
16846
- className: "px-chat-chat-navigator__origin-badge",
16847
- style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
16848
- title: label,
16849
- children: label
16850
- }
16851
- );
16852
- })(),
16853
- (() => {
16854
- const field = config.tagField;
16855
- if (!field) return null;
16856
- const value = item[field];
16857
- if (!value) return null;
16858
- const opt = (config.tagOptions ?? []).find((o) => o.value === String(value));
16859
- if (!opt) return null;
16860
- const c = opt.color;
16861
- return /* @__PURE__ */ jsx(
16862
- "span",
17103
+ /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator__list-wrap", ref: listaRef, children: [
17104
+ /* @__PURE__ */ 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__ */ jsxs("li", { className: "px-chat-chat-navigator__item", children: [
17108
+ isRenaming ? /* @__PURE__ */ 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__ */ jsx(
17117
+ "input",
16863
17118
  {
16864
- className: "px-chat-chat-navigator__origin-badge",
16865
- style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
16866
- title: opt.label,
16867
- 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"
16868
17125
  }
16869
- );
16870
- })(),
16871
- item.service_status ? (() => {
16872
- const opt = (config.serviceStatusOptions ?? []).find(
16873
- (o) => o.value === item.service_status
16874
- );
16875
- const c = opt?.color;
16876
- return /* @__PURE__ */ jsx(
17126
+ )
17127
+ }
17128
+ ) : /* @__PURE__ */ jsxs("button", { type: "button", className: "px-chat-chat-navigator__open", onClick: () => open(item), children: [
17129
+ /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__title", children: [
17130
+ displayTitle,
17131
+ item.unread_proactive_count && item.unread_proactive_count >= 1 ? /* @__PURE__ */ jsx(
16877
17132
  "span",
16878
17133
  {
16879
- className: "px-chat-chat-navigator__service-badge",
16880
- style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
16881
- 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
16882
17138
  }
16883
- );
16884
- })() : null,
16885
- item.closed ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__badge", children: labels.closedBadge ?? "Cerrado" }) : null
16886
- ] })
16887
- ] }),
16888
- config.data.rename && !isRenaming && /* @__PURE__ */ jsx(
16889
- "button",
16890
- {
16891
- type: "button",
16892
- className: "px-chat-chat-navigator__rename-btn",
16893
- "aria-label": "Renombrar",
16894
- title: "Renombrar",
16895
- onClick: () => startRename(item),
16896
- children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
16897
- /* @__PURE__ */ jsx("path", { d: "M12 20h9" }),
16898
- /* @__PURE__ */ 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__ */ jsxs("span", { className: "px-chat-chat-navigator__meta", children: [
17142
+ item.agent_name ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__agent", children: item.agent_name }) : null,
17143
+ /* @__PURE__ */ 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__ */ 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__ */ 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__ */ 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__ */ jsx("span", { className: "px-chat-chat-navigator__badge", children: labels.closedBadge ?? "Cerrado" }) : null
16899
17194
  ] })
16900
- }
16901
- )
16902
- ] }, item.id);
16903
- }) }),
16904
- total > pageSize && /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator__pager", children: [
16905
- /* @__PURE__ */ jsx(
16906
- "button",
16907
- {
16908
- type: "button",
16909
- className: "px-chat-chat-navigator__pager-btn",
16910
- disabled: !canPrev,
16911
- onClick: () => setSkip(Math.max(0, skip - pageSize)),
16912
- "aria-label": "P\xE1gina anterior",
16913
- children: "\u2039"
16914
- }
16915
- ),
16916
- /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__pager-info", children: [
16917
- page,
16918
- " / ",
16919
- totalPages
16920
- ] }),
16921
- /* @__PURE__ */ jsx(
16922
- "button",
16923
- {
16924
- type: "button",
16925
- className: "px-chat-chat-navigator__pager-btn",
16926
- disabled: !canNext,
16927
- onClick: () => setSkip(skip + pageSize),
16928
- "aria-label": "P\xE1gina siguiente",
16929
- children: "\u203A"
16930
- }
16931
- )
17195
+ ] }),
17196
+ config.data.rename && !isRenaming && /* @__PURE__ */ 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__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
17205
+ /* @__PURE__ */ jsx("path", { d: "M12 20h9" }),
17206
+ /* @__PURE__ */ 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__ */ jsx(ScrollToEnd, { hostRef: listaRef, selector: ".px-chat-chat-navigator__list", dep: `${items.length}:${loading}` })
16932
17213
  ] }),
17214
+ total > pageSize && /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__pager", children: /* @__PURE__ */ jsx(
17215
+ Pagination,
17216
+ {
17217
+ current: page,
17218
+ total: totalPages,
17219
+ onChange: (p) => setSkip((p - 1) * pageSize),
17220
+ showTotal: false
17221
+ }
17222
+ ) }),
16933
17223
  config.advancedFilters?.length ? /* @__PURE__ */ jsx(Drawer$1, { isOpen: advOpen, onClose: () => setAdvOpen(false), title: "Filtros avanzados", children: /* @__PURE__ */ jsx(
16934
17224
  Filter,
16935
17225
  {
@@ -24272,4 +24562,4 @@ function buildBreadcrumbPath(items, targetKey, currentPath = []) {
24272
24562
  return [];
24273
24563
  }
24274
24564
 
24275
- export { BottomSheet, CHAT_CONFIG_DEFAULTS, CHAT_EVENT, ICON_TYPES as CHAT_ICON_TYPES, Chat, AssistantMessage as ChatAssistantMessage, AssistantThinkingIndicator as ChatAssistantThinkingIndicator, AttachmentPreview as ChatAttachmentPreview, Avatar as ChatAvatar, Button$1 as ChatButton, ChatConfigProvider, DateDivider as ChatDateDivider, FileViewer as ChatFileViewer, ChatFloatingActions, FullscreenViewer as ChatFullscreenViewer, ChatHeader as ChatHeaderComponent, Icon$1 as ChatIcon, Input$2 as ChatInput, ChatInput as ChatInputComponent, Link as ChatLink, Loader$1 as ChatLoader, Logo as ChatLogo, MessageStatus as ChatMessageStatus, Modal as ChatModal, ChatNavigator, Select$2 as ChatSelect, SelectMenu as ChatSelectMenu, StructuredContent as ChatStructuredContent, Text as ChatText, Textarea as ChatTextarea, UserAttachments as ChatUserAttachments, UserMessage as ChatUserMessage, ChatWidget, FileField, BADGE_VARIANTS as LUMI_BADGE_VARIANTS, DEFAULT_TITLES as LUMI_DEFAULT_TITLES, LUMI_ICON_TYPES, LiveIndicator, LocationField, ActionBar as LumiActionBar, Badge as LumiBadge, Base as LumiBase, BaseNavbar as LumiBaseNavbar, BaseWorkspace as LumiBaseWorkspace, Breadcrumb as LumiBreadcrumb, BreadcrumbItem as LumiBreadcrumbItem, BreadcrumbSeparator as LumiBreadcrumbSeparator, Button as LumiButton, Calendar as LumiCalendar, Card as LumiCard, Checkbox$1 as LumiCheckbox, Crud as LumiCrud, CurrencyDisplay as LumiCurrencyDisplay, Dashboard as LumiDashboard, DashboardNavbar as LumiDashboardNavbar, DashboardSidebar as LumiDashboardSidebar, DashboardWorkspace as LumiDashboardWorkspace, DateDisplay as LumiDateDisplay, DateInput$1 as LumiDateInput, Drawer as LumiDrawer, DropdownMenu as LumiDropdownMenu, DynamicForm as LumiDynamicForm, Filter as LumiFilter, FilterRow as LumiFilterRow, Checkbox as LumiFormCheckbox, DateInput as LumiFormDateInput, FormDrawer as LumiFormDrawer, Input as LumiFormInput, Select as LumiFormSelect, TimeInput as LumiFormTimeInput, Icon as LumiIcon, Input$1 as LumiInput, Loader as LumiLoader, Menu as LumiMenu, MenuItem as LumiMenuItem, MenuItemGroup as LumiMenuItemGroup, Notification as LumiNotification, Pagination as LumiPagination, Popover as LumiPopover, QRCode as LumiQRCode, Select$1 as LumiSelect, StepIndicator as LumiStepIndicator, Stepper as LumiStepper, SubMenu as LumiSubMenu, Table as LumiTable, Tabs as LumiTabs, TextArea as LumiTextArea, TimeInput$1 as LumiTimeInput, Tooltip as LumiTooltip, MapView, ReactiveCrud, ServiceShortcuts, ServicesMap, SessionExpiryPrompt, UserTools, chatEventBus, createDeepPropertySubscriber, createEventBus, createStore, createStoreMerger, deepMerge, getLastFleetSnapshot, setLastFleetSnapshot, useChatConfig };
24565
+ export { BottomSheet, CHAT_CONFIG_DEFAULTS, CHAT_EVENT, ICON_TYPES as CHAT_ICON_TYPES, Chat, AssistantMessage as ChatAssistantMessage, AssistantThinkingIndicator as ChatAssistantThinkingIndicator, AttachmentPreview as ChatAttachmentPreview, Avatar as ChatAvatar, Button$1 as ChatButton, ChatConfigProvider, DateDivider as ChatDateDivider, FileViewer as ChatFileViewer, ChatFloatingActions, FullscreenViewer as ChatFullscreenViewer, ChatHeader as ChatHeaderComponent, Icon$1 as ChatIcon, Input$2 as ChatInput, ChatInput as ChatInputComponent, Link as ChatLink, Loader$1 as ChatLoader, Logo as ChatLogo, MessageStatus as ChatMessageStatus, Modal as ChatModal, ChatNavigator, Select$2 as ChatSelect, SelectMenu as ChatSelectMenu, StructuredContent as ChatStructuredContent, Text as ChatText, Textarea as ChatTextarea, UserAttachments as ChatUserAttachments, UserMessage as ChatUserMessage, ChatWidget, FileField, BADGE_VARIANTS as LUMI_BADGE_VARIANTS, DEFAULT_TITLES as LUMI_DEFAULT_TITLES, LUMI_ICON_TYPES, LiveIndicator, LocationField, ActionBar as LumiActionBar, Badge as LumiBadge, Base as LumiBase, BaseNavbar as LumiBaseNavbar, BaseWorkspace as LumiBaseWorkspace, Breadcrumb as LumiBreadcrumb, BreadcrumbItem as LumiBreadcrumbItem, BreadcrumbSeparator as LumiBreadcrumbSeparator, Button as LumiButton, Calendar as LumiCalendar, Card as LumiCard, Checkbox$1 as LumiCheckbox, Crud as LumiCrud, CurrencyDisplay as LumiCurrencyDisplay, Dashboard as LumiDashboard, DashboardNavbar as LumiDashboardNavbar, DashboardSidebar as LumiDashboardSidebar, DashboardWorkspace as LumiDashboardWorkspace, DateDisplay as LumiDateDisplay, DateInput$1 as LumiDateInput, Drawer as LumiDrawer, DropdownMenu as LumiDropdownMenu, DynamicForm as LumiDynamicForm, FieldValue as LumiFieldValue, Filter as LumiFilter, FilterRow as LumiFilterRow, Checkbox as LumiFormCheckbox, DateInput as LumiFormDateInput, FormDrawer as LumiFormDrawer, Input as LumiFormInput, Select as LumiFormSelect, TimeInput as LumiFormTimeInput, Icon as LumiIcon, Input$1 as LumiInput, Loader as LumiLoader, Menu as LumiMenu, MenuItem as LumiMenuItem, MenuItemGroup as LumiMenuItemGroup, Notification as LumiNotification, Pagination as LumiPagination, Popover as LumiPopover, QRCode as LumiQRCode, RecordCard as LumiRecordCard, Select$1 as LumiSelect, StepIndicator as LumiStepIndicator, Stepper as LumiStepper, SubMenu as LumiSubMenu, Table as LumiTable, Tabs as LumiTabs, TextArea as LumiTextArea, TimeInput$1 as LumiTimeInput, Tooltip as LumiTooltip, MapView, ReactiveCrud, ServiceShortcuts, ServicesMap, SessionExpiryPrompt, UserTools, chatEventBus, createDeepPropertySubscriber, createEventBus, createStore, createStoreMerger, deepMerge, getLastFleetSnapshot, setLastFleetSnapshot, useChatConfig };