@dataemergencia/emergency 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -763,6 +763,7 @@ function createEmergencyStore(config) {
763
763
  lastError: null,
764
764
  geolocation: null,
765
765
  geolocationError: null,
766
+ contactPhone: null,
766
767
  realtimeStatus: "idle",
767
768
  abortResult: null,
768
769
  simulationResult: null,
@@ -1205,7 +1206,8 @@ function createEmergencyController(deps) {
1205
1206
  patch({
1206
1207
  panelOpen: false,
1207
1208
  abortConfirmation: false,
1208
- simulationConfirmation: false
1209
+ simulationConfirmation: false,
1210
+ contactPhone: null
1209
1211
  });
1210
1212
  events.emit("panel:closed", { reason });
1211
1213
  }
@@ -1254,6 +1256,7 @@ function createEmergencyController(deps) {
1254
1256
  lastError: null,
1255
1257
  geolocation: null,
1256
1258
  geolocationError: null,
1259
+ contactPhone: null,
1257
1260
  realtimeStatus: "idle",
1258
1261
  abortResult: null,
1259
1262
  simulationResult: null,
@@ -1295,7 +1298,8 @@ function createEmergencyController(deps) {
1295
1298
  geolocationError: null,
1296
1299
  ...state.geolocationError !== null || state.abortResult?.ok === false || state.simulationResult?.ok === false || state.phase.kind === "ready" && state.lastError !== null ? { lastError: null } : {}
1297
1300
  });
1298
- }
1301
+ },
1302
+ setContactPhone: (phone) => patch({ contactPhone: phone })
1299
1303
  },
1300
1304
  store,
1301
1305
  events
@@ -1526,6 +1530,11 @@ const paths = {
1526
1530
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 7h.01" })
1527
1531
  ] }),
1528
1532
  "arrow-left": /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19 12H5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m12 19-7-7 7-7" })] }),
1533
+ "map-pin": /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 22s7-6.2 7-12a7 7 0 1 0-14 0c0 5.8 7 12 7 12z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1534
+ cx: "12",
1535
+ cy: "10",
1536
+ r: "2.5"
1537
+ })] }),
1529
1538
  "credit-card-plus": /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1530
