@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.cjs CHANGED
@@ -391,6 +391,7 @@ function createBffClient(options) {
391
391
  const copy = {
392
392
  panelLabel: "Panel de emergencia",
393
393
  close: "Cerrar panel de emergencia",
394
+ back: "Volver",
394
395
  question: "¿Tiene usted una emergencia?",
395
396
  illustrationAlt: "Ícono paramédico",
396
397
  emergency: "Emergencia",
@@ -1469,74 +1470,6 @@ function validateConfig(config) {
1469
1470
  };
1470
1471
  }
1471
1472
 
1472
- // src/react/components/icons/paramedical-mark.tsx
1473
- /**
1474
- * Neutral inline illustration used by the idle panel.
1475
- */
1476
- function ParamedicalMark({ className, title }) {
1477
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1478
- xmlns: "http://www.w3.org/2000/svg",
1479
- version: "1.0",
1480
- viewBox: "0 0 192 192",
1481
- width: "192",
1482
- height: "192",
1483
- id: "Star of life",
1484
- className,
1485
- "aria-label": title,
1486
- children: [
1487
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { id: "defs2886" }),
1488
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
1489
- id: "cross",
1490
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1491
- 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",
1492
- id: "outline",
1493
- style: {
1494
- fill: "none",
1495
- stroke: "#3333bb",
1496
- strokeWidth: 6
1497
- }
1498
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1499
- 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",
1500
- id: "inner",
1501
- style: {
1502
- fill: "#0000bb",
1503
- fillRule: "evenodd",
1504
- stroke: "#ffffff",
1505
- strokeWidth: 2
1506
- }
1507
- })]
1508
- }),
1509
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
1510
- id: "symbols",
1511
- style: {
1512
- fill: "#ffffff",
1513
- fillRule: "evenodd",
1514
- stroke: "#3333bb",
1515
- strokeWidth: 2
1516
- },
1517
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1518
- 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",
1519
- id: "rod"
1520
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
1521
- id: "snake",
1522
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1523
- 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",
1524
- id: "body"
1525
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1526
- d: "m 95,43 c 7,-3 -7,-3 0,0 z",
1527
- id: "eye",
1528
- style: {
1529
- fill: "#3333bb",
1530
- fillRule: "evenodd",
1531
- stroke: "none"
1532
- }
1533
- })]
1534
- })]
1535
- })
1536
- ]
1537
- });
1538
- }
1539
-
1540
1473
  // src/react/components/Icon/index.tsx
1541
1474
  /** Icons whose artwork does not fit the default 24x24 grid need their own canvas. */
1542
1475
  const viewBoxes = { emergency: "0 0 640 640" };
@@ -1593,6 +1526,18 @@ const paths = {
1593
1526
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 7h.01" })
1594
1527
  ] }),
1595
1528
  "arrow-left": /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19 12H5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m12 19-7-7 7-7" })] }),
1529
+ "credit-card-plus": /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1530
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5" }),
1531
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 12h.01" }),
1532
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19 22v-6" }),
1533
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m22 19-3-3-3 3" }),
1534
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6 12h.01" }),
1535
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1536
+ cx: "12",
1537
+ cy: "12",
1538
+ r: "2"
1539
+ })
1540
+ ] }),
1596
1541
  emergency: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.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" }) })
1597
1542
  };
