@dataemergencia/emergency 0.5.0 → 0.6.1

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
@@ -1547,6 +1547,12 @@ var panel_module_default = {
1547
1547
  "de-emergency-slide-in": "de-emergency-de-emergency-slide-in-nA5Hwa",
1548
1548
  "de-emergency-slide-up": "de-emergency-de-emergency-slide-up-nA5Hwa",
1549
1549
  "de-emergency-spin": "de-emergency-de-emergency-spin-nA5Hwa",
1550
+ "emergency-alert-row__badge": "de-emergency-emergency-alert-row__badge-nA5Hwa",
1551
+ "emergency-alert-row__button": "de-emergency-emergency-alert-row__button-nA5Hwa",
1552
+ "emergency-alert-row__stat": "de-emergency-emergency-alert-row__stat-nA5Hwa",
1553
+ "emergency-alert-row__stats": "de-emergency-emergency-alert-row__stats-nA5Hwa",
1554
+ "emergency-alert-row__time": "de-emergency-emergency-alert-row__time-nA5Hwa",
1555
+ "emergency-alert-row__top": "de-emergency-emergency-alert-row__top-nA5Hwa",
1550
1556
  "emergency-availability": "de-emergency-emergency-availability-nA5Hwa",
1551
1557
  "emergency-availability__card": "de-emergency-emergency-availability__card-nA5Hwa",
1552
1558
  "emergency-availability__card-body": "de-emergency-emergency-availability__card-body-nA5Hwa",
@@ -1568,8 +1574,22 @@ var panel_module_default = {
1568
1574
  "emergency-confirmation__icon": "de-emergency-emergency-confirmation__icon-nA5Hwa",
1569
1575
  "emergency-confirmation__icon--danger": "de-emergency-emergency-confirmation__icon--danger-nA5Hwa",
1570
1576
  "emergency-confirmation__icon--information": "de-emergency-emergency-confirmation__icon--information-nA5Hwa",
1577
+ "emergency-confirmation__spinner": "de-emergency-emergency-confirmation__spinner-nA5Hwa",
1571
1578
  "emergency-confirmation__text": "de-emergency-emergency-confirmation__text-nA5Hwa",
1572
1579
  "emergency-confirmation__title": "de-emergency-emergency-confirmation__title-nA5Hwa",
1580
+ "emergency-details": "de-emergency-emergency-details-nA5Hwa",
1581
+ "emergency-details__balance": "de-emergency-emergency-details__balance-nA5Hwa",
1582
+ "emergency-details__card": "de-emergency-emergency-details__card-nA5Hwa",
1583
+ "emergency-details__card-body": "de-emergency-emergency-details__card-body-nA5Hwa",
1584
+ "emergency-details__card-header": "de-emergency-emergency-details__card-header-nA5Hwa",
1585
+ "emergency-details__card-title": "de-emergency-emergency-details__card-title-nA5Hwa",
1586
+ "emergency-details__empty": "de-emergency-emergency-details__empty-nA5Hwa",
1587
+ "emergency-details__heading": "de-emergency-emergency-details__heading-nA5Hwa",
1588
+ "emergency-details__history-button": "de-emergency-emergency-details__history-button-nA5Hwa",
1589
+ "emergency-details__list": "de-emergency-emergency-details__list-nA5Hwa",
1590
+ "emergency-details__section": "de-emergency-emergency-details__section-nA5Hwa",
1591
+ "emergency-details__subtitle": "de-emergency-emergency-details__subtitle-nA5Hwa",
1592
+ "emergency-details__title": "de-emergency-emergency-details__title-nA5Hwa",
1573
1593
  "emergency-error-message": "de-emergency-emergency-error-message-nA5Hwa",
1574
1594
  "emergency-error-message__container": "de-emergency-emergency-error-message__container-nA5Hwa",
1575
1595
  "emergency-error-message__content": "de-emergency-emergency-error-message__content-nA5Hwa",
@@ -1580,11 +1600,33 @@ var panel_module_default = {
1580
1600
  "emergency-error-message__title": "de-emergency-emergency-error-message__title-nA5Hwa",
1581
1601
  "emergency-feedback": "de-emergency-emergency-feedback-nA5Hwa",
1582
1602
  "emergency-feedback__dismiss": "de-emergency-emergency-feedback__dismiss-nA5Hwa",
1603
+ "emergency-history": "de-emergency-emergency-history-nA5Hwa",
1604
+ "emergency-history__empty": "de-emergency-emergency-history__empty-nA5Hwa",
1605
+ "emergency-history__heading": "de-emergency-emergency-history__heading-nA5Hwa",
1606
+ "emergency-history__list": "de-emergency-emergency-history__list-nA5Hwa",
1607
+ "emergency-history__more": "de-emergency-emergency-history__more-nA5Hwa",
1608
+ "emergency-history__subtitle": "de-emergency-emergency-history__subtitle-nA5Hwa",
1609
+ "emergency-history__title": "de-emergency-emergency-history__title-nA5Hwa",
1610
+ "emergency-message-detail": "de-emergency-emergency-message-detail-nA5Hwa",
1611
+ "emergency-message-detail__back": "de-emergency-emergency-message-detail__back-nA5Hwa",
1612
+ "emergency-message-detail__card": "de-emergency-emergency-message-detail__card-nA5Hwa",
1613
+ "emergency-message-detail__card-header": "de-emergency-emergency-message-detail__card-header-nA5Hwa",
1614
+ "emergency-message-detail__card-title": "de-emergency-emergency-message-detail__card-title-nA5Hwa",
1615
+ "emergency-message-detail__empty": "de-emergency-emergency-message-detail__empty-nA5Hwa",
1616
+ "emergency-message-detail__empty-text": "de-emergency-emergency-message-detail__empty-text-nA5Hwa",
1617
+ "emergency-message-detail__label": "de-emergency-emergency-message-detail__label-nA5Hwa",
1618
+ "emergency-message-detail__list": "de-emergency-emergency-message-detail__list-nA5Hwa",
1619
+ "emergency-message-detail__row": "de-emergency-emergency-message-detail__row-nA5Hwa",
1620
+ "emergency-message-detail__value": "de-emergency-emergency-message-detail__value-nA5Hwa",
1583
1621
  "emergency-panel": "de-emergency-emergency-panel-nA5Hwa",
1584
1622
  "emergency-panel__availability-region": "de-emergency-emergency-panel__availability-region-nA5Hwa",
1585
1623
  "emergency-panel__body": "de-emergency-emergency-panel__body-nA5Hwa",
1624
+ "emergency-panel__body--details": "de-emergency-emergency-panel__body--details-nA5Hwa",
1625
+ "emergency-panel__body--history": "de-emergency-emergency-panel__body--history-nA5Hwa",
1626
+ "emergency-panel__body--message-detail": "de-emergency-emergency-panel__body--message-detail-nA5Hwa",
1586
1627
  "emergency-panel__body--pre-emergency": "de-emergency-emergency-panel__body--pre-emergency-nA5Hwa",
1587
1628
  "emergency-panel__body--pricing": "de-emergency-emergency-panel__body--pricing-nA5Hwa",
1629
+ "emergency-panel__body--primary": "de-emergency-emergency-panel__body--primary-nA5Hwa",
1588
1630
  "emergency-panel__body-split": "de-emergency-emergency-panel__body-split-nA5Hwa",
1589
1631
  "emergency-panel__body-split--with-availability": "de-emergency-emergency-panel__body-split--with-availability-nA5Hwa",
1590
1632
  "emergency-panel__body-title": "de-emergency-emergency-panel__body-title-nA5Hwa",
@@ -1593,13 +1635,14 @@ var panel_module_default = {
1593
1635
  "emergency-panel__button--emergency": "de-emergency-emergency-panel__button--emergency-nA5Hwa",
1594
1636
  "emergency-panel__button--simulation": "de-emergency-emergency-panel__button--simulation-nA5Hwa",
1595
1637
  "emergency-panel__close-button": "de-emergency-emergency-panel__close-button-nA5Hwa",
1638
+ "emergency-panel__close-button--prompt": "de-emergency-emergency-panel__close-button--prompt-nA5Hwa",
1596
1639
  "emergency-panel__close-icon": "de-emergency-emergency-panel__close-icon-nA5Hwa",
1597
- "emergency-panel__divider": "de-emergency-emergency-panel__divider-nA5Hwa",
1598
1640
  "emergency-panel__footer": "de-emergency-emergency-panel__footer-nA5Hwa",
1599
1641
  "emergency-panel__footer--single": "de-emergency-emergency-panel__footer--single-nA5Hwa",
1642
+ "emergency-panel__footer--square": "de-emergency-emergency-panel__footer--square-nA5Hwa",
1600
1643
  "emergency-panel__header": "de-emergency-emergency-panel__header-nA5Hwa",
1601
- "emergency-panel__header--close-only": "de-emergency-emergency-panel__header--close-only-nA5Hwa",
1602
1644
  "emergency-panel__prompt-region": "de-emergency-emergency-panel__prompt-region-nA5Hwa",
1645
+ "emergency-panel__prompt-region--idle": "de-emergency-emergency-panel__prompt-region--idle-nA5Hwa",
1603
1646
  "emergency-panel__sr-only": "de-emergency-emergency-panel__sr-only-nA5Hwa",
1604
1647
  "emergency-panel__title": "de-emergency-emergency-panel__title-nA5Hwa",
1605
1648
  "emergency-panel--hidden": "de-emergency-emergency-panel--hidden-nA5Hwa",
@@ -1695,11 +1738,12 @@ function cls(...names) {
1695
1738
 
1696
1739
  // src/react/components/EmergencyConfirmation/index.tsx
1697
1740
  function EmergencyConfirmation(props) {
1698
- const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
1741
+ const { abortConfirmation, simulationConfirmation, loading, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
1699
1742
  const cardRef = useRef(null);
1700
1743
  const titleId = useId();
1701
1744
  const danger = abortConfirmation;
1702
1745
  const open = abortConfirmation || simulationConfirmation;
1746
+ const pending = open && loading;
1703
1747
  useEffect(() => {
1704
1748
  if (open) cardRef.current?.querySelector("button")?.focus();
1705
1749
  }, [open]);
@@ -1712,40 +1756,56 @@ function EmergencyConfirmation(props) {
1712
1756
  role: "dialog",
1713
1757
  "aria-modal": "true",
1714
1758
  "aria-labelledby": titleId,
1759
+ "aria-busy": pending,
1715
1760
  children: /* @__PURE__ */ jsxs("div", {
1716
1761
  ref: cardRef,
1717
1762
  className: cls("emergency-confirmation__card"),
1718
- children: [/* @__PURE__ */ jsxs("div", {
1719
- className: cls("emergency-confirmation__body"),
1720
- children: [
1721
- /* @__PURE__ */ jsx("div", {
1722
- className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
1723
- children: /* @__PURE__ */ jsx(Icon, { name: danger ? "alert-triangle" : "info" })
1724
- }),
1725
- /* @__PURE__ */ jsx("h5", {
1726
- className: cls("emergency-confirmation__title"),
1727
- id: titleId,
1728
- children: title
1729
- }),
1730
- /* @__PURE__ */ jsx("p", {
1731
- className: cls("emergency-confirmation__text"),
1732
- children: text
1733
- })
1734
- ]
1735
- }), /* @__PURE__ */ jsxs("div", {
1736
- className: cls("emergency-confirmation__footer"),
1737
- children: [/* @__PURE__ */ jsx("button", {
1738
- type: "button",
1739
- className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
1740
- onClick: danger ? onCancelAbort : onCancelSimulation,
1741
- children: danger ? copy.abortCancel : copy.simulateCancel
1742
- }), /* @__PURE__ */ jsx("button", {
1743
- type: "button",
1744
- className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
1745
- onClick: danger ? onConfirmAbort : onConfirmSimulation,
1746
- children: danger ? copy.abortConfirm : copy.simulateConfirm
1747
- })]
1748
- })]
1763
+ children: [
1764
+ /* @__PURE__ */ jsx("span", {
1765
+ className: cls("emergency-panel__sr-only"),
1766
+ role: "status",
1767
+ "aria-live": "polite",
1768
+ children: pending ? copy.loading : ""
1769
+ }),
1770
+ /* @__PURE__ */ jsxs("div", {
1771
+ className: cls("emergency-confirmation__body"),
1772
+ children: [
1773
+ /* @__PURE__ */ jsx("div", {
1774
+ className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
1775
+ children: /* @__PURE__ */ jsx(Icon, { name: danger ? "alert-triangle" : "info" })
1776
+ }),
1777
+ /* @__PURE__ */ jsx("h5", {
1778
+ className: cls("emergency-confirmation__title"),
1779
+ id: titleId,
1780
+ children: title
1781
+ }),
1782
+ /* @__PURE__ */ jsx("p", {
1783
+ className: cls("emergency-confirmation__text"),
1784
+ children: text
1785
+ })
1786
+ ]
1787
+ }),
1788
+ /* @__PURE__ */ jsxs("div", {
1789
+ className: cls("emergency-confirmation__footer"),
1790
+ children: [/* @__PURE__ */ jsx("button", {
1791
+ type: "button",
1792
+ className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
1793
+ disabled: pending,
1794
+ onClick: danger ? onCancelAbort : onCancelSimulation,
1795
+ children: danger ? copy.abortCancel : copy.simulateCancel
1796
+ }), /* @__PURE__ */ jsxs("button", {
1797
+ type: "button",
1798
+ className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
1799
+ "aria-busy": pending,
1800
+ disabled: pending,
1801
+ onClick: danger ? onConfirmAbort : onConfirmSimulation,
1802
+ children: [pending ? /* @__PURE__ */ jsx("span", {
1803
+ className: cls("emergency-confirmation__spinner"),
1804
+ "aria-hidden": "true"
1805
+ }) : null, /* @__PURE__ */ jsx("span", { children: danger ? copy.abortConfirm : copy.simulateConfirm })]
1806
+ })]
1807
+ })
1808
+ ]
1749
1809
  })
1750
1810
  });
1751
1811
  }
@@ -1818,18 +1878,27 @@ function useFocusTrap(ref, active) {
1818
1878
  }
1819
1879
 
1820
1880
  // src/react/screens/context.tsx
1881
+ const EMPTY_PARAMS = {};
1821
1882
  const EmergencyScreenContext = createContext(null);
1883
+ function paramsEqual(first, second) {
1884
+ return first.messageId === second.messageId;
1885
+ }
1822
1886
  function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
1823
1887
  const [state, setState] = useState({
1824
1888
  screen: initialScreen,
1889
+ params: EMPTY_PARAMS,
1825
1890
  history: []
1826
1891
  });
1827
- const navigate = useCallback((next) => {
1892
+ const navigate = useCallback((next, params = EMPTY_PARAMS) => {
1828
1893
  setState((current) => {
1829
- if (current.screen === next) return current;
1894
+ if (current.screen === next && paramsEqual(current.params, params)) return current;
1830
1895
  return {
1831
1896
  screen: next,
1832
- history: [...current.history, current.screen]
1897
+ params,
1898
+ history: [...current.history, {
1899
+ screen: current.screen,
1900
+ params: current.params
1901
+ }]
1833
1902
  };
1834
1903
  });
1835
1904
  }, []);
@@ -1838,31 +1907,47 @@ function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
1838
1907
  const previous = current.history.at(-1);
1839
1908
  if (!previous) return current;
1840
1909
  return {
1841
- screen: previous,
1910
+ screen: previous.screen,
1911
+ params: previous.params,
1842
1912
  history: current.history.slice(0, -1)
1843
1913
  };
1844
1914
  });
1845
1915
  }, []);
1916
+ const replace = useCallback((next, params = EMPTY_PARAMS) => {
1917
+ setState((current) => {
1918
+ if (current.screen === next && paramsEqual(current.params, params)) return current;
1919
+ return {
1920
+ screen: next,
1921
+ params,
1922
+ history: current.history.at(-1)?.screen === next ? current.history.slice(0, -1) : current.history
1923
+ };
1924
+ });
1925
+ }, []);
1846
1926
  const reset = useCallback(() => {
1847
1927
  setState((current) => {
1848
- if (current.screen === "primary" && current.history.length === 0) return current;
1928
+ if (current.screen === "primary" && current.params.messageId === void 0 && current.history.length === 0) return current;
1849
1929
  return {
1850
1930
  screen: "primary",
1931
+ params: EMPTY_PARAMS,
1851
1932
  history: []
1852
1933
  };
1853
1934
  });
1854
1935
  }, []);
1855
1936
  const value = useMemo(() => ({
1856
1937
  screen: state.screen,
1938
+ params: state.params,
1857
1939
  history: state.history,
1858
1940
  navigate,
1941
+ replace,
1859
1942
  back,
1860
1943
  reset
1861
1944
  }), [
1862
1945
  back,
1863
1946
  navigate,
1947
+ replace,
1864
1948
  reset,
1865
1949
  state.history,
1950
+ state.params,
1866
1951
  state.screen
1867
1952
  ]);
1868
1953
  return /* @__PURE__ */ jsx(EmergencyScreenContext.Provider, {
@@ -1877,12 +1962,11 @@ function useScreenNavigation() {
1877
1962
  }
1878
1963
 
1879
1964
  // src/react/components/EmergencyHeader/index.tsx
1880
- function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack, variant = "default" }) {
1881
- const closeOnly = variant === "close-only";
1965
+ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack }) {
1882
1966
  return /* @__PURE__ */ jsxs("header", {
1883
- className: cls("emergency-panel__header", closeOnly && "emergency-panel__header--close-only"),
1967
+ className: cls("emergency-panel__header"),
1884
1968
  children: [
1885
- onBack && !closeOnly ? /* @__PURE__ */ jsx("button", {
1969
+ onBack ? /* @__PURE__ */ jsx("button", {
1886
1970
  type: "button",
1887
1971
  className: cls("emergency-panel__close-button"),
1888
1972
  "aria-label": copy.back,
@@ -1893,7 +1977,7 @@ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack, va
1893
1977
  })
1894
1978
  }) : null,
1895
1979
  /* @__PURE__ */ jsx("h2", {
1896
- className: cls("emergency-panel__title", closeOnly && "emergency-panel__sr-only"),
1980
+ className: cls("emergency-panel__title"),
1897
1981
  id: titleId,
1898
1982
  tabIndex: -1,
1899
1983
  children: title
@@ -1912,6 +1996,245 @@ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack, va
1912
1996
  });
1913
1997
  }
1914
1998
 
1999
+ // src/react/screens/primary/data.ts
2000
+ /**
2001
+ * Derives the remaining alert rounds from available messages and designated
2002
+ * responders. Invalid or non-positive input collapses to the minimum of one
2003
+ * round instead of producing `Infinity`, `NaN` or a negative value.
2004
+ */
2005
+ function calculateRounds(messages, responders) {
2006
+ if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
2007
+ const availableMessages = Math.floor(messages);
2008
+ const designatedResponders = Math.floor(responders);
2009
+ if (availableMessages <= 0 || designatedResponders <= 0) return 1;
2010
+ return Math.max(1, Math.floor(availableMessages / designatedResponders));
2011
+ }
2012
+
2013
+ // src/react/screens/details/copy.ts
2014
+ const detailsCopy = {
2015
+ title: "Detalles",
2016
+ summaryTitle: "Alertas Disponibles",
2017
+ historyTitle: "Historial de alertas",
2018
+ historySubtitle: "Tus últimas alertas y recargas.",
2019
+ historyEmpty: "Todavía no tienes alertas en tu historial.",
2020
+ summaryEmpty: "Todavía no tienes un saldo de alertas disponible.",
2021
+ viewHistory: "Ver historial",
2022
+ messagesSingular: "mensaje de alerta",
2023
+ messagesPlural: "mensajes de alerta",
2024
+ contactsSingular: "contacto de emergencia",
2025
+ contactsPlural: "contactos de emergencia",
2026
+ roundsSingular: "ronda de avisos",
2027
+ roundsPlural: "rondas de avisos"
2028
+ };
2029
+ /** Returns the balance sentence split into text and emphasized number parts. */
2030
+ function alertBalanceParts(messages, responders) {
2031
+ if (!Number.isFinite(messages) || !Number.isFinite(responders) || messages < 0 || responders < 0) return [];
2032
+ const rounds = calculateRounds(messages, responders);
2033
+ const messagesUnit = messages === 1 ? detailsCopy.messagesSingular : detailsCopy.messagesPlural;
2034
+ const contactsUnit = responders === 1 ? detailsCopy.contactsSingular : detailsCopy.contactsPlural;
2035
+ const roundsUnit = rounds === 1 ? detailsCopy.roundsSingular : detailsCopy.roundsPlural;
2036
+ return [
2037
+ messages === 1 ? "Te queda " : "Te quedan ",
2038
+ messages,
2039
+ ` ${messagesUnit}, dado que tienes `,
2040
+ responders,
2041
+ ` ${contactsUnit}, `,
2042
+ rounds === 1 ? "te queda " : "te quedan ",
2043
+ rounds,
2044
+ ` ${roundsUnit}.`
2045
+ ];
2046
+ }
2047
+
2048
+ // src/react/screens/details/AlertBalanceSentence.tsx
2049
+ /** Renders the balance sentence with its numbers visually emphasized. */
2050
+ function AlertBalanceSentence({ messages, responders }) {
2051
+ const parts = alertBalanceParts(messages, responders);
2052
+ return /* @__PURE__ */ jsx(Fragment, { children: parts.map((part, index) => typeof part === "number" ? /* @__PURE__ */ jsx("strong", { children: part }, index) : part) });
2053
+ }
2054
+
2055
+ // src/core/date.ts
2056
+ /**
2057
+ * Formats a timestamp as `h:mmam` or `h:mmpm` in local time, without a space
2058
+ * or leading zero.
2059
+ */
2060
+ function formatShortTime(value, locale = "es") {
2061
+ if (value === null) return "";
2062
+ const date = value instanceof Date ? value : new Date(value);
2063
+ if (Number.isNaN(date.getTime())) return "";
2064
+ const parts = new Intl.DateTimeFormat(locale, {
2065
+ hour: "numeric",
2066
+ minute: "2-digit",
2067
+ hour12: true
2068
+ }).formatToParts(date);
2069
+ const hour = parts.find((part) => part.type === "hour")?.value;
2070
+ const minute = parts.find((part) => part.type === "minute")?.value;
2071
+ const dayPeriod = parts.find((part) => part.type === "dayPeriod")?.value;
2072
+ if (!hour || !minute) return "";
2073
+ return `${hour}:${minute}${normalizePeriod(dayPeriod)}`;
2074
+ }
2075
+ /**
2076
+ * Formats a timestamp as `dd/mm/yyyy hh:mm` in local time, using 24-hour time
2077
+ * and zero-padded parts. Missing or invalid input collapses to an empty string.
2078
+ */
2079
+ function formatDateTime(value, locale = "es") {
2080
+ if (value === null) return "";
2081
+ const date = value instanceof Date ? value : new Date(value);
2082
+ if (Number.isNaN(date.getTime())) return "";
2083
+ const parts = new Intl.DateTimeFormat(locale, {
2084
+ day: "2-digit",
2085
+ month: "2-digit",
2086
+ year: "numeric",
2087
+ hour: "2-digit",
2088
+ minute: "2-digit",
2089
+ hourCycle: "h23"
2090
+ }).formatToParts(date);
2091
+ const day = parts.find((part) => part.type === "day")?.value;
2092
+ const month = parts.find((part) => part.type === "month")?.value;
2093
+ const year = parts.find((part) => part.type === "year")?.value;
2094
+ const hour = parts.find((part) => part.type === "hour")?.value;
2095
+ const minute = parts.find((part) => part.type === "minute")?.value;
2096
+ if (!day || !month || !year || !hour || !minute) return "";
2097
+ return `${day}/${month}/${year} ${hour}:${minute}`;
2098
+ }
2099
+ function normalizePeriod(dayPeriod) {
2100
+ if (!dayPeriod) return "";
2101
+ if (/^p/i.test(dayPeriod)) return "pm";
2102
+ return /^a/i.test(dayPeriod) ? "am" : "";
2103
+ }
2104
+
2105
+ // src/react/screens/history/copy.ts
2106
+ const historyCopy = {
2107
+ title: "Historial de alertas",
2108
+ subtitle: "Consulta tus alertas, pruebas y recargas.",
2109
+ empty: "Todavía no tienes alertas en tu historial.",
2110
+ showMore: "Ver más",
2111
+ kindLabels: {
2112
+ emergency: "Emergencia",
2113
+ test: "Prueba",
2114
+ recharge: "Recarga"
2115
+ }
2116
+ };
2117
+ function formatContacts(count) {
2118
+ return count === 1 ? "1 contacto" : `${count} contactos`;
2119
+ }
2120
+ function formatMessages(count) {
2121
+ return count === 1 ? "1 mensaje" : `${count} mensajes`;
2122
+ }
2123
+ function formatHistoryRounds(count) {
2124
+ return count === 1 ? "1 ronda" : `${count} rondas`;
2125
+ }
2126
+
2127
+ // src/react/screens/history/data.ts
2128
+ /** Local demo alert history, newest first. */
2129
+ const DEMO_ALERT_HISTORY = [
2130
+ {
2131
+ id: "alert-2026-10-31",
2132
+ kind: "emergency",
2133
+ occurredAt: "2026-10-31T13:50:00",
2134
+ responders: 3,
2135
+ remainingMessages: 27
2136
+ },
2137
+ {
2138
+ id: "alert-2026-10-20",
2139
+ kind: "recharge",
2140
+ occurredAt: "2026-10-20T09:15:00",
2141
+ responders: 3,
2142
+ remainingMessages: 100
2143
+ },
2144
+ {
2145
+ id: "alert-2026-10-12",
2146
+ kind: "test",
2147
+ occurredAt: "2026-10-12T18:40:00",
2148
+ responders: 3,
2149
+ remainingMessages: 73
2150
+ },
2151
+ {
2152
+ id: "alert-2026-10-01",
2153
+ kind: "recharge",
2154
+ occurredAt: "2026-10-01T11:05:00",
2155
+ responders: 3,
2156
+ remainingMessages: 76
2157
+ }
2158
+ ];
2159
+ /** Returns a newest-first copy of the history. */
2160
+ function sortedHistoryEntries(entries = DEMO_ALERT_HISTORY) {
2161
+ return [...entries].sort((first, second) => new Date(second.occurredAt).getTime() - new Date(first.occurredAt).getTime());
2162
+ }
2163
+ /** Newest entry, used as the source of the current balance. */
2164
+ function latestHistoryEntry(entries = DEMO_ALERT_HISTORY) {
2165
+ return sortedHistoryEntries(entries)[0] ?? null;
2166
+ }
2167
+ /** Resolves a single history entry by id. */
2168
+ function historyEntryById(id, entries = DEMO_ALERT_HISTORY) {
2169
+ if (!id) return null;
2170
+ return entries.find((entry) => entry.id === id) ?? null;
2171
+ }
2172
+ /** Rounds represented by the entry's remaining messages and responders. */
2173
+ function historyEntryRounds(entry) {
2174
+ return calculateRounds(entry.remainingMessages, entry.responders);
2175
+ }
2176
+ /** Current balance derived from the newest history entry. */
2177
+ function currentAlertBalance(entries = DEMO_ALERT_HISTORY) {
2178
+ const latest = latestHistoryEntry(entries);
2179
+ if (!latest) return null;
2180
+ return {
2181
+ messages: latest.remainingMessages,
2182
+ responders: latest.responders,
2183
+ rounds: historyEntryRounds(latest)
2184
+ };
2185
+ }
2186
+
2187
+ // src/react/screens/history/AlertHistoryRow.tsx
2188
+ /** Reusable history row. The whole row is a button with a full text label. */
2189
+ function AlertHistoryRow({ entry, locale, onOpen }) {
2190
+ const occurredAt = formatDateTime(entry.occurredAt, locale);
2191
+ const rounds = historyEntryRounds(entry);
2192
+ const label = [
2193
+ historyCopy.kindLabels[entry.kind],
2194
+ occurredAt,
2195
+ formatContacts(entry.responders),
2196
+ `${formatMessages(entry.remainingMessages)} restantes`,
2197
+ `${formatHistoryRounds(rounds)} de avisos`
2198
+ ].join(", ");
2199
+ return /* @__PURE__ */ jsx("li", {
2200
+ className: cls("emergency-alert-row"),
2201
+ children: /* @__PURE__ */ jsxs("button", {
2202
+ type: "button",
2203
+ className: cls("emergency-alert-row__button"),
2204
+ "aria-label": label,
2205
+ onClick: () => onOpen(entry.id),
2206
+ children: [/* @__PURE__ */ jsxs("span", {
2207
+ className: cls("emergency-alert-row__top"),
2208
+ children: [/* @__PURE__ */ jsx("time", {
2209
+ className: cls("emergency-alert-row__time"),
2210
+ dateTime: entry.occurredAt,
2211
+ children: occurredAt
2212
+ }), /* @__PURE__ */ jsx("span", {
2213
+ className: cls("emergency-alert-row__badge"),
2214
+ "data-kind": entry.kind,
2215
+ children: historyCopy.kindLabels[entry.kind]
2216
+ })]
2217
+ }), /* @__PURE__ */ jsxs("span", {
2218
+ className: cls("emergency-alert-row__stats"),
2219
+ children: [
2220
+ /* @__PURE__ */ jsx("span", {
2221
+ className: cls("emergency-alert-row__stat"),
2222
+ children: formatContacts(entry.responders)
2223
+ }),
2224
+ /* @__PURE__ */ jsx("span", {
2225
+ className: cls("emergency-alert-row__stat"),
2226
+ children: formatMessages(entry.remainingMessages)
2227
+ }),
2228
+ /* @__PURE__ */ jsx("span", {
2229
+ className: cls("emergency-alert-row__stat"),
2230
+ children: formatHistoryRounds(rounds)
2231
+ })
2232
+ ]
2233
+ })]
2234
+ })
2235
+ });
2236
+ }
2237
+
1915
2238
  // src/react/screens/layout.tsx
1916
2239
  /**
1917
2240
  * Shared shell for every screen. The wrapper is `display: contents` so the
@@ -1933,46 +2256,254 @@ function EmergencyScreenLayout({ screen, titleId, header, footer, children, body
1933
2256
  });
1934
2257
  }
1935
2258
 
1936
- // src/react/screens/PlaceholderScreen.tsx
1937
- /** Minimal in-design screen used to prove navigation before real designs land. */
1938
- function PlaceholderScreen({ screen, titleId, title, description, onClose, onBack }) {
2259
+ // src/react/screens/details/index.tsx
2260
+ function DetailsScreen({ titleId, locale, onClose }) {
2261
+ const { back, navigate } = useScreenNavigation();
2262
+ const balance = currentAlertBalance();
2263
+ const preview = sortedHistoryEntries().slice(0, 2);
1939
2264
  return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
1940
- screen,
2265
+ screen: "details",
1941
2266
  titleId,
2267
+ bodyClassName: "emergency-panel__body--details",
1942
2268
  header: /* @__PURE__ */ jsx(EmergencyHeader, {
1943
2269
  titleId,
1944
- title,
1945
- onClose,
1946
- onBack
2270
+ title: detailsCopy.title,
2271
+ onClose: () => onClose("close"),
2272
+ onBack: back
1947
2273
  }),
1948
2274
  footer: null,
1949
- children: /* @__PURE__ */ jsx("div", {
1950
- className: cls("emergency-screen__placeholder"),
1951
- children: /* @__PURE__ */ jsx("p", {
1952
- className: cls("emergency-screen__placeholder-text"),
1953
- children: description
1954
- })
2275
+ children: /* @__PURE__ */ jsxs("div", {
2276
+ className: cls("emergency-details"),
2277
+ children: [/* @__PURE__ */ jsx("section", {
2278
+ className: cls("emergency-details__section"),
2279
+ "aria-label": detailsCopy.summaryTitle,
2280
+ children: /* @__PURE__ */ jsxs("div", {
2281
+ className: cls("emergency-details__card"),
2282
+ children: [/* @__PURE__ */ jsx("div", {
2283
+ className: cls("emergency-details__card-header"),
2284
+ children: /* @__PURE__ */ jsx("h3", {
2285
+ className: cls("emergency-details__card-title"),
2286
+ children: detailsCopy.summaryTitle
2287
+ })
2288
+ }), /* @__PURE__ */ jsx("div", {
2289
+ className: cls("emergency-details__card-body"),
2290
+ children: balance ? /* @__PURE__ */ jsx("p", {
2291
+ className: cls("emergency-details__balance"),
2292
+ children: /* @__PURE__ */ jsx(AlertBalanceSentence, {
2293
+ messages: balance.messages,
2294
+ responders: balance.responders
2295
+ })
2296
+ }) : /* @__PURE__ */ jsx("p", {
2297
+ className: cls("emergency-details__empty"),
2298
+ children: detailsCopy.summaryEmpty
2299
+ })
2300
+ })]
2301
+ })
2302
+ }), /* @__PURE__ */ jsxs("section", {
2303
+ className: cls("emergency-details__section"),
2304
+ "aria-label": detailsCopy.historyTitle,
2305
+ children: [
2306
+ /* @__PURE__ */ jsxs("div", {
2307
+ className: cls("emergency-details__heading"),
2308
+ children: [/* @__PURE__ */ jsx("h3", {
2309
+ className: cls("emergency-details__title"),
2310
+ children: detailsCopy.historyTitle
2311
+ }), /* @__PURE__ */ jsx("p", {
2312
+ className: cls("emergency-details__subtitle"),
2313
+ children: detailsCopy.historySubtitle
2314
+ })]
2315
+ }),
2316
+ preview.length > 0 ? /* @__PURE__ */ jsx("ul", {
2317
+ className: cls("emergency-details__list"),
2318
+ children: preview.map((entry) => /* @__PURE__ */ jsx(AlertHistoryRow, {
2319
+ entry,
2320
+ locale,
2321
+ onOpen: (messageId) => navigate("message-detail", { messageId })
2322
+ }, entry.id))
2323
+ }) : /* @__PURE__ */ jsx("p", {
2324
+ className: cls("emergency-details__empty"),
2325
+ children: detailsCopy.historyEmpty
2326
+ }),
2327
+ /* @__PURE__ */ jsx("button", {
2328
+ type: "button",
2329
+ className: cls("emergency-details__history-button"),
2330
+ onClick: () => navigate("history"),
2331
+ children: detailsCopy.viewHistory
2332
+ })
2333
+ ]
2334
+ })]
1955
2335
  })
1956
2336
  });
1957
2337
  }
1958
2338
 
1959
- // src/react/screens/details/copy.ts
1960
- const detailsCopy = {
1961
- title: "Detalles",
1962
- description: "Aquí podrás consultar el detalle de tus alertas disponibles. Esta pantalla estará disponible próximamente."
1963
- };
2339
+ // src/react/screens/history/index.tsx
2340
+ function HistoryScreen({ titleId, locale, onClose }) {
2341
+ const { back, navigate } = useScreenNavigation();
2342
+ const listId = useId();
2343
+ const [expanded, setExpanded] = useState(false);
2344
+ const entries = sortedHistoryEntries();
2345
+ const hiddenCount = Math.max(0, entries.length - 2);
2346
+ const visibleEntries = expanded ? entries : entries.slice(0, 2);
2347
+ const showMore = hiddenCount > 0 && !expanded;
2348
+ return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
2349
+ screen: "history",
2350
+ titleId,
2351
+ bodyClassName: "emergency-panel__body--history",
2352
+ header: /* @__PURE__ */ jsx(EmergencyHeader, {
2353
+ titleId,
2354
+ title: historyCopy.title,
2355
+ onClose: () => onClose("close"),
2356
+ onBack: back
2357
+ }),
2358
+ footer: null,
2359
+ children: /* @__PURE__ */ jsxs("div", {
2360
+ className: cls("emergency-history"),
2361
+ children: [/* @__PURE__ */ jsxs("div", {
2362
+ className: cls("emergency-history__heading"),
2363
+ children: [/* @__PURE__ */ jsx("h3", {
2364
+ className: cls("emergency-history__title"),
2365
+ children: historyCopy.title
2366
+ }), /* @__PURE__ */ jsx("p", {
2367
+ className: cls("emergency-history__subtitle"),
2368
+ children: historyCopy.subtitle
2369
+ })]
2370
+ }), entries.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("ul", {
2371
+ className: cls("emergency-history__list"),
2372
+ id: listId,
2373
+ children: visibleEntries.map((entry) => /* @__PURE__ */ jsx(AlertHistoryRow, {
2374
+ entry,
2375
+ locale,
2376
+ onOpen: (messageId) => navigate("message-detail", { messageId })
2377
+ }, entry.id))
2378
+ }), showMore ? /* @__PURE__ */ jsx("button", {
2379
+ type: "button",
2380
+ className: cls("emergency-history__more"),
2381
+ "aria-expanded": expanded,
2382
+ "aria-controls": listId,
2383
+ onClick: () => setExpanded(true),
2384
+ children: historyCopy.showMore
2385
+ }) : null] }) : /* @__PURE__ */ jsx("p", {
2386
+ className: cls("emergency-history__empty"),
2387
+ children: historyCopy.empty
2388
+ })]
2389
+ })
2390
+ });
2391
+ }
1964
2392
 
1965
- // src/react/screens/details/index.tsx
1966
- function DetailsScreen({ titleId, onClose }) {
1967
- const { back } = useScreenNavigation();
1968
- return /* @__PURE__ */ jsx(PlaceholderScreen, {
1969
- screen: "details",
2393
+ // src/react/screens/message-detail/index.tsx
2394
+ const messageDetailCopy = {
2395
+ title: "Detalle del mensaje",
2396
+ dateTime: "Fecha y hora",
2397
+ kind: "Tipo",
2398
+ responders: "Contactos de emergencia",
2399
+ messages: "Mensajes restantes",
2400
+ rounds: "Rondas de avisos",
2401
+ notFound: "No encontramos este mensaje. Puede que ya no esté disponible en tu historial.",
2402
+ backToHistory: "Volver al historial"
2403
+ };
2404
+ function MessageDetailScreen({ titleId, locale, onClose }) {
2405
+ const { back, params, replace } = useScreenNavigation();
2406
+ const entry = historyEntryById(params.messageId);
2407
+ const header = /* @__PURE__ */ jsx(EmergencyHeader, {
1970
2408
  titleId,
1971
- title: detailsCopy.title,
1972
- description: detailsCopy.description,
2409
+ title: messageDetailCopy.title,
1973
2410
  onClose: () => onClose("close"),
1974
2411
  onBack: back
1975
2412
  });
2413
+ if (!entry) return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
2414
+ screen: "message-detail",
2415
+ titleId,
2416
+ bodyClassName: "emergency-panel__body--message-detail",
2417
+ header,
2418
+ footer: null,
2419
+ children: /* @__PURE__ */ jsxs("div", {
2420
+ className: cls("emergency-message-detail__empty"),
2421
+ children: [/* @__PURE__ */ jsx("p", {
2422
+ className: cls("emergency-message-detail__empty-text"),
2423
+ children: messageDetailCopy.notFound
2424
+ }), /* @__PURE__ */ jsx("button", {
2425
+ type: "button",
2426
+ className: cls("emergency-message-detail__back"),
2427
+ onClick: () => replace("history"),
2428
+ children: messageDetailCopy.backToHistory
2429
+ })]
2430
+ })
2431
+ });
2432
+ const rounds = historyEntryRounds(entry);
2433
+ return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
2434
+ screen: "message-detail",
2435
+ titleId,
2436
+ bodyClassName: "emergency-panel__body--message-detail",
2437
+ header,
2438
+ footer: null,
2439
+ children: /* @__PURE__ */ jsx("div", {
2440
+ className: cls("emergency-message-detail"),
2441
+ children: /* @__PURE__ */ jsxs("div", {
2442
+ className: cls("emergency-message-detail__card"),
2443
+ children: [/* @__PURE__ */ jsx("div", {
2444
+ className: cls("emergency-message-detail__card-header"),
2445
+ children: /* @__PURE__ */ jsx("h3", {
2446
+ className: cls("emergency-message-detail__card-title"),
2447
+ children: messageDetailCopy.title
2448
+ })
2449
+ }), /* @__PURE__ */ jsxs("dl", {
2450
+ className: cls("emergency-message-detail__list"),
2451
+ children: [
2452
+ /* @__PURE__ */ jsxs("div", {
2453
+ className: cls("emergency-message-detail__row"),
2454
+ children: [/* @__PURE__ */ jsx("dt", {
2455
+ className: cls("emergency-message-detail__label"),
2456
+ children: messageDetailCopy.dateTime
2457
+ }), /* @__PURE__ */ jsx("dd", {
2458
+ className: cls("emergency-message-detail__value"),
2459
+ children: formatDateTime(entry.occurredAt, locale)
2460
+ })]
2461
+ }),
2462
+ /* @__PURE__ */ jsxs("div", {
2463
+ className: cls("emergency-message-detail__row"),
2464
+ children: [/* @__PURE__ */ jsx("dt", {
2465
+ className: cls("emergency-message-detail__label"),
2466
+ children: messageDetailCopy.kind
2467
+ }), /* @__PURE__ */ jsx("dd", {
2468
+ className: cls("emergency-message-detail__value"),
2469
+ children: historyCopy.kindLabels[entry.kind]
2470
+ })]
2471
+ }),
2472
+ /* @__PURE__ */ jsxs("div", {
2473
+ className: cls("emergency-message-detail__row"),
2474
+ children: [/* @__PURE__ */ jsx("dt", {
2475
+ className: cls("emergency-message-detail__label"),
2476
+ children: messageDetailCopy.responders
2477
+ }), /* @__PURE__ */ jsx("dd", {
2478
+ className: cls("emergency-message-detail__value"),
2479
+ children: formatContacts(entry.responders)
2480
+ })]
2481
+ }),
2482
+ /* @__PURE__ */ jsxs("div", {
2483
+ className: cls("emergency-message-detail__row"),
2484
+ children: [/* @__PURE__ */ jsx("dt", {
2485
+ className: cls("emergency-message-detail__label"),
2486
+ children: messageDetailCopy.messages
2487
+ }), /* @__PURE__ */ jsx("dd", {
2488
+ className: cls("emergency-message-detail__value"),
2489
+ children: formatMessages(entry.remainingMessages)
2490
+ })]
2491
+ }),
2492
+ /* @__PURE__ */ jsxs("div", {
2493
+ className: cls("emergency-message-detail__row"),
2494
+ children: [/* @__PURE__ */ jsx("dt", {
2495
+ className: cls("emergency-message-detail__label"),
2496
+ children: messageDetailCopy.rounds
2497
+ }), /* @__PURE__ */ jsx("dd", {
2498
+ className: cls("emergency-message-detail__value"),
2499
+ children: formatHistoryRounds(rounds)
2500
+ })]
2501
+ })
2502
+ ]
2503
+ })]
2504
+ })
2505
+ })
2506
+ });
1976
2507
  }