1539
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5" }),
1531
1540
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 12h.01" }),
@@ -1567,6 +1576,12 @@ var panel_module_default = {
1567
1576
  "de-emergency-slide-in": "de-emergency-de-emergency-slide-in-nA5Hwa",
1568
1577
  "de-emergency-slide-up": "de-emergency-de-emergency-slide-up-nA5Hwa",
1569
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",
1570
1585
  "emergency-availability": "de-emergency-emergency-availability-nA5Hwa",
1571
1586
  "emergency-availability__card": "de-emergency-emergency-availability__card-nA5Hwa",
1572
1587
  "emergency-availability__card-body": "de-emergency-emergency-availability__card-body-nA5Hwa",
@@ -1590,6 +1605,19 @@ var panel_module_default = {
1590
1605
  "emergency-confirmation__icon--information": "de-emergency-emergency-confirmation__icon--information-nA5Hwa",
1591
1606
  "emergency-confirmation__text": "de-emergency-emergency-confirmation__text-nA5Hwa",
1592
1607
  "emergency-confirmation__title": "de-emergency-emergency-confirmation__title-nA5Hwa",
1608
+ "emergency-details": "de-emergency-emergency-details-nA5Hwa",
1609
+ "emergency-details__balance": "de-emergency-emergency-details__balance-nA5Hwa",
1610
+ "emergency-details__card": "de-emergency-emergency-details__card-nA5Hwa",
1611
+ "emergency-details__card-body": "de-emergency-emergency-details__card-body-nA5Hwa",
1612
+ "emergency-details__card-header": "de-emergency-emergency-details__card-header-nA5Hwa",
1613
+ "emergency-details__card-title": "de-emergency-emergency-details__card-title-nA5Hwa",
1614
+ "emergency-details__empty": "de-emergency-emergency-details__empty-nA5Hwa",
1615
+ "emergency-details__heading": "de-emergency-emergency-details__heading-nA5Hwa",
1616
+ "emergency-details__history-button": "de-emergency-emergency-details__history-button-nA5Hwa",
1617
+ "emergency-details__list": "de-emergency-emergency-details__list-nA5Hwa",
1618
+ "emergency-details__section": "de-emergency-emergency-details__section-nA5Hwa",
1619
+ "emergency-details__subtitle": "de-emergency-emergency-details__subtitle-nA5Hwa",
1620
+ "emergency-details__title": "de-emergency-emergency-details__title-nA5Hwa",
1593
1621
  "emergency-error-message": "de-emergency-emergency-error-message-nA5Hwa",
1594
1622
  "emergency-error-message__container": "de-emergency-emergency-error-message__container-nA5Hwa",
1595
1623
  "emergency-error-message__content": "de-emergency-emergency-error-message__content-nA5Hwa",
@@ -1600,10 +1628,33 @@ var panel_module_default = {
1600
1628
  "emergency-error-message__title": "de-emergency-emergency-error-message__title-nA5Hwa",
1601
1629
  "emergency-feedback": "de-emergency-emergency-feedback-nA5Hwa",
1602
1630
  "emergency-feedback__dismiss": "de-emergency-emergency-feedback__dismiss-nA5Hwa",
1631
+ "emergency-history": "de-emergency-emergency-history-nA5Hwa",
1632
+ "emergency-history__empty": "de-emergency-emergency-history__empty-nA5Hwa",
1633
+ "emergency-history__heading": "de-emergency-emergency-history__heading-nA5Hwa",
1634
+ "emergency-history__list": "de-emergency-emergency-history__list-nA5Hwa",
1635
+ "emergency-history__more": "de-emergency-emergency-history__more-nA5Hwa",
1636
+ "emergency-history__subtitle": "de-emergency-emergency-history__subtitle-nA5Hwa",
1637
+ "emergency-history__title": "de-emergency-emergency-history__title-nA5Hwa",
1638
+ "emergency-message-detail": "de-emergency-emergency-message-detail-nA5Hwa",
1639
+ "emergency-message-detail__back": "de-emergency-emergency-message-detail__back-nA5Hwa",
1640
+ "emergency-message-detail__card": "de-emergency-emergency-message-detail__card-nA5Hwa",
1641
+ "emergency-message-detail__card-header": "de-emergency-emergency-message-detail__card-header-nA5Hwa",
1642
+ "emergency-message-detail__card-title": "de-emergency-emergency-message-detail__card-title-nA5Hwa",
1643
+ "emergency-message-detail__empty": "de-emergency-emergency-message-detail__empty-nA5Hwa",
1644
+ "emergency-message-detail__empty-text": "de-emergency-emergency-message-detail__empty-text-nA5Hwa",
1645
+ "emergency-message-detail__label": "de-emergency-emergency-message-detail__label-nA5Hwa",
1646
+ "emergency-message-detail__list": "de-emergency-emergency-message-detail__list-nA5Hwa",
1647
+ "emergency-message-detail__row": "de-emergency-emergency-message-detail__row-nA5Hwa",
1648
+ "emergency-message-detail__value": "de-emergency-emergency-message-detail__value-nA5Hwa",
1603
1649
  "emergency-panel": "de-emergency-emergency-panel-nA5Hwa",
1604
1650
  "emergency-panel__availability-region": "de-emergency-emergency-panel__availability-region-nA5Hwa",
1605
1651
  "emergency-panel__body": "de-emergency-emergency-panel__body-nA5Hwa",
1652
+ "emergency-panel__body--details": "de-emergency-emergency-panel__body--details-nA5Hwa",
1653
+ "emergency-panel__body--history": "de-emergency-emergency-panel__body--history-nA5Hwa",
1654
+ "emergency-panel__body--message-detail": "de-emergency-emergency-panel__body--message-detail-nA5Hwa",
1655
+ "emergency-panel__body--pre-emergency": "de-emergency-emergency-panel__body--pre-emergency-nA5Hwa",
1606
1656
  "emergency-panel__body--pricing": "de-emergency-emergency-panel__body--pricing-nA5Hwa",
1657
+ "emergency-panel__body--primary": "de-emergency-emergency-panel__body--primary-nA5Hwa",
1607
1658
  "emergency-panel__body-split": "de-emergency-emergency-panel__body-split-nA5Hwa",
1608
1659
  "emergency-panel__body-split--with-availability": "de-emergency-emergency-panel__body-split--with-availability-nA5Hwa",
1609
1660
  "emergency-panel__body-title": "de-emergency-emergency-panel__body-title-nA5Hwa",
@@ -1612,19 +1663,37 @@ var panel_module_default = {
1612
1663
  "emergency-panel__button--emergency": "de-emergency-emergency-panel__button--emergency-nA5Hwa",
1613
1664
  "emergency-panel__button--simulation": "de-emergency-emergency-panel__button--simulation-nA5Hwa",
1614
1665
  "emergency-panel__close-button": "de-emergency-emergency-panel__close-button-nA5Hwa",
1666
+ "emergency-panel__close-button--prompt": "de-emergency-emergency-panel__close-button--prompt-nA5Hwa",
1615
1667
  "emergency-panel__close-icon": "de-emergency-emergency-panel__close-icon-nA5Hwa",
1616
1668
  "emergency-panel__footer": "de-emergency-emergency-panel__footer-nA5Hwa",
1669
+ "emergency-panel__footer--single": "de-emergency-emergency-panel__footer--single-nA5Hwa",
1670
+ "emergency-panel__footer--square": "de-emergency-emergency-panel__footer--square-nA5Hwa",
1617
1671
  "emergency-panel__header": "de-emergency-emergency-panel__header-nA5Hwa",
1618
1672
  "emergency-panel__prompt-region": "de-emergency-emergency-panel__prompt-region-nA5Hwa",
1673
+ "emergency-panel__prompt-region--idle": "de-emergency-emergency-panel__prompt-region--idle-nA5Hwa",
1674
+ "emergency-panel__sr-only": "de-emergency-emergency-panel__sr-only-nA5Hwa",
1619
1675
  "emergency-panel__title": "de-emergency-emergency-panel__title-nA5Hwa",
1620
1676
  "emergency-panel--hidden": "de-emergency-emergency-panel--hidden-nA5Hwa",
1677
+ "emergency-pre-emergency": "de-emergency-emergency-pre-emergency-nA5Hwa",
1678
+ "emergency-pre-emergency__error": "de-emergency-emergency-pre-emergency__error-nA5Hwa",
1679
+ "emergency-pre-emergency__form": "de-emergency-emergency-pre-emergency__form-nA5Hwa",
1680
+ "emergency-pre-emergency__input": "de-emergency-emergency-pre-emergency__input-nA5Hwa",
1681
+ "emergency-pre-emergency__input--area-code": "de-emergency-emergency-pre-emergency__input--area-code-nA5Hwa",
1682
+ "emergency-pre-emergency__input--invalid": "de-emergency-emergency-pre-emergency__input--invalid-nA5Hwa",
1683
+ "emergency-pre-emergency__input--number": "de-emergency-emergency-pre-emergency__input--number-nA5Hwa",
1684
+ "emergency-pre-emergency__input--valid": "de-emergency-emergency-pre-emergency__input--valid-nA5Hwa",
1685
+ "emergency-pre-emergency__label": "de-emergency-emergency-pre-emergency__label-nA5Hwa",
1686
+ "emergency-pre-emergency__notice": "de-emergency-emergency-pre-emergency__notice-nA5Hwa",
1687
+ "emergency-pre-emergency__notice-icon": "de-emergency-emergency-pre-emergency__notice-icon-nA5Hwa",
1688
+ "emergency-pre-emergency__notice-text": "de-emergency-emergency-pre-emergency__notice-text-nA5Hwa",
1689
+ "emergency-pre-emergency__phone-row": "de-emergency-emergency-pre-emergency__phone-row-nA5Hwa",
1621
1690
  "emergency-pricing": "de-emergency-emergency-pricing-nA5Hwa",
1622
1691
  "emergency-pricing__badge": "de-emergency-emergency-pricing__badge-nA5Hwa",
1623
- "emergency-pricing__footnote": "de-emergency-emergency-pricing__footnote-nA5Hwa",
1624
1692
  "emergency-pricing__heading": "de-emergency-emergency-pricing__heading-nA5Hwa",
1625
1693
  "emergency-pricing__package": "de-emergency-emergency-pricing__package-nA5Hwa",
1626
1694
  "emergency-pricing__package--highlighted": "de-emergency-emergency-pricing__package--highlighted-nA5Hwa",
1627
1695
  "emergency-pricing__package-caption": "de-emergency-emergency-pricing__package-caption-nA5Hwa",
1696
+ "emergency-pricing__package-footnote": "de-emergency-emergency-pricing__package-footnote-nA5Hwa",
1628
1697
  "emergency-pricing__package-header": "de-emergency-emergency-pricing__package-header-nA5Hwa",
1629
1698
  "emergency-pricing__package-label": "de-emergency-emergency-pricing__package-label-nA5Hwa",
1630
1699
  "emergency-pricing__package-name": "de-emergency-emergency-pricing__package-name-nA5Hwa",
@@ -1820,18 +1889,27 @@ function useFocusTrap(ref, active) {
1820
1889
  }
1821
1890
 
1822
1891
  // src/react/screens/context.tsx
1892
+ const EMPTY_PARAMS = {};
1823
1893
  const EmergencyScreenContext = (0, react.createContext)(null);
1894
+ function paramsEqual(first, second) {
1895
+ return first.messageId === second.messageId;
1896
+ }
1824
1897
  function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
1825
1898
  const [state, setState] = (0, react.useState)({
1826
1899
  screen: initialScreen,
1900
+ params: EMPTY_PARAMS,
1827
1901
  history: []
1828
1902
  });
1829
- const navigate = (0, react.useCallback)((next) => {
1903
+ const navigate = (0, react.useCallback)((next, params = EMPTY_PARAMS) => {
1830
1904
  setState((current) => {
1831
- if (current.screen === next) return current;
1905
+ if (current.screen === next && paramsEqual(current.params, params)) return current;
1832
1906
  return {
1833
1907
  screen: next,
1834
- history: [...current.history, current.screen]
1908
+ params,
1909
+ history: [...current.history, {
1910
+ screen: current.screen,
1911
+ params: current.params
1912
+ }]
1835
1913
  };
1836
1914
  });
1837
1915
  }, []);
@@ -1840,31 +1918,47 @@ function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
1840
1918
  const previous = current.history.at(-1);
1841
1919
  if (!previous) return current;
1842
1920
  return {
1843
- screen: previous,
1921
+ screen: previous.screen,
1922
+ params: previous.params,
1844
1923
  history: current.history.slice(0, -1)
1845
1924
  };
1846
1925
  });
1847
1926
  }, []);
1927
+ const replace = (0, react.useCallback)((next, params = EMPTY_PARAMS) => {
1928
+ setState((current) => {
1929
+ if (current.screen === next && paramsEqual(current.params, params)) return current;
1930
+ return {
1931
+ screen: next,
1932
+ params,
1933
+ history: current.history.at(-1)?.screen === next ? current.history.slice(0, -1) : current.history
1934
+ };
1935
+ });
1936
+ }, []);
1848
1937
  const reset = (0, react.useCallback)(() => {
1849
1938
  setState((current) => {
1850
- if (current.screen === "primary" && current.history.length === 0) return current;
1939
+ if (current.screen === "primary" && current.params.messageId === void 0 && current.history.length === 0) return current;
1851
1940
  return {
1852
1941
  screen: "primary",
1942
+ params: EMPTY_PARAMS,
1853
1943
  history: []
1854
1944
  };
1855
1945
  });
1856
1946
  }, []);
1857
1947
  const value = (0, react.useMemo)(() => ({
1858
1948
  screen: state.screen,
1949
+ params: state.params,
1859
1950
  history: state.history,
1860
1951
  navigate,
1952
+ replace,
1861
1953
  back,
1862
1954
  reset
1863
1955
  }), [
1864
1956
  back,
1865
1957
  navigate,
1958
+ replace,
1866
1959
  reset,
1867
1960
  state.history,
1961
+ state.params,
1868
1962
  state.screen
1869
1963
  ]);
1870
1964
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenContext.Provider, {
@@ -1913,6 +2007,245 @@ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack })
1913
2007
  });