1598
1543
  function Icon({ name, className, title }) {
@@ -1622,6 +1567,16 @@ var panel_module_default = {
1622
1567
  "de-emergency-slide-in": "de-emergency-de-emergency-slide-in-nA5Hwa",
1623
1568
  "de-emergency-slide-up": "de-emergency-de-emergency-slide-up-nA5Hwa",
1624
1569
  "de-emergency-spin": "de-emergency-de-emergency-spin-nA5Hwa",
1570
+ "emergency-availability": "de-emergency-emergency-availability-nA5Hwa",
1571
+ "emergency-availability__card": "de-emergency-emergency-availability__card-nA5Hwa",
1572
+ "emergency-availability__card-body": "de-emergency-emergency-availability__card-body-nA5Hwa",
1573
+ "emergency-availability__card-header": "de-emergency-emergency-availability__card-header-nA5Hwa",
1574
+ "emergency-availability__card-title": "de-emergency-emergency-availability__card-title-nA5Hwa",
1575
+ "emergency-availability__details": "de-emergency-emergency-availability__details-nA5Hwa",
1576
+ "emergency-availability__label": "de-emergency-emergency-availability__label-nA5Hwa",
1577
+ "emergency-availability__recharge": "de-emergency-emergency-availability__recharge-nA5Hwa",
1578
+ "emergency-availability__rounds": "de-emergency-emergency-availability__rounds-nA5Hwa",
1579
+ "emergency-availability__rounds-group": "de-emergency-emergency-availability__rounds-group-nA5Hwa",
1625
1580
  "emergency-confirmation": "de-emergency-emergency-confirmation-nA5Hwa",
1626
1581
  "emergency-confirmation__body": "de-emergency-emergency-confirmation__body-nA5Hwa",
1627
1582
  "emergency-confirmation__button": "de-emergency-emergency-confirmation__button-nA5Hwa",
@@ -1646,7 +1601,11 @@ var panel_module_default = {
1646
1601
  "emergency-feedback": "de-emergency-emergency-feedback-nA5Hwa",
1647
1602
  "emergency-feedback__dismiss": "de-emergency-emergency-feedback__dismiss-nA5Hwa",
1648
1603
  "emergency-panel": "de-emergency-emergency-panel-nA5Hwa",
1604
+ "emergency-panel__availability-region": "de-emergency-emergency-panel__availability-region-nA5Hwa",
1649
1605
  "emergency-panel__body": "de-emergency-emergency-panel__body-nA5Hwa",
1606
+ "emergency-panel__body--pricing": "de-emergency-emergency-panel__body--pricing-nA5Hwa",
1607
+ "emergency-panel__body-split": "de-emergency-emergency-panel__body-split-nA5Hwa",
1608
+ "emergency-panel__body-split--with-availability": "de-emergency-emergency-panel__body-split--with-availability-nA5Hwa",
1650
1609
  "emergency-panel__body-title": "de-emergency-emergency-panel__body-title-nA5Hwa",
1651
1610
  "emergency-panel__body-title-container": "de-emergency-emergency-panel__body-title-container-nA5Hwa",
1652
1611
  "emergency-panel__button": "de-emergency-emergency-panel__button-nA5Hwa",
@@ -1656,14 +1615,41 @@ var panel_module_default = {
1656
1615
  "emergency-panel__close-icon": "de-emergency-emergency-panel__close-icon-nA5Hwa",
1657
1616
  "emergency-panel__footer": "de-emergency-emergency-panel__footer-nA5Hwa",
1658
1617
  "emergency-panel__header": "de-emergency-emergency-panel__header-nA5Hwa",
1618
+ "emergency-panel__prompt-region": "de-emergency-emergency-panel__prompt-region-nA5Hwa",
1659
1619
  "emergency-panel__title": "de-emergency-emergency-panel__title-nA5Hwa",
1660
1620
  "emergency-panel--hidden": "de-emergency-emergency-panel--hidden-nA5Hwa",
1621
+ "emergency-pricing": "de-emergency-emergency-pricing-nA5Hwa",
1622
+ "emergency-pricing__badge": "de-emergency-emergency-pricing__badge-nA5Hwa",
1623
+ "emergency-pricing__footnote": "de-emergency-emergency-pricing__footnote-nA5Hwa",
1624
+ "emergency-pricing__heading": "de-emergency-emergency-pricing__heading-nA5Hwa",
1625
+ "emergency-pricing__package": "de-emergency-emergency-pricing__package-nA5Hwa",
1626
+ "emergency-pricing__package--highlighted": "de-emergency-emergency-pricing__package--highlighted-nA5Hwa",
1627
+ "emergency-pricing__package-caption": "de-emergency-emergency-pricing__package-caption-nA5Hwa",
1628
+ "emergency-pricing__package-header": "de-emergency-emergency-pricing__package-header-nA5Hwa",
1629
+ "emergency-pricing__package-label": "de-emergency-emergency-pricing__package-label-nA5Hwa",
1630
+ "emergency-pricing__package-name": "de-emergency-emergency-pricing__package-name-nA5Hwa",
1631
+ "emergency-pricing__price": "de-emergency-emergency-pricing__price-nA5Hwa",
1632
+ "emergency-pricing__price-meta": "de-emergency-emergency-pricing__price-meta-nA5Hwa",
1633
+ "emergency-pricing__price-note": "de-emergency-emergency-pricing__price-note-nA5Hwa",
1634
+ "emergency-pricing__price-row": "de-emergency-emergency-pricing__price-row-nA5Hwa",
1635
+ "emergency-pricing__select": "de-emergency-emergency-pricing__select-nA5Hwa",
1636
+ "emergency-pricing__stat": "de-emergency-emergency-pricing__stat-nA5Hwa",
1637
+ "emergency-pricing__stat-divider": "de-emergency-emergency-pricing__stat-divider-nA5Hwa",
1638
+ "emergency-pricing__stat-label": "de-emergency-emergency-pricing__stat-label-nA5Hwa",
1639
+ "emergency-pricing__stat-value": "de-emergency-emergency-pricing__stat-value-nA5Hwa",
1640
+ "emergency-pricing__stats": "de-emergency-emergency-pricing__stats-nA5Hwa",
1641
+ "emergency-pricing__subtitle": "de-emergency-emergency-pricing__subtitle-nA5Hwa",
1642
+ "emergency-pricing__title": "de-emergency-emergency-pricing__title-nA5Hwa",
1643
+ "emergency-pricing__titles": "de-emergency-emergency-pricing__titles-nA5Hwa",
1661
1644
  "emergency-resume-notification-button": "de-emergency-emergency-resume-notification-button-nA5Hwa",
1662
1645
  "emergency-resume-notification-card": "de-emergency-emergency-resume-notification-card-nA5Hwa",
1663
1646
  "emergency-resume-notification-icon": "de-emergency-emergency-resume-notification-icon-nA5Hwa",
1664
1647
  "emergency-resume-notification-message": "de-emergency-emergency-resume-notification-message-nA5Hwa",
1665
1648
  "emergency-resume-notification-overlay": "de-emergency-emergency-resume-notification-overlay-nA5Hwa",
1666
1649
  "emergency-resume-notification-title": "de-emergency-emergency-resume-notification-title-nA5Hwa",
1650
+ "emergency-screen": "de-emergency-emergency-screen-nA5Hwa",
1651
+ "emergency-screen__placeholder": "de-emergency-emergency-screen__placeholder-nA5Hwa",
1652
+ "emergency-screen__placeholder-text": "de-emergency-emergency-screen__placeholder-text-nA5Hwa",
1667
1653
  "emergency-status__abort-btn": "de-emergency-emergency-status__abort-btn-nA5Hwa",
1668
1654
  "emergency-status__body": "de-emergency-emergency-status__body-nA5Hwa",
1669
1655
  "emergency-status__cell": "de-emergency-emergency-status__cell-nA5Hwa",
@@ -1709,6 +1695,355 @@ function cls(...names) {
1709
1695
  return names.filter((name) => Boolean(name)).map((name) => panel_module_default[name] ?? "").filter(Boolean).join(" ");
1710
1696
  }
1711
1697
 
1698
+ // src/react/components/EmergencyConfirmation/index.tsx
1699
+ function EmergencyConfirmation(props) {
1700
+ const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
1701
+ const cardRef = (0, react.useRef)(null);
1702
+ const titleId = (0, react.useId)();
1703
+ const danger = abortConfirmation;
1704
+ const open = abortConfirmation || simulationConfirmation;
1705
+ (0, react.useEffect)(() => {
1706
+ if (open) cardRef.current?.querySelector("button")?.focus();
1707
+ }, [open]);
1708
+ if (!open) return null;
1709
+ const title = danger ? copy.abortConfirmTitle : copy.simulateConfirmTitle;
1710
+ const text = danger ? copy.abortConfirmText : copy.simulateConfirmText;
1711
+ const tone = danger ? "danger" : "information";
1712
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1713
+ className: cls("emergency-confirmation"),
1714
+ role: "dialog",
1715
+ "aria-modal": "true",
1716
+ "aria-labelledby": titleId,
1717
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1718
+ ref: cardRef,
1719
+ className: cls("emergency-confirmation__card"),
1720
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1721
+ className: cls("emergency-confirmation__body"),
1722
+ children: [
1723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1724
+ className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
1725
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: danger ? "alert-triangle" : "info" })
1726
+ }),
1727
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h5", {
1728
+ className: cls("emergency-confirmation__title"),
1729
+ id: titleId,
1730
+ children: title
1731
+ }),
1732
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1733
+ className: cls("emergency-confirmation__text"),
1734
+ children: text
1735
+ })
1736
+ ]
1737
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1738
+ className: cls("emergency-confirmation__footer"),
1739
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1740
+ type: "button",
1741
+ className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
1742
+ onClick: danger ? onCancelAbort : onCancelSimulation,
1743
+ children: danger ? copy.abortCancel : copy.simulateCancel
1744
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1745
+ type: "button",
1746
+ className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
1747
+ onClick: danger ? onConfirmAbort : onConfirmSimulation,
1748
+ children: danger ? copy.abortConfirm : copy.simulateConfirm
1749
+ })]
1750
+ })]
1751
+ })
1752
+ });
1753
+ }
1754
+
1755
+ // src/react/hooks/useEscapeKey.ts
1756
+ function useEscapeKey(active, onEscape) {
1757
+ (0, react.useEffect)(() => {
1758
+ if (!active || typeof document === "undefined") return;
1759
+ const handleKeyDown = (event) => {
1760
+ if (event.key === "Escape") {
1761
+ event.stopPropagation();
1762
+ onEscape();
1763
+ }
1764
+ };
1765
+ document.addEventListener("keydown", handleKeyDown);
1766
+ return () => {
1767
+ document.removeEventListener("keydown", handleKeyDown);
1768
+ };
1769
+ }, [active, onEscape]);
1770
+ }
1771
+
1772
+ // src/react/hooks/useFocusTrap.ts
1773
+ const FOCUSABLE_SELECTOR = [
1774
+ "a[href]",
1775
+ "button:not([disabled])",
1776
+ "input:not([disabled])",
1777
+ "select:not([disabled])",
1778
+ "textarea:not([disabled])",
1779
+ "[tabindex]:not([tabindex=\"-1\"])"
1780
+ ].join(",");
1781
+ /**
1782
+ * Keeps keyboard focus inside the dialog while it is open and restores focus to
1783
+ * the element that opened it when it closes.
1784
+ */
1785
+ function useFocusTrap(ref, active) {
1786
+ (0, react.useEffect)(() => {
1787
+ if (!active || typeof document === "undefined") return;
1788
+ const container = ref.current;
1789
+ if (!container) return;
1790
+ const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1791
+ const focusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((element) => !element.hasAttribute("hidden") && element.getAttribute("aria-hidden") !== "true");
1792
+ (focusable()[0] ?? container).focus({ preventScroll: true });
1793
+ const handleKeyDown = (event) => {
1794
+ if (event.key !== "Tab") return;
1795
+ const items = focusable();
1796
+ if (items.length === 0) {
1797
+ event.preventDefault();
1798
+ container.focus({ preventScroll: true });
1799
+ return;
1800
+ }
1801
+ const first = items[0];
1802
+ const last = items[items.length - 1];
1803
+ const current = document.activeElement;
1804
+ if (event.shiftKey && (current === first || !container.contains(current))) {
1805
+ event.preventDefault();
1806
+ if (last) last.focus({ preventScroll: true });
1807
+ return;
1808
+ }
1809
+ if (!event.shiftKey && (current === last || !container.contains(current)) && first) {
1810
+ event.preventDefault();
1811
+ first.focus({ preventScroll: true });
1812
+ }
1813
+ };
1814
+ document.addEventListener("keydown", handleKeyDown, true);
1815
+ return () => {
1816
+ document.removeEventListener("keydown", handleKeyDown, true);
1817
+ if (previouslyFocused?.isConnected) previouslyFocused.focus({ preventScroll: true });
1818
+ };
1819
+ }, [active, ref]);
1820
+ }
1821
+
1822
+ // src/react/screens/context.tsx
1823
+ const EmergencyScreenContext = (0, react.createContext)(null);
1824
+ function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
1825
+ const [state, setState] = (0, react.useState)({
1826
+ screen: initialScreen,
1827
+ history: []
1828
+ });
1829
+ const navigate = (0, react.useCallback)((next) => {
1830
+ setState((current) => {
1831
+ if (current.screen === next) return current;
1832
+ return {
1833
+ screen: next,
1834
+ history: [...current.history, current.screen]
1835
+ };
1836
+ });
1837
+ }, []);
1838
+ const back = (0, react.useCallback)(() => {
1839
+ setState((current) => {
1840
+ const previous = current.history.at(-1);
1841
+ if (!previous) return current;
1842
+ return {
1843
+ screen: previous,
1844
+ history: current.history.slice(0, -1)
1845
+ };
1846
+ });
1847
+ }, []);
1848
+ const reset = (0, react.useCallback)(() => {
1849
+ setState((current) => {
1850
+ if (current.screen === "primary" && current.history.length === 0) return current;
1851
+ return {
1852
+ screen: "primary",
1853
+ history: []
1854
+ };
1855
+ });
1856
+ }, []);
1857
+ const value = (0, react.useMemo)(() => ({
1858
+ screen: state.screen,
1859
+ history: state.history,
1860
+ navigate,
1861
+ back,
1862
+ reset
1863
+ }), [
1864
+ back,
1865
+ navigate,
1866
+ reset,
1867
+ state.history,
1868
+ state.screen
1869
+ ]);
1870
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenContext.Provider, {
1871
+ value,
1872
+ children
1873
+ });
1874
+ }
1875
+ function useScreenNavigation() {
1876
+ const value = (0, react.useContext)(EmergencyScreenContext);
1877
+ if (!value) throw new Error("useScreenNavigation must be used inside EmergencyScreenProvider.");
1878
+ return value;
1879
+ }
1880
+
1881
+ // src/react/components/EmergencyHeader/index.tsx
1882
+ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack }) {
1883
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1884
+ className: cls("emergency-panel__header"),
1885
+ children: [
1886
+ onBack ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1887
+ type: "button",
1888
+ className: cls("emergency-panel__close-button"),
1889
+ "aria-label": copy.back,
1890
+ onClick: onBack,
1891
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
1892
+ name: "arrow-left",
1893
+ className: cls("emergency-panel__close-icon")
1894
+ })
1895
+ }) : null,
1896
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1897
+ className: cls("emergency-panel__title"),
1898
+ id: titleId,
1899
+ tabIndex: -1,
1900
+ children: title
1901
+ }),
1902
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1903
+ type: "button",
1904
+ className: cls("emergency-panel__close-button"),
1905
+ "aria-label": copy.close,
1906
+ onClick: onClose,
1907
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
1908
+ name: "close",
1909
+ className: cls("emergency-panel__close-icon")
1910
+ })
1911
+ })
1912
+ ]
1913
+ });
1914
+ }
1915
+
1916
+ // src/react/screens/layout.tsx
1917
+ /**
1918
+ * Shared shell for every screen. The wrapper is `display: contents` so the
1919
+ * header, body and footer stay direct grid rows of the emergency panel.
1920
+ */
1921
+ function EmergencyScreenLayout({ screen, titleId, header, footer, children, bodyClassName }) {
1922
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1923
+ className: cls("emergency-screen"),
1924
+ "data-screen": screen,
1925
+ children: [
1926
+ header,
1927
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1928
+ className: cls("emergency-panel__body", bodyClassName ?? null),
1929
+ "aria-labelledby": titleId,
1930
+ children
1931
+ }),
1932
+ footer
1933
+ ]
1934
+ });
1935
+ }
1936
+
1937
+ // src/react/screens/PlaceholderScreen.tsx
1938
+ /** Minimal in-design screen used to prove navigation before real designs land. */
1939
+ function PlaceholderScreen({ screen, titleId, title, description, onClose, onBack }) {
1940
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
1941
+ screen,
1942
+ titleId,
1943
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
1944
+ titleId,
1945
+ title,
1946
+ onClose,
1947
+ onBack
1948
+ }),
1949
+ footer: null,
1950
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1951
+ className: cls("emergency-screen__placeholder"),
1952
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1953
+ className: cls("emergency-screen__placeholder-text"),
1954
+ children: description
1955
+ })
1956
+ })
1957
+ });
1958
+ }
1959
+
1960
+ // src/react/screens/details/copy.ts
1961
+ const detailsCopy = {
1962
+ title: "Detalles",
1963
+ description: "Aquí podrás consultar el detalle de tus alertas disponibles. Esta pantalla estará disponible próximamente."
1964
+ };
1965
+
1966
+ // src/react/screens/details/index.tsx
1967
+ function DetailsScreen({ titleId, onClose }) {
1968
+ const { back } = useScreenNavigation();
1969
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlaceholderScreen, {
1970
+ screen: "details",
1971
+ titleId,
1972
+ title: detailsCopy.title,
1973
+ description: detailsCopy.description,
1974
+ onClose: () => onClose("close"),
1975
+ onBack: back
1976
+ });
1977
+ }
1978
+
1979
+ // src/react/components/icons/paramedical-mark.tsx
1980
+ /**
1981
+ * Neutral inline illustration used by the idle panel.
1982
+ */
1983
+ function ParamedicalMark({ className, title }) {
1984
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1985
+ xmlns: "http://www.w3.org/2000/svg",
1986
+ version: "1.0",
1987
+ viewBox: "0 0 192 192",
1988
+ width: "192",
1989
+ height: "192",
1990
+ id: "Star of life",
1991
+ className,
1992
+ "aria-label": title,
1993
+ children: [
1994
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { id: "defs2886" }),
1995
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
1996
+ id: "cross",
1997
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1998
+ 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",
1999
+ id: "outline",
2000
+ style: {
2001
+ fill: "none",
2002
+ stroke: "#3333bb",
2003
+ strokeWidth: 6
2004
+ }
2005
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2006
+ 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",
2007
+ id: "inner",
2008
+ style: {
2009
+ fill: "#0000bb",
2010
+ fillRule: "evenodd",
2011
+ stroke: "#ffffff",
2012
+ strokeWidth: 2
2013
+ }
2014
+ })]
2015
+ }),
2016
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
2017
+ id: "symbols",
2018
+ style: {
2019
+ fill: "#ffffff",
2020
+ fillRule: "evenodd",
2021
+ stroke: "#3333bb",
2022
+ strokeWidth: 2
2023
+ },
2024
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2025
+ 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",
2026
+ id: "rod"
2027
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
2028
+ id: "snake",
2029
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2030
+ 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",
2031
+ id: "body"
2032
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2033
+ d: "m 95,43 c 7,-3 -7,-3 0,0 z",
2034
+ id: "eye",
2035
+ style: {
2036
+ fill: "#3333bb",
2037
+ fillRule: "evenodd",
2038
+ stroke: "none"
2039
+ }
2040
+ })]
2041
+ })]
2042
+ })
2043
+ ]
2044
+ });
2045
+ }
2046
+
1712
2047
  // src/react/components/MessageSurface/index.tsx
