@sincpro/mobile 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,27 @@
1
1
  # @sincpro/mobile
2
2
 
3
- Core del framework móvil Sincpro (React Native / Expo): infraestructura offline-first (DB, cola/eventos, cron), patrones DDD, adapters base, composición module-driven y el `AppShell`. Consume el design system `@sincpro/mobile-ui`.
3
+ Core del **framework móvil Sincpro** (React Native / Expo): infraestructura offline-first (DB, cola/eventos, cron), patrones DDD, adapters base, composición **module-driven** y el `AppShell`. Consume el design system `@sincpro/mobile-ui`.
4
+
5
+ > 🤖 **¿Sos un agente de IA?** Leé primero [`AGENTS.md`](AGENTS.md) — orientación rápida del ecosistema, patrones y trampas. Si algo se comporta raro, [`docs/GOTCHAS.md`](docs/GOTCHAS.md).
6
+
7
+ ## Filosofía
8
+
9
+ El objetivo es **generar apps móviles de negocio muy rápido** componiendo piezas estables, no escribir cada app desde cero. Principios:
10
+
11
+ - **Framework, no monolito.** Un ecosistema de paquetes publicables; cada app es delgada y declara solo sus dominios.
12
+ - **Module-driven + inversión de dependencia.** El core no conoce los negocios. Cada dominio es un `DomainModule` que la app registra; el `orchestrator` alimenta cola, repos, migraciones, cron y temas desde los módulos registrados. Agregar/mover un dominio no toca el core.
13
+ - **Offline-first.** SQLite + cola de eventos persistente (con DLQ y reintentos) + sincronización; la app funciona sin red y reconcilia al volver.
14
+ - **Hexagonal.** El dominio define puertos (`IPrinterDriver`, `IRepository`, `IRemoteClient`); los adapters concretos viven afuera y se inyectan. El core es agnóstico de hardware y de backend.
15
+ - **Frontera real verificable.** Capas con dirección estricta `apps → mobile-odoo → mobile → mobile-ui`; el design system no sabe de negocio; los enums/eventos tienen un punto único de export.
16
+
17
+ ## Ecosistema
18
+
19
+ | Paquete | Rol |
20
+ | ---------------------- | ------------------------------------------------------------------------------------------- |
21
+ | `@sincpro/mobile` | Core: infra, patrones DDD, `framework/` (DomainModule, createApp, orchestrator), `AppShell` |
22
+ | `@sincpro/mobile-ui` | Design system standalone (sin router, sin dominios) |
23
+ | `@sincpro/mobile-odoo` | Integración Odoo **opcional** (OdooClient, auth, server, partner) |
24
+ | `sincpro-mobile-<app>` | Apps de negocio (tickets, distribution) que componen lo anterior |
4
25
 
5
26
  ## Instalación
6
27
 
@@ -15,8 +36,9 @@ npx expo install @sincpro/mobile @sincpro/mobile-ui
15
36
  Una app define sus dominios como **clases que extienden `DomainModule`** y las compone con `createAppShell`:
16
37
 
17
38
  ```tsx
18
- import { createAppShell, createTheme, DomainModule } from "@sincpro/mobile";
19
- import type { Subscriber } from "@sincpro/mobile/domain/subscriber";
39
+ import { createAppShell, createTheme } from "@sincpro/mobile";
40
+ import { DomainModule } from "@sincpro/mobile/framework/domain_module";
41
+ import type { Subscriber } from "@sincpro/mobile/domain/event_sourcing";
20
42
 
21
43
  class VentasModule extends DomainModule {
22
44
  readonly key = "VENTAS";
@@ -55,20 +77,15 @@ export default createAppShell({
55
77
  Las primitivas y capas se importan por subpath, p. ej.:
56
78
 
57
79
  ```ts
58
- import { DomainModule } from "@sincpro/mobile/entrypoints/app/domain_module";
59
- import type { Subscriber } from "@sincpro/mobile/domain/subscriber";
80
+ import { DomainModule } from "@sincpro/mobile/framework/domain_module";
81
+ import type { Subscriber } from "@sincpro/mobile/domain/event_sourcing";
60
82
  import { setPrinterDriver } from "@sincpro/mobile/domain/print";