1914
2008
  }
1915
2009
 
2010
+ // src/react/screens/primary/data.ts
2011
+ /**
2012
+ * Derives the remaining alert rounds from available messages and designated
2013
+ * responders. Invalid or non-positive input collapses to the minimum of one
2014
+ * round instead of producing `Infinity`, `NaN` or a negative value.
2015
+ */
2016
+ function calculateRounds(messages, responders) {
2017
+ if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
2018
+ const availableMessages = Math.floor(messages);
2019
+ const designatedResponders = Math.floor(responders);
2020
+ if (availableMessages <= 0 || designatedResponders <= 0) return 1;
2021
+ return Math.max(1, Math.floor(availableMessages / designatedResponders));
2022
+ }
2023
+
2024
+ // src/react/screens/details/copy.ts
2025
+ const detailsCopy = {
2026
+ title: "Detalles",
2027
+ summaryTitle: "Alertas Disponibles",
2028
+ historyTitle: "Historial de alertas",
2029
+ historySubtitle: "Tus últimas alertas y recargas.",
2030
+ historyEmpty: "Todavía no tienes alertas en tu historial.",
2031
+ summaryEmpty: "Todavía no tienes un saldo de alertas disponible.",
2032
+ viewHistory: "Ver historial",
2033
+ messagesSingular: "mensaje de alerta",
2034
+ messagesPlural: "mensajes de alerta",
2035
+ contactsSingular: "contacto de emergencia",
2036
+ contactsPlural: "contactos de emergencia",
2037
+ roundsSingular: "ronda de avisos",
2038
+ roundsPlural: "rondas de avisos"
2039
+ };
2040
+ /** Returns the balance sentence split into text and emphasized number parts. */
2041
+ function alertBalanceParts(messages, responders) {
2042
+ if (!Number.isFinite(messages) || !Number.isFinite(responders) || messages < 0 || responders < 0) return [];
2043
+ const rounds = calculateRounds(messages, responders);
2044
+ const messagesUnit = messages === 1 ? detailsCopy.messagesSingular : detailsCopy.messagesPlural;
2045
+ const contactsUnit = responders === 1 ? detailsCopy.contactsSingular : detailsCopy.contactsPlural;
2046
+ const roundsUnit = rounds === 1 ? detailsCopy.roundsSingular : detailsCopy.roundsPlural;
2047
+ return [
2048
+ messages === 1 ? "Te queda " : "Te quedan ",
2049
+ messages,
2050
+ ` ${messagesUnit}, dado que tienes `,
2051
+ responders,
2052
+ ` ${contactsUnit}, `,
2053
+ rounds === 1 ? "te queda " : "te quedan ",
2054
+ rounds,
2055
+ ` ${roundsUnit}.`
2056
+ ];
2057
+ }
2058
+
2059
+ // src/react/screens/details/AlertBalanceSentence.tsx
2060
+ /** Renders the balance sentence with its numbers visually emphasized. */
2061
+ function AlertBalanceSentence({ messages, responders }) {
2062
+ const parts = alertBalanceParts(messages, responders);
2063
+ 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) });
2064
+ }
2065
+
2066
+ // src/core/date.ts
2067
+ /**
2068
+ * Formats a timestamp as `h:mmam` or `h:mmpm` in local time, without a space
2069
+ * or leading zero.
2070
+ */
2071
+ function formatShortTime(value, locale = "es") {
2072
+ if (value === null) return "";
2073
+ const date = value instanceof Date ? value : new Date(value);
2074
+ if (Number.isNaN(date.getTime())) return "";
2075
+ const parts = new Intl.DateTimeFormat(locale, {
2076
+ hour: "numeric",
2077
+ minute: "2-digit",
2078
+ hour12: true
2079
+ }).formatToParts(date);
2080
+ const hour = parts.find((part) => part.type === "hour")?.value;
2081
+ const minute = parts.find((part) => part.type === "minute")?.value;
2082
+ const dayPeriod = parts.find((part) => part.type === "dayPeriod")?.value;
2083
+ if (!hour || !minute) return "";
2084
+ return `${hour}:${minute}${normalizePeriod(dayPeriod)}`;
2085
+ }
2086
+ /**
2087
+ * Formats a timestamp as `dd/mm/yyyy hh:mm` in local time, using 24-hour time
2088
+ * and zero-padded parts. Missing or invalid input collapses to an empty string.
2089
+ */
2090
+ function formatDateTime(value, locale = "es") {
2091
+ if (value === null) return "";
2092
+ const date = value instanceof Date ? value : new Date(value);
2093
+ if (Number.isNaN(date.getTime())) return "";
2094
+ const parts = new Intl.DateTimeFormat(locale, {
2095
+ day: "2-digit",
2096
+ month: "2-digit",
2097
+ year: "numeric",
2098
+ hour: "2-digit",
2099
+ minute: "2-digit",
2100
+ hourCycle: "h23"
2101
+ }).formatToParts(date);
2102
+ const day = parts.find((part) => part.type === "day")?.value;
2103
+ const month = parts.find((part) => part.type === "month")?.value;
2104
+ const year = parts.find((part) => part.type === "year")?.value;
2105
+ const hour = parts.find((part) => part.type === "hour")?.value;
2106
+ const minute = parts.find((part) => part.type === "minute")?.value;
2107
+ if (!day || !month || !year || !hour || !minute) return "";
2108
+ return `${day}/${month}/${year} ${hour}:${minute}`;
2109
+ }
2110
+ function normalizePeriod(dayPeriod) {
2111
+ if (!dayPeriod) return "";
2112
+ if (/^p/i.test(dayPeriod)) return "pm";
2113
+ return /^a/i.test(dayPeriod) ? "am" : "";
2114
+ }
2115
+
2116
+ // src/react/screens/history/copy.ts
2117
+ const historyCopy = {
2118
+ title: "Historial de alertas",
2119
+ subtitle: "Consulta tus alertas, pruebas y recargas.",
2120
+ empty: "Todavía no tienes alertas en tu historial.",
2121
+ showMore: "Ver más",
2122
+ kindLabels: {
2123
+ emergency: "Emergencia",
2124
+ test: "Prueba",
2125
+ recharge: "Recarga"
2126
+ }
2127
+ };
2128
+ function formatContacts(count) {
2129
+ return count === 1 ? "1 contacto" : `${count} contactos`;
2130
+ }
2131
+ function formatMessages(count) {
2132
+ return count === 1 ? "1 mensaje" : `${count} mensajes`;
2133
+ }
2134
+ function formatHistoryRounds(count) {
2135
+ return count === 1 ? "1 ronda" : `${count} rondas`;
2136
+ }
2137
+
2138
+ // src/react/screens/history/data.ts
2139
+ /** Local demo alert history, newest first. */
2140
+ const DEMO_ALERT_HISTORY = [
2141
+ {
2142
+ id: "alert-2026-10-31",
2143
+ kind: "emergency",
2144
+ occurredAt: "2026-10-31T13:50:00",
2145
+ responders: 3,
2146
+ remainingMessages: 27
2147
+ },
2148
+ {
2149
+ id: "alert-2026-10-20",
2150
+ kind: "recharge",
2151
+ occurredAt: "2026-10-20T09:15:00",
2152
+ responders: 3,
2153
+ remainingMessages: 100
2154
+ },
2155
+ {
2156
+ id: "alert-2026-10-12",
2157
+ kind: "test",
2158
+ occurredAt: "2026-10-12T18:40:00",
2159
+ responders: 3,
2160
+ remainingMessages: 73
2161
+ },
2162
+ {
2163
+ id: "alert-2026-10-01",
2164
+ kind: "recharge",
2165
+ occurredAt: "2026-10-01T11:05:00",
2166
+ responders: 3,
2167
+ remainingMessages: 76
2168
+ }
2169
+ ];
2170
+ /** Returns a newest-first copy of the history. */
2171
+ function sortedHistoryEntries(entries = DEMO_ALERT_HISTORY) {
2172
+ return [...entries].sort((first, second) => new Date(second.occurredAt).getTime() - new Date(first.occurredAt).getTime());
2173
+ }
2174
+ /** Newest entry, used as the source of the current balance. */
2175
+ function latestHistoryEntry(entries = DEMO_ALERT_HISTORY) {
2176
+ return sortedHistoryEntries(entries)[0] ?? null;
2177
+ }
2178
+ /** Resolves a single history entry by id. */
2179
+ function historyEntryById(id, entries = DEMO_ALERT_HISTORY) {
2180
+ if (!id) return null;
2181
+ return entries.find((entry) => entry.id === id) ?? null;
2182
+ }
2183
+ /** Rounds represented by the entry's remaining messages and responders. */
2184
+ function historyEntryRounds(entry) {
2185
+ return calculateRounds(entry.remainingMessages, entry.responders);
2186
+ }
2187
+ /** Current balance derived from the newest history entry. */
2188
+ function currentAlertBalance(entries = DEMO_ALERT_HISTORY) {
2189
+ const latest = latestHistoryEntry(entries);
2190
+ if (!latest) return null;
2191
+ return {
2192
+ messages: latest.remainingMessages,
2193
+ responders: latest.responders,
2194
+ rounds: historyEntryRounds(latest)
2195
+ };
2196
+ }
2197
+
2198
+ // src/react/screens/history/AlertHistoryRow.tsx
2199
+ /** Reusable history row. The whole row is a button with a full text label. */
2200
+ function AlertHistoryRow({ entry, locale, onOpen }) {
2201
+ const occurredAt = formatDateTime(entry.occurredAt, locale);
2202
+ const rounds = historyEntryRounds(entry);
2203
+ const label = [
2204
+ historyCopy.kindLabels[entry.kind],
2205
+ occurredAt,
2206
+ formatContacts(entry.responders),
2207
+ `${formatMessages(entry.remainingMessages)} restantes`,
2208
+ `${formatHistoryRounds(rounds)} de avisos`
2209
+ ].join(", ");
2210
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
2211
+ className: cls("emergency-alert-row"),
2212
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2213
+ type: "button",
2214
+ className: cls("emergency-alert-row__button"),
2215
+ "aria-label": label,
2216
+ onClick: () => onOpen(entry.id),
2217
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2218
+ className: cls("emergency-alert-row__top"),
2219
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
2220
+ className: cls("emergency-alert-row__time"),
2221
+ dateTime: entry.occurredAt,
2222
+ children: occurredAt
2223
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2224
+ className: cls("emergency-alert-row__badge"),
2225
+ "data-kind": entry.kind,
2226
+ children: historyCopy.kindLabels[entry.kind]
2227
+ })]
2228
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2229
+ className: cls("emergency-alert-row__stats"),
2230
+ children: [
2231
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2232
+ className: cls("emergency-alert-row__stat"),
2233
+ children: formatContacts(entry.responders)
2234
+ }),
2235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2236
+ className: cls("emergency-alert-row__stat"),
2237
+ children: formatMessages(entry.remainingMessages)
2238
+ }),
2239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2240
+ className: cls("emergency-alert-row__stat"),
2241
+ children: formatHistoryRounds(rounds)
2242
+ })
2243
+ ]
2244
+ })]
2245
+ })
2246
+ });
2247
+ }
2248
+
1916
2249
  // src/react/screens/layout.tsx