1713
2048
  function MessageSurface(props) {
1714
2049
  const { variant, icon, title, text, details, className, children } = props;
@@ -2033,101 +2368,129 @@ function EmergencyStatusPanel({ responders, refreshing, locale, onRefresh }) {
2033
2368
  });
2034
2369
  }
2035
2370
 
2036
- // src/react/components/EmergencyBody/index.tsx
2037
- function EmergencyBody(props) {
2038
- const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback } = props;
2039
- const showingFeedback = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2040
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2041
- !loading && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2042
- className: cls("sos-img__container"),
2043
- id: "sos-img__container",
2044
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ParamedicalMark, {
2045
- className: cls("sos-img"),
2046
- title: copy.illustrationAlt
2047
- })
2048
- }) : null,
2049
- active || loading || showingFeedback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2050
- className: cls("emergency-panel__body-title-container"),
2051
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
2052
- className: cls("emergency-panel__body-title"),
2053
- children: copy.question
2054
- })
2055
- }),
2056
- active && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyStatusPanel, {
2057
- responders,
2058
- refreshing,
2059
- locale,
2060
- onRefresh
2061
- }) : null,
2062
- loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyLoading, {}) : null,
2063
- error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyError, {
2064
- error,
2065
- onRetry
2066
- }) : null,
2067
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFeedback, {
2068
- abortResult,
2069
- simulationResult,
2070
- geolocationError,
2071
- activationError,
2072
- onDismiss: onDismissFeedback
2073
- })
2074
- ] });
2371
+ // src/react/screens/primary/copy.ts
2372
+ const primaryCopy = {
2373
+ availabilityTitle: "Alertas Disponibles",
2374
+ recharge: "Recargar",
2375
+ details: "Detalles",
2376
+ roundSingular: "ronda disponible",
2377
+ roundPlural: "rondas disponibles"
2378
+ };
2379
+ /** Singular/plural label for the remaining rounds. */
2380
+ function roundsUnit(rounds) {
2381
+ return rounds === 1 ? primaryCopy.roundSingular : primaryCopy.roundPlural;
2382
+ }
2383
+ function formatRounds(rounds) {
2384
+ return `${rounds} ${roundsUnit(rounds)}`;
2075
2385
  }
