@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.cjs CHANGED
@@ -1576,6 +1576,12 @@ var panel_module_default = {
1576
1576
  "de-emergency-slide-in": "de-emergency-de-emergency-slide-in-nA5Hwa",
1577
1577
  "de-emergency-slide-up": "de-emergency-de-emergency-slide-up-nA5Hwa",
1578
1578
  "de-emergency-spin": "de-emergency-de-emergency-spin-nA5Hwa",
1579
+ "emergency-alert-row__badge": "de-emergency-emergency-alert-row__badge-nA5Hwa",
1580
+ "emergency-alert-row__button": "de-emergency-emergency-alert-row__button-nA5Hwa",
1581
+ "emergency-alert-row__stat": "de-emergency-emergency-alert-row__stat-nA5Hwa",
1582
+ "emergency-alert-row__stats": "de-emergency-emergency-alert-row__stats-nA5Hwa",
1583
+ "emergency-alert-row__time": "de-emergency-emergency-alert-row__time-nA5Hwa",
1584
+ "emergency-alert-row__top": "de-emergency-emergency-alert-row__top-nA5Hwa",
1579
1585
  "emergency-availability": "de-emergency-emergency-availability-nA5Hwa",
1580
1586
  "emergency-availability__card": "de-emergency-emergency-availability__card-nA5Hwa",
1581
1587
  "emergency-availability__card-body": "de-emergency-emergency-availability__card-body-nA5Hwa",
@@ -1597,8 +1603,22 @@ var panel_module_default = {
1597
1603
  "emergency-confirmation__icon": "de-emergency-emergency-confirmation__icon-nA5Hwa",
1598
1604
  "emergency-confirmation__icon--danger": "de-emergency-emergency-confirmation__icon--danger-nA5Hwa",
1599
1605
  "emergency-confirmation__icon--information": "de-emergency-emergency-confirmation__icon--information-nA5Hwa",
1606
+ "emergency-confirmation__spinner": "de-emergency-emergency-confirmation__spinner-nA5Hwa",
1600
1607
  "emergency-confirmation__text": "de-emergency-emergency-confirmation__text-nA5Hwa",
1601
1608
  "emergency-confirmation__title": "de-emergency-emergency-confirmation__title-nA5Hwa",
1609
+ "emergency-details": "de-emergency-emergency-details-nA5Hwa",
1610
+ "emergency-details__balance": "de-emergency-emergency-details__balance-nA5Hwa",
1611
+ "emergency-details__card": "de-emergency-emergency-details__card-nA5Hwa",
1612
+ "emergency-details__card-body": "de-emergency-emergency-details__card-body-nA5Hwa",
1613
+ "emergency-details__card-header": "de-emergency-emergency-details__card-header-nA5Hwa",
1614
+ "emergency-details__card-title": "de-emergency-emergency-details__card-title-nA5Hwa",
1615
+ "emergency-details__empty": "de-emergency-emergency-details__empty-nA5Hwa",
1616
+ "emergency-details__heading": "de-emergency-emergency-details__heading-nA5Hwa",
1617
+ "emergency-details__history-button": "de-emergency-emergency-details__history-button-nA5Hwa",
1618
+ "emergency-details__list": "de-emergency-emergency-details__list-nA5Hwa",
1619
+ "emergency-details__section": "de-emergency-emergency-details__section-nA5Hwa",
1620
+ "emergency-details__subtitle": "de-emergency-emergency-details__subtitle-nA5Hwa",
1621
+ "emergency-details__title": "de-emergency-emergency-details__title-nA5Hwa",
1602
1622
  "emergency-error-message": "de-emergency-emergency-error-message-nA5Hwa",
1603
1623
  "emergency-error-message__container": "de-emergency-emergency-error-message__container-nA5Hwa",
1604
1624
  "emergency-error-message__content": "de-emergency-emergency-error-message__content-nA5Hwa",
@@ -1609,11 +1629,33 @@ var panel_module_default = {
1609
1629
  "emergency-error-message__title": "de-emergency-emergency-error-message__title-nA5Hwa",
1610
1630
  "emergency-feedback": "de-emergency-emergency-feedback-nA5Hwa",
1611
1631
  "emergency-feedback__dismiss": "de-emergency-emergency-feedback__dismiss-nA5Hwa",
1632
+ "emergency-history": "de-emergency-emergency-history-nA5Hwa",
1633
+ "emergency-history__empty": "de-emergency-emergency-history__empty-nA5Hwa",
1634
+ "emergency-history__heading": "de-emergency-emergency-history__heading-nA5Hwa",
1635
+ "emergency-history__list": "de-emergency-emergency-history__list-nA5Hwa",
1636
+ "emergency-history__more": "de-emergency-emergency-history__more-nA5Hwa",
1637
+ "emergency-history__subtitle": "de-emergency-emergency-history__subtitle-nA5Hwa",
1638
+ "emergency-history__title": "de-emergency-emergency-history__title-nA5Hwa",
1639
+ "emergency-message-detail": "de-emergency-emergency-message-detail-nA5Hwa",
1640
+ "emergency-message-detail__back": "de-emergency-emergency-message-detail__back-nA5Hwa",
1641
+ "emergency-message-detail__card": "de-emergency-emergency-message-detail__card-nA5Hwa",
1642
+ "emergency-message-detail__card-header": "de-emergency-emergency-message-detail__card-header-nA5Hwa",
1643
+ "emergency-message-detail__card-title": "de-emergency-emergency-message-detail__card-title-nA5Hwa",
1644
+ "emergency-message-detail__empty": "de-emergency-emergency-message-detail__empty-nA5Hwa",
1645
+ "emergency-message-detail__empty-text": "de-emergency-emergency-message-detail__empty-text-nA5Hwa",
1646
+ "emergency-message-detail__label": "de-emergency-emergency-message-detail__label-nA5Hwa",
1647
+ "emergency-message-detail__list": "de-emergency-emergency-message-detail__list-nA5Hwa",
1648
+ "emergency-message-detail__row": "de-emergency-emergency-message-detail__row-nA5Hwa",
1649
+ "emergency-message-detail__value": "de-emergency-emergency-message-detail__value-nA5Hwa",
1612
1650
  "emergency-panel": "de-emergency-emergency-panel-nA5Hwa",
1613
1651
  "emergency-panel__availability-region": "de-emergency-emergency-panel__availability-region-nA5Hwa",
1614
1652
  "emergency-panel__body": "de-emergency-emergency-panel__body-nA5Hwa",
1653
+ "emergency-panel__body--details": "de-emergency-emergency-panel__body--details-nA5Hwa",
1654
+ "emergency-panel__body--history": "de-emergency-emergency-panel__body--history-nA5Hwa",
1655
+ "emergency-panel__body--message-detail": "de-emergency-emergency-panel__body--message-detail-nA5Hwa",
1615
1656
  "emergency-panel__body--pre-emergency": "de-emergency-emergency-panel__body--pre-emergency-nA5Hwa",
1616
1657
  "emergency-panel__body--pricing": "de-emergency-emergency-panel__body--pricing-nA5Hwa",
1658
+ "emergency-panel__body--primary": "de-emergency-emergency-panel__body--primary-nA5Hwa",
1617
1659
  "emergency-panel__body-split": "de-emergency-emergency-panel__body-split-nA5Hwa",
1618
1660
  "emergency-panel__body-split--with-availability": "de-emergency-emergency-panel__body-split--with-availability-nA5Hwa",
1619
1661
  "emergency-panel__body-title": "de-emergency-emergency-panel__body-title-nA5Hwa",
@@ -1622,13 +1664,14 @@ var panel_module_default = {
1622
1664
  "emergency-panel__button--emergency": "de-emergency-emergency-panel__button--emergency-nA5Hwa",
1623
1665
  "emergency-panel__button--simulation": "de-emergency-emergency-panel__button--simulation-nA5Hwa",
1624
1666
  "emergency-panel__close-button": "de-emergency-emergency-panel__close-button-nA5Hwa",
1667
+ "emergency-panel__close-button--prompt": "de-emergency-emergency-panel__close-button--prompt-nA5Hwa",
1625
1668
  "emergency-panel__close-icon": "de-emergency-emergency-panel__close-icon-nA5Hwa",
1626
- "emergency-panel__divider": "de-emergency-emergency-panel__divider-nA5Hwa",
1627
1669
  "emergency-panel__footer": "de-emergency-emergency-panel__footer-nA5Hwa",
1628
1670
  "emergency-panel__footer--single": "de-emergency-emergency-panel__footer--single-nA5Hwa",
1671
+ "emergency-panel__footer--square": "de-emergency-emergency-panel__footer--square-nA5Hwa",
1629
1672
  "emergency-panel__header": "de-emergency-emergency-panel__header-nA5Hwa",
1630
- "emergency-panel__header--close-only": "de-emergency-emergency-panel__header--close-only-nA5Hwa",
1631
1673
  "emergency-panel__prompt-region": "de-emergency-emergency-panel__prompt-region-nA5Hwa",
1674
+ "emergency-panel__prompt-region--idle": "de-emergency-emergency-panel__prompt-region--idle-nA5Hwa",
1632
1675
  "emergency-panel__sr-only": "de-emergency-emergency-panel__sr-only-nA5Hwa",
1633
1676
  "emergency-panel__title": "de-emergency-emergency-panel__title-nA5Hwa",
1634
1677
  "emergency-panel--hidden": "de-emergency-emergency-panel--hidden-nA5Hwa",
@@ -1724,11 +1767,12 @@ function cls(...names) {
1724
1767
 
1725
1768
  // src/react/components/EmergencyConfirmation/index.tsx
1726
1769
  function EmergencyConfirmation(props) {
1727
- const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
1770
+ const { abortConfirmation, simulationConfirmation, loading, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
1728
1771
  const cardRef = (0, react.useRef)(null);
1729
1772
  const titleId = (0, react.useId)();
1730
1773
  const danger = abortConfirmation;
1731
1774
  const open = abortConfirmation || simulationConfirmation;
1775
+ const pending = open && loading;
1732
1776
  (0, react.useEffect)(() => {
1733
1777
  if (open) cardRef.current?.querySelector("button")?.focus();
1734
1778
  }, [open]);
@@ -1741,40 +1785,56 @@ function EmergencyConfirmation(props) {
1741
1785
  role: "dialog",
1742
1786
  "aria-modal": "true",
1743
1787
  "aria-labelledby": titleId,
1788
+ "aria-busy": pending,
1744
1789
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1745
1790
  ref: cardRef,
1746
1791
  className: cls("emergency-confirmation__card"),
1747
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1748
- className: cls("emergency-confirmation__body"),
1749
- children: [
1750
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1751
- className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
1752
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: danger ? "alert-triangle" : "info" })
1753
- }),
1754
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h5", {
1755
- className: cls("emergency-confirmation__title"),
1756
- id: titleId,
1757
- children: title
1758
- }),
1759
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1760
- className: cls("emergency-confirmation__text"),
1761
- children: text
1762
- })
1763
- ]
1764
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1765
- className: cls("emergency-confirmation__footer"),
1766
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1767
- type: "button",
1768
- className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
1769
- onClick: danger ? onCancelAbort : onCancelSimulation,
1770
- children: danger ? copy.abortCancel : copy.simulateCancel
1771
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1772
- type: "button",
1773
- className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
1774
- onClick: danger ? onConfirmAbort : onConfirmSimulation,
1775
- children: danger ? copy.abortConfirm : copy.simulateConfirm
1776
- })]
1777
- })]
1792
+ children: [
1793
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1794
+ className: cls("emergency-panel__sr-only"),
1795
+ role: "status",
1796
+ "aria-live": "polite",
1797
+ children: pending ? copy.loading : ""
1798
+ }),
1799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1800
+ className: cls("emergency-confirmation__body"),
1801
+ children: [
1802
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1803
+ className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
1804
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: danger ? "alert-triangle" : "info" })
1805
+ }),
1806
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h5", {
1807
+ className: cls("emergency-confirmation__title"),
1808
+ id: titleId,
1809
+ children: title
1810
+ }),
1811
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1812
+ className: cls("emergency-confirmation__text"),
1813
+ children: text
1814
+ })
1815
+ ]
1816
+ }),
1817
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1818
+ className: cls("emergency-confirmation__footer"),
1819
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1820
+ type: "button",
1821
+ className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
1822
+ disabled: pending,
1823
+ onClick: danger ? onCancelAbort : onCancelSimulation,
1824
+ children: danger ? copy.abortCancel : copy.simulateCancel
1825
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1826
+ type: "button",
1827
+ className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
1828
+ "aria-busy": pending,
1829
+ disabled: pending,
1830
+ onClick: danger ? onConfirmAbort : onConfirmSimulation,
1831
+ children: [pending ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1832
+ className: cls("emergency-confirmation__spinner"),
1833
+ "aria-hidden": "true"
1834
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: danger ? copy.abortConfirm : copy.simulateConfirm })]
1835
+ })]
1836
+ })
1837
+ ]
1778
1838
  })
