@dataemergencia/emergency 0.3.0 → 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.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { createRoot } from "react-dom/client";
2
2
  import axios, { isAxiosError } from "axios";
3
3
  import Pusher from "pusher-js";
4
4
  import { createStore } from "zustand/vanilla";
5
- import { createContext, useCallback, useContext, useEffect, useId, useRef } from "react";
5
+ import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { useStore } from "zustand";
8
8
 
@@ -362,6 +362,7 @@ function createBffClient(options) {
362
362
  const copy = {
363
363
  panelLabel: "Panel de emergencia",
364
364
  close: "Cerrar panel de emergencia",
365
+ back: "Volver",
365
366
  question: "¿Tiene usted una emergencia?",
366
367
  illustrationAlt: "Ícono paramédico",
367
368
  emergency: "Emergencia",
@@ -1440,74 +1441,6 @@ function validateConfig(config) {
1440
1441
  };
1441
1442
  }
1442
1443
 
1443
- // src/react/components/icons/paramedical-mark.tsx
1444
- /**
1445
- * Neutral inline illustration used by the idle panel.
1446
- */
1447
- function ParamedicalMark({ className, title }) {
1448
- return /* @__PURE__ */ jsxs("svg", {
1449
- xmlns: "http://www.w3.org/2000/svg",
1450
- version: "1.0",
1451
- viewBox: "0 0 192 192",
1452
- width: "192",
1453
- height: "192",
1454
- id: "Star of life",
1455
- className,
1456
- "aria-label": title,
1457
- children: [
1458
- /* @__PURE__ */ jsx("defs", { id: "defs2886" }),
1459
- /* @__PURE__ */ jsxs("g", {
1460
- id: "cross",
1461
- children: [/* @__PURE__ */ jsx("path", {
1462
- d: "m 74,133 0,52 44,0 0,-52 44,26 22,-38 L 140,96 184,70 162,32 117,58 117,7 74,7 74,58 30,32 8,70 52,96 8,121 30,159 z",
1463
- id: "outline",
1464
- style: {
1465
- fill: "none",
1466
- stroke: "#3333bb",
1467
- strokeWidth: 6
1468
- }
1469
- }), /* @__PURE__ */ jsx("path", {
1470
- d: "m 74,133 0,52 44,0 0,-52 44,26 22,-38 L 140,96 184,70 162,32 117,58 117,7 74,7 74,58 30,32 8,70 52,96 8,121 30,159 z",
1471
- id: "inner",
1472
- style: {
1473
- fill: "#0000bb",
1474
- fillRule: "evenodd",
1475
- stroke: "#ffffff",
1476
- strokeWidth: 2
1477
- }
1478
- })]
1479
- }),
1480
- /* @__PURE__ */ jsxs("g", {
1481
- id: "symbols",
1482
- style: {
1483
- fill: "#ffffff",
1484
- fillRule: "evenodd",
1485
- stroke: "#3333bb",
1486
- strokeWidth: 2
1487
- },
1488
- children: [/* @__PURE__ */ jsx("path", {
1489
- d: "m 93,166 c 0,7 6,7 6,0 l 3,-138 c 0,-8 -12,-8 -12,0 l 3,135 0,3 z",
1490
- id: "rod"
1491
- }), /* @__PURE__ */ jsxs("g", {
1492
- id: "snake",
1493
- children: [/* @__PURE__ */ jsx("path", {
1494
- d: "m 100,153 c 7,4 3,15 -3,15 2,-5 3,-9 3,-15 z m 1,-40 c 10,9 4,22 -3,26 -6,3 -6,7 -6,15 -9,-10 -4,-19 4,-24 5,-4 5,-10 5,-17 z m 1,-30 c 0,11 -17,14 -17,22 0,3 1,9 6,12 0,-11 9,-12 15,-18 7,-7 7,-19 -4,-28 0,4 0,8 0,12 z M 90,77 C 80,69 76,55 80,46 c 3,-10 20,-11 20,-3 0,8 -20,3 -13,16 l 3,5 0,13 z",
1495
- id: "body"
1496
- }), /* @__PURE__ */ jsx("path", {
1497
- d: "m 95,43 c 7,-3 -7,-3 0,0 z",
1498
- id: "eye",
1499
- style: {
1500
- fill: "#3333bb",
1501
- fillRule: "evenodd",
1502
- stroke: "none"
1503
- }
1504
- })]
1505
- })]
1506
- })
1507
- ]
1508
- });
1509
- }
1510
-
1511
1444
  // src/react/components/Icon/index.tsx
1512
1445
  /** Icons whose artwork does not fit the default 24x24 grid need their own canvas. */
1513
1446
  const viewBoxes = { emergency: "0 0 640 640" };
@@ -1564,6 +1497,18 @@ const paths = {
1564
1497
  /* @__PURE__ */ jsx("path", { d: "M12 7h.01" })
1565
1498
  ] }),
1566
1499
  "arrow-left": /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("path", { d: "M19 12H5" }), /* @__PURE__ */ jsx("path", { d: "m12 19-7-7 7-7" })] }),
1500
+ "credit-card-plus": /* @__PURE__ */ jsxs(Fragment, { children: [
1501
+ /* @__PURE__ */ jsx("path", { d: "M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5" }),
1502
+ /* @__PURE__ */ jsx("path", { d: "M18 12h.01" }),
1503
+ /* @__PURE__ */ jsx("path", { d: "M19 22v-6" }),
1504
+ /* @__PURE__ */ jsx("path", { d: "m22 19-3-3-3 3" }),
1505
+ /* @__PURE__ */ jsx("path", { d: "M6 12h.01" }),
1506
+ /* @__PURE__ */ jsx("circle", {
1507
+ cx: "12",
1508
+ cy: "12",
1509
+ r: "2"
1510
+ })
1511
+ ] }),
1567
1512
  emergency: /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("path", { d: "M272.5 96C272.5 78.3 286.8 64 304.5 64L336.5 64C354.2 64 368.5 78.3 368.5 96L368.5 236.9L490.5 166.5C505.8 157.7 525.4 162.9 534.2 178.2L550.2 205.9C559 221.2 553.8 240.8 538.5 249.6L416.5 320L538.5 390.4C553.8 399.2 559.1 418.8 550.2 434.1L534.2 461.8C525.4 477.1 505.8 482.4 490.5 473.5L368.5 403.1L368.5 544C368.5 561.7 354.2 576 336.5 576L304.5 576C286.8 576 272.5 561.7 272.5 544L272.5 403.1L150.5 473.5C135.2 482.3 115.6 477.1 106.8 461.8L90.8 434.1C82 418.8 87.2 399.2 102.5 390.4L224.5 320L102.5 249.6C87.2 240.8 82 221.2 90.8 205.9L106.8 178.2C115.6 162.9 135.2 157.7 150.5 166.5L272.5 236.9L272.5 96z" }) })
1568
1513
  };
