@sincpro/mobile 0.1.0 → 0.2.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.
Files changed (58) hide show
  1. package/README.md +29 -12
  2. package/dist/adapters/repositories/domain_event.repository.js +2 -1
  3. package/dist/adapters/repositories/domain_event_dead_letter.repository.js +2 -1
  4. package/dist/adapters/repositories/setting.repository.js +4 -3
  5. package/dist/domain/settings.d.ts +1 -1
  6. package/dist/domain/settings.js +4 -1
  7. package/dist/entrypoints/db/migrations.d.ts +1 -1
  8. package/dist/entrypoints/db/migrations.js +26 -18
  9. package/dist/entrypoints/queue/printImage.subscriber.js +3 -3
  10. package/dist/entrypoints/ui/AppShell.d.ts +7 -0
  11. package/dist/entrypoints/ui/AppShell.js +25 -9
  12. package/dist/entrypoints/ui/common_provider.d.ts +6 -1
  13. package/dist/entrypoints/ui/common_provider.js +45 -1
  14. package/dist/entrypoints/ui/domain_switcher.js +5 -4
  15. package/dist/index.d.ts +0 -1
  16. package/dist/index.js +0 -1
  17. package/dist/infrastructure/logger.d.ts +2 -2
  18. package/dist/infrastructure/logger.js +51 -33
  19. package/dist/infrastructure/ui/ToastHost.d.ts +6 -1
  20. package/dist/infrastructure/ui/ToastHost.js +18 -9
  21. package/dist/services/printer.service.d.ts +6 -0
  22. package/dist/services/printer.service.js +40 -6
  23. package/dist/ui/components/atoms/DebugBanner.js +4 -8
  24. package/dist/ui/components/atoms/PrinterIcon.d.ts +4 -0
  25. package/dist/ui/components/atoms/PrinterIcon.js +4 -0
  26. package/dist/ui/components/atoms/index.d.ts +1 -0
  27. package/dist/ui/components/atoms/index.js +1 -0
  28. package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.js +16 -10
  29. package/dist/ui/components/molecules/DeadLetterErrorBlock.js +3 -2
  30. package/dist/ui/components/molecules/EventTimelineItem.js +31 -33
  31. package/dist/ui/components/molecules/GeoPermissionCard.d.ts +8 -0
  32. package/dist/ui/components/molecules/GeoPermissionCard.js +21 -0
  33. package/dist/ui/components/molecules/ProcessToast.d.ts +7 -4
  34. package/dist/ui/components/molecules/ProcessToast.js +45 -44
  35. package/dist/ui/components/molecules/ProcessToastProvider.js +1 -1
  36. package/dist/ui/components/molecules/TimeZoneSelector.d.ts +7 -0
  37. package/dist/ui/components/molecules/TimeZoneSelector.js +19 -0
  38. package/dist/ui/components/molecules/TimeZoneSelectorModal.d.ts +9 -0
  39. package/dist/ui/components/molecules/TimeZoneSelectorModal.js +61 -0
  40. package/dist/ui/components/molecules/index.d.ts +4 -0
  41. package/dist/ui/components/molecules/index.js +3 -0
  42. package/dist/ui/components/molecules/timezone.d.ts +4 -0
  43. package/dist/ui/components/molecules/timezone.js +1 -0
  44. package/dist/ui/components/organisms/BluetoothPrinterSelector.d.ts +3 -1
  45. package/dist/ui/components/organisms/BluetoothPrinterSelector.js +29 -9
  46. package/dist/ui/components/organisms/DatabaseInfoRow.js +1 -1
  47. package/dist/ui/components/organisms/DeadLetterQueueRow.js +3 -3
  48. package/dist/ui/components/organisms/EventRow.js +15 -15
  49. package/dist/ui/components/organisms/InjectableWebView.js +20 -10
  50. package/dist/ui/components/organisms/TableInfoInfoRow.js +1 -1
  51. package/dist/ui/screens/database/database.json_detail.js +3 -2
  52. package/dist/ui/screens/database/database.list.js +4 -4
  53. package/dist/ui/screens/database/database.table_rows.js +1 -1
  54. package/dist/ui/screens/dead_letter_queue/dead_letter_queue.list.js +4 -4
  55. package/dist/ui/screens/events/events.list.js +4 -4
  56. package/package.json +9 -5
  57. package/dist/ui/layouts/router_layouts.d.ts +0 -17
  58. package/dist/ui/layouts/router_layouts.js +0 -20
@@ -15,17 +15,34 @@ const Colors = {
15
15
  BrightYellow: "\x1b[93m",
16
16
  White: "\x1b[37m",
17
17
  };
18
+ var ELogLevel;
19
+ (function (ELogLevel) {
20
+ ELogLevel["DEBUG"] = "DEBUG";
21
+ ELogLevel["INFO"] = "INFO";
22
+ ELogLevel["WARN"] = "WARN";
23
+ ELogLevel["ERROR"] = "ERROR";
24
+ })(ELogLevel || (ELogLevel = {}));
25
+ var EApplicationLogger;
26
+ (function (EApplicationLogger) {
27
+ EApplicationLogger["GLOBAL"] = "GLOBAL";
28
+ EApplicationLogger["ODOO_CLIENT"] = "ODOO_CLIENT";
29
+ EApplicationLogger["ADAPTER"] = "ADAPTER";
30
+ EApplicationLogger["REPOSITORIES"] = "REPOSITORIES";
31
+ EApplicationLogger["USE_CASES"] = "USE_CASES";
32
+ EApplicationLogger["QUEUE_PROCESSOR"] = "EVENT_BUS";
33
+ EApplicationLogger["CRON_JOBS"] = "CRON_JOBS";
34
+ })(EApplicationLogger || (EApplicationLogger = {}));
18
35
  /**
19
36
  * Color mapping for each logger context
20
37
  */