1779
1839
  });
1780
1840
  }
@@ -1847,18 +1907,27 @@ function useFocusTrap(ref, active) {
1847
1907
  }
1848
1908
 
1849
1909
  // src/react/screens/context.tsx
1910
+ const EMPTY_PARAMS = {};
1850
1911
  const EmergencyScreenContext = (0, react.createContext)(null);
1912
+ function paramsEqual(first, second) {
1913
+ return first.messageId === second.messageId;
1914
+ }
1851
1915
  function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
1852
1916
  const [state, setState] = (0, react.useState)({
1853
1917
  screen: initialScreen,
1918
+ params: EMPTY_PARAMS,
1854
1919
  history: []
1855
1920
  });
1856
- const navigate = (0, react.useCallback)((next) => {
1921
+ const navigate = (0, react.useCallback)((next, params = EMPTY_PARAMS) => {
1857
1922
  setState((current) => {
1858
- if (current.screen === next) return current;
1923
+ if (current.screen === next && paramsEqual(current.params, params)) return current;
1859
1924
  return {
1860
1925
  screen: next,
1861
- history: [...current.history, current.screen]
1926
+ params,
1927
+ history: [...current.history, {
1928
+ screen: current.screen,
1929
+ params: current.params
1930
+ }]
1862
1931
  };
1863
1932
  });
1864
1933
  }, []);
@@ -1867,31 +1936,47 @@ function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
1867
1936
  const previous = current.history.at(-1);
1868
1937
  if (!previous) return current;
1869
1938
  return {
1870
- screen: previous,
1939
+ screen: previous.screen,
1940
+ params: previous.params,
1871
1941
  history: current.history.slice(0, -1)
1872
1942
  };
1873
1943
  });