61
83
  ```
62
84
 
63
85
  ## Impresora (opcional)
64
86
 
65
- El core es agnóstico del hardware: define el puerto `IPrinterDriver` (`@sincpro/mobile/domain/print`). La app registra un driver concreto al bootstrap:
66
-
67
- ```ts
68
- import { setPrinterDriver } from "@sincpro/mobile/domain/print";
69
- setPrinterDriver(miDriver);
70
- ```
87
+ El core es agnóstico del hardware: define el puerto `IPrinterDriver` (`@sincpro/mobile/domain/print`). La app registra un driver concreto al bootstrap (vía `printerService.setDriver(...)`); si no registra ninguno, el driver Noop degrada con un warning.
71
88
 
72
89
  ## Desarrollo
73
90
 
74
- Todo vía Makefile: `make build` (tsc `lib/`), `make check` (lint + typecheck), `make format`, `make publish`.
91
+ Todo vía Makefile. Dos comandos de calidad: `make format` (auto-fix: eslint --fix + prettier + typecheck) y `make verify-format` (gate de CI: corre `format` y falla si quedó algo sin formatear/commitear — cubre lint + formato + tipos). Además `make build` (tsc + tsc-alias → `dist/`) y `make publish`. Detalle del workflow y guardrails en [`AGENTS.md`](AGENTS.md).
@@ -1,10 +1,11 @@
1
1
  import { DomainEvent, EEventStatus } from "../../domain/event_sourcing";
2
2
  import { ECommonRepository } from "../../domain/repositories";
3
+ import { DATABASE_TABLES } from "../../entrypoints/db/migrations";
3
4
  import { DBCursor } from "../../infrastructure/database";
4
5
  import { loggerRepositories } from "../../infrastructure/logger";
5
6
  class DomainEventRepositoryImpl {
6
7
  name = ECommonRepository.DOMAIN_EVENT;
7
- table = "domain_events" /* DATABASE_TABLES.DOMAIN_EVENTS */;
8
+ table = DATABASE_TABLES.DOMAIN_EVENTS;
8
9
  async hasPendingDuplicate(event) {
9
10
  if (event.correlationId) {
10
11
  const existing = await DBCursor.getFirstAsync(`SELECT uuid FROM ${this.table}
@@ -1,10 +1,11 @@
1
1
  import { DomainEvent } from "../../domain/event_sourcing";
2
2
  import { ECommonRepository } from "../../domain/repositories";
3
+ import { DATABASE_TABLES } from "../../entrypoints/db/migrations";
3
4
  import { DBCursor } from "../../infrastructure/database";
4
5
  import { loggerRepositories } from "../../infrastructure/logger";