2076
2386
 
2077
- // src/react/components/EmergencyConfirmation/index.tsx
2078
- function EmergencyConfirmation(props) {
2079
- const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
2080
- const cardRef = (0, react.useRef)(null);
2081
- const titleId = (0, react.useId)();
2082
- const danger = abortConfirmation;
2083
- const open = abortConfirmation || simulationConfirmation;
2084
- (0, react.useEffect)(() => {
2085
- if (open) cardRef.current?.querySelector("button")?.focus();
2086
- }, [open]);
2087
- if (!open) return null;
2088
- const title = danger ? copy.abortConfirmTitle : copy.simulateConfirmTitle;
2089
- const text = danger ? copy.abortConfirmText : copy.simulateConfirmText;
2090
- const tone = danger ? "danger" : "information";
2091
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2092
- className: cls("emergency-confirmation"),
2093
- role: "dialog",
2094
- "aria-modal": "true",
2095
- "aria-labelledby": titleId,
2096
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2097
- ref: cardRef,
2098
- className: cls("emergency-confirmation__card"),
2099
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2100
- className: cls("emergency-confirmation__body"),
2101
- children: [
2102
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2103
- className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
2104
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: danger ? "alert-triangle" : "info" })
2105
- }),
2106
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h5", {
2107
- className: cls("emergency-confirmation__title"),
2108
- id: titleId,
2109
- children: title
2110
- }),
2111
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2112
- className: cls("emergency-confirmation__text"),
2113
- children: text
2114
- })
2115
- ]
2387
+ // src/react/screens/primary/data.ts
2388
+ /**
2389
+ * Derives the remaining alert rounds from available messages and designated
2390
+ * responders. Invalid or non-positive input collapses to the minimum of one
2391
+ * round instead of producing `Infinity`, `NaN` or a negative value.
2392
+ */
2393
+ function calculateRounds(messages, responders) {
2394
+ if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
2395
+ const availableMessages = Math.floor(messages);
2396
+ const designatedResponders = Math.floor(responders);
2397
+ if (availableMessages <= 0 || designatedResponders <= 0) return 1;
2398
+ return Math.max(1, Math.floor(availableMessages / designatedResponders));
2399
+ }
2400
+
2401
+ // src/react/screens/primary/Availability.tsx
2402
+ function Availability({ onRecharge, onDetails }) {
2403
+ const rounds = calculateRounds(100, 3);
2404
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2405
+ className: cls("emergency-availability"),
2406
+ "aria-label": primaryCopy.availabilityTitle,
2407
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2408
+ type: "button",
2409
+ className: cls("emergency-availability__recharge"),
2410
+ onClick: onRecharge,
2411
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "credit-card-plus" }), primaryCopy.recharge]
2412
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2413
+ className: cls("emergency-availability__card"),
2414
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2415
+ className: cls("emergency-availability__card-header"),
2416
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2417
+ className: cls("emergency-availability__card-title"),
2418
+ children: primaryCopy.availabilityTitle
2419
+ })
2116
2420
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2117
- className: cls("emergency-confirmation__footer"),
2118
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2119
- type: "button",
2120
- className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
2121
- onClick: danger ? onCancelAbort : onCancelSimulation,
2122
- children: danger ? copy.abortCancel : copy.simulateCancel
2421
+ className: cls("emergency-availability__card-body"),
2422
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2423
+ className: cls("emergency-availability__rounds-group"),
2424
+ "aria-label": formatRounds(rounds),
2425
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2426
+ className: cls("emergency-availability__rounds"),
2427
+ children: rounds
2428
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2429
+ className: cls("emergency-availability__label"),
2430
+ children: roundsUnit(rounds)
2431
+ })]
2123
2432
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2124
2433
  type: "button",