1977
2508
 
1978
2509
  // src/core/phone.ts
@@ -1997,9 +2528,9 @@ function toVenezuelanE164(input) {
1997
2528
 
1998
2529
  // src/react/components/EmergencyFooter/index.tsx
1999
2530
  function EmergencyFooter(props) {
2000
- const { active, busy, aborting, feedbackVisible, showSimulation = true, emergencyDisabled = false, onActivate, onRequestAbort, onRequestSimulation } = props;
2531
+ const { active, busy, aborting, feedbackVisible, showSimulation = true, emergencyDisabled = false, square = false, onActivate, onRequestAbort, onRequestSimulation } = props;
2001
2532
  if (active) return /* @__PURE__ */ jsx("footer", {
2002
- className: cls("emergency-panel__footer"),
2533
+ className: cls("emergency-panel__footer", square && "emergency-panel__footer--square"),
2003
2534
  children: /* @__PURE__ */ jsxs("button", {
2004
2535
  type: "button",
2005
2536
  className: cls("emergency-status__abort-btn"),
@@ -2010,7 +2541,7 @@ function EmergencyFooter(props) {
2010
2541
  })
2011
2542
  });
2012
2543
  return /* @__PURE__ */ jsxs("footer", {
2013
- className: cls("emergency-panel__footer", !showSimulation && "emergency-panel__footer--single"),
2544
+ className: cls("emergency-panel__footer", !showSimulation && "emergency-panel__footer--single", square && "emergency-panel__footer--square"),
2014
2545
  children: [/* @__PURE__ */ jsxs("button", {
2015
2546
  type: "button",
2016
2547
  className: cls("emergency-panel__button", "emergency-panel__button--emergency"),
@@ -2285,9 +2816,9 @@ function EmergencyError({ error, onRetry }) {
2285
2816
 
2286
2817
  // src/react/components/EmergencyFeedback/index.tsx
2287
2818
  /** Time the feedback stays on screen before dismissing itself. */
2288
- const FEEDBACK_AUTO_DISMISS_MS = 3e3;
2819
+ const FEEDBACK_AUTO_DISMISS_MS = 12e3;
2289
2820
  /** Time the geolocation feedback stays on screen before dismissing itself. */
2290
- const GEOLOCATION_FEEDBACK_AUTO_DISMISS_MS = 5e3;
2821
+ const GEOLOCATION_FEEDBACK_AUTO_DISMISS_MS = 12e3;
2291
2822
  function geolocationText(error) {
2292
2823
  switch (error.code) {
2293
2824
  case "geolocation_denied": return copy.geolocationDenied;
@@ -2373,32 +2904,6 @@ function EmergencyLoading() {
2373
2904
  });
2374
2905
  }
2375
2906
 
2376
- // src/core/date.ts
2377
- /**
2378
- * Formats a timestamp as `h:mmam` or `h:mmpm` in local time, without a space
2379
- * or leading zero.
2380
- */
2381
- function formatShortTime(value, locale = "es") {
2382
- if (value === null) return "";
2383
- const date = value instanceof Date ? value : new Date(value);
2384
- if (Number.isNaN(date.getTime())) return "";
2385
- const parts = new Intl.DateTimeFormat(locale, {
2386
- hour: "numeric",
2387
- minute: "2-digit",
2388
- hour12: true
2389
- }).formatToParts(date);
2390
- const hour = parts.find((part) => part.type === "hour")?.value;
2391
- const minute = parts.find((part) => part.type === "minute")?.value;
2392
- const dayPeriod = parts.find((part) => part.type === "dayPeriod")?.value;
2393
- if (!hour || !minute) return "";
2394
- return `${hour}:${minute}${normalizePeriod(dayPeriod)}`;
2395
- }
2396
- function normalizePeriod(dayPeriod) {
2397
- if (!dayPeriod) return "";
2398
- if (/^p/i.test(dayPeriod)) return "pm";
2399
- return /^a/i.test(dayPeriod) ? "am" : "";
2400
- }
2401
-
2402
2907
  // src/react/components/StatusIcon/index.tsx
2403
2908
  /**
2404
2909
  * Explicit responder state model:
@@ -2559,23 +3064,9 @@ function formatRounds(rounds) {
2559
3064
  return `${rounds} ${roundsUnit(rounds)}`;
2560
3065
  }
2561
3066
 
2562
- // src/react/screens/primary/data.ts
2563
- /**
2564
- * Derives the remaining alert rounds from available messages and designated
2565
- * responders. Invalid or non-positive input collapses to the minimum of one
2566
- * round instead of producing `Infinity`, `NaN` or a negative value.
2567
- */
2568
- function calculateRounds(messages, responders) {
2569
- if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
2570
- const availableMessages = Math.floor(messages);
2571
- const designatedResponders = Math.floor(responders);
2572
- if (availableMessages <= 0 || designatedResponders <= 0) return 1;
2573
- return Math.max(1, Math.floor(availableMessages / designatedResponders));
2574
- }
2575
-
2576
3067
  // src/react/screens/primary/Availability.tsx
2577
3068
  function Availability({ onRecharge, onDetails }) {
2578
- const rounds = calculateRounds(100, 3);
3069
+ const rounds = calculateRounds(27, 3);
2579
3070
  return /* @__PURE__ */ jsxs("section", {
2580
3071
  className: cls("emergency-availability"),
2581
3072
  "aria-label": primaryCopy.availabilityTitle,
@@ -2617,23 +3108,30 @@ function Availability({ onRecharge, onDetails }) {
2617
3108
 
2618
3109
  // src/react/components/EmergencyBody/index.tsx
2619
3110
  function EmergencyBody(props) {
2620
- const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails } = props;
3111
+ const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails, onClose } = props;
2621
3112
  const showingFeedback = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2622
3113
  const idle = !active && !loading && !showingFeedback && error === null;
2623
3114
  return /* @__PURE__ */ jsxs("div", {
2624
3115
  className: cls("emergency-panel__body-split", idle && "emergency-panel__body-split--with-availability"),
2625
- children: [idle ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
3116
+ children: [idle ? /* @__PURE__ */ jsx("div", {
2626
3117
  className: cls("emergency-panel__availability-region"),
2627
3118
  children: /* @__PURE__ */ jsx(Availability, {
2628
3119
  onRecharge,
2629
3120
  onDetails
2630
3121
  })
2631
- }), /* @__PURE__ */ jsx("hr", {
2632
- className: cls("emergency-panel__divider"),
2633
- "aria-hidden": "true"
2634
- })] }) : null, /* @__PURE__ */ jsxs("div", {
2635
- className: cls("emergency-panel__prompt-region"),
3122
+ }) : null, /* @__PURE__ */ jsxs("div", {
3123
+ className: cls("emergency-panel__prompt-region", idle && "emergency-panel__prompt-region--idle"),
2636
3124
  children: [
3125
+ idle ? /* @__PURE__ */ jsx("button", {
3126
+ type: "button",
3127
+ className: cls("emergency-panel__close-button", "emergency-panel__close-button--prompt"),
3128
+ "aria-label": copy.close,
3129
+ onClick: onClose,
3130
+ children: /* @__PURE__ */ jsx(Icon, {
3131
+ name: "close",
3132
+ className: cls("emergency-panel__close-icon")
3133
+ })
3134
+ }) : null,
2637
3135
  !loading && !showingFeedback ? /* @__PURE__ */ jsx("div", {
2638
3136
  className: cls("sos-img__container"),
2639
3137
  id: "sos-img__container",
@@ -2677,12 +3175,12 @@ function PrimaryScreen(props) {
2677
3175
  const { navigate } = useScreenNavigation();
2678
3176
  const idle = !props.active && !props.loading && !props.feedbackVisible && props.error === null;
2679
3177
  const needsContactPhone = props.contactPhone === null;
2680
- return /* @__PURE__ */ jsx(EmergencyScreenLayout, {
3178
+ return /* @__PURE__ */ jsxs(EmergencyScreenLayout, {
2681
3179
  screen: "primary",
2682
3180
  titleId: props.titleId,
2683
- header: /* @__PURE__ */ jsx(EmergencyHeader, {
3181
+ bodyClassName: idle ? "emergency-panel__body--primary" : void 0,
3182
+ header: idle ? null : /* @__PURE__ */ jsx(EmergencyHeader, {
2684
3183
  titleId: props.titleId,
2685
- variant: idle ? "close-only" : "default",
2686
3184
  onClose: () => props.onClose("close")
2687
3185
  }),
2688
3186
  footer: /* @__PURE__ */ jsx(EmergencyFooter, {
@@ -2690,11 +3188,17 @@ function PrimaryScreen(props) {
2690
3188
  busy: props.busy,
2691
3189
  aborting: props.aborting,
2692
3190
  feedbackVisible: props.feedbackVisible,
3191
+ square: idle,
2693
3192
  onActivate: needsContactPhone ? () => navigate("pre-emergency") : props.onActivate,
2694
3193
  onRequestAbort: props.onRequestAbort,
2695
3194
  onRequestSimulation: props.onRequestSimulation
2696
3195
  }),
2697
- children: /* @__PURE__ */ jsx(EmergencyBody, {
3196
+ children: [idle ? /* @__PURE__ */ jsx("h2", {
3197
+ className: cls("emergency-panel__sr-only"),
3198
+ id: props.titleId,
3199
+ tabIndex: -1,
3200
+ children: copy.panelLabel
3201
+ }) : null, /* @__PURE__ */ jsx(EmergencyBody, {
2698
3202
  active: props.active,
2699
3203
  responders: props.responders,
2700
3204
  refreshing: props.refreshing,
@@ -2709,8 +3213,9 @@ function PrimaryScreen(props) {
2709
3213
  onRetry: props.onRetry,
2710
3214
  onDismissFeedback: props.onDismissFeedback,
2711
3215
  onRecharge: () => navigate("pricing"),
2712
- onDetails: () => navigate("details")
2713
- })
3216
+ onDetails: () => navigate("details"),
3217
+ onClose: () => props.onClose("close")
3218
+ })]
2714
3219
  });
2715
3220
  }
2716
3221
 
@@ -2883,7 +3388,9 @@ const screenRegistry = {
2883
3388
  primary: PrimaryScreen,
2884
3389
  "pre-emergency": PreEmergencyScreen,
2885
3390
  pricing: PricingScreen,
2886
- details: DetailsScreen
3391
+ details: DetailsScreen,
3392
+ history: HistoryScreen,
3393
+ "message-detail": MessageDetailScreen
2887
3394
  };
2888
3395
 
2889
3396
  // src/react/components/EmergencyPanel/index.tsx
@@ -2912,7 +3419,7 @@ function ActiveScreen({ screenProps, open, forcePrimary }) {
2912
3419
  return /* @__PURE__ */ jsx(Screen, { ...screenProps });
2913
3420
  }
2914
3421
  function EmergencyPanel(props) {
2915
- const { open, theme, active, busy, aborting, loading, locale, responders, refreshing, geolocationError, contactPhone, activationError, error, abortResult, simulationResult, abortConfirmation, simulationConfirmation, onClose, onActivate, onRefresh, onRetry, onDismissFeedback, onRequestAbort, onRequestSimulation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation, onSetContactPhone } = props;
3422
+ const { open, theme, active, busy, aborting, simulating, loading, locale, responders, refreshing, geolocationError, contactPhone, activationError, error, abortResult, simulationResult, abortConfirmation, simulationConfirmation, onClose, onActivate, onRefresh, onRetry, onDismissFeedback, onRequestAbort, onRequestSimulation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation, onSetContactPhone } = props;
2916
3423
  const titleId = useId();
2917
3424
  const dialogRef = useRef(null);
2918
3425
  const feedbackVisible = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
@@ -2964,6 +3471,7 @@ function EmergencyPanel(props) {
2964
3471
  }) }), /* @__PURE__ */ jsx(EmergencyConfirmation, {
2965
3472
  abortConfirmation,
2966
3473
  simulationConfirmation,
3474
+ loading: abortConfirmation ? aborting : simulating,
2967
3475
  onCancelAbort,
2968
3476
  onCancelSimulation,
2969
3477
  onConfirmAbort,
@@ -3073,6 +3581,7 @@ function EmergencyRoot() {
3073
3581
  active,
3074
3582
  busy,
3075
3583
  aborting: phase.kind === "aborting",
3584
+ simulating: phase.kind === "simulating",
3076
3585
  loading: phase.kind === "initializing" || phase.kind === "activating",
3077
3586
  locale: config.locale ?? "es",
3078
3587
  responders,