1917
2250
  /**
1918
2251
  * Shared shell for every screen. The wrapper is `display: contents` so the
@@ -1934,45 +2267,429 @@ function EmergencyScreenLayout({ screen, titleId, header, footer, children, body
1934
2267
  });
1935
2268
  }
1936
2269
 
1937
- // src/react/screens/PlaceholderScreen.tsx
1938
- /** Minimal in-design screen used to prove navigation before real designs land. */
1939
- function PlaceholderScreen({ screen, titleId, title, description, onClose, onBack }) {
2270
+ // src/react/screens/details/index.tsx
2271
+ function DetailsScreen({ titleId, locale, onClose }) {
2272
+ const { back, navigate } = useScreenNavigation();
2273
+ const balance = currentAlertBalance();
2274
+ const preview = sortedHistoryEntries().slice(0, 2);
1940
2275
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
1941
- screen,
2276
+ screen: "details",
1942
2277
  titleId,
2278
+ bodyClassName: "emergency-panel__body--details",
1943
2279
  header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
1944
2280
  titleId,
1945
- title,
1946
- onClose,
1947
- onBack
2281
+ title: detailsCopy.title,
2282
+ onClose: () => onClose("close"),
2283
+ onBack: back
1948
2284
  }),
1949
2285
  footer: null,