1569
1514
  function Icon({ name, className, title }) {
@@ -1593,6 +1538,16 @@ var panel_module_default = {
1593
1538
  "de-emergency-slide-in": "de-emergency-de-emergency-slide-in-nA5Hwa",
1594
1539
  "de-emergency-slide-up": "de-emergency-de-emergency-slide-up-nA5Hwa",
1595
1540
  "de-emergency-spin": "de-emergency-de-emergency-spin-nA5Hwa",
1541
+ "emergency-availability": "de-emergency-emergency-availability-nA5Hwa",
1542
+ "emergency-availability__card": "de-emergency-emergency-availability__card-nA5Hwa",
1543
+ "emergency-availability__card-body": "de-emergency-emergency-availability__card-body-nA5Hwa",
1544
+ "emergency-availability__card-header": "de-emergency-emergency-availability__card-header-nA5Hwa",
1545
+ "emergency-availability__card-title": "de-emergency-emergency-availability__card-title-nA5Hwa",
1546
+ "emergency-availability__details": "de-emergency-emergency-availability__details-nA5Hwa",
1547
+ "emergency-availability__label": "de-emergency-emergency-availability__label-nA5Hwa",
1548
+ "emergency-availability__recharge": "de-emergency-emergency-availability__recharge-nA5Hwa",
1549
+ "emergency-availability__rounds": "de-emergency-emergency-availability__rounds-nA5Hwa",
1550
+ "emergency-availability__rounds-group": "de-emergency-emergency-availability__rounds-group-nA5Hwa",
1596
1551
  "emergency-confirmation": "de-emergency-emergency-confirmation-nA5Hwa",
1597
1552
  "emergency-confirmation__body": "de-emergency-emergency-confirmation__body-nA5Hwa",
1598
1553
  "emergency-confirmation__button": "de-emergency-emergency-confirmation__button-nA5Hwa",
@@ -1617,7 +1572,11 @@ var panel_module_default = {
1617
1572
  "emergency-feedback": "de-emergency-emergency-feedback-nA5Hwa",
1618
1573
  "emergency-feedback__dismiss": "de-emergency-emergency-feedback__dismiss-nA5Hwa",
1619
1574
  "emergency-panel": "de-emergency-emergency-panel-nA5Hwa",
1575
+ "emergency-panel__availability-region": "de-emergency-emergency-panel__availability-region-nA5Hwa",
1620
1576
  "emergency-panel__body": "de-emergency-emergency-panel__body-nA5Hwa",
1577
+ "emergency-panel__body--pricing": "de-emergency-emergency-panel__body--pricing-nA5Hwa",
1578
+ "emergency-panel__body-split": "de-emergency-emergency-panel__body-split-nA5Hwa",
1579
+ "emergency-panel__body-split--with-availability": "de-emergency-emergency-panel__body-split--with-availability-nA5Hwa",
1621
1580
  "emergency-panel__body-title": "de-emergency-emergency-panel__body-title-nA5Hwa",
1622
1581
  "emergency-panel__body-title-container": "de-emergency-emergency-panel__body-title-container-nA5Hwa",
1623
1582
  "emergency-panel__button": "de-emergency-emergency-panel__button-nA5Hwa",
@@ -1627,14 +1586,41 @@ var panel_module_default = {
1627
1586
  "emergency-panel__close-icon": "de-emergency-emergency-panel__close-icon-nA5Hwa",
1628
1587
  "emergency-panel__footer": "de-emergency-emergency-panel__footer-nA5Hwa",
1629
1588
  "emergency-panel__header": "de-emergency-emergency-panel__header-nA5Hwa",
1589
+ "emergency-panel__prompt-region": "de-emergency-emergency-panel__prompt-region-nA5Hwa",
1630
1590
  "emergency-panel__title": "de-emergency-emergency-panel__title-nA5Hwa",
1631
1591
  "emergency-panel--hidden": "de-emergency-emergency-panel--hidden-nA5Hwa",
1592
+ "emergency-pricing": "de-emergency-emergency-pricing-nA5Hwa",
1593
+ "emergency-pricing__badge": "de-emergency-emergency-pricing__badge-nA5Hwa",
1594
+ "emergency-pricing__footnote": "de-emergency-emergency-pricing__footnote-nA5Hwa",
1595
+ "emergency-pricing__heading": "de-emergency-emergency-pricing__heading-nA5Hwa",
1596
+ "emergency-pricing__package": "de-emergency-emergency-pricing__package-nA5Hwa",
1597
+ "emergency-pricing__package--highlighted": "de-emergency-emergency-pricing__package--highlighted-nA5Hwa",
1598
+ "emergency-pricing__package-caption": "de-emergency-emergency-pricing__package-caption-nA5Hwa",
1599
+ "emergency-pricing__package-header": "de-emergency-emergency-pricing__package-header-nA5Hwa",
1600
+ "emergency-pricing__package-label": "de-emergency-emergency-pricing__package-label-nA5Hwa",
1601
+ "emergency-pricing__package-name": "de-emergency-emergency-pricing__package-name-nA5Hwa",
1602
+ "emergency-pricing__price": "de-emergency-emergency-pricing__price-nA5Hwa",
1603
+ "emergency-pricing__price-meta": "de-emergency-emergency-pricing__price-meta-nA5Hwa",
1604
+ "emergency-pricing__price-note": "de-emergency-emergency-pricing__price-note-nA5Hwa",
1605
+ "emergency-pricing__price-row": "de-emergency-emergency-pricing__price-row-nA5Hwa",
1606
+ "emergency-pricing__select": "de-emergency-emergency-pricing__select-nA5Hwa",
1607
+ "emergency-pricing__stat": "de-emergency-emergency-pricing__stat-nA5Hwa",
1608
+ "emergency-pricing__stat-divider": "de-emergency-emergency-pricing__stat-divider-nA5Hwa",
1609
+ "emergency-pricing__stat-label": "de-emergency-emergency-pricing__stat-label-nA5Hwa",
1610
+ "emergency-pricing__stat-value": "de-emergency-emergency-pricing__stat-value-nA5Hwa",
1611
+ "emergency-pricing__stats": "de-emergency-emergency-pricing__stats-nA5Hwa",
1612
+ "emergency-pricing__subtitle": "de-emergency-emergency-pricing__subtitle-nA5Hwa",
1613
+ "emergency-pricing__title": "de-emergency-emergency-pricing__title-nA5Hwa",
1614
+ "emergency-pricing__titles": "de-emergency-emergency-pricing__titles-nA5Hwa",
1632
1615
  "emergency-resume-notification-button": "de-emergency-emergency-resume-notification-button-nA5Hwa",
1633
1616
  "emergency-resume-notification-card": "de-emergency-emergency-resume-notification-card-nA5Hwa",
1634
1617
  "emergency-resume-notification-icon": "de-emergency-emergency-resume-notification-icon-nA5Hwa",
1635
1618
  "emergency-resume-notification-message": "de-emergency-emergency-resume-notification-message-nA5Hwa",
1636
1619
  "emergency-resume-notification-overlay": "de-emergency-emergency-resume-notification-overlay-nA5Hwa",
1637
1620
  "emergency-resume-notification-title": "de-emergency-emergency-resume-notification-title-nA5Hwa",
1621
+ "emergency-screen": "de-emergency-emergency-screen-nA5Hwa",
1622
+ "emergency-screen__placeholder": "de-emergency-emergency-screen__placeholder-nA5Hwa",
1623
+ "emergency-screen__placeholder-text": "de-emergency-emergency-screen__placeholder-text-nA5Hwa",
1638
1624
  "emergency-status__abort-btn": "de-emergency-emergency-status__abort-btn-nA5Hwa",
1639
1625
  "emergency-status__body": "de-emergency-emergency-status__body-nA5Hwa",
1640
1626
  "emergency-status__cell": "de-emergency-emergency-status__cell-nA5Hwa",
@@ -1680,6 +1666,355 @@ function cls(...names) {
1680
1666
  return names.filter((name) => Boolean(name)).map((name) => panel_module_default[name] ?? "").filter(Boolean).join(" ");
1681
1667
  }
1682
1668
 
1669
+ // src/react/components/EmergencyConfirmation/index.tsx
1670
+ function EmergencyConfirmation(props) {
1671
+ const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
1672
+ const cardRef = useRef(null);
1673
+ const titleId = useId();
1674
+ const danger = abortConfirmation;
1675
+ const open = abortConfirmation || simulationConfirmation;
1676
+ useEffect(() => {
1677
+ if (open) cardRef.current?.querySelector("button")?.focus();
1678
+ }, [open]);
1679
+ if (!open) return null;
1680
+ const title = danger ? copy.abortConfirmTitle : copy.simulateConfirmTitle;
1681
+ const text = danger ? copy.abortConfirmText : copy.simulateConfirmText;
1682
+ const tone = danger ? "danger" : "information";
1683
+ return /* @__PURE__ */ jsx("div", {
1684
+ className: cls("emergency-confirmation"),
1685
+ role: "dialog",
1686
+ "aria-modal": "true",
1687
+ "aria-labelledby": titleId,
1688
+ children: /* @__PURE__ */ jsxs("div", {
1689
+ ref: cardRef,
1690
+ className: cls("emergency-confirmation__card"),
1691
+ children: [/* @__PURE__ */ jsxs("div", {
1692
+ className: cls("emergency-confirmation__body"),
1693
+ children: [
1694
+ /* @__PURE__ */ jsx("div", {
1695
+ className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
1696
+ children: /* @__PURE__ */ jsx(Icon, { name: danger ? "alert-triangle" : "info" })
1697
+ }),
1698
+ /* @__PURE__ */ jsx("h5", {
1699
+ className: cls("emergency-confirmation__title"),
1700
+ id: titleId,
1701
+ children: title
1702
+ }),
1703
+ /* @__PURE__ */ jsx("p", {
1704
+ className: cls("emergency-confirmation__text"),
1705
+ children: text
1706
+ })
1707
+ ]
1708
+ }), /* @__PURE__ */ jsxs("div", {
1709
+ className: cls("emergency-confirmation__footer"),
1710
+ children: [/* @__PURE__ */ jsx("button", {
1711
+ type: "button",
1712
+ className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
1713
+ onClick: danger ? onCancelAbort : onCancelSimulation,
1714
+ children: danger ? copy.abortCancel : copy.simulateCancel
1715
+ }), /* @__PURE__ */ jsx("button", {
1716
+ type: "button",
1717
+ className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
1718
+ onClick: danger ? onConfirmAbort : onConfirmSimulation,
1719
+ children: danger ? copy.abortConfirm : copy.simulateConfirm
1720
+ })]
1721
+ })]
1722
+ })
1723
+ });
1724
+ }
1725
+
1726
+ // src/react/hooks/useEscapeKey.ts
1727
+ function useEscapeKey(active, onEscape) {
1728
+ useEffect(() => {
1729
+ if (!active || typeof document === "undefined") return;
1730
+ const handleKeyDown = (event) => {
1731
+ if (event.key === "Escape") {
1732
+ event.stopPropagation();
1733
+ onEscape();
1734
+ }
1735
+ };
1736
+ document.addEventListener("keydown", handleKeyDown);
1737
+ return () => {
1738
+ document.removeEventListener("keydown", handleKeyDown);
1739
+ };
1740
+ }, [active, onEscape]);
1741
+ }
1742
+
1743
+ // src/react/hooks/useFocusTrap.ts
1744
+ const FOCUSABLE_SELECTOR = [
1745
+ "a[href]",
1746
+ "button:not([disabled])",
1747
+ "input:not([disabled])",
1748
+ "select:not([disabled])",
1749
+ "textarea:not([disabled])",
1750
+ "[tabindex]:not([tabindex=\"-1\"])"
1751
+ ].join(",");
1752
+ /**
1753
+ * Keeps keyboard focus inside the dialog while it is open and restores focus to
1754
+ * the element that opened it when it closes.
1755
+ */
1756
+ function useFocusTrap(ref, active) {
1757
+ useEffect(() => {
1758
+ if (!active || typeof document === "undefined") return;
1759
+ const container = ref.current;
1760
+ if (!container) return;
1761
+ const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1762
+ const focusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((element) => !element.hasAttribute("hidden") && element.getAttribute("aria-hidden") !== "true");
1763
+ (focusable()[0] ?? container).focus({ preventScroll: true });
1764
+ const handleKeyDown = (event) => {
1765
+ if (event.key !== "Tab") return;
1766
+ const items = focusable();
1767
+ if (items.length === 0) {
1768
+ event.preventDefault();
1769
+ container.focus({ preventScroll: true });
1770
+ return;
1771
+ }
1772
+ const first = items[0];
1773
+ const last = items[items.length - 1];
1774
+ const current = document.activeElement;
1775
+ if (event.shiftKey && (current === first || !container.contains(current))) {
1776
+ event.preventDefault();
1777
+ if (last) last.focus({ preventScroll: true });
1778
+ return;
1779
+ }
1780
+ if (!event.shiftKey && (current === last || !container.contains(current)) && first) {
1781
+ event.preventDefault();
1782
+ first.focus({ preventScroll: true });
1783
+ }
1784
+ };
1785
+ document.addEventListener("keydown", handleKeyDown, true);
1786
+ return () => {
1787
+ document.removeEventListener("keydown", handleKeyDown, true);
1788
+ if (previouslyFocused?.isConnected) previouslyFocused.focus({ preventScroll: true });
1789
+ };
1790
+ }, [active, ref]);
1791
+ }
1792
+
1793
+ // src/react/screens/context.tsx
1794
+ const EmergencyScreenContext = createContext(null);
1795
+ function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
1796
+ const [state, setState] = useState({
1797
+ screen: initialScreen,
1798
+ history: []
1799
+ });
1800
+ const navigate = useCallback((next) => {
1801
+ setState((current) => {
1802
+ if (current.screen === next) return current;
1803
+ return {
1804
+ screen: next,
1805
+ history: [...current.history, current.screen]
1806
+ };
1807
+ });
1808
+ }, []);
1809
+ const back = useCallback(() => {
1810
+ setState((current) => {
1811
+ const previous = current.history.at(-1);
1812
+ if (!previous) return current;
1813
+ return {
1814
+ screen: previous,
1815
+ history: current.history.slice(0, -1)
1816
+ };
1817
+ });
1818
+ }, []);
1819
+ const reset = useCallback(() => {
1820
+ setState((current) => {
1821
+ if (current.screen === "primary" && current.history.length === 0) return current;
1822
+ return {
1823
+ screen: "primary",
1824
+ history: []
1825
+ };
1826
+ });
1827
+ }, []);
1828
+ const value = useMemo(() => ({
1829
+ screen: state.screen,
1830
+ history: state.history,
1831
+ navigate,
1832
+ back,
1833
+ reset
1834
+ }), [
1835
+ back,
1836
+ navigate,
1837
+ reset,
1838
+ state.history,
1839
+ state.screen
1840
+ ]);
1841
+ return /* @__PURE__ */ jsx(EmergencyScreenContext.Provider, {
1842
+ value,
1843
+ children
1844
+ });
1845
+ }
1846
+ function useScreenNavigation() {
1847
+ const value = useContext(EmergencyScreenContext);
1848
+ if (!value) throw new Error("useScreenNavigation must be used inside EmergencyScreenProvider.");
1849
+ return value;
1850
+ }
1851
+
1852
+ // src/react/components/EmergencyHeader/index.tsx
1853
+ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack }) {
1854
+ return /* @__PURE__ */ jsxs("header", {
1855
+ className: cls("emergency-panel__header"),
1856
+ children: [
1857
+ onBack ? /* @__PURE__ */ jsx("button", {
1858
+ type: "button",
1859
+ className: cls("emergency-panel__close-button"),
1860
+ "aria-label": copy.back,
1861
+ onClick: onBack,
1862
+ children: /* @__PURE__ */ jsx(Icon, {
1863
+ name: "arrow-left",
1864
+ className: cls("emergency-panel__close-icon")
1865
+ })
1866
+ }) : null,
1867
+ /* @__PURE__ */ jsx("h2", {
1868
+ className: cls("emergency-panel__title"),
1869
+ id: titleId,
1870
+ tabIndex: -1,
1871
+ children: title
1872
+ }),
1873
+ /* @__PURE__ */ jsx("button", {
1874
+ type: "button",
1875
+ className: cls("emergency-panel__close-button"),
1876
+ "aria-label": copy.close,
1877
+ onClick: onClose,
1878
+ children: /* @__PURE__ */ jsx(Icon, {
1879
+ name: "close",
1880
+ className: cls("emergency-panel__close-icon")
1881
+ })
1882
+ })
1883
+ ]
1884
+ });
1885
+ }
1886
+
1887
+ // src/react/screens/layout.tsx
1888
+ /**
1889
+ * Shared shell for every screen. The wrapper is `display: contents` so the
1890
+ * header, body and footer stay direct grid rows of the emergency panel.
1891
+ */
1892
+ function EmergencyScreenLayout({ screen, titleId, header, footer, children, bodyClassName }) {
1893
+ return /* @__PURE__ */ jsxs("div", {
1894
+ className: cls("emergency-screen"),
1895
+ "data-screen": screen,
1896
+ children: [
1897
+ header,
1898
+ /* @__PURE__ */ jsx("section", {
1899
+ className: cls("emergency-panel__body", bodyClassName ?? null),
1900
+ "aria-labelledby": titleId,
1901
+ children
1902
+ }),
1903
+ footer
1904
+ ]
1905
+ });
1906
+ }
1907
+
1908
+ // src/react/screens/PlaceholderScreen.tsx
1909
+ /** Minimal in-design screen used to prove navigation before real designs land. */
1910
+ function PlaceholderScreen({ screen, titleId, title, description, onClose, onBack }) {
1911
+ return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
1912
+ screen,
1913
+ titleId,
1914
+ header: /* @__PURE__ */ jsx(EmergencyHeader, {
1915
+ titleId,
1916
+ title,
1917
+ onClose,
1918
+ onBack
1919
+ }),
1920
+ footer: null,
1921
+ children: /* @__PURE__ */ jsx("div", {
1922
+ className: cls("emergency-screen__placeholder"),
1923
+ children: /* @__PURE__ */ jsx("p", {
1924
+ className: cls("emergency-screen__placeholder-text"),
1925
+ children: description
1926
+ })
1927
+ })
1928
+ });
1929
+ }
1930
+
1931
+ // src/react/screens/details/copy.ts
1932
+ const detailsCopy = {
1933
+ title: "Detalles",
1934
+ description: "Aquí podrás consultar el detalle de tus alertas disponibles. Esta pantalla estará disponible próximamente."
1935
+ };
1936
+
1937
+ // src/react/screens/details/index.tsx
1938
+ function DetailsScreen({ titleId, onClose }) {
1939
+ const { back } = useScreenNavigation();
1940
+ return /* @__PURE__ */ jsx(PlaceholderScreen, {
1941
+ screen: "details",
1942
+ titleId,
1943
+ title: detailsCopy.title,
1944
+ description: detailsCopy.description,
1945
+ onClose: () => onClose("close"),
1946
+ onBack: back
1947
+ });
1948
+ }
1949
+
1950
+ // src/react/components/icons/paramedical-mark.tsx
1951
+ /**
1952
+ * Neutral inline illustration used by the idle panel.
1953
+ */
1954
+ function ParamedicalMark({ className, title }) {
1955
+ return /* @__PURE__ */ jsxs("svg", {
1956
+ xmlns: "http://www.w3.org/2000/svg",
1957
+ version: "1.0",
1958
+ viewBox: "0 0 192 192",
1959
+ width: "192",
1960
+ height: "192",
1961
+ id: "Star of life",
1962
+ className,
1963
+ "aria-label": title,
1964
+ children: [
1965
+ /* @__PURE__ */ jsx("defs", { id: "defs2886" }),
1966
+ /* @__PURE__ */ jsxs("g", {
1967
+ id: "cross",
1968
+ children: [/* @__PURE__ */ jsx("path", {
1969
+ d: "m 74,133 0,52 44,0 0,-52 44,26 22,-38 L 140,96 184,70 162,32 117,58 117,7 74,7 74,58 30,32 8,70 52,96 8,121 30,159 z",
1970
+ id: "outline",
1971
+ style: {
1972
+ fill: "none",
1973
+ stroke: "#3333bb",
1974
+ strokeWidth: 6
1975
+ }
1976
+ }), /* @__PURE__ */ jsx("path", {
1977
+ d: "m 74,133 0,52 44,0 0,-52 44,26 22,-38 L 140,96 184,70 162,32 117,58 117,7 74,7 74,58 30,32 8,70 52,96 8,121 30,159 z",
1978
+ id: "inner",
1979
+ style: {
1980
+ fill: "#0000bb",
1981
+ fillRule: "evenodd",
1982
+ stroke: "#ffffff",
1983
+ strokeWidth: 2
1984
+ }
1985
+ })]
1986
+ }),
1987
+ /* @__PURE__ */ jsxs("g", {
1988
+ id: "symbols",
1989
+ style: {
1990
+ fill: "#ffffff",
1991
+ fillRule: "evenodd",
1992
+ stroke: "#3333bb",
1993
+ strokeWidth: 2
1994
+ },
1995
+ children: [/* @__PURE__ */ jsx("path", {
1996
+ d: "m 93,166 c 0,7 6,7 6,0 l 3,-138 c 0,-8 -12,-8 -12,0 l 3,135 0,3 z",
1997
+ id: "rod"
1998
+ }), /* @__PURE__ */ jsxs("g", {
1999
+ id: "snake",
2000
+ children: [/* @__PURE__ */ jsx("path", {
2001
+ d: "m 100,153 c 7,4 3,15 -3,15 2,-5 3,-9 3,-15 z m 1,-40 c 10,9 4,22 -3,26 -6,3 -6,7 -6,15 -9,-10 -4,-19 4,-24 5,-4 5,-10 5,-17 z m 1,-30 c 0,11 -17,14 -17,22 0,3 1,9 6,12 0,-11 9,-12 15,-18 7,-7 7,-19 -4,-28 0,4 0,8 0,12 z M 90,77 C 80,69 76,55 80,46 c 3,-10 20,-11 20,-3 0,8 -20,3 -13,16 l 3,5 0,13 z",
2002
+ id: "body"
2003
+ }), /* @__PURE__ */ jsx("path", {
2004
+ d: "m 95,43 c 7,-3 -7,-3 0,0 z",
2005
+ id: "eye",
2006
+ style: {
2007
+ fill: "#3333bb",
2008
+ fillRule: "evenodd",
2009
+ stroke: "none"
2010
+ }
2011
+ })]
2012
+ })]
2013
+ })
2014
+ ]
2015
+ });
2016
+ }
2017
+
1683
2018
  // src/react/components/MessageSurface/index.tsx