21
38
  const LoggerColors = {
22
- ["GLOBAL" /* EApplicationLogger.GLOBAL */]: Colors.White,
23
- ["ODOO_CLIENT" /* EApplicationLogger.ODOO_CLIENT */]: Colors.Gray,
24
- ["ADAPTER" /* EApplicationLogger.ADAPTER */]: Colors.Gray,
25
- ["REPOSITORIES" /* EApplicationLogger.REPOSITORIES */]: Colors.Gray,
26
- ["USE_CASES" /* EApplicationLogger.USE_CASES */]: Colors.BrightGreen,
27
- ["EVENT_BUS" /* EApplicationLogger.QUEUE_PROCESSOR */]: Colors.BrightYellow,
28
- ["CRON_JOBS" /* EApplicationLogger.CRON_JOBS */]: Colors.Gray,
39
+ [EApplicationLogger.GLOBAL]: Colors.White,
40
+ [EApplicationLogger.ODOO_CLIENT]: Colors.Gray,
41
+ [EApplicationLogger.ADAPTER]: Colors.Gray,
42
+ [EApplicationLogger.REPOSITORIES]: Colors.Gray,
43
+ [EApplicationLogger.USE_CASES]: Colors.BrightGreen,
44
+ [EApplicationLogger.QUEUE_PROCESSOR]: Colors.BrightYellow,
45
+ [EApplicationLogger.CRON_JOBS]: Colors.Gray,
29
46
  };
30
47
  /**
31
48
  * Production flag - manually toggle to disable all logs
@@ -35,35 +52,35 @@ export const IS_PRODUCTION = false;
35
52
  /**
36
53
  * Global log level for the application.
37
54
  */
38
- export const LOG_LEVEL = "INFO" /* ELogLevel.INFO */;
55
+ export const LOG_LEVEL = ELogLevel.INFO;
39
56
  /**
40
57
  * Configuration for enabling or disabling logs for specific application contexts.
41
58
  */
42
59
  export const ENABLED_LOGS = {
43
- ["GLOBAL" /* EApplicationLogger.GLOBAL */]: true,
44
- ["ODOO_CLIENT" /* EApplicationLogger.ODOO_CLIENT */]: false,
45
- ["ADAPTER" /* EApplicationLogger.ADAPTER */]: false,
46
- ["REPOSITORIES" /* EApplicationLogger.REPOSITORIES */]: false,
47
- ["USE_CASES" /* EApplicationLogger.USE_CASES */]: true,
48
- ["EVENT_BUS" /* EApplicationLogger.QUEUE_PROCESSOR */]: true,
49
- ["CRON_JOBS" /* EApplicationLogger.CRON_JOBS */]: true,
60
+ [EApplicationLogger.GLOBAL]: true,
61
+ [EApplicationLogger.ODOO_CLIENT]: false,
62
+ [EApplicationLogger.ADAPTER]: false,
63
+ [EApplicationLogger.REPOSITORIES]: false,
64
+ [EApplicationLogger.USE_CASES]: true,
65
+ [EApplicationLogger.QUEUE_PROCESSOR]: true,
66
+ [EApplicationLogger.CRON_JOBS]: true,
50
67
  };