2125
- className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
2126
- onClick: danger ? onConfirmAbort : onConfirmSimulation,
2127
- children: danger ? copy.abortConfirm : copy.simulateConfirm
2434
+ className: cls("emergency-availability__details"),
2435
+ onClick: onDetails,
2436
+ children: primaryCopy.details
2128
2437
  })]
2129
2438
  })]
2130
- })
2439
+ })]
2440
+ });
2441
+ }
2442
+
2443
+ // src/react/components/EmergencyBody/index.tsx
2444
+ function EmergencyBody(props) {
2445
+ const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails } = props;
2446
+ const showingFeedback = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2447
+ const idle = !active && !loading && !showingFeedback && error === null;
2448
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2449
+ className: cls("emergency-panel__body-split", idle && "emergency-panel__body-split--with-availability"),
2450
+ children: [idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2451
+ className: cls("emergency-panel__availability-region"),
2452
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Availability, {
2453
+ onRecharge,
2454
+ onDetails
2455
+ })
2456
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2457
+ className: cls("emergency-panel__prompt-region"),
2458
+ children: [
2459
+ !loading && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2460
+ className: cls("sos-img__container"),
2461
+ id: "sos-img__container",
2462
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ParamedicalMark, {
2463
+ className: cls("sos-img"),
2464
+ title: copy.illustrationAlt
2465
+ })
2466
+ }) : null,
2467
+ active || loading || showingFeedback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2468
+ className: cls("emergency-panel__body-title-container"),
2469
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
2470
+ className: cls("emergency-panel__body-title"),
2471
+ children: copy.question
2472
+ })
2473
+ }),
2474
+ active && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyStatusPanel, {
2475
+ responders,
2476
+ refreshing,
2477
+ locale,
2478
+ onRefresh
2479
+ }) : null,
2480
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyLoading, {}) : null,
2481
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyError, {
2482
+ error,
2483
+ onRetry
2484
+ }) : null,
2485
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFeedback, {
2486
+ abortResult,
2487
+ simulationResult,
2488
+ geolocationError,
2489
+ activationError,
2490
+ onDismiss: onDismissFeedback
2491
+ })
2492
+ ]
2493
+ })]
2131
2494
  });
