pixelon 0.3.84 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,6 @@ export { ActionMenu } from './ActionMenu/ActionMenu';
13
13
  export { ReactiveCrud } from './ReactiveCrud/ReactiveCrud';
14
14
  export { ReactiveCrudChat } from './ReactiveCrud/ReactiveCrudChat';
15
15
  export { ReactiveCrudItemChat } from './ReactiveCrud/ReactiveCrudItemChat';
16
- export { PriceNegotiationChat } from './ReactiveCrud/PriceNegotiationChat';
17
16
  export { MapView } from './MapView/MapView';
18
17
  export { MapViewChat } from './MapView/MapViewChat';
19
18
  export type { MapPoint, MapViewProps, MapViewLabels } from './MapView/types';
@@ -1,3 +1,16 @@
1
1
  import React from 'react';
2
2
  import type { ChatComponentProps } from './types';
3
+ export interface HostChatComponent {
4
+ render: React.FC<ChatComponentProps>;
5
+ /** No se oculta al llegar a un estado terminal (paneles y navegación, no acciones de un uso). */
6
+ persistent?: boolean;
7
+ /** Contenido del autor, sin interacción: nunca se oculta al re-hidratar el historial. */
8
+ pureDisplay?: boolean;
9
+ }
10
+ type EntradaHost = React.FC<ChatComponentProps> | HostChatComponent;
11
+ /** Registra componentes del consumidor. Se llama una vez, al arrancar la app. */
12
+ export declare function registerChatComponents(comps: Record<string, EntradaHost>): void;
13
+ /** Vacía el registro del host. Para tests: sin esto un caso contamina al siguiente. */
14
+ export declare function clearChatComponents(): void;
3
15
  export declare function renderComponent(props: ChatComponentProps): React.ReactNode;
16
+ export {};
package/dist/index.d.ts CHANGED
@@ -25,6 +25,11 @@ export { default as ChatModal } from './components/molecules/chat/Modal/Modal';
25
25
  export { default as ChatUserAttachments } from './components/molecules/chat/UserAttachments/UserAttachments';
26
26
  export { default as ChatUserMessage } from './components/molecules/chat/UserMessage/UserMessage';
27
27
  export { StructuredContent as ChatStructuredContent, type StructuredContentProps as ChatStructuredContentProps } from './components/molecules/chat/StructuredContent';
28
+ export { useReactiveCrud } from './components/molecules/chat/StructuredContent/components/ReactiveCrud/useReactiveCrud';
29
+ export { ActionFormDrawer } from './components/molecules/chat/StructuredContent/components/ReactiveCrud/ActionFormDrawer';
30
+ export { matchesVisibleWhen, matchesDisabledWhen, matchesDoneWhen, disabledReasonFor, } from './components/molecules/chat/StructuredContent/components/ReactiveCrud/ReactiveCrud';
31
+ export { registerChatComponents, clearChatComponents, type HostChatComponent, } from './components/molecules/chat/StructuredContent/helpers/componentRegistry';
32
+ export type { ChatComponentProps as ChatHostComponentProps } from './components/molecules/chat/StructuredContent/helpers/types';
28
33
  export { ServiceShortcuts } from './components/molecules/chat/StructuredContent/components/ServiceShortcuts/ServiceShortcuts';
29
34
  export type { ServiceShortcut, ServiceShortcutsProps } from './components/molecules/chat/StructuredContent/components/ServiceShortcuts/ServiceShortcuts';
30
35
  export { ServicesMap } from './components/molecules/chat/StructuredContent/components/ServicesMap/ServicesMap';
package/dist/index.esm.js CHANGED
@@ -1651,7 +1651,7 @@ function getSubmitActionId(component) {
1651
1651
  }
1652
1652
  const TERMINAL_STATUS = /* @__PURE__ */ new Set(["answered", "cancelled", "expired"]);
1653
1653
  const DISPLAY_COMPONENTS = /* @__PURE__ */ new Set(["card", "badge", "reactive_form", "carousel"]);
1654
- const PERSISTENT_COMPONENTS = /* @__PURE__ */ new Set(["reactive_crud", "reactive_crud_item", "price_negotiation", "chat_navigator", "map_view", "services_map", "service_shortcuts", "user_tools"]);
1654
+ const PERSISTENT_COMPONENTS = /* @__PURE__ */ new Set(["reactive_crud", "reactive_crud_item", "chat_navigator", "map_view", "user_tools"]);
1655
1655
  const PURE_DISPLAY_COMPONENTS = /* @__PURE__ */ new Set(["card", "badge"]);
1656
1656
 