1874
1944
  }, []);
1945
+ const replace = (0, react.useCallback)((next, params = EMPTY_PARAMS) => {
1946
+ setState((current) => {
1947
+ if (current.screen === next && paramsEqual(current.params, params)) return current;
1948
+ return {
1949
+ screen: next,
1950
+ params,
1951
+ history: current.history.at(-1)?.screen === next ? current.history.slice(0, -1) : current.history
1952
+ };
1953
+ });
1954
+ }, []);
1875
1955
  const reset = (0, react.useCallback)(() => {
1876
1956
  setState((current) => {
1877
- if (current.screen === "primary" && current.history.length === 0) return current;
1957
+ if (current.screen === "primary" && current.params.messageId === void 0 && current.history.length === 0) return current;
1878
1958
  return {
1879
1959
  screen: "primary",
1960
+ params: EMPTY_PARAMS,
1880
1961
  history: []
1881
1962
  };
1882
1963
  });
1883
1964
  }, []);
1884
1965
  const value = (0, react.useMemo)(() => ({
1885
1966
  screen: state.screen,
1967
+ params: state.params,
1886
1968
  history: state.history,
1887
1969
  navigate,
1970
+ replace,
1888
1971
  back,
1889
1972
  reset
1890
1973
  }), [
1891
1974
  back,
1892
1975
  navigate,
1976
+ replace,
1893
1977
  reset,
1894
1978
  state.history,
1979
+ state.params,
1895
1980
  state.screen
1896
1981
  ]);
1897
1982
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenContext.Provider, {
@@ -1906,12 +1991,11 @@ function useScreenNavigation() {
1906
1991
  }
1907
1992
 
1908
1993
  // src/react/components/EmergencyHeader/index.tsx
1909
- function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack, variant = "default" }) {
1910
- const closeOnly = variant === "close-only";
1994
+ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack }) {
1911
1995
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1912
- className: cls("emergency-panel__header", closeOnly && "emergency-panel__header--close-only"),
1996
+ className: cls("emergency-panel__header"),
1913
1997
  children: [
1914
- onBack && !closeOnly ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1998
+ onBack ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1915
1999
  type: "button",
1916
2000
  className: cls("emergency-panel__close-button"),
1917
2001
  "aria-label": copy.back,
@@ -1922,7 +2006,7 @@ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack, va
1922
2006
  })
1923
2007
  }) : null,
1924
2008
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1925
- className: cls("emergency-panel__title", closeOnly && "emergency-panel__sr-only"),
2009
+ className: cls("emergency-panel__title"),
1926
2010
  id: titleId,
1927
2011
  tabIndex: -1,
1928
2012
  children: title
@@ -1941,6 +2025,245 @@ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack, va
1941
2025
  });
1942
2026
  }
1943
2027
 