1684
2019
  function MessageSurface(props) {
1685
2020
  const { variant, icon, title, text, details, className, children } = props;
@@ -2004,101 +2339,129 @@ function EmergencyStatusPanel({ responders, refreshing, locale, onRefresh }) {
2004
2339
  });
2005
2340
  }
2006
2341
 
2007
- // src/react/components/EmergencyBody/index.tsx
2008
- function EmergencyBody(props) {
2009
- const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback } = props;
2010
- const showingFeedback = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2011
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2012
- !loading && !showingFeedback ? /* @__PURE__ */ jsx("div", {
2013
- className: cls("sos-img__container"),
2014
- id: "sos-img__container",
2015
- children: /* @__PURE__ */ jsx(ParamedicalMark, {
2016
- className: cls("sos-img"),
2017
- title: copy.illustrationAlt
2018
- })
2019
- }) : null,
2020
- active || loading || showingFeedback ? null : /* @__PURE__ */ jsx("div", {
2021
- className: cls("emergency-panel__body-title-container"),
2022
- children: /* @__PURE__ */ jsx("h4", {
2023
- className: cls("emergency-panel__body-title"),
2024
- children: copy.question
2025
- })
2026
- }),
2027
- active && !showingFeedback ? /* @__PURE__ */ jsx(EmergencyStatusPanel, {
2028
- responders,
2029
- refreshing,
2030
- locale,
2031
- onRefresh
2032
- }) : null,
2033
- loading ? /* @__PURE__ */ jsx(EmergencyLoading, {}) : null,
2034
- error ? /* @__PURE__ */ jsx(EmergencyError, {
2035
- error,
2036
- onRetry
2037
- }) : null,
2038
- /* @__PURE__ */ jsx(EmergencyFeedback, {
2039
- abortResult,
2040
- simulationResult,
2041
- geolocationError,
2042
- activationError,
2043
- onDismiss: onDismissFeedback
2044
- })
2045
- ] });
2342
+ // src/react/screens/primary/copy.ts
2343
+ const primaryCopy = {
2344
+ availabilityTitle: "Alertas Disponibles",
2345
+ recharge: "Recargar",
2346
+ details: "Detalles",
2347
+ roundSingular: "ronda disponible",
2348
+ roundPlural: "rondas disponibles"
2349
+ };
2350
+ /** Singular/plural label for the remaining rounds. */
2351
+ function roundsUnit(rounds) {
2352
+ return rounds === 1 ? primaryCopy.roundSingular : primaryCopy.roundPlural;
2353
+ }
2354
+ function formatRounds(rounds) {
2355
+ return `${rounds} ${roundsUnit(rounds)}`;
2046
2356
  }
