@arela/uploader 0.3.0 → 1.0.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/README.md +130 -5
- package/docs/API_ENDPOINTS_FOR_DETECTION.md +647 -0
- package/docs/QUICK_REFERENCE_API_DETECTION.md +264 -0
- package/docs/REFACTORING_SUMMARY_DETECT_PEDIMENTOS.md +200 -0
- package/package.json +1 -1
- package/src/commands/WatchCommand.js +47 -10
- package/src/config/config.js +157 -2
- package/src/document-types/support-document.js +4 -5
- package/src/file-detection.js +7 -0
- package/src/index.js +119 -4
- package/src/services/AutoProcessingService.js +146 -36
- package/src/services/DatabaseService.js +341 -517
- package/src/services/upload/ApiUploadService.js +426 -4
- package/src/services/upload/MultiApiUploadService.js +233 -0
- package/src/services/upload/UploadServiceFactory.js +24 -0
- package/src/utils/FileOperations.js +6 -3
- package/src/utils/WatchEventHandler.js +14 -9
- package/.envbackup +0 -37
- package/SUPABASE_UPLOAD_FIX.md +0 -157
package/src/config/config.js
CHANGED
|
@@ -50,15 +50,168 @@ class Config {
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Load API configuration
|
|
53
|
+
* Supports 3 API targets: default, agencia, cliente
|
|
54
|
+
* Configure ARELA_API_CLIENTE_URL/TOKEN for the specific client you want to use
|
|
53
55
|
* @private
|
|
54
56
|
*/
|
|
55
57
|
#loadApiConfig() {
|
|
56
58
|
return {
|
|
59
|
+
// Default/legacy API (backward compatible)
|
|
57
60
|
baseUrl: process.env.ARELA_API_URL,
|
|
58
61
|
token: process.env.ARELA_API_TOKEN,
|
|
62
|
+
// Multi-tenant API targets (only 3 options)
|
|
63
|
+
targets: {
|
|
64
|
+
agencia: {
|
|
65
|
+
baseUrl:
|
|
66
|
+
process.env.ARELA_API_AGENCIA_URL || process.env.ARELA_API_URL,
|
|
67
|
+
token:
|
|
68
|
+
process.env.ARELA_API_AGENCIA_TOKEN || process.env.ARELA_API_TOKEN,
|
|
69
|
+
},
|
|
70
|
+
cliente: {
|
|
71
|
+
baseUrl:
|
|
72
|
+
process.env.ARELA_API_CLIENTE_URL || process.env.ARELA_API_URL,
|
|
73
|
+
token:
|
|
74
|
+
process.env.ARELA_API_CLIENTE_TOKEN || process.env.ARELA_API_TOKEN,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
// Current active target (can be changed at runtime)
|
|
78
|
+
activeTarget: process.env.ARELA_API_TARGET || 'default',
|
|
59
79
|
};
|
|
60
80
|
}
|
|
61
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Get API configuration for a specific target
|
|
84
|
+
* @param {string} target - API target: 'agencia', 'cliente', or 'default'
|
|
85
|
+
* @returns {Object} API configuration with baseUrl and token
|
|
86
|
+
*/
|
|
87
|
+
getApiConfig(target = null) {
|
|
88
|
+
const targetName = target || this.api.activeTarget || 'default';
|
|
89
|
+
|
|
90
|
+
if (targetName === 'default') {
|
|
91
|
+
return {
|
|
92
|
+
baseUrl: this.api.baseUrl,
|
|
93
|
+
token: this.api.token,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const targetConfig = this.api.targets[targetName];
|
|
98
|
+
if (!targetConfig) {
|
|
99
|
+
console.warn(
|
|
100
|
+
`⚠️ Unknown API target '${targetName}', falling back to default`,
|
|
101
|
+
);
|
|
102
|
+
return {
|
|
103
|
+
baseUrl: this.api.baseUrl,
|
|
104
|
+
token: this.api.token,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return targetConfig;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Set the active API target at runtime
|
|
113
|
+
* @param {string} target - API target: 'default', 'agencia', or 'cliente'
|
|
114
|
+
*/
|
|
115
|
+
setApiTarget(target) {
|
|
116
|
+
const validTargets = ['default', 'agencia', 'cliente'];
|
|
117
|
+
if (!validTargets.includes(target.toLowerCase())) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Invalid API target '${target}'. Must be one of: ${validTargets.join(', ')}`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
this.api.activeTarget = target.toLowerCase();
|
|
123
|
+
const config = this.getApiConfig(target.toLowerCase());
|
|
124
|
+
console.log(`🎯 API target set to: ${target} → ${config.baseUrl}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Set source and target APIs for cross-tenant operations
|
|
129
|
+
* @param {string} sourceTarget - Source API target (for reading data)
|
|
130
|
+
* @param {string} targetTarget - Target API target (for writing data)
|
|
131
|
+
*/
|
|
132
|
+
setCrossTenantTargets(sourceTarget, targetTarget) {
|
|
133
|
+
const validTargets = ['default', 'agencia', 'cliente'];
|
|
134
|
+
|
|
135
|
+
if (!validTargets.includes(sourceTarget.toLowerCase())) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Invalid source API target '${sourceTarget}'. Must be one of: ${validTargets.join(', ')}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (!validTargets.includes(targetTarget.toLowerCase())) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Invalid target API target '${targetTarget}'. Must be one of: ${validTargets.join(', ')}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.api.sourceTarget = sourceTarget.toLowerCase();
|
|
147
|
+
this.api.targetTarget = targetTarget.toLowerCase();
|
|
148
|
+
|
|
149
|
+
const sourceConfig = this.getApiConfig(sourceTarget.toLowerCase());
|
|
150
|
+
const targetConfig = this.getApiConfig(targetTarget.toLowerCase());
|
|
151
|
+
|
|
152
|
+
console.log(`🔗 Cross-tenant mode enabled:`);
|
|
153
|
+
console.log(
|
|
154
|
+
` 📖 Source (read): ${sourceTarget} → ${sourceConfig.baseUrl}`,
|
|
155
|
+
);
|
|
156
|
+
console.log(
|
|
157
|
+
` 📝 Target (write): ${targetTarget} → ${targetConfig.baseUrl}`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get source API config for cross-tenant operations
|
|
163
|
+
* @returns {Object} Source API configuration
|
|
164
|
+
*/
|
|
165
|
+
getSourceApiConfig() {
|
|
166
|
+
if (this.api.sourceTarget) {
|
|
167
|
+
return this.getApiConfig(this.api.sourceTarget);
|
|
168
|
+
}
|
|
169
|
+
return this.getApiConfig();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get target API config for cross-tenant operations
|
|
174
|
+
* @returns {Object} Target API configuration
|
|
175
|
+
*/
|
|
176
|
+
getTargetApiConfig() {
|
|
177
|
+
if (this.api.targetTarget) {
|
|
178
|
+
return this.getApiConfig(this.api.targetTarget);
|
|
179
|
+
}
|
|
180
|
+
return this.getApiConfig();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if cross-tenant mode is enabled
|
|
185
|
+
* @returns {boolean}
|
|
186
|
+
*/
|
|
187
|
+
isCrossTenantMode() {
|
|
188
|
+
return !!(
|
|
189
|
+
this.api.sourceTarget &&
|
|
190
|
+
this.api.targetTarget &&
|
|
191
|
+
this.api.sourceTarget !== this.api.targetTarget
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get API target by RFC
|
|
197
|
+
* @param {string} rfc - The RFC to find API target for
|
|
198
|
+
* @returns {string|null} The API target name or null if not found
|
|
199
|
+
*/
|
|
200
|
+
getApiTargetByRfc(rfc) {
|
|
201
|
+
if (!rfc) return null;
|
|
202
|
+
|
|
203
|
+
const normalizedRfc = rfc.toUpperCase();
|
|
204
|
+
for (const [targetName, targetConfig] of Object.entries(this.api.targets)) {
|
|
205
|
+
if (
|
|
206
|
+
targetConfig.rfc &&
|
|
207
|
+
targetConfig.rfc.toUpperCase() === normalizedRfc
|
|
208
|
+
) {
|
|
209
|
+
return targetName;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
62
215
|
/**
|
|
63
216
|
* Load upload configuration
|
|
64
217
|
* @private
|
|
@@ -169,10 +322,12 @@ class Config {
|
|
|
169
322
|
|
|
170
323
|
/**
|
|
171
324
|
* Check if API mode is available
|
|
325
|
+
* @param {string} target - Optional API target to check
|
|
172
326
|
* @returns {boolean}
|
|
173
327
|
*/
|
|
174
|
-
isApiModeAvailable() {
|
|
175
|
-
|
|
328
|
+
isApiModeAvailable(target = null) {
|
|
329
|
+
const config = this.getApiConfig(target);
|
|
330
|
+
return !!(config.baseUrl && config.token);
|
|
176
331
|
}
|
|
177
332
|
|
|
178
333
|
/**
|
|
@@ -19,13 +19,12 @@ export const supportDocumentDefinition = {
|
|
|
19
19
|
];
|
|
20
20
|
|
|
21
21
|
// Detect customs-related metadata
|
|
22
|
-
const customsClues = [
|
|
23
|
-
/rfc|RFC/,
|
|
24
|
-
/patente|aduana|customs|pedimento/i,
|
|
25
|
-
];
|
|
22
|
+
const customsClues = [/rfc|RFC/, /patente|aduana|customs|pedimento/i];
|
|
26
23
|
|
|
27
24
|
const soapFound = soapClues.filter((clue) => clue.test(source)).length;
|
|
28
|
-
const customsFound = customsClues.filter((clue) =>
|
|
25
|
+
const customsFound = customsClues.filter((clue) =>
|
|
26
|
+
clue.test(source),
|
|
27
|
+
).length;
|
|
29
28
|
|
|
30
29
|
// Must have SOAP structure OR customs metadata
|
|
31
30
|
return soapFound >= 2 || customsFound >= 2;
|
package/src/file-detection.js
CHANGED
|
@@ -89,6 +89,7 @@ export class FileDetectionService {
|
|
|
89
89
|
fields: [],
|
|
90
90
|
detectedPedimento: null,
|
|
91
91
|
detectedPedimentoYear: null,
|
|
92
|
+
rfc: null,
|
|
92
93
|
arelaPath: null,
|
|
93
94
|
text: '',
|
|
94
95
|
error: `Unsupported file type: ${fileExtension}`,
|
|
@@ -102,6 +103,7 @@ export class FileDetectionService {
|
|
|
102
103
|
fields: [],
|
|
103
104
|
detectedPedimento: null,
|
|
104
105
|
detectedPedimentoYear: null,
|
|
106
|
+
rfc: null,
|
|
105
107
|
arelaPath: null,
|
|
106
108
|
text: '',
|
|
107
109
|
error: 'No text could be extracted from file',
|
|
@@ -112,6 +114,9 @@ export class FileDetectionService {
|
|
|
112
114
|
const [detectedType, fields, detectedPedimento, detectedPedimentoYear] =
|
|
113
115
|
extractDocumentFields(text, fileExtension, filePath);
|
|
114
116
|
|
|
117
|
+
// Extract RFC from fields
|
|
118
|
+
const rfc = fields?.find((f) => f.name === 'rfc')?.value ?? null;
|
|
119
|
+
|
|
115
120
|
// Compose arela_path for pedimento_simplificado documents
|
|
116
121
|
const arelaPath = composeArelaPath(
|
|
117
122
|
detectedType,
|
|
@@ -125,6 +130,7 @@ export class FileDetectionService {
|
|
|
125
130
|
fields,
|
|
126
131
|
detectedPedimento,
|
|
127
132
|
detectedPedimentoYear,
|
|
133
|
+
rfc,
|
|
128
134
|
arelaPath,
|
|
129
135
|
text,
|
|
130
136
|
error: null,
|
|
@@ -135,6 +141,7 @@ export class FileDetectionService {
|
|
|
135
141
|
detectedType: null,
|
|
136
142
|
fields: [],
|
|
137
143
|
detectedPedimento: null,
|
|
144
|
+
rfc: null,
|
|
138
145
|
detectedPedimentoYear: null,
|
|
139
146
|
arelaPath: null,
|
|
140
147
|
text: '',
|
package/src/index.js
CHANGED
|
@@ -47,6 +47,19 @@ class ArelaUploaderCLI {
|
|
|
47
47
|
this.program
|
|
48
48
|
.command('upload')
|
|
49
49
|
.description('Upload files to Arela with automatic processing')
|
|
50
|
+
.option(
|
|
51
|
+
'--api <target>',
|
|
52
|
+
'API target: default|agencia|cliente',
|
|
53
|
+
'default',
|
|
54
|
+
)
|
|
55
|
+
.option(
|
|
56
|
+
'--source-api <target>',
|
|
57
|
+
'Source API for reading data (cross-tenant mode): agencia|cliente',
|
|
58
|
+
)
|
|
59
|
+
.option(
|
|
60
|
+
'--target-api <target>',
|
|
61
|
+
'Target API for uploading files (cross-tenant mode): agencia|cliente',
|
|
62
|
+
)
|
|
50
63
|
.option(
|
|
51
64
|
'-b, --batch-size <size>',
|
|
52
65
|
'Number of files to process in each batch',
|
|
@@ -77,6 +90,17 @@ class ArelaUploaderCLI {
|
|
|
77
90
|
)
|
|
78
91
|
.action(async (options) => {
|
|
79
92
|
try {
|
|
93
|
+
// Handle cross-tenant mode (source and target APIs)
|
|
94
|
+
if (options.sourceApi && options.targetApi) {
|
|
95
|
+
appConfig.setCrossTenantTargets(
|
|
96
|
+
options.sourceApi,
|
|
97
|
+
options.targetApi,
|
|
98
|
+
);
|
|
99
|
+
} else if (options.api && options.api !== 'default') {
|
|
100
|
+
// Set single API target if specified
|
|
101
|
+
appConfig.setApiTarget(options.api);
|
|
102
|
+
}
|
|
103
|
+
|
|
80
104
|
// Handle --upload-by-rfc as a specific operation
|
|
81
105
|
if (options.uploadByRfc) {
|
|
82
106
|
const databaseService = await import(
|
|
@@ -87,6 +111,9 @@ class ArelaUploaderCLI {
|
|
|
87
111
|
batchSize: parseInt(options.batchSize) || 10,
|
|
88
112
|
showProgress: true,
|
|
89
113
|
folderStructure: options.folderStructure,
|
|
114
|
+
apiTarget: options.api,
|
|
115
|
+
sourceApi: options.sourceApi,
|
|
116
|
+
targetApi: options.targetApi,
|
|
90
117
|
});
|
|
91
118
|
console.log(
|
|
92
119
|
`✅ RFC upload completed: ${result.processedCount} processed, ${result.uploadedCount} uploaded, ${result.errorCount} errors`,
|
|
@@ -104,6 +131,11 @@ class ArelaUploaderCLI {
|
|
|
104
131
|
this.program
|
|
105
132
|
.command('stats')
|
|
106
133
|
.description('Collect file statistics without uploading')
|
|
134
|
+
.option(
|
|
135
|
+
'--api <target>',
|
|
136
|
+
'API target: agencia|cliente|default',
|
|
137
|
+
'default',
|
|
138
|
+
)
|
|
107
139
|
.option(
|
|
108
140
|
'-b, --batch-size <size>',
|
|
109
141
|
'Number of files to process in each batch',
|
|
@@ -121,6 +153,10 @@ class ArelaUploaderCLI {
|
|
|
121
153
|
.option('--show-stats', 'Show performance statistics')
|
|
122
154
|
.action(async (options) => {
|
|
123
155
|
try {
|
|
156
|
+
// Set API target if specified
|
|
157
|
+
if (options.api && options.api !== 'default') {
|
|
158
|
+
appConfig.setApiTarget(options.api);
|
|
159
|
+
}
|
|
124
160
|
const statsOptions = { ...options, statsOnly: true };
|
|
125
161
|
await this.uploadCommand.execute(statsOptions);
|
|
126
162
|
} catch (error) {
|
|
@@ -132,6 +168,11 @@ class ArelaUploaderCLI {
|
|
|
132
168
|
this.program
|
|
133
169
|
.command('detect')
|
|
134
170
|
.description('Run document detection on existing file records')
|
|
171
|
+
.option(
|
|
172
|
+
'--api <target>',
|
|
173
|
+
'API target: agencia|cliente|default',
|
|
174
|
+
'default',
|
|
175
|
+
)
|
|
135
176
|
.option(
|
|
136
177
|
'-b, --batch-size <size>',
|
|
137
178
|
'Number of files to process in each batch',
|
|
@@ -147,6 +188,11 @@ class ArelaUploaderCLI {
|
|
|
147
188
|
)
|
|
148
189
|
.action(async (options) => {
|
|
149
190
|
try {
|
|
191
|
+
// Set API target if specified
|
|
192
|
+
if (options.api && options.api !== 'default') {
|
|
193
|
+
appConfig.setApiTarget(options.api);
|
|
194
|
+
}
|
|
195
|
+
|
|
150
196
|
const databaseService = await import('./services/DatabaseService.js');
|
|
151
197
|
|
|
152
198
|
// Handle --propagate-arela-path as a specific operation
|
|
@@ -237,6 +283,19 @@ class ArelaUploaderCLI {
|
|
|
237
283
|
.description(
|
|
238
284
|
'Monitor directories for file changes and upload automatically',
|
|
239
285
|
)
|
|
286
|
+
.option(
|
|
287
|
+
'--api <target>',
|
|
288
|
+
'API target: default|agencia|cliente',
|
|
289
|
+
'default',
|
|
290
|
+
)
|
|
291
|
+
.option(
|
|
292
|
+
'--source-api <target>',
|
|
293
|
+
'Source API for reading data (cross-tenant mode): agencia|cliente',
|
|
294
|
+
)
|
|
295
|
+
.option(
|
|
296
|
+
'--target-api <target>',
|
|
297
|
+
'Target API for uploading files (cross-tenant mode): agencia|cliente',
|
|
298
|
+
)
|
|
240
299
|
.option(
|
|
241
300
|
'-d, --directories <paths>',
|
|
242
301
|
'Comma-separated directories to watch',
|
|
@@ -267,6 +326,17 @@ class ArelaUploaderCLI {
|
|
|
267
326
|
.option('--verbose', 'Enable verbose logging')
|
|
268
327
|
.action(async (options) => {
|
|
269
328
|
try {
|
|
329
|
+
// Handle cross-tenant mode (source and target APIs)
|
|
330
|
+
if (options.sourceApi && options.targetApi) {
|
|
331
|
+
appConfig.setCrossTenantTargets(
|
|
332
|
+
options.sourceApi,
|
|
333
|
+
options.targetApi,
|
|
334
|
+
);
|
|
335
|
+
} else if (options.api && options.api !== 'default') {
|
|
336
|
+
// Set single API target if specified
|
|
337
|
+
appConfig.setApiTarget(options.api);
|
|
338
|
+
}
|
|
339
|
+
|
|
270
340
|
await this.watchCommand.execute(options);
|
|
271
341
|
} catch (error) {
|
|
272
342
|
this.errorHandler.handleFatalError(error, { command: 'watch' });
|
|
@@ -330,9 +400,27 @@ class ArelaUploaderCLI {
|
|
|
330
400
|
#showConfiguration() {
|
|
331
401
|
console.log('🔧 Current Configuration:');
|
|
332
402
|
console.log(` Version: ${appConfig.packageVersion}`);
|
|
333
|
-
console.log('\n📡 API Configuration:');
|
|
334
|
-
console.log(`
|
|
335
|
-
console.log(
|
|
403
|
+
console.log('\n📡 API Configuration (Multi-Tenant):');
|
|
404
|
+
console.log(` Active Target: ${appConfig.api.activeTarget || 'default'}`);
|
|
405
|
+
console.log('\n 🌐 Default API:');
|
|
406
|
+
console.log(` URL: ${appConfig.api.baseUrl || 'Not configured'}`);
|
|
407
|
+
console.log(
|
|
408
|
+
` Token: ${appConfig.api.token ? '✅ Set' : '❌ Not set'}`,
|
|
409
|
+
);
|
|
410
|
+
console.log('\n 🏢 Agencia API:');
|
|
411
|
+
console.log(
|
|
412
|
+
` URL: ${appConfig.api.targets.agencia?.baseUrl || 'Not configured'}`,
|
|
413
|
+
);
|
|
414
|
+
console.log(
|
|
415
|
+
` Token: ${appConfig.api.targets.agencia?.token ? '✅ Set' : '❌ Not set'}`,
|
|
416
|
+
);
|
|
417
|
+
console.log('\n 👤 Cliente API:');
|
|
418
|
+
console.log(
|
|
419
|
+
` URL: ${appConfig.api.targets.cliente?.baseUrl || 'Not configured'}`,
|
|
420
|
+
);
|
|
421
|
+
console.log(
|
|
422
|
+
` Token: ${appConfig.api.targets.cliente?.token ? '✅ Set' : '❌ Not set'}`,
|
|
423
|
+
);
|
|
336
424
|
console.log('\n🗄️ Supabase Configuration:');
|
|
337
425
|
console.log(` URL: ${appConfig.supabase.url || 'Not configured'}`);
|
|
338
426
|
console.log(` Key: ${appConfig.supabase.key ? '✅ Set' : '❌ Not set'}`);
|
|
@@ -360,11 +448,38 @@ class ArelaUploaderCLI {
|
|
|
360
448
|
console.log(` Log File: ${appConfig.logging.logFilePath}`);
|
|
361
449
|
console.log('\n🎯 Service Availability:');
|
|
362
450
|
console.log(
|
|
363
|
-
` API Mode: ${appConfig.isApiModeAvailable() ? '✅ Available' : '❌ Not available'}`,
|
|
451
|
+
` API Mode (default): ${appConfig.isApiModeAvailable() ? '✅ Available' : '❌ Not available'}`,
|
|
452
|
+
);
|
|
453
|
+
console.log(
|
|
454
|
+
` API Mode (agencia): ${appConfig.isApiModeAvailable('agencia') ? '✅ Available' : '❌ Not available'}`,
|
|
455
|
+
);
|
|
456
|
+
console.log(
|
|
457
|
+
` API Mode (cliente): ${appConfig.isApiModeAvailable('cliente') ? '✅ Available' : '❌ Not available'}`,
|
|
364
458
|
);
|
|
365
459
|
console.log(
|
|
366
460
|
` Supabase Mode: ${appConfig.isSupabaseModeAvailable() ? '✅ Available' : '❌ Not available'}`,
|
|
367
461
|
);
|
|
462
|
+
console.log('\n📋 Uso Multi-Tenant:');
|
|
463
|
+
console.log(
|
|
464
|
+
' arela stats --api agencia # Registrar archivos en BD agencia',
|
|
465
|
+
);
|
|
466
|
+
console.log(
|
|
467
|
+
' arela detect --api agencia # Detectar pedimentos en BD agencia',
|
|
468
|
+
);
|
|
469
|
+
console.log(
|
|
470
|
+
' arela upload --api cliente # Subir archivos al cliente configurado',
|
|
471
|
+
);
|
|
472
|
+
console.log('\n👁️ Modo Watch Multi-Tenant:');
|
|
473
|
+
console.log(
|
|
474
|
+
' arela watch --api cliente # Watch con API cliente',
|
|
475
|
+
);
|
|
476
|
+
console.log(
|
|
477
|
+
' arela watch --source-api agencia --target-api cliente # Cross-tenant watch',
|
|
478
|
+
);
|
|
479
|
+
console.log(
|
|
480
|
+
'\n💡 Tip: Configura ARELA_API_CLIENTE_URL y ARELA_API_CLIENTE_TOKEN',
|
|
481
|
+
);
|
|
482
|
+
console.log(' en .env para apuntar al cliente específico que necesites.');
|
|
368
483
|
}
|
|
369
484
|
|
|
370
485
|
/**
|