5
6
  class DomainEventDeadLetterRepositoryImpl {
6
7
  name = ECommonRepository.DOMAIN_EVENT_DEAD_LETTER;
7
- table = "domain_events_dead_letter" /* DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER */;
8
+ table = DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER;
8
9
  async save(event, errorMessage) {
9
10
  await DBCursor.mutateDatabase(`INSERT INTO ${this.table}
10
11
  (uuid, name, label, data, requires_network, created_at, attempts, error_message, failed_at, aggregate_id, source_id, correlation_id, sequence)
@@ -1,16 +1,17 @@
1
1
  import { ECommonRepository } from "../../domain/repositories";
2
+ import { DATABASE_TABLES } from "../../entrypoints/db/migrations";
2
3
  import { DBCursor } from "../../infrastructure/database";
3
4
  class SettingsRepositoryImpl {
4
5
  name = ECommonRepository.SETTINGS;
5
- table = "settings" /* DATABASE_TABLES.SETTINGS */;
6
+ table = DATABASE_TABLES.SETTINGS;
6
7
  async saveOneSetting(name, value) {
7
8
  const type = typeof value;
8
- await DBCursor.mutateDatabase(`INSERT OR REPLACE INTO ${"settings" /* DATABASE_TABLES.SETTINGS */} (name, value, type)
9
+ await DBCursor.mutateDatabase(`INSERT OR REPLACE INTO ${DATABASE_TABLES.SETTINGS} (name, value, type)
9
10
  VALUES (?, ?, ?)`, name, JSON.stringify(value), type);
10
11
  }
11
12
  async getSettingByName(name) {
12
13
  const row = await DBCursor.getFirstAsync(`SELECT value, type
13
- FROM ${"settings" /* DATABASE_TABLES.SETTINGS */}
14
+ FROM ${DATABASE_TABLES.SETTINGS}
14
15
  WHERE name = ?`, name);
15
16
  if (!row) {
16
17
  return null;
@@ -1,4 +1,4 @@
1
- export declare const enum ECommonSetting {
1
+ export declare enum ECommonSetting {
2
2
  SELECTED_PRINTER = "common.selected_printer"
3
3
  }
4
4
  export interface ISetting {
@@ -1 +1,4 @@
1
- export {};
1
+ export var ECommonSetting;
2
+ (function (ECommonSetting) {
3
+ ECommonSetting["SELECTED_PRINTER"] = "common.selected_printer";
4
+ })(ECommonSetting || (ECommonSetting = {}));
@@ -1,5 +1,5 @@
1
1
  import { IMigration } from "../../domain/database";
2
- export declare const enum DATABASE_TABLES {
2
+ export declare enum DATABASE_TABLES {
3
3
  SETTINGS = "settings",
4
4
  EVENT_QUEUE = "event_queue",
5
5
  DEAD_LETTER_QUEUE = "dead_letter_queue",
@@ -1,7 +1,15 @@
1
1
  import { DBCursor } from "../../infrastructure/database";
2
+ export var DATABASE_TABLES;
3
+ (function (DATABASE_TABLES) {
4
+ DATABASE_TABLES["SETTINGS"] = "settings";
5
+ DATABASE_TABLES["EVENT_QUEUE"] = "event_queue";
6
+ DATABASE_TABLES["DEAD_LETTER_QUEUE"] = "dead_letter_queue";
7
+ DATABASE_TABLES["DOMAIN_EVENTS"] = "domain_events";
8
+ DATABASE_TABLES["DOMAIN_EVENTS_DEAD_LETTER"] = "domain_events_dead_letter";
9
+ })(DATABASE_TABLES || (DATABASE_TABLES = {}));
2
10
  async function createSettingsTable() {
3
11
  await DBCursor.execAsync(`
4
- CREATE TABLE IF NOT EXISTS ${"settings" /* DATABASE_TABLES.SETTINGS */}(
12
+ CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.SETTINGS}(
5
13
  name TEXT PRIMARY KEY NOT NULL,
6
14
  value TEXT NOT NULL,
7
15
  type TEXT NOT NULL,
@@ -11,7 +19,7 @@ async function createSettingsTable() {
11
19
  }
12
20
  async function createDeadLetterQueueTable() {
13
21
  await DBCursor.execAsync(`
14
- CREATE TABLE IF NOT EXISTS ${"dead_letter_queue" /* DATABASE_TABLES.DEAD_LETTER_QUEUE */} (
22
+ CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.DEAD_LETTER_QUEUE} (
15
23
  uuid TEXT PRIMARY KEY NOT NULL, -- UUID v7 for chronological ordering
16
24
  name TEXT NOT NULL,
17
25
  payload TEXT NOT NULL,
@@ -23,7 +31,7 @@ async function createDeadLetterQueueTable() {
23
31
  }
24
32
  async function createEventQueueTable() {
25
33
  await DBCursor.execAsync(`
26
- CREATE TABLE IF NOT EXISTS ${"event_queue" /* DATABASE_TABLES.EVENT_QUEUE */}(
34
+ CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.EVENT_QUEUE}(
27
35
  uuid TEXT PRIMARY KEY NOT NULL,
28
36
  name TEXT NOT NULL,
29
37
  payload TEXT NOT NULL,
@@ -40,7 +48,7 @@ async function createEventQueueTable() {
40
48
  }
41
49
  async function createDomainEventsTable() {
42
50
  await DBCursor.execAsync(`
43
- CREATE TABLE IF NOT EXISTS ${"domain_events" /* DATABASE_TABLES.DOMAIN_EVENTS */} (
51
+ CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.DOMAIN_EVENTS} (
44
52
  uuid TEXT PRIMARY KEY NOT NULL,
45
53
  name TEXT NOT NULL,
46
54
  label TEXT NOT NULL,
@@ -59,24 +67,24 @@ async function createDomainEventsTable() {
59
67
  );
60
68
  `);
61
69
  await DBCursor.execAsync(`
62
- CREATE INDEX IF NOT EXISTS idx_domain_events_status ON ${"domain_events" /* DATABASE_TABLES.DOMAIN_EVENTS */}(status);
70
+ CREATE INDEX IF NOT EXISTS idx_domain_events_status ON ${DATABASE_TABLES.DOMAIN_EVENTS}(status);
63
71
  `);
64
72
  await DBCursor.execAsync(`
65
- CREATE INDEX IF NOT EXISTS idx_domain_events_correlation ON ${"domain_events" /* DATABASE_TABLES.DOMAIN_EVENTS */}(correlation_id);
73
+ CREATE INDEX IF NOT EXISTS idx_domain_events_correlation ON ${DATABASE_TABLES.DOMAIN_EVENTS}(correlation_id);
66
74
  `);
67
75
  await DBCursor.execAsync(`
68
- CREATE INDEX IF NOT EXISTS idx_domain_events_aggregate ON ${"domain_events" /* DATABASE_TABLES.DOMAIN_EVENTS */}(aggregate_id);
76
+ CREATE INDEX IF NOT EXISTS idx_domain_events_aggregate ON ${DATABASE_TABLES.DOMAIN_EVENTS}(aggregate_id);
69
77
  `);
70
78
  await DBCursor.execAsync(`
71
- CREATE INDEX IF NOT EXISTS idx_domain_events_name ON ${"domain_events" /* DATABASE_TABLES.DOMAIN_EVENTS */}(name);
79
+ CREATE INDEX IF NOT EXISTS idx_domain_events_name ON ${DATABASE_TABLES.DOMAIN_EVENTS}(name);
72
80
  `);
73
81
  await DBCursor.execAsync(`
74
- CREATE INDEX IF NOT EXISTS idx_domain_events_created ON ${"domain_events" /* DATABASE_TABLES.DOMAIN_EVENTS */}(created_at);
82
+ CREATE INDEX IF NOT EXISTS idx_domain_events_created ON ${DATABASE_TABLES.DOMAIN_EVENTS}(created_at);
75
83
  `);
76
84
  }
77
85
  async function createDomainEventsDeadLetterTable() {
78
86
  await DBCursor.execAsync(`
79
- CREATE TABLE IF NOT EXISTS ${"domain_events_dead_letter" /* DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER */} (
87
+ CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER} (
80
88
  uuid TEXT PRIMARY KEY NOT NULL,
81
89
  name TEXT NOT NULL,
82
90
  label TEXT NOT NULL,
@@ -93,22 +101,22 @@ async function createDomainEventsDeadLetterTable() {
93
101
  );
94
102
  `);
95
103
  await DBCursor.execAsync(`
96
- CREATE INDEX IF NOT EXISTS idx_dead_letter_correlation ON ${"domain_events_dead_letter" /* DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER */}(correlation_id);
104
+ CREATE INDEX IF NOT EXISTS idx_dead_letter_correlation ON ${DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER}(correlation_id);
97
105
  `);
98
106
  await DBCursor.execAsync(`
99
- CREATE INDEX IF NOT EXISTS idx_dead_letter_name ON ${"domain_events_dead_letter" /* DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER */}(name);
107
+ CREATE INDEX IF NOT EXISTS idx_dead_letter_name ON ${DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER}(name);
100
108
  `);
101
109
  await DBCursor.execAsync(`
102
- CREATE INDEX IF NOT EXISTS idx_dead_letter_failed ON ${"domain_events_dead_letter" /* DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER */}(failed_at);
110
+ CREATE INDEX IF NOT EXISTS idx_dead_letter_failed ON ${DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER}(failed_at);
103
111
  `);
104
112
  }
105
113
  const MIGRATIONS = [
106
- { name: "settings" /* DATABASE_TABLES.SETTINGS */, migrationFn: createSettingsTable },
107
- { name: "event_queue" /* DATABASE_TABLES.EVENT_QUEUE */, migrationFn: createEventQueueTable },
108
- { name: "dead_letter_queue" /* DATABASE_TABLES.DEAD_LETTER_QUEUE */, migrationFn: createDeadLetterQueueTable },
109
- { name: "domain_events" /* DATABASE_TABLES.DOMAIN_EVENTS */, migrationFn: createDomainEventsTable },
114
+ { name: DATABASE_TABLES.SETTINGS, migrationFn: createSettingsTable },
115
+ { name: DATABASE_TABLES.EVENT_QUEUE, migrationFn: createEventQueueTable },
116
+ { name: DATABASE_TABLES.DEAD_LETTER_QUEUE, migrationFn: createDeadLetterQueueTable },
117
+ { name: DATABASE_TABLES.DOMAIN_EVENTS, migrationFn: createDomainEventsTable },
110
118
  {
111
- name: "domain_events_dead_letter" /* DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER */,
119
+ name: DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER,
112
120
  migrationFn: createDomainEventsDeadLetterTable,
113
121
  },
114
122
  ];
@@ -5,13 +5,13 @@ interface ILogger {
5
5
  warn(...args: LoggerArgs): void;
6
6
  error(...args: LoggerArgs): void;
7
7
  }
8
- declare const enum ELogLevel {
8
+ declare enum ELogLevel {
9
9
  DEBUG = "DEBUG",
10
10
  INFO = "INFO",
11
11
  WARN = "WARN",
12
12
  ERROR = "ERROR"
13
13
  }
14
- declare const enum EApplicationLogger {
14
+ declare enum EApplicationLogger {
15
15
  GLOBAL = "GLOBAL",
16
16
  ODOO_CLIENT = "ODOO_CLIENT",
17
17
  ADAPTER = "ADAPTER",
@@ -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,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() {
@@ -5,6 +5,7 @@ import { theme } from "@sincpro/mobile-ui/theme";
5
5
  import { Typography } from "@sincpro/mobile-ui/Typography";
6
6
  import { FormViewV2 } from "@sincpro/mobile-ui/views/FormViewV2";
7
7
  import JsonPreview from "@sincpro/mobile-ui/widgets/JSONViewer";
8
+ import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
8
9
  import * as Clipboard from "expo-clipboard";
9
10
  import { View } from "react-native";
10
11
  import { useDatabase } from "./database.context";
@@ -12,7 +13,7 @@ const Button = Form.Button;
12
13
  const Icon = Display.Icon;
13
14
  function JsonDetailView() {
14
15
  const { selectedTable, selectedRowJson, goBackFromJson, currentRowIndex, filteredData, hasNextRow, hasPreviousRow, goToNextRow, goToPreviousRow, } = useDatabase();
15
- return (_jsxs(FormViewV2.Root, { description: `JSON VIEWER - ${selectedTable}`, isLoading: false, item: { json: selectedRowJson }, name: "Detalles", onBack: goBackFromJson, children: [_jsx(FormViewV2.Header, { variant: "FLAT_HEADER" /* EVariantScreenHeader.FLAT_HEADER */, children: _jsx(FormViewV2.Header.Actions, { children: _jsxs(Typography.Text, { className: "text-gray-500", variant: "bodySmall", children: [currentRowIndex + 1, " de ", filteredData.length] }) }) }), _jsx(FormViewV2.Content, { children: _jsx(JsonPreview, { selectedJson: selectedRowJson }) }), _jsx(FormViewV2.Footer, { children: _jsxs(View, { className: "flex-row items-center justify-between gap-3", children: [_jsx(Button, { className: "flex-none min-w-[50px]", disabled: !hasPreviousRow, icon: _jsx(Icon, { color: hasPreviousRow ? theme.secondary : theme.text.tertiary, name: "chevron-left", size: 24, type: "feather" }), onPress: goToPreviousRow, title: "", variant: "secondary" }), _jsx(Button, { className: "flex-1", onPress: async () => {
16
+ return (_jsxs(FormViewV2.Root, { description: `JSON VIEWER - ${selectedTable}`, isLoading: false, item: { json: selectedRowJson }, name: "Detalles", onBack: goBackFromJson, children: [_jsx(FormViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER, children: _jsx(FormViewV2.Header.Actions, { children: _jsxs(Typography.Text, { className: "text-gray-500", variant: "bodySmall", children: [currentRowIndex + 1, " de ", filteredData.length] }) }) }), _jsx(FormViewV2.Content, { children: _jsx(JsonPreview, { selectedJson: selectedRowJson }) }), _jsx(FormViewV2.Footer, { children: _jsxs(View, { className: "flex-row items-center justify-between gap-3", children: [_jsx(Button, { className: "flex-none min-w-[50px]", disabled: !hasPreviousRow, icon: _jsx(Icon, { color: hasPreviousRow ? theme.secondary : theme.text.tertiary, name: "chevron-left", size: 24, type: "feather" }), onPress: goToPreviousRow, title: "", variant: "secondary" }), _jsx(Button, { className: "flex-1", onPress: async () => {
16
17
  await Clipboard.setStringAsync(selectedRowJson);
17
18
  }, title: "Copiar JSON", variant: "primary" }), _jsx(Button, { className: "flex-none min-w-[50px]", disabled: !hasNextRow, icon: _jsx(Icon, { color: hasNextRow ? theme.secondary : theme.text.tertiary, name: "chevron-right", size: 24, type: "feather" }), onPress: goToNextRow, title: "", variant: "secondary" })] }) })] }));
18
19
  }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import DatabaseInfoRow from "../../../ui/components/organisms/DatabaseInfoRow";
3
3
  import { ListViewV2 } from "@sincpro/mobile-ui/views/ListViewV2";
4
+ import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
4
5
  import { useEffect } from "react";
5
6
  import { useNavigate } from "react-router-native";
6
7
  import { DatabaseProvider, EDatabaseView, useDatabase } from "./database.context";
@@ -15,7 +16,7 @@ function TablesView() {
15
16
  const onBack = () => navigate(-1);
16
17
  return (_jsxs(ListViewV2.Root, { isLoading: isLoading, items: tables, name: "Lista de tablas", onBack: onBack, onPressItem: async (table) => {
17
18
  await selectTable(table.name);
18
- }, children: [_jsx(ListViewV2.Header, { variant: "FLAT_HEADER" /* EVariantScreenHeader.FLAT_HEADER */ }), _jsx(ListViewV2.Content, { children: (table) => (_jsx(DatabaseInfoRow, { item: {
19
+ }, children: [_jsx(ListViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER }), _jsx(ListViewV2.Content, { children: (table) => (_jsx(DatabaseInfoRow, { item: {
19
20
  name: table.displayName || table.name,
20
21
  description: "Tabla de base de datos",
21
22
  tableName: table.name,
@@ -2,9 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import { safeJsonStringify } from "../../../tools/utils/serializer";
3
3
  import TableInfoRow from "../../../ui/components/organisms/TableInfoInfoRow";
4
4
  import { ListViewV2 } from "@sincpro/mobile-ui/views/ListViewV2";
5
+ import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
5
6
  import { useDatabase } from "./database.context";
6
7
  function TableRowsView() {
7
8
  const { isLoading, filteredData, selectedTable, goBackFromRows, selectRowJson, setSearchQuery, } = useDatabase();
8
- return (_jsxs(ListViewV2.Root, { isLoading: isLoading, items: filteredData, name: `Datos - ${selectedTable}`, onBack: goBackFromRows, onPressItem: (item) => selectRowJson(safeJsonStringify(item), item.index), onSearch: setSearchQuery, children: [_jsx(ListViewV2.Header, { variant: "FLAT_HEADER" /* EVariantScreenHeader.FLAT_HEADER */, children: _jsx(ListViewV2.Header.Search, {}) }), _jsx(ListViewV2.Content, { children: (item) => (_jsx(TableInfoRow, { item: { data: safeJsonStringify(item) }, onPress: () => selectRowJson(safeJsonStringify(item), item.index) })) })] }));
9
+ return (_jsxs(ListViewV2.Root, { isLoading: isLoading, items: filteredData, name: `Datos - ${selectedTable}`, onBack: goBackFromRows, onPressItem: (item) => selectRowJson(safeJsonStringify(item), item.index), onSearch: setSearchQuery, children: [_jsx(ListViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER, children: _jsx(ListViewV2.Header.Search, {}) }), _jsx(ListViewV2.Content, { children: (item) => (_jsx(TableInfoRow, { item: { data: safeJsonStringify(item) }, onPress: () => selectRowJson(safeJsonStringify(item), item.index) })) })] }));
9
10
  }
10
11
  export default TableRowsView;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import DeadLetterQueueRow from "../../../ui/components/organisms/DeadLetterQueueRow";
3
3
  import { ListViewV2 } from "@sincpro/mobile-ui/views/ListViewV2";
4
+ import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
4
5
  import { useEffect } from "react";
5
6
  import { useNavigate } from "react-router-native";
6
7
  import { DeadLetterQueueProvider, useDeadLetterQueue } from "./dead_letter_queue.context";
@@ -11,7 +12,7 @@ function DeadLetterQueueScreenComponent() {
11
12
  loadEvents();
12
13
  }, [loadEvents]);
13
14
  const onBack = () => navigate(-1);
14
- return (_jsxs(ListViewV2.Root, { description: "Eventos que no pudieron ser procesados", isLoading: isLoading, items: enrichedEvents, name: "Eventos fallidos", onBack: onBack, onRefresh: loadEvents, children: [_jsx(ListViewV2.Header, { variant: "FLAT_HEADER" /* EVariantScreenHeader.FLAT_HEADER */ }), _jsx(ListViewV2.Content, { children: (event) => (_jsx(DeadLetterQueueRow, { item: event, onRetry: async (evt) => {
15
+ return (_jsxs(ListViewV2.Root, { description: "Eventos que no pudieron ser procesados", isLoading: isLoading, items: enrichedEvents, name: "Eventos fallidos", onBack: onBack, onRefresh: loadEvents, children: [_jsx(ListViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER }), _jsx(ListViewV2.Content, { children: (event) => (_jsx(DeadLetterQueueRow, { item: event, onRetry: async (evt) => {
15
16
  await retryEvent(evt);
16
17
  setTimeout(() => {
17
18
  void loadEvents();
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import EventRow from "../../../ui/components/organisms/EventRow";
3
3
  import { ListViewV2 } from "@sincpro/mobile-ui/views/ListViewV2";
4
+ import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
4
5
  import { useEffect } from "react";
5
6
  import { useNavigate } from "react-router-native";
6
7
  import { EventsProvider, useEvents } from "./events.context";
@@ -17,7 +18,7 @@ function EventsScreenComponent() {
17
18
  void loadEvents();
18
19
  }, [loadEvents]);
19
20
  const onBack = () => navigate(-1);
20
- return (_jsxs(ListViewV2.Root, { description: "Eventos registrados", isLoading: isLoading, items: pagedEvents, name: "Eventos", onBack: onBack, onRefresh: loadEvents, children: [_jsx(ListViewV2.Header, { variant: "FLAT_HEADER" /* EVariantScreenHeader.FLAT_HEADER */, children: _jsx(ListViewV2.Header.Filters, { children: _jsx(ListViewV2.Header.Filters.Chips, { children: FILTER_OPTIONS.map((option) => (_jsx(ListViewV2.Header.Filters.Chip, { active: filter === option.value, label: option.label, onPress: () => setFilter(option.value) }, option.value))) }) }) }), _jsx(ListViewV2.Content, { children: (event) => (_jsx(EventRow, { item: event, onRetry: async (evt) => {
21
+ return (_jsxs(ListViewV2.Root, { description: "Eventos registrados", isLoading: isLoading, items: pagedEvents, name: "Eventos", onBack: onBack, onRefresh: loadEvents, children: [_jsx(ListViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER, children: _jsx(ListViewV2.Header.Filters, { children: _jsx(ListViewV2.Header.Filters.Chips, { children: FILTER_OPTIONS.map((option) => (_jsx(ListViewV2.Header.Filters.Chip, { active: filter === option.value, label: option.label, onPress: () => setFilter(option.value) }, option.value))) }) }) }), _jsx(ListViewV2.Content, { children: (event) => (_jsx(EventRow, { item: event, onRetry: async (evt) => {
21
22
  await retryEvent(evt);
22
23
  } })) })] }));
23
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sincpro/mobile",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Sincpro Mobile framework core: offline-first infra (DB, queue/events, cron), DDD patterns, adapters, AppShell and module-driven composition for React Native",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -59,7 +59,7 @@
59
59
  "react-native-view-shot": "*",
60
60
  "react-native-webview": "*",
61
61
  "react-router-native": ">=6",
62
- "@sincpro/mobile-ui": "^0.2.0"
62
+ "@sincpro/mobile-ui": "^0.2.1"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/big.js": "^6.2.2",
@@ -99,7 +99,7 @@
99
99
  "react-router-native": "^6.30.0",
100
100
  "tsc-alias": "^1.8.17",
101
101
  "typescript": "~5.9.2",
102
- "@sincpro/mobile-ui": "^0.2.0"
102
+ "@sincpro/mobile-ui": "^0.2.1"
103
103
  },
104
104
  "exports": {
105
105
  ".": "./dist/index.js",