2028
+ // src/react/screens/primary/data.ts
2029
+ /**
2030
+ * Derives the remaining alert rounds from available messages and designated
2031
+ * responders. Invalid or non-positive input collapses to the minimum of one
2032
+ * round instead of producing `Infinity`, `NaN` or a negative value.
2033
+ */
2034
+ function calculateRounds(messages, responders) {
2035
+ if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
2036
+ const availableMessages = Math.floor(messages);
2037
+ const designatedResponders = Math.floor(responders);
2038
+ if (availableMessages <= 0 || designatedResponders <= 0) return 1;
2039
+ return Math.max(1, Math.floor(availableMessages / designatedResponders));
2040
+ }
2041
+
2042
+ // src/react/screens/details/copy.ts
2043
+ const detailsCopy = {
2044
+ title: "Detalles",
2045
+ summaryTitle: "Alertas Disponibles",
2046
+ historyTitle: "Historial de alertas",
2047
+ historySubtitle: "Tus últimas alertas y recargas.",
2048
+ historyEmpty: "Todavía no tienes alertas en tu historial.",
2049
+ summaryEmpty: "Todavía no tienes un saldo de alertas disponible.",
2050
+ viewHistory: "Ver historial",
2051
+ messagesSingular: "mensaje de alerta",
2052
+ messagesPlural: "mensajes de alerta",
2053
+ contactsSingular: "contacto de emergencia",
2054
+ contactsPlural: "contactos de emergencia",
2055
+ roundsSingular: "ronda de avisos",
2056
+ roundsPlural: "rondas de avisos"
2057
+ };
2058
+ /** Returns the balance sentence split into text and emphasized number parts. */
2059
+ function alertBalanceParts(messages, responders) {
2060
+ if (!Number.isFinite(messages) || !Number.isFinite(responders) || messages < 0 || responders < 0) return [];
2061
+ const rounds = calculateRounds(messages, responders);
2062
+ const messagesUnit = messages === 1 ? detailsCopy.messagesSingular : detailsCopy.messagesPlural;
2063
+ const contactsUnit = responders === 1 ? detailsCopy.contactsSingular : detailsCopy.contactsPlural;
2064
+ const roundsUnit = rounds === 1 ? detailsCopy.roundsSingular : detailsCopy.roundsPlural;
2065
+ return [
2066
+ messages === 1 ? "Te queda " : "Te quedan ",
2067
+ messages,
2068
+ ` ${messagesUnit}, dado que tienes `,
2069
+ responders,
2070
+ ` ${contactsUnit}, `,
2071
+ rounds === 1 ? "te queda " : "te quedan ",
2072
+ rounds,
2073
+ ` ${roundsUnit}.`
2074
+ ];
2075
+ }
2076
+
2077
+ // src/react/screens/details/AlertBalanceSentence.tsx
2078
+ /** Renders the balance sentence with its numbers visually emphasized. */
2079
+ function AlertBalanceSentence({ messages, responders }) {
2080
+ const parts = alertBalanceParts(messages, responders);
2081
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: parts.map((part, index) => typeof part === "number" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: part }, index) : part) });
2082
+ }
2083
+
2084
+ // src/core/date.ts
2085
+ /**
2086
+ * Formats a timestamp as `h:mmam` or `h:mmpm` in local time, without a space
2087
+ * or leading zero.
2088
+ */
2089
+ function formatShortTime(value, locale = "es") {
2090
+ if (value === null) return "";
2091
+ const date = value instanceof Date ? value : new Date(value);
2092
+ if (Number.isNaN(date.getTime())) return "";
2093
+ const parts = new Intl.DateTimeFormat(locale, {
2094
+ hour: "numeric",
2095
+ minute: "2-digit",
2096
+ hour12: true
2097
+ }).formatToParts(date);
2098
+ const hour = parts.find((part) => part.type === "hour")?.value;
2099
+ const minute = parts.find((part) => part.type === "minute")?.value;
2100
+ const dayPeriod = parts.find((part) => part.type === "dayPeriod")?.value;
2101
+ if (!hour || !minute) return "";
2102
+ return `${hour}:${minute}${normalizePeriod(dayPeriod)}`;
2103
+ }
2104
+ /**
2105
+ * Formats a timestamp as `dd/mm/yyyy hh:mm` in local time, using 24-hour time
2106
+ * and zero-padded parts. Missing or invalid input collapses to an empty string.
2107
+ */
2108
+ function formatDateTime(value, locale = "es") {
2109
+ if (value === null) return "";
2110
+ const date = value instanceof Date ? value : new Date(value);
2111
+ if (Number.isNaN(date.getTime())) return "";
2112
+ const parts = new Intl.DateTimeFormat(locale, {
2113
+ day: "2-digit",
2114
+ month: "2-digit",
2115
+ year: "numeric",
2116
+ hour: "2-digit",
2117
+ minute: "2-digit",
2118
+ hourCycle: "h23"
2119
+ }).formatToParts(date);
2120
+ const day = parts.find((part) => part.type === "day")?.value;
2121
+ const month = parts.find((part) => part.type === "month")?.value;
2122
+ const year = parts.find((part) => part.type === "year")?.value;
2123
+ const hour = parts.find((part) => part.type === "hour")?.value;
2124
+ const minute = parts.find((part) => part.type === "minute")?.value;
2125
+ if (!day || !month || !year || !hour || !minute) return "";
2126
+ return `${day}/${month}/${year} ${hour}:${minute}`;
2127
+ }
2128
+ function normalizePeriod(dayPeriod) {
2129
+ if (!dayPeriod) return "";
2130
+ if (/^p/i.test(dayPeriod)) return "pm";
2131
+ return /^a/i.test(dayPeriod) ? "am" : "";
2132
+ }
2133
+
2134
+ // src/react/screens/history/copy.ts
2135
+ const historyCopy = {
2136
+ title: "Historial de alertas",
2137
+ subtitle: "Consulta tus alertas, pruebas y recargas.",
2138
+ empty: "Todavía no tienes alertas en tu historial.",
2139
+ showMore: "Ver más",
2140
+ kindLabels: {
2141
+ emergency: "Emergencia",
2142
+ test: "Prueba",
2143
+ recharge: "Recarga"
2144
+ }
2145
+ };
2146
+ function formatContacts(count) {
2147
+ return count === 1 ? "1 contacto" : `${count} contactos`;
2148
+ }
2149
+ function formatMessages(count) {
2150
+ return count === 1 ? "1 mensaje" : `${count} mensajes`;
2151
+ }
2152
+ function formatHistoryRounds(count) {
2153
+ return count === 1 ? "1 ronda" : `${count} rondas`;
2154
+ }
2155
+
2156
+ // src/react/screens/history/data.ts
2157
+ /** Local demo alert history, newest first. */
2158
+ const DEMO_ALERT_HISTORY = [
2159
+ {
2160
+ id: "alert-2026-10-31",
2161
+ kind: "emergency",
2162
+ occurredAt: "2026-10-31T13:50:00",
2163
+ responders: 3,
2164
+ remainingMessages: 27
2165
+ },
2166
+ {
2167
+ id: "alert-2026-10-20",
2168
+ kind: "recharge",
2169
+ occurredAt: "2026-10-20T09:15:00",
2170
+ responders: 3,
2171
+ remainingMessages: 100
2172
+ },
2173
+ {
2174
+ id: "alert-2026-10-12",
2175
+ kind: "test",
2176
+ occurredAt: "2026-10-12T18:40:00",
2177
+ responders: 3,
2178
+ remainingMessages: 73
2179
+ },
2180
+ {
2181
+ id: "alert-2026-10-01",
2182
+ kind: "recharge",
2183
+ occurredAt: "2026-10-01T11:05:00",
2184
+ responders: 3,
2185
+ remainingMessages: 76
2186
+ }
2187
+ ];
2188
+ /** Returns a newest-first copy of the history. */
2189
+ function sortedHistoryEntries(entries = DEMO_ALERT_HISTORY) {
2190
+ return [...entries].sort((first, second) => new Date(second.occurredAt).getTime() - new Date(first.occurredAt).getTime());
2191
+ }
2192
+ /** Newest entry, used as the source of the current balance. */
2193
+ function latestHistoryEntry(entries = DEMO_ALERT_HISTORY) {
2194
+ return sortedHistoryEntries(entries)[0] ?? null;
2195
+ }
2196
+ /** Resolves a single history entry by id. */
2197
+ function historyEntryById(id, entries = DEMO_ALERT_HISTORY) {
2198
+ if (!id) return null;
2199
+ return entries.find((entry) => entry.id === id) ?? null;
2200
+ }
2201
+ /** Rounds represented by the entry's remaining messages and responders. */
2202
+ function historyEntryRounds(entry) {
2203
+ return calculateRounds(entry.remainingMessages, entry.responders);
2204
+ }
2205
+ /** Current balance derived from the newest history entry. */
2206
+ function currentAlertBalance(entries = DEMO_ALERT_HISTORY) {
2207
+ const latest = latestHistoryEntry(entries);
2208
+ if (!latest) return null;
2209
+ return {
2210
+ messages: latest.remainingMessages,
2211
+ responders: latest.responders,
2212
+ rounds: historyEntryRounds(latest)
2213
+ };
2214
+ }
2215
+
2216
+ // src/react/screens/history/AlertHistoryRow.tsx
2217
+ /** Reusable history row. The whole row is a button with a full text label. */
2218
+ function AlertHistoryRow({ entry, locale, onOpen }) {
2219
+ const occurredAt = formatDateTime(entry.occurredAt, locale);
2220
+ const rounds = historyEntryRounds(entry);
2221
+ const label = [
2222
+ historyCopy.kindLabels[entry.kind],
2223
+ occurredAt,
2224
+ formatContacts(entry.responders),
2225
+ `${formatMessages(entry.remainingMessages)} restantes`,
2226
+ `${formatHistoryRounds(rounds)} de avisos`
2227
+ ].join(", ");
2228
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
2229
+ className: cls("emergency-alert-row"),
2230
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2231
+ type: "button",
2232
+ className: cls("emergency-alert-row__button"),
2233
+ "aria-label": label,
2234
+ onClick: () => onOpen(entry.id),
2235
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2236
+ className: cls("emergency-alert-row__top"),
2237
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
2238
+ className: cls("emergency-alert-row__time"),
2239
+ dateTime: entry.occurredAt,
2240
+ children: occurredAt
2241
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2242
+ className: cls("emergency-alert-row__badge"),
2243
+ "data-kind": entry.kind,
2244
+ children: historyCopy.kindLabels[entry.kind]
2245
+ })]
2246
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2247
+ className: cls("emergency-alert-row__stats"),
2248
+ children: [
2249
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2250
+ className: cls("emergency-alert-row__stat"),
2251
+ children: formatContacts(entry.responders)
2252
+ }),
2253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2254
+ className: cls("emergency-alert-row__stat"),
2255
+ children: formatMessages(entry.remainingMessages)
2256
+ }),
2257
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2258
+ className: cls("emergency-alert-row__stat"),
2259
+ children: formatHistoryRounds(rounds)
2260
+ })
2261
+ ]
2262
+ })]
2263
+ })
2264
+ });
2265
+ }
2266
+
1944
2267
  // src/react/screens/layout.tsx