2132
2495
  }
2133
2496
 
@@ -2165,105 +2528,279 @@ function EmergencyFooter(props) {
2165
2528
  });
2166
2529
  }
2167
2530
 
2168
- // src/react/components/EmergencyHeader/index.tsx
2169
- function EmergencyHeader({ titleId, onClose }) {
2170
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2171
- className: cls("emergency-panel__header"),
2172
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2173
- className: cls("emergency-panel__title"),
2174
- id: titleId,
2175
- children: copy.panelLabel
2176
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2177
- type: "button",
2178
- className: cls("emergency-panel__close-button"),
2179
- "aria-label": copy.close,
2180
- onClick: onClose,
2181
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
2182
- name: "close",
2183
- className: cls("emergency-panel__close-icon")
2184
- })
2185
- })]
2531
+ // src/react/screens/primary/index.tsx
2532
+ function PrimaryScreen(props) {
2533
+ const { navigate } = useScreenNavigation();
2534
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2535
+ screen: "primary",
2536
+ titleId: props.titleId,
2537
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2538
+ titleId: props.titleId,
2539
+ onClose: () => props.onClose("close")
2540
+ }),
2541
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFooter, {
2542
+ active: props.active,
2543
+ busy: props.busy,
2544
+ aborting: props.aborting,
2545
+ feedbackVisible: props.feedbackVisible,
2546
+ onActivate: props.onActivate,
2547
+ onRequestAbort: props.onRequestAbort,
2548
+ onRequestSimulation: props.onRequestSimulation
2549
+ }),
2550
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyBody, {
2551
+ active: props.active,
2552
+ responders: props.responders,
2553
+ refreshing: props.refreshing,
2554
+ loading: props.loading,
2555
+ locale: props.locale,
2556
+ geolocationError: props.geolocationError,
2557
+ activationError: props.activationError,
2558
+ error: props.error,
2559
+ abortResult: props.abortResult,
2560
+ simulationResult: props.simulationResult,
2561
+ onRefresh: props.onRefresh,
2562
+ onRetry: props.onRetry,
2563
+ onDismissFeedback: props.onDismissFeedback,
2564
+ onRecharge: () => navigate("pricing"),
2565
+ onDetails: () => navigate("details")
2566
+ })
2186
2567
  });
2187
2568
  }
2188
2569
 
2189
- // src/react/hooks/useEscapeKey.ts
2190
- function useEscapeKey(active, onEscape) {
2191
- (0, react.useEffect)(() => {
2192
- if (!active || typeof document === "undefined") return;
2193
- const handleKeyDown = (event) => {
2194
- if (event.key === "Escape") {
2195
- event.stopPropagation();
2196
- onEscape();
2197
- }
2198
- };
2199
- document.addEventListener("keydown", handleKeyDown);
2200
- return () => {
2201
- document.removeEventListener("keydown", handleKeyDown);
2202
- };
2203
- }, [active, onEscape]);
2204
- }
2570
+ // src/react/screens/pricing/copy.ts
2571
+ const pricingCopy = {
2572
+ title: "Recargar",
2573
+ heading: "Elige tu paquete",
2574
+ subtitle: "Alertas de WhatsApp para tus contactos",
2575
+ bestValue: "MEJOR VALOR",
2576
+ choose: "Seleccionar",
2577
+ chosen: "Seleccionado",
2578
+ whatsapp: "de WhatsApp",
2579
+ rounds: "rondas de alerta",
2580
+ perMessage: "por mensaje + IVA",
2581
+ footnote: "Rondas calculadas con 3 contactos de emergencia. Válidas para pruebas y emergencias."
2582
+ };
2205
2583
 
2206
- // src/react/hooks/useFocusTrap.ts
2207
- const FOCUSABLE_SELECTOR = [
2208
- "a[href]",
2209
- "button:not([disabled])",
2210
- "input:not([disabled])",
2211
- "select:not([disabled])",
2212
- "textarea:not([disabled])",
2213
- "[tabindex]:not([tabindex=\"-1\"])"
2214
- ].join(",");
2584
+ // src/react/screens/pricing/data.ts
2215
2585
  /**
2216
- * Keeps keyboard focus inside the dialog while it is open and restores focus to
2217
- * the element that opened it when it closes.
2586
+ * Local demo catalog. Prices and per-message labels are literal values from the
2587
+ * mock; they are not derived from the package totals.
2218
2588
  */