1657
1657
  const AnsweredSummary = ({ component }) => {
@@ -16311,7 +16311,8 @@ function ReactiveCrudItemChat({ component, eventBus }) {
16311
16311
  const moduleKey = pick("module");
16312
16312
  const recordId = pick("record_id") ?? pick("recordId") ?? pick("id") ?? pick("service_id");
16313
16313
  const recordLabel = pick("record_label") ?? pick("label");
16314
- const allowed = pick("actions");
16314
+ const allowedRaw = pick("actions");
16315
+ const allowed = Array.isArray(allowedRaw) && allowedRaw.length > 0 ? allowedRaw : void 0;
16315
16316
  const config = typeof moduleKey === "string" && resolveModuleConfig ? resolveModuleConfig(moduleKey) : void 0;
16316
16317
  if (!config || recordId == null || recordId === "") return null;
16317
16318
  return /* @__PURE__ */ jsx(
@@ -16440,108 +16441,6 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
16440
16441
  ] });
16441
16442
  }
16442
16443
 
16443
- const ACTOR_LABEL = { COLLA: "la empresa aliada", ADMIN: "la operadora" };
16444
- const TURN_GATED = /* @__PURE__ */ new Set(["accept_price", "propose_price"]);
16445
- function PriceNegotiationChat({ component, eventBus }) {
16446
- const { resolveModuleConfig } = useChatConfig();
16447
- const props = component.props ?? {};
16448
- const top = component;
16449
- const pick = (key) => props[key] !== void 0 ? props[key] : top[key];
16450
- const moduleKey = pick("module");
16451
- const recordId = pick("record_id") ?? pick("service_id") ?? pick("id");
16452
- const recordLabel = pick("record_label") ?? pick("label");
16453
- const allowed = pick("actions");
16454
- const config = typeof moduleKey === "string" && resolveModuleConfig ? resolveModuleConfig(moduleKey) : void 0;
16455
- if (!config || recordId == null || recordId === "") return null;
16456
- return /* @__PURE__ */ jsx(
16457
- NegotiationPanel,
16458
- {
16459
- config,
16460
- recordId: String(recordId),
16461
- recordLabel,
16462
- allowed: Array.isArray(allowed) ? allowed.map(String) : void 0,
16463
- eventBus
16464
- }
16465
- );
16466
- }
16467
- function NegotiationPanel({ config, recordId, recordLabel, allowed, eventBus }) {
16468
- const { chatIsForeign } = useChatConfig();
16469
- const crud = useReactiveCrud(config, eventBus);
16470
- const [activeAction, setActiveAction] = useState(null);
16471
- const row = useMemo(
16472
- () => crud.rows.find((r) => String(r[config.idField]) === recordId),
16473
- [crud.rows, config.idField, recordId]
16474
- );
16475
- const actions = useMemo(() => {
16476
- if (!row) return [];
16477
- return (config.rowActions ?? []).filter((a) => !allowed || allowed.includes(a.key)).filter((a) => matchesVisibleWhen(a, row));
16478
- }, [config.rowActions, row, allowed]);
16479
- const title = recordLabel ?? config.title ?? "Servicio";
16480
- const proposed = row?.proposed_price;
16481
- const proposer = row?.price_proposed_by;
16482
- const myTurn = row?.my_turn === true || String(row?.my_turn) === "true";
16483
- const hasOffer = proposed != null && proposed !== "";
16484
- const canPropose = !allowed || allowed.includes("propose_price");
16485
- const agreed = row?.price_amount;
16486
- const esperandoRespuesta = ["QUOTED", "PRE_ASSIGNED"].includes(String(row?.status ?? ""));
16487
- const cerrada = !esperandoRespuesta && agreed != null && agreed !== "";
16488
- const ofertaMia = hasOffer && !myTurn;
16489
- const quienOfrece = ofertaMia ? "tu oferta" : `oferta de ${ACTOR_LABEL[proposer ?? ""] ?? "la otra parte"}`;
16490
- return /* @__PURE__ */ jsxs("div", { className: "px-chat-price-neg", children: [
16491
- /* @__PURE__ */ jsxs("div", { className: "px-chat-price-neg__header", children: [
16492
- /* @__PURE__ */ jsx("span", { className: "px-chat-price-neg__title", children: title }),
16493
- /* @__PURE__ */ jsx("span", { className: `px-chat-price-neg__badge${cerrada ? " is-closed" : ""}`, children: cerrada ? "Precio acordado" : canPropose ? "Negociaci\xF3n de precio" : "Cotizaci\xF3n" })
16494
- ] }),
16495
- crud.actionError && /* @__PURE__ */ jsxs("div", { className: "px-chat-price-neg__error", role: "alert", children: [
16496
- /* @__PURE__ */ jsx("span", { children: crud.actionError }),
16497
- /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Cerrar", onClick: crud.clearActionError, children: "\u2715" })
16498
- ] }),
16499
- crud.loading && !row && /* @__PURE__ */ jsx("div", { className: "px-chat-price-neg__hint", children: "Cargando\u2026" }),
16500
- !crud.loading && !row && /* @__PURE__ */ jsx("div", { className: "px-chat-price-neg__hint", children: "No se encontr\xF3 el servicio." }),
16501
- row && /* @__PURE__ */ jsxs(Fragment, { children: [
16502
- /* @__PURE__ */ jsx("div", { className: "px-chat-price-neg__offer", children: cerrada ? /* @__PURE__ */ jsxs(Fragment, { children: [
16503
- /* @__PURE__ */ jsx("span", { className: "px-chat-price-neg__amount", children: /* @__PURE__ */ jsx(CurrencyDisplay, { value: agreed }) }),
16504
- /* @__PURE__ */ jsx("span", { className: "px-chat-price-neg__by", children: "precio acordado" })
16505
- ] }) : hasOffer ? /* @__PURE__ */ jsxs(Fragment, { children: [
16506
- /* @__PURE__ */ jsx("span", { className: "px-chat-price-neg__amount", children: /* @__PURE__ */ jsx(CurrencyDisplay, { value: proposed }) }),
16507
- /* @__PURE__ */ jsx("span", { className: "px-chat-price-neg__by", children: canPropose ? quienOfrece : "costo del servicio" })
16508
- ] }) : /* @__PURE__ */ jsx("span", { className: "px-chat-price-neg__by", children: "A\xFAn no hay una oferta. Prop\xF3n un valor." }) }),
16509
- !cerrada && /* @__PURE__ */ jsx("div", { className: `px-chat-price-neg__turn${myTurn ? " is-mine" : ""}`, children: myTurn ? canPropose ? "Es tu turno: acepta o contraprop\xF3n." : "Es tu turno: acepta o rechaza." : "Esperando la respuesta de la otra parte\u2026" }),
16510
- !cerrada && /* @__PURE__ */ jsxs("div", { className: "px-chat-price-neg__actions", children: [
16511
- actions.map((a) => {
16512
- const gated = TURN_GATED.has(a.key) && !myTurn;
16513
- const noOffer = a.key === "accept_price" && !hasOffer;
16514
- const disabled = gated || noOffer || Boolean(chatIsForeign);
16515
- return /* @__PURE__ */ jsx(
16516
- "button",
16517
- {
16518
- type: "button",
16519
- className: `px-chat-price-neg__action${a.intent === "danger" ? " is-danger" : a.key === "accept_price" ? " is-primary" : ""}`,
16520
- disabled,
16521
- title: gated ? "Esperando la respuesta de la otra parte" : void 0,
16522
- onClick: () => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] }),
16523
- children: a.label
16524
- },
16525
- a.key
16526
- );
16527
- }),
16528
- actions.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-chat-price-neg__hint", children: "Sin acciones disponibles." })
16529
- ] })
16530
- ] }),
16531
- activeAction && /* @__PURE__ */ jsx(
16532
- ActionFormDrawer,
16533
- {
16534
- action: activeAction.action,
16535
- row: activeAction.row,
16536
- idField: config.idField,
16537
- eventBus,
16538
- onExecute: (ctx) => crud.runEndpoints(activeAction.action.endpoints ?? [activeAction.action.endpoint], ctx),
16539
- onClose: () => setActiveAction(null)
16540
- }
16541
- )
16542
- ] });
16543
- }
16544
-
16545
16444
  function MapViewChat({ component, eventBus }) {
16546
16445
  const { googleMapsApiKey } = useChatConfig();
16547
16446
  const props = component.props ?? {};
@@ -16756,30 +16655,6 @@ const ServicesMap = ({ config, eventBus, apiKey, filter, fill }) => {
16756
16655
  };
16757
16656
  ServicesMap.displayName = "ServicesMap";
16758
16657
 
16759
- function ServicesMapChat({ component, eventBus }) {
16760
- const { resolveModuleConfig, googleMapsApiKey } = useChatConfig();
16761
- const props = component.props ?? {};
16762
- const top = component;
16763
- const pick = (key) => props[key] !== void 0 ? props[key] : top[key];
16764
- const moduleKey = pick("module");
16765
- let config;
16766
- if (typeof moduleKey === "string" && resolveModuleConfig) {
16767
- config = resolveModuleConfig(moduleKey);
16768
- } else if (pick("data")) {
16769
- config = component.props ? props : top;
16770
- }
16771
- if (!config?.data?.list) return null;
16772
- return /* @__PURE__ */ jsx(
16773
- ServicesMap,
16774
- {
16775
- config,
16776
- eventBus,
16777
- apiKey: googleMapsApiKey,
16778
- filter: pick("filter")
16779
- }
16780
- );
16781
- }
16782
-
16783
16658
  const CLASS$5 = `${COMPONENT_PREFIX$1}service-shortcuts`;
16784
16659
  const ICONS = {
16785
16660
  map: /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
@@ -16856,62 +16731,6 @@ const ServiceShortcuts = ({
16856
16731
  };
16857
16732
  ServiceShortcuts.displayName = "ServiceShortcuts";
16858
16733
 
16859
- const USED_KEY = (messageId) => `px:service_shortcuts:used:${messageId}`;
16860
- function readUsed(messageId) {
16861
- if (!messageId) return void 0;
16862
- try {
16863
- return localStorage.getItem(USED_KEY(messageId)) ?? void 0;
16864
- } catch {
16865
- return void 0;
16866
- }
16867
- }
16868
- const DEFAULTS = {
16869
- map: { id: "map", label: "Ver en el mapa", hint: "D\xF3nde est\xE1n ubicados", route: "/mapa", icon: "map" },
16870
- list: { id: "list", label: "Ver la lista", hint: "Gestionar y filtrar", route: "/servicios", icon: "list" },
16871
- // SPEC-030 — las dos puertas de quien llega por el enlace público y no sabe qué escribir.
16872
- // No navegan: dicen por él lo que tendría que teclear, y el agente responde con el formulario
16873
- // que toque. Así el componente no necesita saber qué formulario es cada cosa.
16874
- request: {
16875
- id: "request",
16876
- label: "Solicitar una gr\xFAa",
16877
- hint: "Pedir el servicio ahora",
16878
- prompt: "Quiero solicitar una gr\xFAa",
16879
- icon: "tow"
16880
- },
16881
- check: {
16882
- id: "check",
16883
- label: "Consultar mi servicio",
16884
- hint: "Ya ped\xED una gr\xFAa antes",
16885
- prompt: "Quiero consultar el servicio que ya ped\xED",
16886
- icon: "search"
16887
- }
16888
- };
16889
- function ServiceShortcutsChat({ component, eventBus, assistantMessageId }) {
16890
- const [elegido, setElegido] = useState(() => readUsed(assistantMessageId));
16891
- const props = component.props ?? {};
16892
- const top = component;
16893
- const pick = (key) => props[key] !== void 0 ? props[key] : top[key];
16894
- const recordar = (id) => {
16895
- setElegido(id);
16896
- if (!assistantMessageId) return;
16897
- try {
16898
- localStorage.setItem(USED_KEY(assistantMessageId), id);
16899
- } catch {
16900
- }
16901
- };
16902
- const custom = pick("options");
16903
- const options = custom?.length ? custom : (pick("show") ?? ["map", "list"]).map((k) => DEFAULTS[k]).filter(Boolean);
16904
- return /* @__PURE__ */ jsx(
16905
- ServiceShortcuts,
16906
- {
16907
- options,
16908
- eventBus,
16909
- selectedId: elegido,
16910
- onSelect: recordar
16911
- }
16912
- );
16913
- }
16914
-
16915
16734
  const DATE_CHIPS = [
16916
16735
  { key: "todo", label: "Todo" },
16917
16736
  { key: "hoy", label: "Hoy" },
@@ -17575,16 +17394,39 @@ const COMPONENT_MAP = {
17575
17394
  action_menu: ActionMenu,
17576
17395
  reactive_crud: ReactiveCrudChat,
17577
17396
  reactive_crud_item: ReactiveCrudItemChat,
17578
- price_negotiation: PriceNegotiationChat,
17579
17397
  chat_navigator: ChatNavigatorChat,
17580
17398
  user_tools: UserToolsChat,
17581
- map_view: MapViewChat,
17582
- services_map: ServicesMapChat,
17583
- service_shortcuts: ServiceShortcutsChat
17584
- };
17399
+ map_view: MapViewChat
17400
+ // SPEC-115 — `price_negotiation`, `services_map` y `service_shortcuts` salieron de aquí: son del flujo del
17401
+ // solicitante de grúas, no primitivos de diseño. Los registra el host (towing-web) con
17402
+ // `registerChatComponents`. Los componentes de presentación (ServicesMap, ServiceShortcuts)
17403
+ // SÍ se quedan y siguen exportándose: lo que se fue es su adaptador al chat.
17404
+ };
17405
+ const HOST_COMPONENTS = {};
17406
+ function normalizar(e) {
17407
+ return typeof e === "function" ? { render: e } : e;
17408
+ }
17409
+ function registerChatComponents(comps) {
17410
+ for (const [tipo, entrada] of Object.entries(comps)) {
17411
+ HOST_COMPONENTS[tipo] = normalizar(entrada);
17412
+ }
17413
+ }
17414
+ function clearChatComponents() {
17415
+ for (const tipo of Object.keys(HOST_COMPONENTS)) delete HOST_COMPONENTS[tipo];
17416
+ }
17417
+ function esPersistente(tipo) {
17418
+ const h = HOST_COMPONENTS[tipo];
17419
+ if (h) return h.persistent === true;
17420
+ return PERSISTENT_COMPONENTS.has(tipo);
17421
+ }
17422
+ function esDisplayPuro(tipo) {
17423
+ const h = HOST_COMPONENTS[tipo];
17424
+ if (h) return h.pureDisplay === true;
17425
+ return PURE_DISPLAY_COMPONENTS.has(tipo);
17426
+ }
17585
17427
  function renderComponent(props) {
17586
17428
  const { component } = props;
17587
- if (!PERSISTENT_COMPONENTS.has(component.type) && !PURE_DISPLAY_COMPONENTS.has(component.type)) {
17429
+ if (!esPersistente(component.type) && !esDisplayPuro(component.type)) {
17588
17430
  if (component.status) {
17589
17431
  if (TERMINAL_STATUS.has(component.status)) {
17590
17432
  const policy = component.visibility_policy ?? "hide_on_terminal";
@@ -17597,7 +17439,7 @@ function renderComponent(props) {
17597
17439
  return null;
17598
17440
  }
17599
17441
  }
17600
- const Component = COMPONENT_MAP[component.type];
17442
+ const Component = HOST_COMPONENTS[component.type]?.render ?? COMPONENT_MAP[component.type];
17601
17443
  if (!Component) return null;
17602
17444
  return /* @__PURE__ */ jsx(Component, { ...props });
17603
17445
  }
@@ -24735,4 +24577,4 @@ function buildBreadcrumbPath(items, targetKey, currentPath = []) {
24735
24577
  return [];
24736
24578
  }
24737
24579
 
24738
- 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 };
24580
+ export { ActionFormDrawer, 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, clearChatComponents, createDeepPropertySubscriber, createEventBus, createStore, createStoreMerger, deepMerge, disabledReasonFor, getLastFleetSnapshot, matchesDisabledWhen, matchesDoneWhen, matchesVisibleWhen, registerChatComponents, setLastFleetSnapshot, useChatConfig, useReactiveCrud };
package/dist/index.js CHANGED
@@ -1672,7 +1672,7 @@ function getSubmitActionId(component) {
1672
1672
  }
1673
1673
  const TERMINAL_STATUS = /* @__PURE__ */ new Set(["answered", "cancelled", "expired"]);
1674
1674
  const DISPLAY_COMPONENTS = /* @__PURE__ */ new Set(["card", "badge", "reactive_form", "carousel"]);
1675
- const PERSISTENT_COMPONENTS = /* @__PURE__ */ new Set(["reactive_crud", "reactive_crud_item", "price_negotiation", "chat_navigator", "map_view", "services_map", "service_shortcuts", "user_tools"]);
1675
+ const PERSISTENT_COMPONENTS = /* @__PURE__ */ new Set(["reactive_crud", "reactive_crud_item", "chat_navigator", "map_view", "user_tools"]);
1676
1676
  const PURE_DISPLAY_COMPONENTS = /* @__PURE__ */ new Set(["card", "badge"]);
1677
1677
 
1678
1678
  const AnsweredSummary = ({ component }) => {
@@ -16332,7 +16332,8 @@ function ReactiveCrudItemChat({ component, eventBus }) {
16332
16332
  const moduleKey = pick("module");
16333
16333
  const recordId = pick("record_id") ?? pick("recordId") ?? pick("id") ?? pick("service_id");
16334
16334
  const recordLabel = pick("record_label") ?? pick("label");
16335
- const allowed = pick("actions");
16335
+ const allowedRaw = pick("actions");
16336
+ const allowed = Array.isArray(allowedRaw) && allowedRaw.length > 0 ? allowedRaw : void 0;
16336
16337
  const config = typeof moduleKey === "string" && resolveModuleConfig ? resolveModuleConfig(moduleKey) : void 0;
16337
16338
  if (!config || recordId == null || recordId === "") return null;
16338
16339
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -16461,108 +16462,6 @@ function ServiceActionsPanel({ config, recordId, recordLabel, allowed, eventBus
16461
16462
  ] });
16462
16463
  }
16463
16464
 
16464
- const ACTOR_LABEL = { COLLA: "la empresa aliada", ADMIN: "la operadora" };
16465
- const TURN_GATED = /* @__PURE__ */ new Set(["accept_price", "propose_price"]);
16466
- function PriceNegotiationChat({ component, eventBus }) {
16467
- const { resolveModuleConfig } = useChatConfig();
16468
- const props = component.props ?? {};
16469
- const top = component;
16470
- const pick = (key) => props[key] !== void 0 ? props[key] : top[key];
16471
- const moduleKey = pick("module");
16472
- const recordId = pick("record_id") ?? pick("service_id") ?? pick("id");
16473
- const recordLabel = pick("record_label") ?? pick("label");
16474
- const allowed = pick("actions");
16475
- const config = typeof moduleKey === "string" && resolveModuleConfig ? resolveModuleConfig(moduleKey) : void 0;
16476
- if (!config || recordId == null || recordId === "") return null;
16477
- return /* @__PURE__ */ jsxRuntime.jsx(
16478
- NegotiationPanel,
16479
- {
16480
- config,
16481
- recordId: String(recordId),
16482
- recordLabel,
16483
- allowed: Array.isArray(allowed) ? allowed.map(String) : void 0,
16484
- eventBus
16485
- }
16486
- );
16487
- }
16488
- function NegotiationPanel({ config, recordId, recordLabel, allowed, eventBus }) {
16489
- const { chatIsForeign } = useChatConfig();
16490
- const crud = useReactiveCrud(config, eventBus);
16491
- const [activeAction, setActiveAction] = React.useState(null);
16492
- const row = React.useMemo(
16493
- () => crud.rows.find((r) => String(r[config.idField]) === recordId),
16494
- [crud.rows, config.idField, recordId]
16495
- );
16496
- const actions = React.useMemo(() => {
16497
- if (!row) return [];
16498
- return (config.rowActions ?? []).filter((a) => !allowed || allowed.includes(a.key)).filter((a) => matchesVisibleWhen(a, row));
16499
- }, [config.rowActions, row, allowed]);
16500
- const title = recordLabel ?? config.title ?? "Servicio";
16501
- const proposed = row?.proposed_price;
16502
- const proposer = row?.price_proposed_by;
16503
- const myTurn = row?.my_turn === true || String(row?.my_turn) === "true";
16504
- const hasOffer = proposed != null && proposed !== "";
16505
- const canPropose = !allowed || allowed.includes("propose_price");
16506
- const agreed = row?.price_amount;
16507
- const esperandoRespuesta = ["QUOTED", "PRE_ASSIGNED"].includes(String(row?.status ?? ""));
16508
- const cerrada = !esperandoRespuesta && agreed != null && agreed !== "";
16509
- const ofertaMia = hasOffer && !myTurn;
16510
- const quienOfrece = ofertaMia ? "tu oferta" : `oferta de ${ACTOR_LABEL[proposer ?? ""] ?? "la otra parte"}`;
16511
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-price-neg", children: [
16512
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-price-neg__header", children: [
16513
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-price-neg__title", children: title }),
16514
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `px-chat-price-neg__badge${cerrada ? " is-closed" : ""}`, children: cerrada ? "Precio acordado" : canPropose ? "Negociaci\xF3n de precio" : "Cotizaci\xF3n" })
16515
- ] }),
16516
- crud.actionError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-price-neg__error", role: "alert", children: [
16517
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: crud.actionError }),
16518
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Cerrar", onClick: crud.clearActionError, children: "\u2715" })
16519
- ] }),
16520
- crud.loading && !row && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-price-neg__hint", children: "Cargando\u2026" }),
16521
- !crud.loading && !row && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-price-neg__hint", children: "No se encontr\xF3 el servicio." }),
16522
- row && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
16523
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-price-neg__offer", children: cerrada ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
16524
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-price-neg__amount", children: /* @__PURE__ */ jsxRuntime.jsx(CurrencyDisplay, { value: agreed }) }),
16525
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-price-neg__by", children: "precio acordado" })
16526
- ] }) : hasOffer ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
16527
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-price-neg__amount", children: /* @__PURE__ */ jsxRuntime.jsx(CurrencyDisplay, { value: proposed }) }),
16528
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-price-neg__by", children: canPropose ? quienOfrece : "costo del servicio" })
16529
- ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-price-neg__by", children: "A\xFAn no hay una oferta. Prop\xF3n un valor." }) }),
16530
- !cerrada && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `px-chat-price-neg__turn${myTurn ? " is-mine" : ""}`, children: myTurn ? canPropose ? "Es tu turno: acepta o contraprop\xF3n." : "Es tu turno: acepta o rechaza." : "Esperando la respuesta de la otra parte\u2026" }),
16531
- !cerrada && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-price-neg__actions", children: [
16532
- actions.map((a) => {
16533
- const gated = TURN_GATED.has(a.key) && !myTurn;
16534
- const noOffer = a.key === "accept_price" && !hasOffer;
16535
- const disabled = gated || noOffer || Boolean(chatIsForeign);
16536
- return /* @__PURE__ */ jsxRuntime.jsx(
16537
- "button",
16538
- {
16539
- type: "button",
16540
- className: `px-chat-price-neg__action${a.intent === "danger" ? " is-danger" : a.key === "accept_price" ? " is-primary" : ""}`,
16541
- disabled,
16542
- title: gated ? "Esperando la respuesta de la otra parte" : void 0,
16543
- onClick: () => a.form ? setActiveAction({ action: a, row }) : crud.runEndpoints(a.endpoints ?? [a.endpoint], { ...row, id: row[config.idField] }),
16544
- children: a.label
16545
- },
16546
- a.key
16547
- );
16548
- }),
16549
- actions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-price-neg__hint", children: "Sin acciones disponibles." })
16550
- ] })
16551
- ] }),
16552
- activeAction && /* @__PURE__ */ jsxRuntime.jsx(
16553
- ActionFormDrawer,
16554
- {
16555
- action: activeAction.action,
16556
- row: activeAction.row,
16557
- idField: config.idField,
16558
- eventBus,
16559
- onExecute: (ctx) => crud.runEndpoints(activeAction.action.endpoints ?? [activeAction.action.endpoint], ctx),
16560
- onClose: () => setActiveAction(null)
16561
- }
16562
- )
16563
- ] });
16564
- }
16565
-
16566
16465
  function MapViewChat({ component, eventBus }) {
16567
16466
  const { googleMapsApiKey } = useChatConfig();
16568
16467
  const props = component.props ?? {};
@@ -16777,30 +16676,6 @@ const ServicesMap = ({ config, eventBus, apiKey, filter, fill }) => {
16777
16676
  };
16778
16677
  ServicesMap.displayName = "ServicesMap";
16779
16678
 
16780
- function ServicesMapChat({ component, eventBus }) {
16781
- const { resolveModuleConfig, googleMapsApiKey } = useChatConfig();
16782
- const props = component.props ?? {};
16783
- const top = component;
16784
- const pick = (key) => props[key] !== void 0 ? props[key] : top[key];
16785
- const moduleKey = pick("module");
16786
- let config;
16787
- if (typeof moduleKey === "string" && resolveModuleConfig) {
16788
- config = resolveModuleConfig(moduleKey);
16789
- } else if (pick("data")) {
16790
- config = component.props ? props : top;
16791
- }
16792
- if (!config?.data?.list) return null;
16793
- return /* @__PURE__ */ jsxRuntime.jsx(
16794
- ServicesMap,
16795
- {
16796
- config,
16797
- eventBus,
16798
- apiKey: googleMapsApiKey,
16799
- filter: pick("filter")
16800
- }
16801
- );
16802
- }
16803
-
16804
16679
  const CLASS$5 = `${COMPONENT_PREFIX$1}service-shortcuts`;
16805
16680
  const ICONS = {
16806
16681
  map: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
@@ -16877,62 +16752,6 @@ const ServiceShortcuts = ({
16877
16752
  };
16878
16753
  ServiceShortcuts.displayName = "ServiceShortcuts";
16879
16754
 
16880
- const USED_KEY = (messageId) => `px:service_shortcuts:used:${messageId}`;
16881
- function readUsed(messageId) {
16882
- if (!messageId) return void 0;
16883
- try {
16884
- return localStorage.getItem(USED_KEY(messageId)) ?? void 0;
16885
- } catch {
16886
- return void 0;
16887
- }
16888
- }
16889
- const DEFAULTS = {
16890
- map: { id: "map", label: "Ver en el mapa", hint: "D\xF3nde est\xE1n ubicados", route: "/mapa", icon: "map" },
16891
- list: { id: "list", label: "Ver la lista", hint: "Gestionar y filtrar", route: "/servicios", icon: "list" },
16892
- // SPEC-030 — las dos puertas de quien llega por el enlace público y no sabe qué escribir.
16893
- // No navegan: dicen por él lo que tendría que teclear, y el agente responde con el formulario
16894
- // que toque. Así el componente no necesita saber qué formulario es cada cosa.
16895
- request: {
16896
- id: "request",
16897
- label: "Solicitar una gr\xFAa",
16898
- hint: "Pedir el servicio ahora",
16899
- prompt: "Quiero solicitar una gr\xFAa",
16900
- icon: "tow"
16901
- },
16902
- check: {
16903
- id: "check",
16904
- label: "Consultar mi servicio",
16905
- hint: "Ya ped\xED una gr\xFAa antes",
16906
- prompt: "Quiero consultar el servicio que ya ped\xED",
16907
- icon: "search"
16908
- }
16909
- };
16910
- function ServiceShortcutsChat({ component, eventBus, assistantMessageId }) {
16911
- const [elegido, setElegido] = React.useState(() => readUsed(assistantMessageId));
16912
- const props = component.props ?? {};
16913
- const top = component;
16914
- const pick = (key) => props[key] !== void 0 ? props[key] : top[key];
16915
- const recordar = (id) => {
16916
- setElegido(id);
16917
- if (!assistantMessageId) return;
16918
- try {
16919
- localStorage.setItem(USED_KEY(assistantMessageId), id);
16920
- } catch {
16921
- }
16922
- };
16923
- const custom = pick("options");
16924
- const options = custom?.length ? custom : (pick("show") ?? ["map", "list"]).map((k) => DEFAULTS[k]).filter(Boolean);
16925
- return /* @__PURE__ */ jsxRuntime.jsx(
16926
- ServiceShortcuts,
16927
- {
16928
- options,
16929
- eventBus,
16930
- selectedId: elegido,
16931
- onSelect: recordar
16932
- }
16933
- );
16934
- }
16935
-
16936
16755
  const DATE_CHIPS = [
16937
16756
  { key: "todo", label: "Todo" },
16938
16757
  { key: "hoy", label: "Hoy" },
@@ -17596,16 +17415,39 @@ const COMPONENT_MAP = {
17596
17415
  action_menu: ActionMenu,
17597
17416
  reactive_crud: ReactiveCrudChat,
17598
17417
  reactive_crud_item: ReactiveCrudItemChat,
17599
- price_negotiation: PriceNegotiationChat,
17600
17418
  chat_navigator: ChatNavigatorChat,
17601
17419
  user_tools: UserToolsChat,
17602
- map_view: MapViewChat,
17603
- services_map: ServicesMapChat,
17604
- service_shortcuts: ServiceShortcutsChat
17605
- };
17420
+ map_view: MapViewChat
17421
+ // SPEC-115 — `price_negotiation`, `services_map` y `service_shortcuts` salieron de aquí: son del flujo del
17422
+ // solicitante de grúas, no primitivos de diseño. Los registra el host (towing-web) con
17423
+ // `registerChatComponents`. Los componentes de presentación (ServicesMap, ServiceShortcuts)
17424
+ // SÍ se quedan y siguen exportándose: lo que se fue es su adaptador al chat.
17425
+ };
17426
+ const HOST_COMPONENTS = {};
17427
+ function normalizar(e) {
17428
+ return typeof e === "function" ? { render: e } : e;
17429
+ }
17430
+ function registerChatComponents(comps) {
17431
+ for (const [tipo, entrada] of Object.entries(comps)) {
17432
+ HOST_COMPONENTS[tipo] = normalizar(entrada);
17433
+ }
17434
+ }
17435
+ function clearChatComponents() {
17436
+ for (const tipo of Object.keys(HOST_COMPONENTS)) delete HOST_COMPONENTS[tipo];
17437
+ }
17438
+ function esPersistente(tipo) {
17439
+ const h = HOST_COMPONENTS[tipo];
17440
+ if (h) return h.persistent === true;
17441
+ return PERSISTENT_COMPONENTS.has(tipo);
17442
+ }
17443
+ function esDisplayPuro(tipo) {
17444
+ const h = HOST_COMPONENTS[tipo];
17445
+ if (h) return h.pureDisplay === true;
17446
+ return PURE_DISPLAY_COMPONENTS.has(tipo);
17447
+ }
17606
17448
  function renderComponent(props) {
17607
17449
  const { component } = props;
17608
- if (!PERSISTENT_COMPONENTS.has(component.type) && !PURE_DISPLAY_COMPONENTS.has(component.type)) {
17450
+ if (!esPersistente(component.type) && !esDisplayPuro(component.type)) {
17609
17451
  if (component.status) {
17610
17452
  if (TERMINAL_STATUS.has(component.status)) {
17611
17453
  const policy = component.visibility_policy ?? "hide_on_terminal";
@@ -17618,7 +17460,7 @@ function renderComponent(props) {
17618
17460
  return null;
17619
17461
  }
17620
17462
  }
17621
- const Component = COMPONENT_MAP[component.type];
17463
+ const Component = HOST_COMPONENTS[component.type]?.render ?? COMPONENT_MAP[component.type];
17622
17464
  if (!Component) return null;
17623
17465
  return /* @__PURE__ */ jsxRuntime.jsx(Component, { ...props });
17624
17466
  }
@@ -24756,6 +24598,7 @@ function buildBreadcrumbPath(items, targetKey, currentPath = []) {
24756
24598
  return [];
24757
24599
  }
24758
24600
 
24601
+ exports.ActionFormDrawer = ActionFormDrawer;
24759
24602
  exports.BottomSheet = BottomSheet;
24760
24603
  exports.CHAT_CONFIG_DEFAULTS = CHAT_CONFIG_DEFAULTS;
24761
24604
  exports.CHAT_EVENT = CHAT_EVENT;
@@ -24854,11 +24697,18 @@ exports.ServicesMap = ServicesMap;
24854
24697
  exports.SessionExpiryPrompt = SessionExpiryPrompt;
24855
24698
  exports.UserTools = UserTools;
24856
24699
  exports.chatEventBus = chatEventBus;
24700
+ exports.clearChatComponents = clearChatComponents;
24857
24701
  exports.createDeepPropertySubscriber = createDeepPropertySubscriber;
24858
24702
  exports.createEventBus = createEventBus;
24859
24703
  exports.createStore = createStore;
24860
24704
  exports.createStoreMerger = createStoreMerger;
24861
24705
  exports.deepMerge = deepMerge;
24706
+ exports.disabledReasonFor = disabledReasonFor;
24862
24707
  exports.getLastFleetSnapshot = getLastFleetSnapshot;
24708
+ exports.matchesDisabledWhen = matchesDisabledWhen;
24709
+ exports.matchesDoneWhen = matchesDoneWhen;
24710
+ exports.matchesVisibleWhen = matchesVisibleWhen;
24711
+ exports.registerChatComponents = registerChatComponents;
24863
24712
  exports.setLastFleetSnapshot = setLastFleetSnapshot;
24864
24713
  exports.useChatConfig = useChatConfig;
24714
+ exports.useReactiveCrud = useReactiveCrud;