1945
2268
  /**
1946
2269
  * Shared shell for every screen. The wrapper is `display: contents` so the
@@ -1962,46 +2285,254 @@ function EmergencyScreenLayout({ screen, titleId, header, footer, children, body
1962
2285
  });
1963
2286
  }
1964
2287
 
1965
- // src/react/screens/PlaceholderScreen.tsx
1966
- /** Minimal in-design screen used to prove navigation before real designs land. */
1967
- function PlaceholderScreen({ screen, titleId, title, description, onClose, onBack }) {
2288
+ // src/react/screens/details/index.tsx
2289
+ function DetailsScreen({ titleId, locale, onClose }) {
2290
+ const { back, navigate } = useScreenNavigation();
2291
+ const balance = currentAlertBalance();
2292
+ const preview = sortedHistoryEntries().slice(0, 2);
1968
2293
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
1969
- screen,
2294
+ screen: "details",
1970
2295
  titleId,
2296
+ bodyClassName: "emergency-panel__body--details",
1971
2297
  header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
1972
2298
  titleId,
1973
- title,
1974
- onClose,
1975
- onBack
2299
+ title: detailsCopy.title,
2300
+ onClose: () => onClose("close"),
2301
+ onBack: back
1976
2302
  }),
1977
2303
  footer: null,