2047
2357
 
2048
- // src/react/components/EmergencyConfirmation/index.tsx
2049
- function EmergencyConfirmation(props) {
2050
- const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
2051
- const cardRef = useRef(null);
2052
- const titleId = useId();
2053
- const danger = abortConfirmation;
2054
- const open = abortConfirmation || simulationConfirmation;
2055
- useEffect(() => {
2056
- if (open) cardRef.current?.querySelector("button")?.focus();
2057
- }, [open]);
2058
- if (!open) return null;
2059
- const title = danger ? copy.abortConfirmTitle : copy.simulateConfirmTitle;
2060
- const text = danger ? copy.abortConfirmText : copy.simulateConfirmText;
2061
- const tone = danger ? "danger" : "information";
2062
- return /* @__PURE__ */ jsx("div", {
2063
- className: cls("emergency-confirmation"),
2064
- role: "dialog",
2065
- "aria-modal": "true",
2066
- "aria-labelledby": titleId,
2067
- children: /* @__PURE__ */ jsxs("div", {
2068
- ref: cardRef,
2069
- className: cls("emergency-confirmation__card"),
2070
- children: [/* @__PURE__ */ jsxs("div", {
2071
- className: cls("emergency-confirmation__body"),
2072
- children: [
2073
- /* @__PURE__ */ jsx("div", {
2074
- className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
2075
- children: /* @__PURE__ */ jsx(Icon, { name: danger ? "alert-triangle" : "info" })
2076
- }),
2077
- /* @__PURE__ */ jsx("h5", {
2078
- className: cls("emergency-confirmation__title"),
2079
- id: titleId,
2080
- children: title
2081
- }),
2082
- /* @__PURE__ */ jsx("p", {
2083
- className: cls("emergency-confirmation__text"),
2084
- children: text
2085
- })
2086
- ]
2358
+ // src/react/screens/primary/data.ts
2359
+ /**
2360
+ * Derives the remaining alert rounds from available messages and designated
2361
+ * responders. Invalid or non-positive input collapses to the minimum of one
2362
+ * round instead of producing `Infinity`, `NaN` or a negative value.
2363
+ */
2364
+ function calculateRounds(messages, responders) {
2365
+ if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
2366
+ const availableMessages = Math.floor(messages);
2367
+ const designatedResponders = Math.floor(responders);
2368
+ if (availableMessages <= 0 || designatedResponders <= 0) return 1;
2369
+ return Math.max(1, Math.floor(availableMessages / designatedResponders));
2370
+ }
2371
+
2372
+ // src/react/screens/primary/Availability.tsx
2373
+ function Availability({ onRecharge, onDetails }) {
2374
+ const rounds = calculateRounds(100, 3);
2375
+ return /* @__PURE__ */ jsxs("section", {
2376
+ className: cls("emergency-availability"),
2377
+ "aria-label": primaryCopy.availabilityTitle,
2378
+ children: [/* @__PURE__ */ jsxs("button", {
2379
+ type: "button",
2380
+ className: cls("emergency-availability__recharge"),
2381
+ onClick: onRecharge,
2382
+ children: [/* @__PURE__ */ jsx(Icon, { name: "credit-card-plus" }), primaryCopy.recharge]
2383
+ }), /* @__PURE__ */ jsxs("div", {
2384
+ className: cls("emergency-availability__card"),
2385
+ children: [/* @__PURE__ */ jsx("div", {
2386
+ className: cls("emergency-availability__card-header"),
2387
+ children: /* @__PURE__ */ jsx("h3", {
2388
+ className: cls("emergency-availability__card-title"),
2389
+ children: primaryCopy.availabilityTitle
2390
+ })
2087
2391
  }), /* @__PURE__ */ jsxs("div", {
2088
- className: cls("emergency-confirmation__footer"),
2089
- children: [/* @__PURE__ */ jsx("button", {
2090
- type: "button",
2091
- className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
2092
- onClick: danger ? onCancelAbort : onCancelSimulation,
2093
- children: danger ? copy.abortCancel : copy.simulateCancel
2392
+ className: cls("emergency-availability__card-body"),
2393
+ children: [/* @__PURE__ */ jsxs("div", {
2394
+ className: cls("emergency-availability__rounds-group"),
2395
+ "aria-label": formatRounds(rounds),
2396
+ children: [/* @__PURE__ */ jsx("p", {
2397
+ className: cls("emergency-availability__rounds"),
2398
+ children: rounds
2399
+ }), /* @__PURE__ */ jsx("p", {
2400
+ className: cls("emergency-availability__label"),
2401
+ children: roundsUnit(rounds)
2402
+ })]
2094
2403
  }), /* @__PURE__ */ jsx("button", {
2095
2404
  type: "button",
2096
- className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
2097
- onClick: danger ? onConfirmAbort : onConfirmSimulation,
2098
- children: danger ? copy.abortConfirm : copy.simulateConfirm
2405
+ className: cls("emergency-availability__details"),
2406
+ onClick: onDetails,
2407
+ children: primaryCopy.details
2099
2408
  })]
2100
2409
  })]
2101
- })
2410
+ })]
2411
+ });
2412
+ }
2413
+
2414
+ // src/react/components/EmergencyBody/index.tsx
2415
+ function EmergencyBody(props) {
2416
+ const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails } = props;
2417
+ const showingFeedback = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2418
+ const idle = !active && !loading && !showingFeedback && error === null;
2419
+ return /* @__PURE__ */ jsxs("div", {
2420
+ className: cls("emergency-panel__body-split", idle && "emergency-panel__body-split--with-availability"),
2421
+ children: [idle ? /* @__PURE__ */ jsx("div", {
2422
+ className: cls("emergency-panel__availability-region"),
2423
+ children: /* @__PURE__ */ jsx(Availability, {
2424
+ onRecharge,
2425
+ onDetails
2426
+ })
2427
+ }) : null, /* @__PURE__ */ jsxs("div", {
2428
+ className: cls("emergency-panel__prompt-region"),
2429
+ children: [
2430
+ !loading && !showingFeedback ? /* @__PURE__ */ jsx("div", {
2431
+ className: cls("sos-img__container"),
2432
+ id: "sos-img__container",
2433
+ children: /* @__PURE__ */ jsx(ParamedicalMark, {
2434
+ className: cls("sos-img"),
2435
+ title: copy.illustrationAlt
2436
+ })
2437
+ }) : null,
2438
+ active || loading || showingFeedback ? null : /* @__PURE__ */ jsx("div", {
2439
+ className: cls("emergency-panel__body-title-container"),
2440
+ children: /* @__PURE__ */ jsx("h4", {
2441
+ className: cls("emergency-panel__body-title"),
2442
+ children: copy.question
2443
+ })
2444
+ }),
2445
+ active && !showingFeedback ? /* @__PURE__ */ jsx(EmergencyStatusPanel, {
2446
+ responders,
2447
+ refreshing,
2448
+ locale,
2449
+ onRefresh
2450
+ }) : null,
2451
+ loading ? /* @__PURE__ */ jsx(EmergencyLoading, {}) : null,
2452
+ error ? /* @__PURE__ */ jsx(EmergencyError, {
2453
+ error,
2454
+ onRetry
2455
+ }) : null,
2456
+ /* @__PURE__ */ jsx(EmergencyFeedback, {
2457
+ abortResult,
2458
+ simulationResult,
2459
+ geolocationError,
2460
+ activationError,
2461
+ onDismiss: onDismissFeedback
2462
+ })
2463
+ ]
2464
+ })]
2102
2465
  });
2103
2466
  }
2104
2467
 
@@ -2136,105 +2499,279 @@ function EmergencyFooter(props) {
2136
2499
  });
2137
2500
  }
2138
2501
 
2139
- // src/react/components/EmergencyHeader/index.tsx
2140
- function EmergencyHeader({ titleId, onClose }) {
2141
- return /* @__PURE__ */ jsxs("header", {
2142
- className: cls("emergency-panel__header"),
2143
- children: [/* @__PURE__ */ jsx("h2", {
2144
- className: cls("emergency-panel__title"),
2145
- id: titleId,
2146
- children: copy.panelLabel
2147
- }), /* @__PURE__ */ jsx("button", {
2148
- type: "button",
2149
- className: cls("emergency-panel__close-button"),
2150
- "aria-label": copy.close,
2151
- onClick: onClose,
2152
- children: /* @__PURE__ */ jsx(Icon, {
2153
- name: "close",
2154
- className: cls("emergency-panel__close-icon")
2155
- })
2156
- })]
2502
+ // src/react/screens/primary/index.tsx
2503
+ function PrimaryScreen(props) {
2504
+ const { navigate } = useScreenNavigation();
2505
+ return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
2506
+ screen: "primary",
2507
+ titleId: props.titleId,
2508
+ header: /* @__PURE__ */ jsx(EmergencyHeader, {
2509
+ titleId: props.titleId,
2510
+ onClose: () => props.onClose("close")
2511
+ }),
2512
+ footer: /* @__PURE__ */ jsx(EmergencyFooter, {
2513
+ active: props.active,
2514
+ busy: props.busy,
2515
+ aborting: props.aborting,
2516
+ feedbackVisible: props.feedbackVisible,
2517
+ onActivate: props.onActivate,
2518
+ onRequestAbort: props.onRequestAbort,
2519
+ onRequestSimulation: props.onRequestSimulation
2520
+ }),
2521
+ children: /* @__PURE__ */ jsx(EmergencyBody, {
2522
+ active: props.active,
2523
+ responders: props.responders,
2524
+ refreshing: props.refreshing,
2525
+ loading: props.loading,
2526
+ locale: props.locale,
2527
+ geolocationError: props.geolocationError,
2528
+ activationError: props.activationError,
2529
+ error: props.error,
2530
+ abortResult: props.abortResult,
2531
+ simulationResult: props.simulationResult,
2532
+ onRefresh: props.onRefresh,
2533
+ onRetry: props.onRetry,
2534
+ onDismissFeedback: props.onDismissFeedback,
2535
+ onRecharge: () => navigate("pricing"),
2536
+ onDetails: () => navigate("details")
2537
+ })
2157
2538
  });
2158
2539
  }
2159
2540
 
2160
- // src/react/hooks/useEscapeKey.ts
2161
- function useEscapeKey(active, onEscape) {
2162
- useEffect(() => {
2163
- if (!active || typeof document === "undefined") return;
2164
- const handleKeyDown = (event) => {
2165
- if (event.key === "Escape") {
2166
- event.stopPropagation();
2167
- onEscape();
2168
- }
2169
- };
2170
- document.addEventListener("keydown", handleKeyDown);
2171
- return () => {
2172
- document.removeEventListener("keydown", handleKeyDown);
2173
- };
2174
- }, [active, onEscape]);
2175
- }
2541
+ // src/react/screens/pricing/copy.ts
2542
+ const pricingCopy = {
2543
+ title: "Recargar",
2544
+ heading: "Elige tu paquete",
2545
+ subtitle: "Alertas de WhatsApp para tus contactos",
2546
+ bestValue: "MEJOR VALOR",
2547
+ choose: "Seleccionar",
2548
+ chosen: "Seleccionado",
2549
+ whatsapp: "de WhatsApp",
2550
+ rounds: "rondas de alerta",
2551
+ perMessage: "por mensaje + IVA",
2552
+ footnote: "Rondas calculadas con 3 contactos de emergencia. Válidas para pruebas y emergencias."
2553
+ };
2176
2554
 
2177
- // src/react/hooks/useFocusTrap.ts
2178
- const FOCUSABLE_SELECTOR = [
2179
- "a[href]",
2180
- "button:not([disabled])",
2181
- "input:not([disabled])",
2182
- "select:not([disabled])",
2183
- "textarea:not([disabled])",
2184
- "[tabindex]:not([tabindex=\"-1\"])"
2185
- ].join(",");
2555
+ // src/react/screens/pricing/data.ts
2186
2556
  /**
2187
- * Keeps keyboard focus inside the dialog while it is open and restores focus to
2188
- * the element that opened it when it closes.
2557
+ * Local demo catalog. Prices and per-message labels are literal values from the
2558
+ * mock; they are not derived from the package totals.
2189
2559
  */
2190
- function useFocusTrap(ref, active) {
2191
- useEffect(() => {
2192
- if (!active || typeof document === "undefined") return;
2193
- const container = ref.current;
2194
- if (!container) return;
2195
- const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2196
- const focusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((element) => !element.hasAttribute("hidden") && element.getAttribute("aria-hidden") !== "true");
2197
- (focusable()[0] ?? container).focus({ preventScroll: true });
2198
- const handleKeyDown = (event) => {
2199
- if (event.key !== "Tab") return;
2200
- const items = focusable();
2201
- if (items.length === 0) {
2202
- event.preventDefault();
2203
- container.focus({ preventScroll: true });
2204
- return;
2205
- }
2206
- const first = items[0];
2207
- const last = items[items.length - 1];
2208
- const current = document.activeElement;
2209
- if (event.shiftKey && (current === first || !container.contains(current))) {
2210
- event.preventDefault();
2211
- if (last) last.focus({ preventScroll: true });
2212
- return;
2213
- }
2214
- if (!event.shiftKey && current === last && first) {
2215
- event.preventDefault();
2216
- first.focus({ preventScroll: true });
2217
- }
2218
- };
2219
- document.addEventListener("keydown", handleKeyDown, true);
2220
- return () => {
2221
- document.removeEventListener("keydown", handleKeyDown, true);
2222
- if (previouslyFocused?.isConnected) previouslyFocused.focus({ preventScroll: true });
2223
- };
2224
- }, [active, ref]);
2560
+ const PRICING_PACKAGES = [{
2561
+ id: "package-1",
2562
+ label: "PAQUETE 1",
2563
+ messages: 100,
2564
+ priceLabel: "$7",
2565
+ priceCaption: "IVA incluido",
2566
+ perMessageLabel: "$0.06",
2567
+ highlighted: false
2568
+ }, {
2569
+ id: "package-2",
2570
+ label: "PAQUETE 2",
2571
+ messages: 200,
2572
+ priceLabel: "12$",
2573
+ priceNote: "($10 + IVA)",
2574
+ priceCaption: "IVA incluido",
2575
+ perMessageLabel: "$0.05",
2576
+ highlighted: true,
2577
+ badge: "MEJOR VALOR"
2578
+ }];
2579
+ /** Derives the alert rounds a package provides for the demo responder count. */
2580
+ function packageRounds(pkg) {
2581
+ return calculateRounds(pkg.messages, 3);
2582
+ }
2583
+
2584
+ // src/react/screens/pricing/PackageCard.tsx
2585
+ function PackageCard({ pkg, selected, onSelect }) {
2586
+ const labelId = useId();
2587
+ const rounds = packageRounds(pkg);
2588
+ return /* @__PURE__ */ jsxs("section", {
2589
+ className: cls("emergency-pricing__package", pkg.highlighted && "emergency-pricing__package--highlighted"),
2590
+ "aria-labelledby": labelId,
2591
+ "data-selected": selected ? "true" : void 0,
2592
+ children: [
2593
+ /* @__PURE__ */ jsxs("div", {
2594
+ className: cls("emergency-pricing__package-header"),
2595
+ children: [/* @__PURE__ */ jsx("span", {
2596
+ className: cls("emergency-pricing__package-label"),
2597
+ id: labelId,
2598
+ children: pkg.label
2599
+ }), pkg.badge ? /* @__PURE__ */ jsx("span", {
2600
+ className: cls("emergency-pricing__badge"),
2601
+ children: pkg.badge
2602
+ }) : null]
2603
+ }),
2604
+ /* @__PURE__ */ jsxs("div", {
2605
+ className: cls("emergency-pricing__price-row"),
2606
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("h4", {
2607
+ className: cls("emergency-pricing__package-name"),
2608
+ children: [pkg.messages, " alertas"]
2609
+ }), /* @__PURE__ */ jsx("p", {
2610
+ className: cls("emergency-pricing__package-caption"),
2611
+ children: pricingCopy.whatsapp
2612
+ })] }), /* @__PURE__ */ jsxs("div", {
2613
+ className: cls("emergency-pricing__price-meta"),
2614
+ children: [/* @__PURE__ */ jsx("span", {
2615
+ className: cls("emergency-pricing__price"),
2616
+ children: pkg.priceLabel
2617
+ }), /* @__PURE__ */ jsxs("span", {
2618
+ className: cls("emergency-pricing__price-note"),
2619
+ children: [pkg.priceCaption, pkg.priceNote ? ` ${pkg.priceNote}` : null]
2620
+ })]
2621
+ })]
2622
+ }),
2623
+ /* @__PURE__ */ jsxs("div", {
2624
+ className: cls("emergency-pricing__stats"),
2625
+ children: [
2626
+ /* @__PURE__ */ jsxs("div", {
2627
+ className: cls("emergency-pricing__stat"),
2628
+ children: [/* @__PURE__ */ jsx("p", {
2629
+ className: cls("emergency-pricing__stat-value"),
2630
+ children: rounds
2631
+ }), /* @__PURE__ */ jsx("p", {
2632
+ className: cls("emergency-pricing__stat-label"),
2633
+ children: pricingCopy.rounds
2634
+ })]
2635
+ }),
2636
+ /* @__PURE__ */ jsx("div", {
2637
+ className: cls("emergency-pricing__stat-divider"),
2638
+ "aria-hidden": "true"
2639
+ }),
2640
+ /* @__PURE__ */ jsxs("div", {
2641
+ className: cls("emergency-pricing__stat"),
2642
+ children: [/* @__PURE__ */ jsx("p", {
2643
+ className: cls("emergency-pricing__stat-value"),
2644
+ children: pkg.perMessageLabel
2645
+ }), /* @__PURE__ */ jsx("p", {
2646
+ className: cls("emergency-pricing__stat-label"),
2647
+ children: pricingCopy.perMessage
2648
+ })]
2649
+ })
2650
+ ]
2651
+ }),
2652
+ /* @__PURE__ */ jsx("button", {
2653
+ type: "button",
2654
+ className: cls("emergency-pricing__select"),
2655
+ "aria-pressed": selected,
2656
+ onClick: () => onSelect(pkg.id),
2657
+ children: selected ? pricingCopy.chosen : pricingCopy.choose
2658
+ })
2659
+ ]
2660
+ });
2661
+ }
2662
+
2663
+ // src/react/screens/pricing/index.tsx
2664
+ function PricingScreen(props) {
2665
+ const { back } = useScreenNavigation();
2666
+ const [selected, setSelected] = useState(null);
2667
+ return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
2668
+ screen: "pricing",
2669
+ titleId: props.titleId,
2670
+ bodyClassName: "emergency-panel__body--pricing",
2671
+ header: /* @__PURE__ */ jsx(EmergencyHeader, {
2672
+ titleId: props.titleId,
2673
+ title: pricingCopy.title,
2674
+ onClose: () => props.onClose("close"),
2675
+ onBack: back
2676
+ }),
2677
+ footer: null,
2678
+ children: /* @__PURE__ */ jsxs("div", {
2679
+ className: cls("emergency-pricing"),
2680
+ children: [
2681
+ /* @__PURE__ */ jsx("div", {
2682
+ className: cls("emergency-pricing__heading"),
2683
+ children: /* @__PURE__ */ jsxs("div", {
2684
+ className: cls("emergency-pricing__titles"),
2685
+ children: [/* @__PURE__ */ jsx("h3", {
2686
+ className: cls("emergency-pricing__title"),
2687
+ children: pricingCopy.heading
2688
+ }), /* @__PURE__ */ jsx("p", {
2689
+ className: cls("emergency-pricing__subtitle"),
2690
+ children: pricingCopy.subtitle
2691
+ })]
2692
+ })
2693
+ }),
2694
+ PRICING_PACKAGES.map((pkg) => /* @__PURE__ */ jsx(PackageCard, {
2695
+ pkg,
2696
+ selected: selected === pkg.id,
2697
+ onSelect: setSelected
2698
+ }, pkg.id)),
2699
+ /* @__PURE__ */ jsx("p", {
2700
+ className: cls("emergency-pricing__footnote"),
2701
+ children: pricingCopy.footnote
2702
+ })
2703
+ ]
2704
+ })
2705
+ });
2225
2706
  }
2226
2707
 
2708
+ // src/react/screens/registry.ts
2709
+ /** Every screen the panel can render. Add one entry to add a screen. */
2710
+ const screenRegistry = {
2711
+ primary: PrimaryScreen,
2712
+ pricing: PricingScreen,
2713
+ details: DetailsScreen
2714
+ };
2715
+
2227
2716
  // src/react/components/EmergencyPanel/index.tsx
2717
+ function ActiveScreen({ screenProps, open, forcePrimary }) {
2718
+ const { screen, reset } = useScreenNavigation();
2719
+ const shouldReset = !open || forcePrimary;
2720
+ const effectiveScreen = shouldReset ? "primary" : screen;
2721
+ const previousOpen = useRef(open);
2722
+ const previousScreen = useRef(effectiveScreen);
2723
+ useEffect(() => {
2724
+ if (shouldReset) reset();
2725
+ }, [reset, shouldReset]);
2726
+ useEffect(() => {
2727
+ const wasOpen = previousOpen.current;
2728
+ const previousEffectiveScreen = previousScreen.current;
2729
+ previousOpen.current = open;
2730
+ previousScreen.current = effectiveScreen;
2731
+ if (!open || !wasOpen || previousEffectiveScreen === effectiveScreen) return;
2732
+ document.getElementById(screenProps.titleId)?.focus({ preventScroll: true });
2733
+ }, [
2734
+ effectiveScreen,
2735
+ open,
2736
+ screenProps.titleId
2737
+ ]);
2738
+ const Screen = screenRegistry[effectiveScreen];
2739
+ return /* @__PURE__ */ jsx(Screen, { ...screenProps });
2740
+ }
2228
2741
  function EmergencyPanel(props) {
2229
2742
  const { open, theme, active, busy, aborting, loading, locale, responders, refreshing, geolocationError, activationError, error, abortResult, simulationResult, abortConfirmation, simulationConfirmation, onClose, onActivate, onRefresh, onRetry, onDismissFeedback, onRequestAbort, onRequestSimulation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
2230
2743
  const titleId = useId();
2231
2744
  const dialogRef = useRef(null);
2232
2745
  const feedbackVisible = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2746
+ const forcePrimary = active || loading || error !== null || feedbackVisible;
2233
2747
  const closeOnEscape = useCallback(() => {
2234
2748
  onClose("escape");
2235
2749
  }, [onClose]);
2236
2750
  useFocusTrap(dialogRef, open);
2237
2751
  useEscapeKey(open, closeOnEscape);
2752
+ const screenProps = {
2753
+ titleId,
2754
+ active,
2755
+ busy,
2756
+ aborting,
2757
+ loading,
2758
+ locale,
2759
+ responders,
2760
+ refreshing,
2761
+ geolocationError,
2762
+ activationError,
2763
+ error,
2764
+ abortResult,
2765
+ simulationResult,
2766
+ feedbackVisible,
2767
+ onClose,
2768
+ onActivate,
2769
+ onRefresh,
2770
+ onRetry,
2771
+ onDismissFeedback,
2772
+ onRequestAbort,
2773
+ onRequestSimulation
2774
+ };
2238
2775
  return /* @__PURE__ */ jsxs("div", {
2239
2776
  ref: dialogRef,
2240
2777
  className: cls("emergency-panel", !open && "emergency-panel--hidden"),
@@ -2245,48 +2782,18 @@ function EmergencyPanel(props) {
2245
2782
  "aria-hidden": !open,
2246
2783
  inert: !open,
2247
2784
  tabIndex: -1,
2248
- children: [
2249
- /* @__PURE__ */ jsx(EmergencyHeader, {
2250
- titleId,
2251
- onClose: () => onClose("close")
2252
- }),
2253
- /* @__PURE__ */ jsx("section", {
2254
- className: cls("emergency-panel__body"),
2255
- "aria-labelledby": titleId,
2256
- children: /* @__PURE__ */ jsx(EmergencyBody, {
2257
- active,
2258
- responders,
2259
- refreshing,
2260
- loading,
2261
- locale,
2262
- geolocationError,
2263
- activationError,
2264
- error,
2265
- abortResult,
2266
- simulationResult,
2267
- onRefresh,
2268
- onRetry,
2269
- onDismissFeedback
2270
- })
2271
- }),
2272
- /* @__PURE__ */ jsx(EmergencyFooter, {
2273
- active,
2274
- busy,
2275
- aborting,
2276
- feedbackVisible,
2277
- onActivate,
2278
- onRequestAbort,
2279
- onRequestSimulation
2280
- }),
2281
- /* @__PURE__ */ jsx(EmergencyConfirmation, {
2282
- abortConfirmation,
2283
- simulationConfirmation,
2284
- onCancelAbort,
2285
- onCancelSimulation,
2286
- onConfirmAbort,
2287
- onConfirmSimulation
2288
- })
2289
- ]
2785
+ children: [/* @__PURE__ */ jsx(EmergencyScreenProvider, { children: /* @__PURE__ */ jsx(ActiveScreen, {
2786
+ screenProps,
2787
+ open,
2788
+ forcePrimary
2789
+ }) }), /* @__PURE__ */ jsx(EmergencyConfirmation, {
2790
+ abortConfirmation,
2791
+ simulationConfirmation,
2792
+ onCancelAbort,
2793
+ onCancelSimulation,
2794
+ onConfirmAbort,
2795
+ onConfirmSimulation
2796
+ })]
2290
2797
  });
2291
2798
  }
2292
2799