2219
- function useFocusTrap(ref, active) {
2220
- (0, react.useEffect)(() => {
2221
- if (!active || typeof document === "undefined") return;
2222
- const container = ref.current;
2223
- if (!container) return;
2224
- const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2225
- const focusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((element) => !element.hasAttribute("hidden") && element.getAttribute("aria-hidden") !== "true");
2226
- (focusable()[0] ?? container).focus({ preventScroll: true });
2227
- const handleKeyDown = (event) => {
2228
- if (event.key !== "Tab") return;
2229
- const items = focusable();
2230
- if (items.length === 0) {
2231
- event.preventDefault();
2232
- container.focus({ preventScroll: true });
2233
- return;
2234
- }
2235
- const first = items[0];
2236
- const last = items[items.length - 1];
2237
- const current = document.activeElement;
2238
- if (event.shiftKey && (current === first || !container.contains(current))) {
2239
- event.preventDefault();
2240
- if (last) last.focus({ preventScroll: true });
2241
- return;
2242
- }
2243
- if (!event.shiftKey && current === last && first) {
2244
- event.preventDefault();
2245
- first.focus({ preventScroll: true });
2246
- }
2247
- };
2248
- document.addEventListener("keydown", handleKeyDown, true);
2249
- return () => {
2250
- document.removeEventListener("keydown", handleKeyDown, true);
2251
- if (previouslyFocused?.isConnected) previouslyFocused.focus({ preventScroll: true });
2252
- };
2253
- }, [active, ref]);
2589
+ const PRICING_PACKAGES = [{
2590
+ id: "package-1",
2591
+ label: "PAQUETE 1",
2592
+ messages: 100,
2593
+ priceLabel: "$7",
2594
+ priceCaption: "IVA incluido",
2595
+ perMessageLabel: "$0.06",
2596
+ highlighted: false
2597
+ }, {
2598
+ id: "package-2",
2599
+ label: "PAQUETE 2",
2600
+ messages: 200,
2601
+ priceLabel: "12$",
2602
+ priceNote: "($10 + IVA)",
2603
+ priceCaption: "IVA incluido",
2604
+ perMessageLabel: "$0.05",
2605
+ highlighted: true,
2606
+ badge: "MEJOR VALOR"
2607
+ }];
2608
+ /** Derives the alert rounds a package provides for the demo responder count. */
2609
+ function packageRounds(pkg) {
2610
+ return calculateRounds(pkg.messages, 3);
2611
+ }
2612
+
2613
+ // src/react/screens/pricing/PackageCard.tsx
2614
+ function PackageCard({ pkg, selected, onSelect }) {
2615
+ const labelId = (0, react.useId)();
2616
+ const rounds = packageRounds(pkg);
2617
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2618
+ className: cls("emergency-pricing__package", pkg.highlighted && "emergency-pricing__package--highlighted"),
2619
+ "aria-labelledby": labelId,
2620
+ "data-selected": selected ? "true" : void 0,
2621
+ children: [
2622
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2623
+ className: cls("emergency-pricing__package-header"),
2624
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2625
+ className: cls("emergency-pricing__package-label"),
2626
+ id: labelId,
2627
+ children: pkg.label
2628
+ }), pkg.badge ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2629
+ className: cls("emergency-pricing__badge"),
2630
+ children: pkg.badge
2631
+ }) : null]
2632
+ }),
2633
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2634
+ className: cls("emergency-pricing__price-row"),
2635
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h4", {
2636
+ className: cls("emergency-pricing__package-name"),
2637
+ children: [pkg.messages, " alertas"]
2638
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2639
+ className: cls("emergency-pricing__package-caption"),
2640
+ children: pricingCopy.whatsapp
2641
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2642
+ className: cls("emergency-pricing__price-meta"),
2643
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2644
+ className: cls("emergency-pricing__price"),
2645
+ children: pkg.priceLabel
2646
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2647
+ className: cls("emergency-pricing__price-note"),
2648
+ children: [pkg.priceCaption, pkg.priceNote ? ` ${pkg.priceNote}` : null]
2649
+ })]
2650
+ })]
2651
+ }),
2652
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2653
+ className: cls("emergency-pricing__stats"),
2654
+ children: [
2655
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2656
+ className: cls("emergency-pricing__stat"),
2657
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2658
+ className: cls("emergency-pricing__stat-value"),
2659
+ children: rounds
2660
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2661
+ className: cls("emergency-pricing__stat-label"),
2662
+ children: pricingCopy.rounds
2663
+ })]
2664
+ }),
2665
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2666
+ className: cls("emergency-pricing__stat-divider"),
2667
+ "aria-hidden": "true"
2668
+ }),
2669
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2670
+ className: cls("emergency-pricing__stat"),
2671
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2672
+ className: cls("emergency-pricing__stat-value"),
2673
+ children: pkg.perMessageLabel
2674
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2675
+ className: cls("emergency-pricing__stat-label"),
2676
+ children: pricingCopy.perMessage
2677
+ })]
2678
+ })
2679
+ ]
2680
+ }),
2681
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2682
+ type: "button",
2683
+ className: cls("emergency-pricing__select"),
2684
+ "aria-pressed": selected,
2685
+ onClick: () => onSelect(pkg.id),
2686
+ children: selected ? pricingCopy.chosen : pricingCopy.choose
2687
+ })
2688
+ ]
2689
+ });
2690
+ }
2691
+
2692
+ // src/react/screens/pricing/index.tsx
2693
+ function PricingScreen(props) {
2694
+ const { back } = useScreenNavigation();
2695
+ const [selected, setSelected] = (0, react.useState)(null);
2696
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2697
+ screen: "pricing",
2698
+ titleId: props.titleId,
2699
+ bodyClassName: "emergency-panel__body--pricing",
2700
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2701
+ titleId: props.titleId,
2702
+ title: pricingCopy.title,
2703
+ onClose: () => props.onClose("close"),
2704
+ onBack: back
2705
+ }),
2706
+ footer: null,
2707
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2708
+ className: cls("emergency-pricing"),
2709
+ children: [
2710
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2711
+ className: cls("emergency-pricing__heading"),
2712
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2713
+ className: cls("emergency-pricing__titles"),
2714
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2715
+ className: cls("emergency-pricing__title"),
2716
+ children: pricingCopy.heading
2717
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2718
+ className: cls("emergency-pricing__subtitle"),
2719
+ children: pricingCopy.subtitle
2720
+ })]
2721
+ })
2722
+ }),
2723
+ PRICING_PACKAGES.map((pkg) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageCard, {
2724
+ pkg,
2725
+ selected: selected === pkg.id,
2726
+ onSelect: setSelected
2727
+ }, pkg.id)),
2728
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2729
+ className: cls("emergency-pricing__footnote"),
2730
+ children: pricingCopy.footnote
2731
+ })
2732
+ ]
2733
+ })
2734
+ });
2254
2735
  }