1978
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1979
- className: cls("emergency-screen__placeholder"),
1980
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1981
- className: cls("emergency-screen__placeholder-text"),
1982
- children: description
1983
- })
2304
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2305
+ className: cls("emergency-details"),
2306
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
2307
+ className: cls("emergency-details__section"),
2308
+ "aria-label": detailsCopy.summaryTitle,
2309
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2310
+ className: cls("emergency-details__card"),
2311
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2312
+ className: cls("emergency-details__card-header"),
2313
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2314
+ className: cls("emergency-details__card-title"),
2315
+ children: detailsCopy.summaryTitle
2316
+ })
2317
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2318
+ className: cls("emergency-details__card-body"),
2319
+ children: balance ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2320
+ className: cls("emergency-details__balance"),
2321
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlertBalanceSentence, {
2322
+ messages: balance.messages,
2323
+ responders: balance.responders
2324
+ })
2325
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2326
+ className: cls("emergency-details__empty"),
2327
+ children: detailsCopy.summaryEmpty
2328
+ })
2329
+ })]
2330
+ })
2331
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2332
+ className: cls("emergency-details__section"),
2333
+ "aria-label": detailsCopy.historyTitle,
2334
+ children: [
2335
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2336
+ className: cls("emergency-details__heading"),
2337
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2338
+ className: cls("emergency-details__title"),
2339
+ children: detailsCopy.historyTitle
2340
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2341
+ className: cls("emergency-details__subtitle"),
2342
+ children: detailsCopy.historySubtitle
2343
+ })]
2344
+ }),
2345
+ preview.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2346
+ className: cls("emergency-details__list"),
2347
+ children: preview.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlertHistoryRow, {
2348
+ entry,
2349
+ locale,
2350
+ onOpen: (messageId) => navigate("message-detail", { messageId })
2351
+ }, entry.id))
2352
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2353
+ className: cls("emergency-details__empty"),
2354
+ children: detailsCopy.historyEmpty
2355
+ }),
2356
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2357
+ type: "button",
2358
+ className: cls("emergency-details__history-button"),
2359
+ onClick: () => navigate("history"),
2360
+ children: detailsCopy.viewHistory
2361
+ })
2362
+ ]
2363
+ })]
1984
2364
  })
1985
2365
  });
1986
2366
  }
1987
2367
 
1988
- // src/react/screens/details/copy.ts
1989
- const detailsCopy = {
1990
- title: "Detalles",
1991
- description: "Aquí podrás consultar el detalle de tus alertas disponibles. Esta pantalla estará disponible próximamente."
1992
- };
2368
+ // src/react/screens/history/index.tsx
2369
+ function HistoryScreen({ titleId, locale, onClose }) {
2370
+ const { back, navigate } = useScreenNavigation();
2371
+ const listId = (0, react.useId)();
2372
+ const [expanded, setExpanded] = (0, react.useState)(false);
2373
+ const entries = sortedHistoryEntries();
2374
+ const hiddenCount = Math.max(0, entries.length - 2);
2375
+ const visibleEntries = expanded ? entries : entries.slice(0, 2);
2376
+ const showMore = hiddenCount > 0 && !expanded;
2377
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2378
+ screen: "history",
2379
+ titleId,
2380
+ bodyClassName: "emergency-panel__body--history",
2381
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2382
+ titleId,
2383
+ title: historyCopy.title,
2384
+ onClose: () => onClose("close"),
2385
+ onBack: back
2386
+ }),
2387
+ footer: null,
2388
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2389
+ className: cls("emergency-history"),
2390
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2391
+ className: cls("emergency-history__heading"),
2392
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2393
+ className: cls("emergency-history__title"),
2394
+ children: historyCopy.title
2395
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2396
+ className: cls("emergency-history__subtitle"),
2397
+ children: historyCopy.subtitle
2398
+ })]
2399
+ }), entries.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2400
+ className: cls("emergency-history__list"),
2401
+ id: listId,
2402
+ children: visibleEntries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlertHistoryRow, {
2403
+ entry,
2404
+ locale,
2405
+ onOpen: (messageId) => navigate("message-detail", { messageId })
2406
+ }, entry.id))
2407
+ }), showMore ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2408
+ type: "button",
2409
+ className: cls("emergency-history__more"),
2410
+ "aria-expanded": expanded,
2411
+ "aria-controls": listId,
2412
+ onClick: () => setExpanded(true),
2413
+ children: historyCopy.showMore
2414
+ }) : null] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2415
+ className: cls("emergency-history__empty"),
2416
+ children: historyCopy.empty
2417
+ })]
2418
+ })
2419
+ });
2420
+ }
1993
2421
 
1994
- // src/react/screens/details/index.tsx
1995
- function DetailsScreen({ titleId, onClose }) {
1996
- const { back } = useScreenNavigation();
1997
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlaceholderScreen, {
1998
- screen: "details",
2422
+ // src/react/screens/message-detail/index.tsx
2423
+ const messageDetailCopy = {
2424
+ title: "Detalle del mensaje",
2425
+ dateTime: "Fecha y hora",
2426
+ kind: "Tipo",
2427
+ responders: "Contactos de emergencia",
2428
+ messages: "Mensajes restantes",
2429
+ rounds: "Rondas de avisos",
2430
+ notFound: "No encontramos este mensaje. Puede que ya no esté disponible en tu historial.",
2431
+ backToHistory: "Volver al historial"
2432
+ };
2433
+ function MessageDetailScreen({ titleId, locale, onClose }) {
2434
+ const { back, params, replace } = useScreenNavigation();
2435
+ const entry = historyEntryById(params.messageId);
2436
+ const header = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
1999
2437
  titleId,
2000
- title: detailsCopy.title,
2001
- description: detailsCopy.description,
2438
+ title: messageDetailCopy.title,
2002
2439
  onClose: () => onClose("close"),
2003
2440
  onBack: back
2004
2441
  });
2442
+ if (!entry) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2443
+ screen: "message-detail",
2444
+ titleId,
2445
+ bodyClassName: "emergency-panel__body--message-detail",
2446
+ header,
2447
+ footer: null,
2448
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2449
+ className: cls("emergency-message-detail__empty"),
2450
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2451
+ className: cls("emergency-message-detail__empty-text"),
2452
+ children: messageDetailCopy.notFound
2453
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2454
+ type: "button",
2455
+ className: cls("emergency-message-detail__back"),
2456
+ onClick: () => replace("history"),
2457
+ children: messageDetailCopy.backToHistory
2458
+ })]
2459
+ })
2460
+ });
2461
+ const rounds = historyEntryRounds(entry);
2462
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2463
+ screen: "message-detail",
2464
+ titleId,
2465
+ bodyClassName: "emergency-panel__body--message-detail",
2466
+ header,
2467
+ footer: null,
2468
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2469
+ className: cls("emergency-message-detail"),
2470
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2471
+ className: cls("emergency-message-detail__card"),
2472
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2473
+ className: cls("emergency-message-detail__card-header"),
2474
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2475
+ className: cls("emergency-message-detail__card-title"),
2476
+ children: messageDetailCopy.title
2477
+ })
2478
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
2479
+ className: cls("emergency-message-detail__list"),
2480
+ children: [
2481
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2482
+ className: cls("emergency-message-detail__row"),
2483
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2484
+ className: cls("emergency-message-detail__label"),
2485
+ children: messageDetailCopy.dateTime
2486
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2487
+ className: cls("emergency-message-detail__value"),
2488
+ children: formatDateTime(entry.occurredAt, locale)
2489
+ })]
2490
+ }),
2491
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2492
+ className: cls("emergency-message-detail__row"),
2493
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2494
+ className: cls("emergency-message-detail__label"),
2495
+ children: messageDetailCopy.kind
2496
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2497
+ className: cls("emergency-message-detail__value"),
2498
+ children: historyCopy.kindLabels[entry.kind]
2499
+ })]
2500
+ }),
2501
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2502
+ className: cls("emergency-message-detail__row"),
2503
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2504
+ className: cls("emergency-message-detail__label"),
2505
+ children: messageDetailCopy.responders
2506
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2507
+ className: cls("emergency-message-detail__value"),
2508
+ children: formatContacts(entry.responders)
2509
+ })]
2510
+ }),
2511
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2512
+ className: cls("emergency-message-detail__row"),
2513
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2514
+ className: cls("emergency-message-detail__label"),
2515
+ children: messageDetailCopy.messages
2516
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2517
+ className: cls("emergency-message-detail__value"),
2518
+ children: formatMessages(entry.remainingMessages)
2519
+ })]
2520
+ }),
2521
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2522
+ className: cls("emergency-message-detail__row"),
2523
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2524
+ className: cls("emergency-message-detail__label"),
2525
+ children: messageDetailCopy.rounds
2526
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2527
+ className: cls("emergency-message-detail__value"),
2528
+ children: formatHistoryRounds(rounds)
2529
+ })]
2530
+ })
2531
+ ]
2532
+ })]
2533
+ })
2534
+ })
2535
+ });
2005
2536
  }