2286
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2287
+ className: cls("emergency-details"),
2288
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
2289
+ className: cls("emergency-details__section"),
2290
+ "aria-label": detailsCopy.summaryTitle,
2291
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2292
+ className: cls("emergency-details__card"),
2293
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2294
+ className: cls("emergency-details__card-header"),
2295
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2296
+ className: cls("emergency-details__card-title"),
2297
+ children: detailsCopy.summaryTitle
2298
+ })
2299
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2300
+ className: cls("emergency-details__card-body"),
2301
+ children: balance ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2302
+ className: cls("emergency-details__balance"),
2303
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlertBalanceSentence, {
2304
+ messages: balance.messages,
2305
+ responders: balance.responders
2306
+ })
2307
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2308
+ className: cls("emergency-details__empty"),
2309
+ children: detailsCopy.summaryEmpty
2310
+ })
2311
+ })]
2312
+ })
2313
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2314
+ className: cls("emergency-details__section"),
2315
+ "aria-label": detailsCopy.historyTitle,
2316
+ children: [
2317
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2318
+ className: cls("emergency-details__heading"),
2319
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2320
+ className: cls("emergency-details__title"),
2321
+ children: detailsCopy.historyTitle
2322
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2323
+ className: cls("emergency-details__subtitle"),
2324
+ children: detailsCopy.historySubtitle
2325
+ })]
2326
+ }),
2327
+ preview.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2328
+ className: cls("emergency-details__list"),
2329
+ children: preview.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlertHistoryRow, {
2330
+ entry,
2331
+ locale,
2332
+ onOpen: (messageId) => navigate("message-detail", { messageId })
2333
+ }, entry.id))
2334
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2335
+ className: cls("emergency-details__empty"),
2336
+ children: detailsCopy.historyEmpty
2337
+ }),
2338
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2339
+ type: "button",
2340
+ className: cls("emergency-details__history-button"),
2341
+ onClick: () => navigate("history"),
2342
+ children: detailsCopy.viewHistory
2343
+ })
2344
+ ]
2345
+ })]
2346
+ })
2347
+ });
2348
+ }
2349
+
2350
+ // src/react/screens/history/index.tsx
2351
+ function HistoryScreen({ titleId, locale, onClose }) {
2352
+ const { back, navigate } = useScreenNavigation();
2353
+ const listId = (0, react.useId)();
2354
+ const [expanded, setExpanded] = (0, react.useState)(false);
2355
+ const entries = sortedHistoryEntries();
2356
+ const hiddenCount = Math.max(0, entries.length - 2);
2357
+ const visibleEntries = expanded ? entries : entries.slice(0, 2);
2358
+ const showMore = hiddenCount > 0 && !expanded;
2359
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2360
+ screen: "history",
2361
+ titleId,
2362
+ bodyClassName: "emergency-panel__body--history",
2363
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2364
+ titleId,
2365
+ title: historyCopy.title,
2366
+ onClose: () => onClose("close"),
2367
+ onBack: back
2368
+ }),
2369
+ footer: null,
2370
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2371
+ className: cls("emergency-history"),
2372
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2373
+ className: cls("emergency-history__heading"),
2374
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2375
+ className: cls("emergency-history__title"),
2376
+ children: historyCopy.title
2377
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2378
+ className: cls("emergency-history__subtitle"),
2379
+ children: historyCopy.subtitle
2380
+ })]
2381
+ }), entries.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2382
+ className: cls("emergency-history__list"),
2383
+ id: listId,
2384
+ children: visibleEntries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlertHistoryRow, {
2385
+ entry,
2386
+ locale,
2387
+ onOpen: (messageId) => navigate("message-detail", { messageId })
2388
+ }, entry.id))
2389
+ }), showMore ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2390
+ type: "button",
2391
+ className: cls("emergency-history__more"),
2392
+ "aria-expanded": expanded,
2393
+ "aria-controls": listId,
2394
+ onClick: () => setExpanded(true),
2395
+ children: historyCopy.showMore
2396
+ }) : null] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2397
+ className: cls("emergency-history__empty"),
2398
+ children: historyCopy.empty
2399
+ })]
2400
+ })
2401
+ });
2402
+ }
2403
+
2404
+ // src/react/screens/message-detail/index.tsx
2405
+ const messageDetailCopy = {
2406
+ title: "Detalle del mensaje",
2407
+ dateTime: "Fecha y hora",
2408
+ kind: "Tipo",
2409
+ responders: "Contactos de emergencia",
2410
+ messages: "Mensajes restantes",
2411
+ rounds: "Rondas de avisos",
2412
+ notFound: "No encontramos este mensaje. Puede que ya no esté disponible en tu historial.",
2413
+ backToHistory: "Volver al historial"
2414
+ };
2415
+ function MessageDetailScreen({ titleId, locale, onClose }) {
2416
+ const { back, params, replace } = useScreenNavigation();
2417
+ const entry = historyEntryById(params.messageId);
2418
+ const header = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2419
+ titleId,
2420
+ title: messageDetailCopy.title,
2421
+ onClose: () => onClose("close"),
2422
+ onBack: back
2423
+ });
2424
+ if (!entry) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2425
+ screen: "message-detail",
2426
+ titleId,
2427
+ bodyClassName: "emergency-panel__body--message-detail",
2428
+ header,
2429
+ footer: null,
2430
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2431
+ className: cls("emergency-message-detail__empty"),
2432
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2433
+ className: cls("emergency-message-detail__empty-text"),
2434
+ children: messageDetailCopy.notFound
2435
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2436
+ type: "button",
2437
+ className: cls("emergency-message-detail__back"),
2438
+ onClick: () => replace("history"),
2439
+ children: messageDetailCopy.backToHistory
2440
+ })]
2441
+ })
2442
+ });
2443
+ const rounds = historyEntryRounds(entry);
2444
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2445
+ screen: "message-detail",
2446
+ titleId,
2447
+ bodyClassName: "emergency-panel__body--message-detail",
2448
+ header,
2449
+ footer: null,
1950
2450
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1951
- className: cls("emergency-screen__placeholder"),
1952
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1953
- className: cls("emergency-screen__placeholder-text"),
1954
- children: description
2451
+ className: cls("emergency-message-detail"),
2452
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2453
+ className: cls("emergency-message-detail__card"),
2454
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2455
+ className: cls("emergency-message-detail__card-header"),
2456
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2457
+ className: cls("emergency-message-detail__card-title"),
2458
+ children: messageDetailCopy.title
2459
+ })
2460
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
2461
+ className: cls("emergency-message-detail__list"),
2462
+ children: [
2463
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2464
+ className: cls("emergency-message-detail__row"),
2465
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2466
+ className: cls("emergency-message-detail__label"),
2467
+ children: messageDetailCopy.dateTime
2468
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2469
+ className: cls("emergency-message-detail__value"),
2470
+ children: formatDateTime(entry.occurredAt, locale)
2471
+ })]
2472
+ }),
2473
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2474
+ className: cls("emergency-message-detail__row"),
2475
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2476
+ className: cls("emergency-message-detail__label"),
2477
+ children: messageDetailCopy.kind
2478
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2479
+ className: cls("emergency-message-detail__value"),
2480
+ children: historyCopy.kindLabels[entry.kind]
2481
+ })]
2482
+ }),
2483
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2484
+ className: cls("emergency-message-detail__row"),
2485
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2486
+ className: cls("emergency-message-detail__label"),
2487
+ children: messageDetailCopy.responders
2488
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2489
+ className: cls("emergency-message-detail__value"),
2490
+ children: formatContacts(entry.responders)
2491
+ })]
2492
+ }),
2493
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2494
+ className: cls("emergency-message-detail__row"),
2495
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2496
+ className: cls("emergency-message-detail__label"),
2497
+ children: messageDetailCopy.messages
2498
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2499
+ className: cls("emergency-message-detail__value"),
2500
+ children: formatMessages(entry.remainingMessages)
2501
+ })]
2502
+ }),
2503
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2504
+ className: cls("emergency-message-detail__row"),
2505
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
2506
+ className: cls("emergency-message-detail__label"),
2507
+ children: messageDetailCopy.rounds
2508
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
2509
+ className: cls("emergency-message-detail__value"),
2510
+ children: formatHistoryRounds(rounds)
2511
+ })]
2512
+ })
2513
+ ]
2514
+ })]
1955
2515
  })
1956
2516
  })
1957
2517
  });
1958
2518
  }
1959
2519
 