2255
2736
 
2737
+ // src/react/screens/registry.ts
2738
+ /** Every screen the panel can render. Add one entry to add a screen. */
2739
+ const screenRegistry = {
2740
+ primary: PrimaryScreen,
2741
+ pricing: PricingScreen,
2742
+ details: DetailsScreen
2743
+ };
2744
+
2256
2745
  // src/react/components/EmergencyPanel/index.tsx
2746
+ function ActiveScreen({ screenProps, open, forcePrimary }) {
2747
+ const { screen, reset } = useScreenNavigation();
2748
+ const shouldReset = !open || forcePrimary;
2749
+ const effectiveScreen = shouldReset ? "primary" : screen;
2750
+ const previousOpen = (0, react.useRef)(open);
2751
+ const previousScreen = (0, react.useRef)(effectiveScreen);
2752
+ (0, react.useEffect)(() => {
2753
+ if (shouldReset) reset();
2754
+ }, [reset, shouldReset]);
2755
+ (0, react.useEffect)(() => {
2756
+ const wasOpen = previousOpen.current;
2757
+ const previousEffectiveScreen = previousScreen.current;
2758
+ previousOpen.current = open;
2759
+ previousScreen.current = effectiveScreen;
2760
+ if (!open || !wasOpen || previousEffectiveScreen === effectiveScreen) return;
2761
+ document.getElementById(screenProps.titleId)?.focus({ preventScroll: true });
2762
+ }, [
2763
+ effectiveScreen,
2764
+ open,
2765
+ screenProps.titleId
2766
+ ]);
2767
+ const Screen = screenRegistry[effectiveScreen];
2768
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Screen, { ...screenProps });
2769
+ }
2257
2770
  function EmergencyPanel(props) {
2258
2771
  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;
2259
2772
  const titleId = (0, react.useId)();
2260
2773
  const dialogRef = (0, react.useRef)(null);
2261
2774
  const feedbackVisible = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2775
+ const forcePrimary = active || loading || error !== null || feedbackVisible;
2262
2776
  const closeOnEscape = (0, react.useCallback)(() => {
2263
2777
  onClose("escape");
2264
2778
  }, [onClose]);
2265
2779
  useFocusTrap(dialogRef, open);
2266
2780
  useEscapeKey(open, closeOnEscape);
2781
+ const screenProps = {
2782
+ titleId,
2783
+ active,
2784
+ busy,
2785
+ aborting,
2786
+ loading,
2787
+ locale,
2788
+ responders,
2789
+ refreshing,
2790
+ geolocationError,
2791
+ activationError,
2792
+ error,
2793
+ abortResult,
2794
+ simulationResult,
2795
+ feedbackVisible,
2796
+ onClose,
2797
+ onActivate,
2798
+ onRefresh,
2799
+ onRetry,
2800
+ onDismissFeedback,
2801
+ onRequestAbort,
2802
+ onRequestSimulation
2803
+ };
2267
2804
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2268
2805
  ref: dialogRef,
2269
2806
  className: cls("emergency-panel", !open && "emergency-panel--hidden"),
@@ -2274,48 +2811,18 @@ function EmergencyPanel(props) {
2274
2811
  "aria-hidden": !open,
2275
2812
  inert: !open,
2276
2813
  tabIndex: -1,
2277
- children: [
2278
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2279
- titleId,
2280
- onClose: () => onClose("close")
2281
- }),
2282
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
2283
- className: cls("emergency-panel__body"),
2284
- "aria-labelledby": titleId,
2285
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyBody, {
2286
- active,
2287
- responders,
2288
- refreshing,
2289
- loading,
2290
- locale,
2291
- geolocationError,
2292
- activationError,
2293
- error,
2294
- abortResult,
2295
- simulationResult,
2296
- onRefresh,
2297
- onRetry,
2298
- onDismissFeedback
2299
- })
2300
- }),
2301
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFooter, {
2302
- active,
2303
- busy,
2304
- aborting,
2305
- feedbackVisible,
2306
- onActivate,
2307
- onRequestAbort,
2308
- onRequestSimulation
2309
- }),
2310
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyConfirmation, {
2311
- abortConfirmation,
2312
- simulationConfirmation,
2313
- onCancelAbort,
2314
- onCancelSimulation,
2315
- onConfirmAbort,
2316
- onConfirmSimulation
2317
- })
2318
- ]
2814
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActiveScreen, {
2815
+ screenProps,
2816
+ open,
2817
+ forcePrimary
2818
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyConfirmation, {
2819
+ abortConfirmation,
2820
+ simulationConfirmation,
2821
+ onCancelAbort,
2822
+ onCancelSimulation,
2823
+ onConfirmAbort,
2824
+ onConfirmSimulation
2825
+ })]
2319
2826
  });
2320
2827
  }
2321
2828