2006
2537
 
2007
2538
  // src/core/phone.ts
@@ -2026,9 +2557,9 @@ function toVenezuelanE164(input) {
2026
2557
 
2027
2558
  // src/react/components/EmergencyFooter/index.tsx
2028
2559
  function EmergencyFooter(props) {
2029
- const { active, busy, aborting, feedbackVisible, showSimulation = true, emergencyDisabled = false, onActivate, onRequestAbort, onRequestSimulation } = props;
2560
+ const { active, busy, aborting, feedbackVisible, showSimulation = true, emergencyDisabled = false, square = false, onActivate, onRequestAbort, onRequestSimulation } = props;
2030
2561
  if (active) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
2031
- className: cls("emergency-panel__footer"),
2562
+ className: cls("emergency-panel__footer", square && "emergency-panel__footer--square"),
2032
2563
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2033
2564
  type: "button",
2034
2565
  className: cls("emergency-status__abort-btn"),
@@ -2039,7 +2570,7 @@ function EmergencyFooter(props) {
2039
2570
  })
2040
2571
  });
2041
2572
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2042
- className: cls("emergency-panel__footer", !showSimulation && "emergency-panel__footer--single"),
2573
+ className: cls("emergency-panel__footer", !showSimulation && "emergency-panel__footer--single", square && "emergency-panel__footer--square"),
2043
2574
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2044
2575
  type: "button",
2045
2576
  className: cls("emergency-panel__button", "emergency-panel__button--emergency"),
@@ -2314,9 +2845,9 @@ function EmergencyError({ error, onRetry }) {
2314
2845
 
2315
2846
  // src/react/components/EmergencyFeedback/index.tsx
2316
2847
  /** Time the feedback stays on screen before dismissing itself. */
2317
- const FEEDBACK_AUTO_DISMISS_MS = 3e3;
2848
+ const FEEDBACK_AUTO_DISMISS_MS = 12e3;
2318
2849
  /** Time the geolocation feedback stays on screen before dismissing itself. */
2319
- const GEOLOCATION_FEEDBACK_AUTO_DISMISS_MS = 5e3;
2850
+ const GEOLOCATION_FEEDBACK_AUTO_DISMISS_MS = 12e3;
2320
2851
  function geolocationText(error) {
2321
2852
  switch (error.code) {
2322
2853
  case "geolocation_denied": return copy.geolocationDenied;
@@ -2402,32 +2933,6 @@ function EmergencyLoading() {
2402
2933
  });
2403
2934
  }
2404
2935
 
2405
- // src/core/date.ts
2406
- /**
2407
- * Formats a timestamp as `h:mmam` or `h:mmpm` in local time, without a space
2408
- * or leading zero.
2409
- */
2410
- function formatShortTime(value, locale = "es") {
2411
- if (value === null) return "";
2412
- const date = value instanceof Date ? value : new Date(value);
2413
- if (Number.isNaN(date.getTime())) return "";
2414
- const parts = new Intl.DateTimeFormat(locale, {
2415
- hour: "numeric",
2416
- minute: "2-digit",
2417
- hour12: true
2418
- }).formatToParts(date);
2419
- const hour = parts.find((part) => part.type === "hour")?.value;
2420
- const minute = parts.find((part) => part.type === "minute")?.value;
2421
- const dayPeriod = parts.find((part) => part.type === "dayPeriod")?.value;
2422
- if (!hour || !minute) return "";
2423
- return `${hour}:${minute}${normalizePeriod(dayPeriod)}`;
2424
- }
2425
- function normalizePeriod(dayPeriod) {
2426
- if (!dayPeriod) return "";
2427
- if (/^p/i.test(dayPeriod)) return "pm";
2428
- return /^a/i.test(dayPeriod) ? "am" : "";
2429
- }
2430
-
2431
2936
  // src/react/components/StatusIcon/index.tsx
2432
2937
  /**
2433
2938
  * Explicit responder state model:
@@ -2588,23 +3093,9 @@ function formatRounds(rounds) {
2588
3093
  return `${rounds} ${roundsUnit(rounds)}`;
2589
3094
  }
2590
3095
 
2591
- // src/react/screens/primary/data.ts
2592
- /**
2593
- * Derives the remaining alert rounds from available messages and designated
2594
- * responders. Invalid or non-positive input collapses to the minimum of one
2595
- * round instead of producing `Infinity`, `NaN` or a negative value.
2596
- */
2597
- function calculateRounds(messages, responders) {
2598
- if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
2599
- const availableMessages = Math.floor(messages);
2600
- const designatedResponders = Math.floor(responders);
2601
- if (availableMessages <= 0 || designatedResponders <= 0) return 1;
2602
- return Math.max(1, Math.floor(availableMessages / designatedResponders));
2603
- }
2604
-
2605
3096
  // src/react/screens/primary/Availability.tsx
2606
3097
  function Availability({ onRecharge, onDetails }) {
2607
- const rounds = calculateRounds(100, 3);
3098
+ const rounds = calculateRounds(27, 3);
2608
3099
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2609
3100
  className: cls("emergency-availability"),
2610
3101
  "aria-label": primaryCopy.availabilityTitle,
@@ -2646,23 +3137,30 @@ function Availability({ onRecharge, onDetails }) {
2646
3137
 
2647
3138
  // src/react/components/EmergencyBody/index.tsx
2648
3139
  function EmergencyBody(props) {
2649
- const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails } = props;
3140
+ const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails, onClose } = props;
2650
3141
  const showingFeedback = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2651
3142
  const idle = !active && !loading && !showingFeedback && error === null;
2652
3143
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2653
3144
  className: cls("emergency-panel__body-split", idle && "emergency-panel__body-split--with-availability"),
2654
- children: [idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3145
+ children: [idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2655
3146
  className: cls("emergency-panel__availability-region"),
2656
3147
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Availability, {
2657
3148
  onRecharge,
2658
3149
  onDetails
2659
3150
  })
2660
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", {
2661
- className: cls("emergency-panel__divider"),
2662
- "aria-hidden": "true"
2663
- })] }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2664
- className: cls("emergency-panel__prompt-region"),
3151
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3152
+ className: cls("emergency-panel__prompt-region", idle && "emergency-panel__prompt-region--idle"),
2665
3153
  children: [
3154
+ idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3155
+ type: "button",
3156
+ className: cls("emergency-panel__close-button", "emergency-panel__close-button--prompt"),
3157
+ "aria-label": copy.close,
3158
+ onClick: onClose,
3159
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
3160
+ name: "close",
3161
+ className: cls("emergency-panel__close-icon")
3162
+ })
3163
+ }) : null,
2666
3164
  !loading && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2667