1960
- // src/react/screens/details/copy.ts
1961
- const detailsCopy = {
1962
- title: "Detalles",
1963
- description: "Aquí podrás consultar el detalle de tus alertas disponibles. Esta pantalla estará disponible próximamente."
2520
+ // src/core/phone.ts
2521
+ const VENEZUELA_DIALLING_CODE = "+58";
2522
+ const VENEZUELAN_MOBILE_PATTERN = /^4\d{9}$/;
2523
+ function normalizeVenezuelanPhone(input) {
2524
+ return input.replace(/\D/g, "").replace(/^0+/, "");
2525
+ }
2526
+ function formatVenezuelanPhone(input) {
2527
+ const digits = normalizeVenezuelanPhone(input).slice(0, 10);
2528
+ if (digits.length <= 3) return digits;
2529
+ return `${digits.slice(0, 3)} ${digits.slice(3)}`;
2530
+ }
2531
+ function isValidVenezuelanPhone(input) {
2532
+ return VENEZUELAN_MOBILE_PATTERN.test(normalizeVenezuelanPhone(input));
2533
+ }
2534
+ function toVenezuelanE164(input) {
2535
+ const digits = normalizeVenezuelanPhone(input);
2536
+ if (!VENEZUELAN_MOBILE_PATTERN.test(digits)) return null;
2537
+ return `${"+58"}${digits}`;
2538
+ }
2539
+
2540
+ // src/react/components/EmergencyFooter/index.tsx
2541
+ function EmergencyFooter(props) {
2542
+ const { active, busy, aborting, feedbackVisible, showSimulation = true, emergencyDisabled = false, square = false, onActivate, onRequestAbort, onRequestSimulation } = props;
2543
+ if (active) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
2544
+ className: cls("emergency-panel__footer", square && "emergency-panel__footer--square"),
2545
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2546
+ type: "button",
2547
+ className: cls("emergency-status__abort-btn"),
2548
+ "aria-label": copy.abort,
2549
+ disabled: aborting || feedbackVisible,
2550
+ onClick: onRequestAbort,
2551
+ children: [copy.abort, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "alert-triangle" })]
2552
+ })
2553
+ });
2554
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2555
+ className: cls("emergency-panel__footer", !showSimulation && "emergency-panel__footer--single", square && "emergency-panel__footer--square"),
2556
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2557
+ type: "button",
2558
+ className: cls("emergency-panel__button", "emergency-panel__button--emergency"),
2559
+ "aria-label": copy.emergencyAria,
2560
+ disabled: busy || feedbackVisible || emergencyDisabled,
2561
+ onClick: onActivate,
2562
+ children: [copy.emergency, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "emergency" })]
2563
+ }), showSimulation ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2564
+ type: "button",
2565
+ className: cls("emergency-panel__button", "emergency-panel__button--simulation"),
2566
+ "aria-label": copy.testAria,
2567
+ disabled: busy || feedbackVisible,
2568
+ onClick: onRequestSimulation,
2569
+ children: [copy.test, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "list-check" })]
2570
+ }) : null]
2571
+ });
2572
+ }
2573
+
2574
+ // src/react/screens/pre-emergency/copy.ts
2575
+ const preEmergencyCopy = {
2576
+ title: "Pre-emergencia",
2577
+ permissionNotice: "Debes autorizar todos los permisos de ubicación",
2578
+ phoneLabel: "Indique su número de teléfono",
2579
+ phonePlaceholder: "Ej. 0412 000 0000",
2580
+ areaCodeLabel: "Código de país",
2581
+ areaCode: "+58",
2582
+ invalidPhone: "Ingresa un número móvil venezolano válido. Ej. 412 1301234"
1964
2583
  };
1965
2584
 
1966
- // src/react/screens/details/index.tsx
1967
- function DetailsScreen({ titleId, onClose }) {
2585
+ // src/react/screens/pre-emergency/index.tsx
2586
+ function PreEmergencyScreen(props) {
1968
2587
  const { back } = useScreenNavigation();
1969
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlaceholderScreen, {
1970
- screen: "details",
1971
- titleId,
1972
- title: detailsCopy.title,
1973
- description: detailsCopy.description,
1974
- onClose: () => onClose("close"),
1975
- onBack: back
2588
+ const phoneId = (0, react.useId)();
2589
+ const errorId = `${phoneId}-error`;
2590
+ const permissionId = `${phoneId}-permission`;
2591
+ const [digits, setDigits] = (0, react.useState)("");
2592
+ const [touched, setTouched] = (0, react.useState)(false);
2593
+ const valid = isValidVenezuelanPhone(digits);
2594
+ const invalid = touched && digits.length > 0 && !valid;
2595
+ const handleChange = (event) => {
2596
+ const next = normalizeVenezuelanPhone(event.target.value).slice(0, 10);
2597
+ setDigits(next);
2598
+ };
2599
+ const handleSubmit = () => {
2600
+ setTouched(true);
2601
+ if (!valid) return;
2602
+ const phone = toVenezuelanE164(digits);
2603
+ if (phone === null) return;
2604
+ props.onSetContactPhone(phone);
2605
+ props.onActivate();
2606
+ };
2607
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
2608
+ screen: "pre-emergency",
2609
+ titleId: props.titleId,
2610
+ bodyClassName: "emergency-panel__body--pre-emergency",
2611
+ header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2612
+ titleId: props.titleId,
2613
+ title: preEmergencyCopy.title,
2614
+ onClose: () => props.onClose("close"),
2615
+ onBack: back
2616
+ }),
2617
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFooter, {
2618
+ active: props.active,
2619
+ busy: props.busy,
2620
+ aborting: props.aborting,
2621
+ feedbackVisible: props.feedbackVisible,
2622
+ emergencyDisabled: !valid,
2623
+ showSimulation: false,
2624
+ onActivate: handleSubmit,
2625
+ onRequestAbort: props.onRequestAbort,
2626
+ onRequestSimulation: props.onRequestSimulation
2627
+ }),
2628
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2629
+ className: cls("emergency-pre-emergency"),
2630
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2631
+ role: "note",
2632
+ "aria-labelledby": permissionId,
2633
+ className: cls("emergency-pre-emergency__notice"),
2634
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
2635
+ name: "map-pin",
2636
+ className: cls("emergency-pre-emergency__notice-icon")
2637
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2638
+ id: permissionId,
2639
+ className: cls("emergency-pre-emergency__notice-text"),
2640
+ children: preEmergencyCopy.permissionNotice
2641
+ })]
2642
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
2643
+ className: cls("emergency-pre-emergency__form"),
2644
+ noValidate: true,
2645
+ onSubmit: (event) => {
2646
+ event.preventDefault();
2647
+ handleSubmit();
2648
+ },
2649
+ onKeyDown: (event) => {
2650
+ if (event.key === "Enter") {
2651
+ event.preventDefault();
2652
+ handleSubmit();
2653
+ }
2654
+ },
2655
+ children: [
2656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2657
+ htmlFor: phoneId,
2658
+ className: cls("emergency-pre-emergency__label"),
2659
+ children: preEmergencyCopy.phoneLabel
2660
+ }),
2661
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2662
+ className: cls("emergency-pre-emergency__phone-row"),
2663
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2664
+ type: "tel",
2665
+ "aria-label": preEmergencyCopy.areaCodeLabel,
2666
+ className: cls("emergency-pre-emergency__input", "emergency-pre-emergency__input--area-code", valid && "emergency-pre-emergency__input--valid"),
2667
+ value: preEmergencyCopy.areaCode,
2668
+ disabled: true,
2669
+ readOnly: true
2670
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2671
+ id: phoneId,
2672
+ type: "tel",
2673
+ inputMode: "tel",
2674
+ autoComplete: "tel-national",
2675
+ placeholder: preEmergencyCopy.phonePlaceholder,
2676
+ className: cls("emergency-pre-emergency__input", "emergency-pre-emergency__input--number", valid && "emergency-pre-emergency__input--valid", invalid && "emergency-pre-emergency__input--invalid"),
2677
+ value: formatVenezuelanPhone(digits),
2678
+ "aria-invalid": invalid,
2679
+ "aria-describedby": invalid ? errorId : void 0,
2680
+ onChange: handleChange,
2681
+ onBlur: () => setTouched(true)
2682
+ })]
2683
+ }),
2684
+ invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2685
+ id: errorId,
2686
+ role: "alert",
2687
+ className: cls("emergency-pre-emergency__error"),
2688
+ children: preEmergencyCopy.invalidPhone
2689
+ }) : null
2690
+ ]
2691
+ })]
2692
+ })
1976
2693
  });
1977
2694
  }
1978
2695
 
@@ -2198,32 +2915,6 @@ function EmergencyLoading() {
2198
2915
  });
2199
2916
  }
2200
2917
 