51
68
  class BaseLogger {
52
69
  context;
53
70
  contextPrefix;
54
71
  constructor(context) {
55
72
  this.context = context;
56
- this.contextPrefix = context === "GLOBAL" /* EApplicationLogger.GLOBAL */ ? "" : `[${context}]`;
73
+ this.contextPrefix = context === EApplicationLogger.GLOBAL ? "" : `[${context}]`;
57
74
  }
58
75
  getLogColor(level) {
59
76
  switch (level) {
60
- case "DEBUG" /* ELogLevel.DEBUG */:
77
+ case ELogLevel.DEBUG:
61
78
  return Colors.Gray;
62
- case "INFO" /* ELogLevel.INFO */:
79
+ case ELogLevel.INFO:
63
80
  return Colors.Cyan;
64
- case "WARN" /* ELogLevel.WARN */:
81
+ case ELogLevel.WARN:
65
82
  return Colors.Yellow;
66
- case "ERROR" /* ELogLevel.ERROR */:
83
+ case ELogLevel.ERROR:
67
84
  return Colors.Red;
68
85
  default:
69
86
  return Colors.White;
@@ -80,9 +97,9 @@ class BaseLogger {
80
97
  shouldLog(level) {
81
98
  if (IS_PRODUCTION)
82
99
  return false;
83
- if (level === "ERROR" /* ELogLevel.ERROR */ || level === "WARN" /* ELogLevel.WARN */)
100
+ if (level === ELogLevel.ERROR || level === ELogLevel.WARN)
84
101
  return true;
85
- if (level === "DEBUG" /* ELogLevel.DEBUG */ && LOG_LEVEL !== "DEBUG" /* ELogLevel.DEBUG */)
102
+ if (level === ELogLevel.DEBUG && LOG_LEVEL !== ELogLevel.DEBUG)
86
103
  return false;
87
104
  if (!ENABLED_LOGS[this.context])
88
105
  return false;
@@ -100,26 +117,27 @@ class BaseLogger {
100
117
  }
101
118
  }
102
119
  debug(...args) {
103
- this.log("DEBUG" /* ELogLevel.DEBUG */, console.debug, args);
120
+ this.log(ELogLevel.DEBUG, console.debug, args);
104
121
  }
105
122
  info(...args) {
106
- this.log("INFO" /* ELogLevel.INFO */, console.log, args);
123
+ this.log(ELogLevel.INFO, console.log, args);
107
124
  }
108
125
  warn(...args) {
109
- this.log("WARN" /* ELogLevel.WARN */, console.warn, args);
126
+ this.log(ELogLevel.WARN, console.warn, args);
110
127
  }
111
128
  error(...args) {
112
- this.log("ERROR" /* ELogLevel.ERROR */, console.error, args);
129
+ this.log(ELogLevel.ERROR, console.error, args);
113
130
  }
114
131
  }
115
132
  export function createLogger(context) {
116
133
  return new BaseLogger(context);
117
134
  }
118
- const logger = createLogger("GLOBAL" /* EApplicationLogger.GLOBAL */);
119
- export const loggerOdooClient = createLogger("ODOO_CLIENT" /* EApplicationLogger.ODOO_CLIENT */);
120
- export const loggerAdapter = createLogger("ADAPTER" /* EApplicationLogger.ADAPTER */);
121
- export const loggerRepositories = createLogger("REPOSITORIES" /* EApplicationLogger.REPOSITORIES */);
122
- export const loggerUseCases = createLogger("USE_CASES" /* EApplicationLogger.USE_CASES */);
123
- export const loggerQueueProcessor = createLogger("EVENT_BUS" /* EApplicationLogger.QUEUE_PROCESSOR */);
124
- export const loggerCronJobs = createLogger("CRON_JOBS" /* EApplicationLogger.CRON_JOBS */);
135
+ const logger = createLogger(EApplicationLogger.GLOBAL);
136
+ export const loggerOdooClient = createLogger(EApplicationLogger.ODOO_CLIENT);
137
+ export const loggerAdapter = createLogger(EApplicationLogger.ADAPTER);
138
+ export const loggerRepositories = createLogger(EApplicationLogger.REPOSITORIES);
139
+ export const loggerUseCases = createLogger(EApplicationLogger.USE_CASES);
140
+ export const loggerQueueProcessor = createLogger(EApplicationLogger.QUEUE_PROCESSOR);
141
+ export const loggerCronJobs = createLogger(EApplicationLogger.CRON_JOBS);
142
+ export { EApplicationLogger };
125
143
  export default logger;
@@ -1 +1,6 @@
1
- export declare function ToastHost(): import("react/jsx-runtime").JSX.Element;
1
+ /**
2
+ * Bridges the framework's `UI_NOTIFICATION_EVENT` (emitted by services via UIEventBus)
3
+ * to the design-system Toast. Renders nothing; the toast stack is rendered by
4
+ * `ToastProvider` (mounted in AppShell). Must live inside `ToastProvider`.
5
+ */
6
+ export declare function ToastHost(): null;
@@ -1,19 +1,28 @@
1
- import { jsx as _jsx } from "nativewind/jsx-runtime";
1
+ import { useToast } from "@sincpro/mobile-ui/Feedback";
2
2
  import { useEffect } from "react";
3
- import Toast from "react-native-toast-message";
4
3
  import { UI_NOTIFICATION_EVENT } from "./events";
5
4
  import { UIEventBus } from "./UIEventBus";
5
+ const VARIANT_BY_TYPE = {
6
+ success: "success",
7
+ error: "danger",
8
+ info: "info",
9
+ };
10
+ /**
11
+ * Bridges the framework's `UI_NOTIFICATION_EVENT` (emitted by services via UIEventBus)
12
+ * to the design-system Toast. Renders nothing; the toast stack is rendered by
13
+ * `ToastProvider` (mounted in AppShell). Must live inside `ToastProvider`.
14
+ */
6
15
  export function ToastHost() {
16
+ const toast = useToast();
7
17
  useEffect(() => {
8
18
  const off = UIEventBus.on(UI_NOTIFICATION_EVENT, (payload) => {
9
19
  const notification = payload;
10
- Toast.show({
11
- type: notification.type ?? "info",
12
- text1: notification.text1,
13
- text2: notification.text2,
14
- });
20
+ const variant = VARIANT_BY_TYPE[notification.type ?? "info"];
21
+ const message = notification.text2 ?? notification.text1 ?? "";
22
+ const title = notification.text2 ? notification.text1 : undefined;
23
+ toast.show({ message, title, variant });
15
24
  });
16
25
  return off;
17
- }, []);
18
- return _jsx(Toast, {});
26
+ }, [toast]);
27
+ return null;
19
28
  }
@@ -19,6 +19,12 @@ declare class PrinterService {
19
19
  printFromView(viewRef: RefObject<View | null>): Promise<boolean>;
20
20
  printHtml(html: string): Promise<boolean>;
21
21
  private htmlToPdfBase64;
22
+ /**
23
+ * Drops the current BLE connection (even if cached as "connected") and
24
+ * reconnects to the last saved printer. Used as a one-shot recovery after
25
+ * a failed print caused by a stale connection.
26
+ */
27
+ private forceReconnect;
22
28
  private ensureConnected;
23
29
  }
24
30
  export declare const printerService: PrinterService;
@@ -1,6 +1,7 @@
1
1
  import { ReceiptExporterAdapter } from "../adapters/ReceiptExporter.adapter";
2
2
  import { SettingsRepository } from "../adapters/repositories/setting.repository";
3
3
  import { getPrinterDriver, hasPrinterDriver, setPrinterDriver, } from "../domain/print";
4
+ import { ECommonSetting } from "../domain/settings";
4
5
  import { loggerUseCases } from "../infrastructure/logger";
5
6
  import { UI_NOTIFICATION_EVENT } from "../infrastructure/ui/events";
6
7
  import { UIEventBus } from "../infrastructure/ui/UIEventBus";
@@ -72,16 +73,16 @@ class PrinterService {
72
73
  if (this.cachedPrinter) {
73
74
  return this.cachedPrinter;
74
75
  }
75
- const printer = await SettingsRepository.getSettingByName("common.selected_printer" /* ECommonSetting.SELECTED_PRINTER */);
76
+ const printer = await SettingsRepository.getSettingByName(ECommonSetting.SELECTED_PRINTER);
76
77
  this.cachedPrinter = printer;
77
78
  return printer;
78
79
  }
79
80
  async saveSelectedPrinter(printer) {
80
- await SettingsRepository.saveOneSetting("common.selected_printer" /* ECommonSetting.SELECTED_PRINTER */, printer);
81
+ await SettingsRepository.saveOneSetting(ECommonSetting.SELECTED_PRINTER, printer);
81
82
  this.cachedPrinter = printer;
82
83
  }
83
84
  async clearSelectedPrinter() {
84
- await SettingsRepository.saveOneSetting("common.selected_printer" /* ECommonSetting.SELECTED_PRINTER */, null);
85
+ await SettingsRepository.saveOneSetting(ECommonSetting.SELECTED_PRINTER, null);
85
86
  this.cachedPrinter = null;
86
87
  }
87
88
  async printTestPage() {
@@ -132,9 +133,24 @@ class PrinterService {
132
133
  await getPrinterDriver().printImageBase64(base64Data);
133
134
  return true;
134
135
  }
135
- catch (error) {
136
- loggerUseCases.error("Error printing image", error);
137
- return false;
136
+ catch (firstError) {
137
+ loggerUseCases.warn("Print failed — attempting grace reconnect", firstError);
138
+ const reconnected = await this.forceReconnect();
139
+ if (!reconnected)
140
+ return false; // connect() ya notificó al usuario
141
+ try {
142
+ await getPrinterDriver().printImageBase64(base64Data);
143
+ return true;
144
+ }
145
+ catch (retryError) {
146
+ loggerUseCases.error("Print retry after reconnect also failed", retryError);
147
+ UIEventBus.emit(UI_NOTIFICATION_EVENT, {
148
+ type: "error",
149
+ text1: "Error de impresión",
150
+ text2: "No se pudo imprimir incluso tras reconectar la impresora",
151
+ });
152
+ return false;
153
+ }
138
154
  }
139
155
  }
140
156
  async printFromView(viewRef) {
@@ -201,6 +217,24 @@ class PrinterService {
201
217
  return null;
202
218
  }
203
219
  }
220
+ /**
221
+ * Drops the current BLE connection (even if cached as "connected") and
222
+ * reconnects to the last saved printer. Used as a one-shot recovery after
223
+ * a failed print caused by a stale connection.
224
+ */
225
+ async forceReconnect() {
226
+ try {
227
+ await getPrinterDriver().disconnect();
228
+ }
229
+ catch {
230
+ // Expected when the BLE link is already dead — ignore.
231
+ }
232
+ const printer = await this.getSelectedPrinter();
233
+ if (!printer)
234
+ return false;
235
+ loggerUseCases.info(`Grace reconnect: reconnecting to ${printer.name} (${printer.address})`);
236
+ return this.connect(printer.address, printer.name);
237
+ }
204
238
  async ensureConnected() {
205
239
  if (this.isConnected()) {
206
240
  return true;
@@ -1,15 +1,11 @@
1
1
  import { jsx as _jsx } from "nativewind/jsx-runtime";
2
2
  import { useCommon } from "../../../entrypoints/ui/common_provider";
3
- import { Typography } from "@sincpro/mobile-ui/Typography";
4
- import { View } from "react-native";
3
+ import { Feedback } from "@sincpro/mobile-ui/Feedback";
5
4
  function DebugBanner() {
6
5
  const { debugMode } = useCommon();
7
- function renderBanner() {
8
- if (!debugMode) {
9
- return null;
10
- }
11
- return (_jsx(View, { className: "bg-accent py-2 px-4", children: _jsx(Typography.Text, { className: "text-white font-bold text-center", semibold: true, children: "⚠️ Estás en modo Debug" }) }));
6
+ if (!debugMode) {
7
+ return null;
12
8
  }
13
- return renderBanner();
9
+ return _jsx(Feedback.Banner, { message: "Est\u00E1s en modo Debug", tone: "warning" });
14
10
  }
15
11
  export { DebugBanner };
@@ -0,0 +1,4 @@
1
+ import { CustomIconProps } from "@sincpro/mobile-ui/icons/props";
2
+ import React from "react";
3
+ declare const PrinterIcon: React.FC<CustomIconProps>;
4
+ export default PrinterIcon;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import Svg, { Path, Rect } from "react-native-svg";
3
+ const PrinterIcon = ({ size = 24, color = "#000000" }) => (_jsxs(Svg, { fill: "none", height: size, viewBox: "0 0 24 24", width: size, children: [_jsx(Path, { d: "M6 9V2H18V9", stroke: color, strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2" }), _jsx(Rect, { height: "8", rx: "1", stroke: color, strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", width: "16", x: "4", y: "9" }), _jsx(Path, { d: "M6 18H4C3.44772 18 3 17.5523 3 17V10C3 9.44772 3.44772 9 4 9H20C20.5523 9 21 9.44772 21 10V17C21 17.5523 20.5523 18 20 18H18", stroke: color, strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2" }), _jsx(Rect, { height: "6", stroke: color, strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", width: "12", x: "6", y: "14" })] }));
4
+ export default PrinterIcon;
@@ -1 +1,2 @@
1
1
  export { DebugBanner } from "./DebugBanner";
2
+ export { default as PrinterIcon } from "./PrinterIcon";
@@ -1 +1,2 @@
1
1
  export { DebugBanner } from "./DebugBanner";
2
+ export { default as PrinterIcon } from "./PrinterIcon";
@@ -1,20 +1,24 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import { bluetoothService } from "../../../services/bluetooth.service";
3
3
  import { printerService } from "../../../services/printer.service";
4
- import { Display, Form } from "@sincpro/mobile-ui";
5
- import { BottomSheet } from "@sincpro/mobile-ui/Dialog/BottomSheet";
6
- import PrinterIcon from "@sincpro/mobile-ui/icons/PrinterIcon";
7
- import { theme } from "@sincpro/mobile-ui/theme";
4
+ import PrinterIcon from "../../../ui/components/atoms/PrinterIcon";
5
+ import Sheet from "@sincpro/mobile-ui/Dialog/Sheet";
6
+ import { Display } from "@sincpro/mobile-ui/Display";
7
+ import { Form } from "@sincpro/mobile-ui/Form";
8
+ import { useTheme } from "@sincpro/mobile-ui/theme";
8
9
  import { Typography } from "@sincpro/mobile-ui/Typography";
9
10
  import { useCallback, useEffect, useState } from "react";
10
- import { FlatList, RefreshControl, TouchableOpacity, View } from "react-native";
11
+ import { TouchableOpacity, View } from "react-native";
11
12
  function DeviceRow({ device, onSelect, isSelected }) {
12
- return (_jsxs(TouchableOpacity, { activeOpacity: 0.7, className: `flex-row items-center p-4 border-b border-gray-100 ${isSelected ? "bg-green-50" : "bg-white"}`, onPress: onSelect, children: [_jsx(Display.Icon, { color: device.isPrinter ? theme.success : theme.text.secondary, customIcon: PrinterIcon, size: 24, type: "custom" }), _jsxs(View, { className: "ml-3 flex-1", children: [_jsx(Typography.Text, { semibold: true, children: device.name || "Dispositivo sin nombre" }), _jsx(Typography.Text, { className: "text-gray-400 text-xs", children: device.address }), device.isPrinter && (_jsx(Typography.Text, { className: "text-green-600 text-xs", children: "Impresora detectada" }))] }), isSelected && (_jsx(View, { className: "w-6 h-6 rounded-full bg-green-500 items-center justify-center", children: _jsx(Display.Icon, { color: theme.text.inverse, name: "check", size: 16, type: "material" }) }))] }));
13
+ const theme = useTheme();
14
+ return (_jsxs(TouchableOpacity, { activeOpacity: 0.7, className: `flex-row items-center p-4 rounded-xl ${isSelected ? "bg-success-light" : "bg-bg-card"}`, onPress: onSelect, children: [_jsx(Display.Icon, { color: device.isPrinter ? theme.success : theme.text.secondary, customIcon: PrinterIcon, size: 24, type: "custom" }), _jsxs(View, { className: "ml-3 flex-1", children: [_jsx(Typography.Text, { semibold: true, children: device.name || "Dispositivo sin nombre" }), _jsx(Typography.Text, { className: "text-text-tertiary text-xs", children: device.address }), device.isPrinter && (_jsx(Typography.Text, { className: "text-success text-xs", children: "Impresora detectada" }))] }), isSelected && (_jsx(View, { className: "w-6 h-6 rounded-full bg-success items-center justify-center", children: _jsx(Display.Icon, { color: theme.text.inverse, name: "check", size: 16, type: "material" }) }))] }));
13
15
  }
14
16
  function EmptyState() {
15
- return (_jsxs(View, { className: "p-8 items-center justify-center", style: { minHeight: 250 }, children: [_jsx(Display.Icon, { color: theme.text.tertiary, customIcon: PrinterIcon, size: 64, type: "custom" }), _jsx(Typography.Text, { className: "text-gray-500 text-center mt-4", children: "No hay dispositivos vinculados" }), _jsx(Typography.Text, { className: "text-gray-400 text-center text-sm mt-2", children: "Vincula una impresora desde configuración de Bluetooth del sistema" })] }));
17
+ const theme = useTheme();
18
+ return (_jsxs(View, { className: "p-8 items-center justify-center", style: { minHeight: 250 }, children: [_jsx(Display.Icon, { color: theme.text.tertiary, customIcon: PrinterIcon, size: 64, type: "custom" }), _jsx(Typography.Text, { className: "text-text-secondary text-center mt-4", children: "No hay dispositivos vinculados" }), _jsx(Typography.Text, { className: "text-text-tertiary text-center text-sm mt-2", children: "Vincula una impresora desde configuración de Bluetooth del sistema" })] }));
16
19
  }
17
20
  function BluetoothDeviceSelectorModal({ visible, onClose, onSelect, }) {
21
+ const theme = useTheme();
18
22
  const [devices, setDevices] = useState([]);
19
23
  const [loading, setLoading] = useState(false);
20
24
  const [selectedDevice, setSelectedDevice] = useState(null);
@@ -55,7 +59,9 @@ function BluetoothDeviceSelectorModal({ visible, onClose, onSelect, }) {
55
59
  }
56
60
  const printers = devices.filter((d) => d.isPrinter);
57
61
  const otherDevices = devices.filter((d) => !d.isPrinter);
58
- const allDevices = [...printers, ...otherDevices];
59
- return (_jsxs(BottomSheet.Root, { onClose: onClose, size: "large", visible: visible, children: [_jsx(BottomSheet.Header, { children: _jsxs(View, { className: "px-5 pb-3", children: [_jsx(Typography.Text, { bold: true, variant: "subtitle", children: "Seleccionar Impresora" }), _jsx(Typography.Text, { className: "text-gray-500 text-xs", children: "Dispositivos Bluetooth vinculados" })] }) }), _jsx(BottomSheet.Content, { scrollable: true, children: devices.length === 0 && !loading ? (_jsx(EmptyState, {})) : (_jsx(FlatList, { data: allDevices, keyExtractor: (item) => item.address, ListHeaderComponent: printers.length > 0 ? (_jsxs(Typography.Text, { className: "text-gray-600 px-4 py-2 bg-gray-50", semibold: true, children: ["Impresoras (", printers.length, ")"] })) : null, refreshControl: _jsx(RefreshControl, { onRefresh: loadDevices, refreshing: loading }), renderItem: ({ item, index }) => (_jsxs(_Fragment, { children: [index === printers.length && otherDevices.length > 0 && (_jsxs(Typography.Text, { className: "text-gray-600 px-4 py-2 bg-gray-50", semibold: true, children: ["Otros dispositivos (", otherDevices.length, ")"] })), _jsx(DeviceRow, { device: item, isSelected: selectedDevice?.address === item.address, onSelect: () => setSelectedDevice(item) })] })), scrollEnabled: false })) }), _jsxs(BottomSheet.Actions, { layout: "horizontal", children: [_jsx(View, { className: "flex-1", children: _jsx(Form.Button, { onPress: onClose, title: "Cancelar", variant: "outline" }) }), selectedDevice && (_jsx(View, { className: "flex-1", children: _jsx(Form.Button, { loading: connecting, onPress: handleConfirm, title: "Conectar", variant: "primary" }) }))] })] }));
62
+ function renderRow(item) {
63
+ return (_jsx(DeviceRow, { device: item, isSelected: selectedDevice?.address === item.address, onSelect: () => setSelectedDevice(item) }, item.address));
64
+ }
65
+ return (_jsxs(Sheet, { onClose: onClose, title: "Seleccionar Impresora", visible: visible, children: [_jsxs(View, { className: "flex-row items-center justify-between mb-3", children: [_jsx(Typography.Text, { className: "text-text-secondary text-xs", children: "Dispositivos Bluetooth vinculados" }), _jsx(TouchableOpacity, { accessibilityLabel: "Actualizar", disabled: loading, hitSlop: 8, onPress: loadDevices, children: _jsx(Display.Icon, { color: theme.icon.secondary, name: "refresh", size: 20, type: "material" }) })] }), devices.length === 0 && !loading ? (_jsx(EmptyState, {})) : (_jsxs(View, { className: "gap-1", children: [printers.length > 0 ? (_jsxs(Typography.Text, { className: "text-text-secondary px-1 py-1", semibold: true, children: ["Impresoras (", printers.length, ")"] })) : null, printers.map(renderRow), otherDevices.length > 0 ? (_jsxs(Typography.Text, { className: "text-text-secondary px-1 py-1 mt-1", semibold: true, children: ["Otros dispositivos (", otherDevices.length, ")"] })) : null, otherDevices.map(renderRow)] })), _jsxs(View, { className: "flex-row gap-2 mt-4", children: [_jsx(View, { className: "flex-1", children: _jsx(Form.Button, { onPress: onClose, title: "Cancelar", variant: "outline" }) }), selectedDevice ? (_jsx(View, { className: "flex-1", children: _jsx(Form.Button, { loading: connecting, onPress: handleConfirm, title: "Conectar", variant: "cta" }) })) : null] })] }));
60
66
  }
61
67
  export { BluetoothDeviceSelectorModal };
@@ -1,14 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import { Display } from "@sincpro/mobile-ui/Display";
3
- import { theme } from "@sincpro/mobile-ui/theme";
3
+ import { useTheme } from "@sincpro/mobile-ui/theme";
4
4
  import { cn } from "@sincpro/mobile-ui/theme/tw";
5
5
  import { Typography } from "@sincpro/mobile-ui/Typography";
6
6
  import { TouchableOpacity, View } from "react-native";
7
7
  function DeadLetterErrorBlock({ errorMessage, expanded, onToggle, previewChars = 140, }) {
8
+ const theme = useTheme();
8
9
  if (!errorMessage)
9
10
  return null;
10
11
  const preview = errorMessage.substring(0, previewChars);
11
12
  const truncated = errorMessage.length > previewChars;
12
- return (_jsxs(View, { className: cn("border-l-[3px] border-red-500 bg-red-50 p-2.5 rounded-xl mb-3", expanded && "bg-red-100"), children: [_jsxs(TouchableOpacity, { accessibilityRole: "button", activeOpacity: 0.75, className: "flex-row items-center mb-1", onPress: onToggle, children: [_jsx(Display.Icon, { color: theme.danger, name: "alert-circle", size: 16, type: "feather" }), _jsx(Typography.Text, { className: "text-red-600 ml-1.5", semibold: true, variant: "bodySmall", children: "Error" })] }), !expanded && (_jsxs(Typography.Text, { className: "text-red-600", numberOfLines: 3, variant: "bodySmall", children: [preview, truncated ? "…" : ""] })), expanded && (_jsx(View, { className: "bg-white rounded-lg p-2.5 border border-red-100", children: _jsx(Typography.Text, { className: "text-red-600 text-xs leading-4", variant: "bodySmall", children: errorMessage }) })), truncated && !expanded && (_jsx(TouchableOpacity, { accessibilityRole: "button", activeOpacity: 0.7, className: "mt-1.5", onPress: onToggle, children: _jsx(Typography.Text, { className: "text-red-600 underline", semibold: true, variant: "bodySmall", children: "Ver error completo" }) }))] }));
13
+ return (_jsxs(View, { className: cn("border-l-[3px] border-danger bg-danger-light p-2.5 rounded-xl mb-3", expanded && "bg-danger-light"), children: [_jsxs(TouchableOpacity, { accessibilityRole: "button", activeOpacity: 0.75, className: "flex-row items-center mb-1", onPress: onToggle, children: [_jsx(Display.Icon, { color: theme.danger, name: "alert-circle", size: 16, type: "feather" }), _jsx(Typography.Text, { className: "text-danger ml-1.5", semibold: true, variant: "bodySmall", children: "Error" })] }), !expanded && (_jsxs(Typography.Text, { className: "text-danger", numberOfLines: 3, variant: "bodySmall", children: [preview, truncated ? "…" : ""] })), expanded && (_jsx(View, { className: "bg-bg-card rounded-lg p-2.5 border border-danger/30", children: _jsx(Typography.Text, { className: "text-danger text-xs leading-4", variant: "bodySmall", children: errorMessage }) })), truncated && !expanded && (_jsx(TouchableOpacity, { accessibilityRole: "button", activeOpacity: 0.7, className: "mt-1.5", onPress: onToggle, children: _jsx(Typography.Text, { className: "text-danger underline", semibold: true, variant: "bodySmall", children: "Ver error completo" }) }))] }));
13
14
  }
14
15
  export default DeadLetterErrorBlock;
@@ -2,41 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import { EEventStatus } from "../../../domain/event_sourcing";
3
3
  import { Display } from "@sincpro/mobile-ui/Display";
4
4
  import CopyableText from "@sincpro/mobile-ui/Display/Display.CopyableText";
5
- import { theme } from "@sincpro/mobile-ui/theme";
5
+ import { useTheme } from "@sincpro/mobile-ui/theme";
6
6
  import { Typography } from "@sincpro/mobile-ui/Typography";
7
7
  import JsonPreview from "@sincpro/mobile-ui/widgets/JSONViewer";
8
8
  import { useState } from "react";
9
9
  import { TouchableOpacity, View } from "react-native";
10
- const statusColorMap = {
11
- [EEventStatus.ACKNOWLEDGED]: {
12
- bg: "bg-emerald-600",
13
- color: theme.success,
14
- light: `${theme.success}4D`,
15
- },
16
- [EEventStatus.PENDING]: {
17
- bg: "bg-amber-500",
18
- color: theme.warning,
19
- light: `${theme.warning}4D`,
20
- },
21
- [EEventStatus.PROCESSING]: {
22
- bg: "bg-accent",
23
- color: theme.warning,
24
- light: `${theme.warning}4D`,
25
- },
26
- [EEventStatus.FAILED]: {
27
- bg: "bg-red-500",
28
- color: theme.danger,
29
- light: `${theme.danger}4D`,
30
- },
31
- };
32
- const defaultStatus = {
33
- bg: "bg-gray-500",
34
- color: theme.text.secondary,
35
- light: `${theme.text.secondary}4D`,
36
- };
37
- function getStatusColor(status) {
38
- return statusColorMap[status] ?? defaultStatus;
39
- }
40
10
  function getStatusIcon(status) {
41
11
  switch (status) {
42
12
  case EEventStatus.ACKNOWLEDGED:
@@ -57,9 +27,37 @@ function formatEventName(name) {
57
27
  }
58
28
  function EventTimelineItem({ event, isLast = false }) {
59
29
  const [expanded, setExpanded] = useState(false);
60
- const statusInfo = getStatusColor(event.status);
30
+ const theme = useTheme();
31
+ const statusColorMap = {
32
+ [EEventStatus.ACKNOWLEDGED]: {
33
+ bg: "bg-success",
34
+ color: theme.success,
35
+ light: `${theme.success}4D`,
36
+ },
37
+ [EEventStatus.PENDING]: {
38
+ bg: "bg-warning",
39
+ color: theme.warning,
40
+ light: `${theme.warning}4D`,
41
+ },
42
+ [EEventStatus.PROCESSING]: {
43
+ bg: "bg-accent",
44
+ color: theme.warning,
45
+ light: `${theme.warning}4D`,
46
+ },
47
+ [EEventStatus.FAILED]: {
48
+ bg: "bg-danger",
49
+ color: theme.danger,
50
+ light: `${theme.danger}4D`,
51
+ },
52
+ };
53
+ const defaultStatus = {
54
+ bg: "bg-text-tertiary",
55
+ color: theme.text.secondary,
56
+ light: `${theme.text.secondary}4D`,
57
+ };
58
+ const statusInfo = statusColorMap[event.status] ?? defaultStatus;
61
59
  const statusIcon = getStatusIcon(event.status);
62
60
  const friendlyName = formatEventName(event.name);
63
- return (_jsxs(View, { className: "flex-row px-4", children: [_jsxs(View, { className: "items-center w-8 mr-3", children: [_jsx(View, { className: `w-6 h-6 rounded-full items-center justify-center ${statusInfo.bg}`, children: _jsx(Display.Icon, { color: "#ffffff", name: statusIcon, size: 12, type: "feather" }) }), !isLast && (_jsx(View, { className: "w-0.5 flex-1 mt-1", style: { backgroundColor: statusInfo.light } }))] }), _jsxs(TouchableOpacity, { activeOpacity: 0.7, className: "flex-1 bg-white rounded-xl p-3 mb-3 border border-gray-100 shadow-sm", onPress: () => setExpanded(!expanded), children: [_jsxs(View, { className: "flex-row justify-between items-center mb-2", children: [_jsx(Typography.Text, { semibold: true, variant: "body", children: friendlyName }), _jsx(Display.Icon, { color: theme.text.secondary, name: expanded ? "chevron-up" : "chevron-down", size: 16, type: "feather" })] }), _jsxs(View, { className: "flex-row items-center mb-1 gap-1.5", children: [_jsx(Display.Icon, { color: theme.text.secondary, name: "clock", size: 12, type: "feather" }), _jsx(Display.Date, { className: "text-text-tertiary", showTime: true, textVariant: "caption", value: event.createdAt })] }), _jsxs(View, { className: "flex-row items-center mb-1 gap-1.5", children: [_jsx(Display.Icon, { color: statusInfo.color, name: statusIcon, size: 12, type: "feather" }), _jsxs(Typography.Text, { style: { color: statusInfo.color }, variant: "caption", children: [event.status, " \u2022 Intentos: ", event.attempts] })] }), (event.sequence ?? 0) > 0 && (_jsxs(View, { className: "flex-row items-center mb-1 gap-1.5", children: [_jsx(Display.Icon, { color: theme.text.secondary, name: "hash", size: 12, type: "feather" }), _jsxs(Typography.Text, { className: "text-text-tertiary", variant: "caption", children: ["Secuencia: ", event.sequence] })] })), expanded && event && (_jsxs(View, { className: "mt-3 border-t border-gray-100 pt-3", children: [_jsxs(View, { className: "flex-row justify-between items-center mb-2", children: [_jsx(Typography.Text, { className: "text-text-tertiary", semibold: true, variant: "caption", children: "Payload" }), _jsx(CopyableText, { className: "text-primary text-xs", getValue: () => event.asJSON(true), label: "copy" })] }), _jsx(View, { className: "bg-gray-50 rounded-lg border border-gray-200 overflow-hidden", children: _jsx(JsonPreview, { selectedJson: event.asJSON(true) }) })] }))] })] }));
61
+ return (_jsxs(View, { className: "flex-row px-4", children: [_jsxs(View, { className: "items-center w-8 mr-3", children: [_jsx(View, { className: `w-6 h-6 rounded-full items-center justify-center ${statusInfo.bg}`, children: _jsx(Display.Icon, { color: "#ffffff", name: statusIcon, size: 12, type: "feather" }) }), !isLast && (_jsx(View, { className: "w-0.5 flex-1 mt-1", style: { backgroundColor: statusInfo.light } }))] }), _jsxs(TouchableOpacity, { activeOpacity: 0.7, className: "flex-1 bg-bg-card rounded-xl p-3 mb-3 border border-border-light shadow-sm", onPress: () => setExpanded(!expanded), children: [_jsxs(View, { className: "flex-row justify-between items-center mb-2", children: [_jsx(Typography.Text, { semibold: true, variant: "body", children: friendlyName }), _jsx(Display.Icon, { color: theme.text.secondary, name: expanded ? "chevron-up" : "chevron-down", size: 16, type: "feather" })] }), _jsxs(View, { className: "flex-row items-center mb-1 gap-1.5", children: [_jsx(Display.Icon, { color: theme.text.secondary, name: "clock", size: 12, type: "feather" }), _jsx(Display.Date, { className: "text-text-tertiary", showTime: true, textVariant: "caption", value: event.createdAt })] }), _jsxs(View, { className: "flex-row items-center mb-1 gap-1.5", children: [_jsx(Display.Icon, { color: statusInfo.color, name: statusIcon, size: 12, type: "feather" }), _jsxs(Typography.Text, { style: { color: statusInfo.color }, variant: "caption", children: [event.status, " \u2022 Intentos: ", event.attempts] })] }), (event.sequence ?? 0) > 0 && (_jsxs(View, { className: "flex-row items-center mb-1 gap-1.5", children: [_jsx(Display.Icon, { color: theme.text.secondary, name: "hash", size: 12, type: "feather" }), _jsxs(Typography.Text, { className: "text-text-tertiary", variant: "caption", children: ["Secuencia: ", event.sequence] })] })), expanded && event && (_jsxs(View, { className: "mt-3 border-t border-border-light pt-3", children: [_jsxs(View, { className: "flex-row justify-between items-center mb-2", children: [_jsx(Typography.Text, { className: "text-text-tertiary", semibold: true, variant: "caption", children: "Payload" }), _jsx(CopyableText, { className: "text-primary text-xs", getValue: () => event.asJSON(true), label: "copy" })] }), _jsx(View, { className: "bg-bg-muted rounded-lg border border-border-default overflow-hidden", children: _jsx(JsonPreview, { selectedJson: event.asJSON(true) }) })] }))] })] }));
64
62
  }
65
63
  export default EventTimelineItem;
@@ -0,0 +1,8 @@
1
+ interface GeoPermissionCardProps {
2
+ title: string;
3
+ hasPermission?: boolean;
4
+ loading?: boolean;
5
+ handleRequestPermission?: () => void;
6
+ }
7
+ declare function GeoPermissionCard({ title, loading, handleRequestPermission, hasPermission, }: GeoPermissionCardProps): import("react/jsx-runtime").JSX.Element;
8
+ export default GeoPermissionCard;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import { Display } from "@sincpro/mobile-ui/Display";
3
+ import { Form } from "@sincpro/mobile-ui/Form";
4
+ import { useTheme } from "@sincpro/mobile-ui/theme";
5
+ import { Typography } from "@sincpro/mobile-ui/Typography";
6
+ import { View } from "react-native";
7
+ import { tv } from "tailwind-variants";
8
+ const statusTextVariants = tv({
9
+ variants: {
10
+ granted: {
11
+ true: "text-success",
12
+ false: "text-danger",
13
+ },
14
+ },
15
+ });
16
+ function GeoPermissionCard({ title, loading, handleRequestPermission, hasPermission, }) {
17
+ const theme = useTheme();
18
+ const granted = hasPermission !== undefined ? hasPermission : false;
19
+ return (_jsx(Display.Card, { className: "my-2.5", children: _jsxs(View, { className: "flex-row items-start", children: [_jsx(Display.Icon, { color: granted ? theme.success : theme.danger, name: granted ? "check-circle" : "location-off", size: 35, type: "material" }), _jsxs(View, { className: "ml-2.5 flex-1", children: [_jsx(Typography.Text, { className: "text-base mb-1", semibold: true, children: title }), _jsx(Typography.Text, { className: statusTextVariants({ granted }), children: granted ? "Permiso activo." : "Permiso inactivo." }), !granted && (_jsx(Form.Button, { loading: loading, onPress: handleRequestPermission, size: "small", title: "Abrir Configuración", variant: "cta" }))] })] }) }));
20
+ }
21
+ export default GeoPermissionCard;
@@ -1,5 +1,8 @@
1
- interface Props {
2
- position?: "top" | "bottom";
3
- }
4
- declare function ProcessToast({ position }: Props): import("react/jsx-runtime").JSX.Element | null;
1
+ /**
2
+ * Bridges the framework's queue/cron activity state (from CommonProvider) to the
3
+ * design-system Toast: a persistent `loading` toast while a queue/cron task runs, and a
4
+ * `danger` toast on failure. Renders nothing — the toast UI is owned by the DS
5
+ * `ToastProvider` (mounted in AppShell), so this must live inside it.
6
+ */
7
+ declare function ProcessToast(): null;
5
8
  export default ProcessToast;