3165
  className: cls("sos-img__container"),
2668
3166
  id: "sos-img__container",
@@ -2706,12 +3204,12 @@ function PrimaryScreen(props) {
2706
3204
  const { navigate } = useScreenNavigation();
2707
3205
  const idle = !props.active && !props.loading && !props.feedbackVisible && props.error === null;
2708
3206
  const needsContactPhone = props.contactPhone === null;
2709
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
3207
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(EmergencyScreenLayout, {
2710
3208
  screen: "primary",
2711
3209
  titleId: props.titleId,
2712
- header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
3210
+ bodyClassName: idle ? "emergency-panel__body--primary" : void 0,
3211
+ header: idle ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2713
3212
  titleId: props.titleId,
2714
- variant: idle ? "close-only" : "default",
2715
3213
  onClose: () => props.onClose("close")
2716
3214
  }),
2717
3215
  footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFooter, {
@@ -2719,11 +3217,17 @@ function PrimaryScreen(props) {
2719
3217
  busy: props.busy,
2720
3218
  aborting: props.aborting,
2721
3219
  feedbackVisible: props.feedbackVisible,
3220
+ square: idle,
2722
3221
  onActivate: needsContactPhone ? () => navigate("pre-emergency") : props.onActivate,
2723
3222
  onRequestAbort: props.onRequestAbort,
2724
3223
  onRequestSimulation: props.onRequestSimulation
2725
3224
  }),
2726
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyBody, {
3225
+ children: [idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
3226
+ className: cls("emergency-panel__sr-only"),
3227
+ id: props.titleId,
3228
+ tabIndex: -1,
3229
+ children: copy.panelLabel
3230
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyBody, {
2727
3231
  active: props.active,
2728
3232
  responders: props.responders,
2729
3233
  refreshing: props.refreshing,
@@ -2738,8 +3242,9 @@ function PrimaryScreen(props) {
2738
3242
  onRetry: props.onRetry,
2739
3243
  onDismissFeedback: props.onDismissFeedback,
2740
3244
  onRecharge: () => navigate("pricing"),
2741
- onDetails: () => navigate("details")
2742
- })
3245
+ onDetails: () => navigate("details"),
3246
+ onClose: () => props.onClose("close")
3247
+ })]
2743
3248
  });
2744
3249
  }
2745
3250
 
@@ -2912,7 +3417,9 @@ const screenRegistry = {
2912
3417
  primary: PrimaryScreen,
2913
3418
  "pre-emergency": PreEmergencyScreen,
2914
3419
  pricing: PricingScreen,
2915
- details: DetailsScreen
3420
+ details: DetailsScreen,
3421
+ history: HistoryScreen,
3422
+ "message-detail": MessageDetailScreen
2916
3423
  };
2917
3424
 
2918
3425
  // src/react/components/EmergencyPanel/index.tsx
@@ -2941,7 +3448,7 @@ function ActiveScreen({ screenProps, open, forcePrimary }) {
2941
3448
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Screen, { ...screenProps });
2942
3449
  }
2943
3450
  function EmergencyPanel(props) {
2944
- 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;
3451
+ 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;
2945
3452
  const titleId = (0, react.useId)();
2946
3453
  const dialogRef = (0, react.useRef)(null);
2947
3454
  const feedbackVisible = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
@@ -2993,6 +3500,7 @@ function EmergencyPanel(props) {
2993
3500
  }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyConfirmation, {
2994
3501
  abortConfirmation,
2995
3502
  simulationConfirmation,
3503
+ loading: abortConfirmation ? aborting : simulating,
2996
3504
  onCancelAbort,
2997
3505
  onCancelSimulation,
2998
3506
  onConfirmAbort,
@@ -3102,6 +3610,7 @@ function EmergencyRoot() {
3102
3610
  active,
3103
3611
  busy,
3104
3612
  aborting: phase.kind === "aborting",
3613
+ simulating: phase.kind === "simulating",
3105
3614
  loading: phase.kind === "initializing" || phase.kind === "activating",
3106
3615
  locale: config.locale ?? "es",
3107
3616
  responders,