2201
- // src/core/date.ts
2202
- /**
2203
- * Formats a timestamp as `h:mmam` or `h:mmpm` in local time, without a space
2204
- * or leading zero.
2205
- */
2206
- function formatShortTime(value, locale = "es") {
2207
- if (value === null) return "";
2208
- const date = value instanceof Date ? value : new Date(value);
2209
- if (Number.isNaN(date.getTime())) return "";
2210
- const parts = new Intl.DateTimeFormat(locale, {
2211
- hour: "numeric",
2212
- minute: "2-digit",
2213
- hour12: true
2214
- }).formatToParts(date);
2215
- const hour = parts.find((part) => part.type === "hour")?.value;
2216
- const minute = parts.find((part) => part.type === "minute")?.value;
2217
- const dayPeriod = parts.find((part) => part.type === "dayPeriod")?.value;
2218
- if (!hour || !minute) return "";
2219
- return `${hour}:${minute}${normalizePeriod(dayPeriod)}`;
2220
- }
2221
- function normalizePeriod(dayPeriod) {
2222
- if (!dayPeriod) return "";
2223
- if (/^p/i.test(dayPeriod)) return "pm";
2224
- return /^a/i.test(dayPeriod) ? "am" : "";
2225
- }
2226
-
2227
2918
  // src/react/components/StatusIcon/index.tsx
2228
2919
  /**
2229
2920
  * Explicit responder state model:
@@ -2384,23 +3075,9 @@ function formatRounds(rounds) {
2384
3075
  return `${rounds} ${roundsUnit(rounds)}`;
2385
3076
  }
2386
3077
 
2387
- // src/react/screens/primary/data.ts
2388
- /**
2389
- * Derives the remaining alert rounds from available messages and designated
2390
- * responders. Invalid or non-positive input collapses to the minimum of one
2391
- * round instead of producing `Infinity`, `NaN` or a negative value.
2392
- */
2393
- function calculateRounds(messages, responders) {
2394
- if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
2395
- const availableMessages = Math.floor(messages);
2396
- const designatedResponders = Math.floor(responders);
2397
- if (availableMessages <= 0 || designatedResponders <= 0) return 1;
2398
- return Math.max(1, Math.floor(availableMessages / designatedResponders));
2399
- }
2400
-
2401
3078
  // src/react/screens/primary/Availability.tsx
2402
3079
  function Availability({ onRecharge, onDetails }) {
2403
- const rounds = calculateRounds(100, 3);
3080
+ const rounds = calculateRounds(27, 3);
2404
3081
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2405
3082
  className: cls("emergency-availability"),
2406
3083
  "aria-label": primaryCopy.availabilityTitle,
@@ -2442,7 +3119,7 @@ function Availability({ onRecharge, onDetails }) {
2442
3119
 
2443
3120
  // src/react/components/EmergencyBody/index.tsx
2444
3121
  function EmergencyBody(props) {
2445
- const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails } = props;
3122
+ const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails, onClose } = props;
2446
3123
  const showingFeedback = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
2447
3124
  const idle = !active && !loading && !showingFeedback && error === null;
2448
3125
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2454,8 +3131,18 @@ function EmergencyBody(props) {
2454
3131
  onDetails
2455
3132
  })
2456
3133
  }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2457
- className: cls("emergency-panel__prompt-region"),
3134
+ className: cls("emergency-panel__prompt-region", idle && "emergency-panel__prompt-region--idle"),
2458
3135
  children: [
3136
+ idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3137
+ type: "button",
3138
+ className: cls("emergency-panel__close-button", "emergency-panel__close-button--prompt"),
3139
+ "aria-label": copy.close,
3140
+ onClick: onClose,
3141
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
3142
+ name: "close",
3143
+ className: cls("emergency-panel__close-icon")
3144
+ })
3145
+ }) : null,
2459
3146
  !loading && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2460
3147
  className: cls("sos-img__container"),
2461
3148
  id: "sos-img__container",
@@ -2494,47 +3181,16 @@ function EmergencyBody(props) {
2494
3181
  });
2495
3182
  }
2496
3183
 
2497
- // src/react/components/EmergencyFooter/index.tsx
2498
- function EmergencyFooter(props) {
2499
- const { active, busy, aborting, feedbackVisible, onActivate, onRequestAbort, onRequestSimulation } = props;
2500
- if (active) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
2501
- className: cls("emergency-panel__footer"),
2502
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2503
- type: "button",
2504
- className: cls("emergency-status__abort-btn"),
2505
- "aria-label": copy.abort,
2506
- disabled: aborting || feedbackVisible,
2507
- onClick: onRequestAbort,
2508
- children: [copy.abort, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "alert-triangle" })]
2509
- })
2510
- });
2511
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2512
- className: cls("emergency-panel__footer"),
2513
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2514
- type: "button",
2515
- className: cls("emergency-panel__button", "emergency-panel__button--emergency"),
2516
- "aria-label": copy.emergencyAria,
2517
- disabled: busy || feedbackVisible,
2518
- onClick: onActivate,
2519
- children: [copy.emergency, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "emergency" })]
2520
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2521
- type: "button",
2522
- className: cls("emergency-panel__button", "emergency-panel__button--simulation"),
2523
- "aria-label": copy.testAria,
2524
- disabled: busy || feedbackVisible,
2525
- onClick: onRequestSimulation,
2526
- children: [copy.test, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "list-check" })]
2527
- })]
2528
- });
2529
- }
2530
-
2531
3184
  // src/react/screens/primary/index.tsx
2532
3185
  function PrimaryScreen(props) {
2533
3186
  const { navigate } = useScreenNavigation();
2534
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
3187
+ const idle = !props.active && !props.loading && !props.feedbackVisible && props.error === null;
3188
+ const needsContactPhone = props.contactPhone === null;
3189
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(EmergencyScreenLayout, {
2535
3190
  screen: "primary",
2536
3191
  titleId: props.titleId,
2537
- header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
3192
+ bodyClassName: idle ? "emergency-panel__body--primary" : void 0,
3193
+ header: idle ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
2538
3194
  titleId: props.titleId,
2539
3195
  onClose: () => props.onClose("close")
2540
3196
  }),
@@ -2543,11 +3199,17 @@ function PrimaryScreen(props) {
2543
3199
  busy: props.busy,
2544
3200
  aborting: props.aborting,
2545
3201
  feedbackVisible: props.feedbackVisible,
2546
- onActivate: props.onActivate,
3202
+ square: idle,
3203
+ onActivate: needsContactPhone ? () => navigate("pre-emergency") : props.onActivate,
2547
3204
  onRequestAbort: props.onRequestAbort,
2548
3205
  onRequestSimulation: props.onRequestSimulation
2549
3206
  }),
2550
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyBody, {
3207
+ children: [idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
3208
+ className: cls("emergency-panel__sr-only"),
3209
+ id: props.titleId,
3210
+ tabIndex: -1,
3211
+ children: copy.panelLabel
3212
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyBody, {
2551
3213
  active: props.active,
2552
3214
  responders: props.responders,
2553
3215
  refreshing: props.refreshing,
@@ -2562,8 +3224,9 @@ function PrimaryScreen(props) {
2562
3224
  onRetry: props.onRetry,
2563
3225
  onDismissFeedback: props.onDismissFeedback,
2564
3226
  onRecharge: () => navigate("pricing"),
2565
- onDetails: () => navigate("details")
2566
- })
3227
+ onDetails: () => navigate("details"),
3228
+ onClose: () => props.onClose("close")
3229
+ })]
2567
3230
  });
2568
3231
  }
2569
3232
 
@@ -2577,7 +3240,7 @@ const pricingCopy = {
2577
3240
  chosen: "Seleccionado",
2578
3241
  whatsapp: "de WhatsApp",
2579
3242
  rounds: "rondas de alerta",
2580
- perMessage: "por mensaje + IVA",
3243
+ perMessage: "IVA Incluido",
2581
3244
  footnote: "Rondas calculadas con 3 contactos de emergencia. Válidas para pruebas y emergencias."
2582
3245
  };
2583
3246
 
@@ -2591,17 +3254,16 @@ const PRICING_PACKAGES = [{
2591
3254
  label: "PAQUETE 1",
2592
3255
  messages: 100,
2593
3256
  priceLabel: "$7",
2594
- priceCaption: "IVA incluido",
2595
- perMessageLabel: "$0.06",
3257
+ priceCaption: "IVA incluído",
3258
+ perMessageLabel: "$0.07",
2596
3259
  highlighted: false
2597
3260
  }, {
2598
3261
  id: "package-2",
2599
3262
  label: "PAQUETE 2",
2600
3263
  messages: 200,
2601
3264
  priceLabel: "12$",
2602
- priceNote: "($10 + IVA)",
2603
- priceCaption: "IVA incluido",
2604
- perMessageLabel: "$0.05",
3265
+ priceCaption: "IVA incluído",
3266
+ perMessageLabel: "$0.06",
2605
3267
  highlighted: true,
2606
3268
  badge: "MEJOR VALOR"
2607
3269
  }];
@@ -2678,6 +3340,10 @@ function PackageCard({ pkg, selected, onSelect }) {
2678
3340
  })
2679
3341
  ]
2680
3342
  }),
3343
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3344
+ className: cls("emergency-pricing__package-footnote"),
3345
+ children: pricingCopy.footnote
3346
+ }),
2681
3347
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2682
3348
  type: "button",
2683
3349
  className: cls("emergency-pricing__select"),
@@ -2706,30 +3372,23 @@ function PricingScreen(props) {
2706
3372
  footer: null,
2707
3373
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2708
3374
  className: cls("emergency-pricing"),
2709
- children: [
2710
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2711
- className: cls("emergency-pricing__heading"),
2712
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2713
- className: cls("emergency-pricing__titles"),
2714
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2715
- className: cls("emergency-pricing__title"),
2716
- children: pricingCopy.heading
2717
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2718
- className: cls("emergency-pricing__subtitle"),
2719
- children: pricingCopy.subtitle
2720
- })]
2721
- })
2722
- }),
2723
- PRICING_PACKAGES.map((pkg) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageCard, {
2724
- pkg,
2725
- selected: selected === pkg.id,
2726
- onSelect: setSelected
2727
- }, pkg.id)),
2728
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2729
- className: cls("emergency-pricing__footnote"),
2730
- children: pricingCopy.footnote
3375
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3376
+ className: cls("emergency-pricing__heading"),
3377
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3378
+ className: cls("emergency-pricing__titles"),
3379
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3380
+ className: cls("emergency-pricing__title"),
3381
+ children: pricingCopy.heading
3382
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3383
+ className: cls("emergency-pricing__subtitle"),
3384
+ children: pricingCopy.subtitle
3385
+ })]
2731
3386
  })
2732
- ]
3387
+ }), PRICING_PACKAGES.map((pkg) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageCard, {
3388
+ pkg,
3389
+ selected: selected === pkg.id,
3390
+ onSelect: setSelected
3391
+ }, pkg.id))]
2733
3392
  })
2734
3393
  });
2735
3394
  }
@@ -2738,8 +3397,11 @@ function PricingScreen(props) {
2738
3397
  /** Every screen the panel can render. Add one entry to add a screen. */
2739
3398
  const screenRegistry = {
2740
3399
  primary: PrimaryScreen,
3400
+ "pre-emergency": PreEmergencyScreen,
2741
3401
  pricing: PricingScreen,
2742
- details: DetailsScreen
3402
+ details: DetailsScreen,
3403
+ history: HistoryScreen,
3404
+ "message-detail": MessageDetailScreen
2743
3405
  };
2744
3406
 
2745
3407
  // src/react/components/EmergencyPanel/index.tsx
@@ -2768,7 +3430,7 @@ function ActiveScreen({ screenProps, open, forcePrimary }) {
2768
3430
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Screen, { ...screenProps });
2769
3431
  }
2770
3432
  function EmergencyPanel(props) {
2771
- const { open, theme, active, busy, aborting, loading, locale, responders, refreshing, geolocationError, activationError, error, abortResult, simulationResult, abortConfirmation, simulationConfirmation, onClose, onActivate, onRefresh, onRetry, onDismissFeedback, onRequestAbort, onRequestSimulation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
3433
+ 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;
2772
3434
  const titleId = (0, react.useId)();
2773
3435
  const dialogRef = (0, react.useRef)(null);
2774
3436
  const feedbackVisible = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
@@ -2793,13 +3455,15 @@ function EmergencyPanel(props) {
2793
3455
  abortResult,
2794
3456
  simulationResult,
2795
3457
  feedbackVisible,
3458
+ contactPhone,
2796
3459
  onClose,
2797
3460
  onActivate,
2798
3461
  onRefresh,
2799
3462
  onRetry,
2800
3463
  onDismissFeedback,
2801
3464
  onRequestAbort,
2802
- onRequestSimulation
3465
+ onRequestSimulation,
3466
+ onSetContactPhone
2803
3467
  };
2804
3468
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2805
3469
  ref: dialogRef,
@@ -2906,6 +3570,7 @@ function EmergencyRoot() {
2906
3570
  const responders = useEmergencyStore((state) => state.responders);
2907
3571
  const resumeAvailable = useEmergencyStore((state) => state.resumeAvailable);
2908
3572
  const geolocationError = useEmergencyStore((state) => state.geolocationError);
3573
+ const contactPhone = useEmergencyStore((state) => state.contactPhone);
2909
3574
  const lastError = useEmergencyStore((state) => state.lastError);
2910
3575
  const abortResult = useEmergencyStore((state) => state.abortResult);
2911
3576
  const simulationResult = useEmergencyStore((state) => state.simulationResult);
@@ -2931,6 +3596,7 @@ function EmergencyRoot() {
2931
3596
  responders,
2932
3597
  refreshing: phase.kind === "refreshing",
2933
3598
  geolocationError,
3599
+ contactPhone,
2934
3600
  activationError,
2935
3601
  error: showError ? error : null,
2936
3602
  abortResult,
@@ -2957,7 +3623,8 @@ function EmergencyRoot() {
2957
3623
  },
2958
3624
  onConfirmSimulation: () => {
2959
3625
  client.simulate();
2960
- }
3626
+ },
3627
+ onSetContactPhone: (phone) => client.setContactPhone(phone)
2961
3628
  })] });
2962
3629
  }
2963
3630
 
@@ -2989,6 +3656,7 @@ function createFailedClient(error) {
2989
3656
  lastError: error,
2990
3657
  geolocation: null,
2991
3658
  geolocationError: null,
3659
+ contactPhone: null,
2992
3660
  realtimeStatus: "idle",
2993
3661
  abortResult: null,
2994
3662
  simulationResult: null,
@@ -3017,7 +3685,8 @@ function createFailedClient(error) {
3017
3685
  cancelAbortConfirmation: noop,
3018
3686
  requestSimulationConfirmation: noop,
3019
3687
  cancelSimulationConfirmation: noop,
3020
- dismissFeedback: noop
3688
+ dismissFeedback: noop,
3689
+ setContactPhone: noop
3021
3690
  };
3022
3691
  }
3023
3692
  function attachTrigger(trigger, client) {
@@ -3106,6 +3775,7 @@ function mount(options) {
3106
3775
  requestSimulationConfirmation: () => client.requestSimulationConfirmation(),
3107
3776
  cancelSimulationConfirmation: () => client.cancelSimulationConfirmation(),
3108
3777
  dismissFeedback: () => client.dismissFeedback(),
3778
+ setContactPhone: (phone) => client.setContactPhone(phone),
3109
3779
  destroy
3110
3780
  };
3111
3781
  }