@arela/uploader 1.1.4 → 1.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 (95) hide show
  1. package/.claude/worktrees/agent-multi-profile/.env.template +224 -0
  2. package/.claude/worktrees/agent-multi-profile/.prettierrc +13 -0
  3. package/.claude/worktrees/agent-multi-profile/README.md +405 -0
  4. package/.claude/worktrees/agent-multi-profile/package-lock.json +7096 -0
  5. package/.claude/worktrees/agent-multi-profile/package.json +78 -0
  6. package/.claude/worktrees/agent-multi-profile/scripts/cleanup-ds-store.js +109 -0
  7. package/.claude/worktrees/agent-multi-profile/scripts/cleanup-system-files.js +69 -0
  8. package/.claude/worktrees/agent-multi-profile/scripts/scoring-compare.js +243 -0
  9. package/.claude/worktrees/agent-multi-profile/scripts/scoring-phase4-check.js +96 -0
  10. package/.claude/worktrees/agent-multi-profile/scripts/tests/phase-7-features.test.js +415 -0
  11. package/.claude/worktrees/agent-multi-profile/scripts/tests/signal-handling.test.js +275 -0
  12. package/.claude/worktrees/agent-multi-profile/scripts/tests/smart-watch-integration.test.js +554 -0
  13. package/.claude/worktrees/agent-multi-profile/scripts/tests/watch-service-integration.test.js +584 -0
  14. package/.claude/worktrees/agent-multi-profile/src/commands/AgentCommand.js +229 -0
  15. package/.claude/worktrees/agent-multi-profile/src/commands/AgentInitCommand.js +316 -0
  16. package/.claude/worktrees/agent-multi-profile/src/commands/DatastageCommand.js +164 -0
  17. package/.claude/worktrees/agent-multi-profile/src/commands/GDriveSyncCommand.js +475 -0
  18. package/.claude/worktrees/agent-multi-profile/src/commands/IdentifyCommand.js +708 -0
  19. package/.claude/worktrees/agent-multi-profile/src/commands/PollWorkerCommand.js +169 -0
  20. package/.claude/worktrees/agent-multi-profile/src/commands/PropagateCommand.js +636 -0
  21. package/.claude/worktrees/agent-multi-profile/src/commands/PushCommand.js +743 -0
  22. package/.claude/worktrees/agent-multi-profile/src/commands/ScanCommand.js +722 -0
  23. package/.claude/worktrees/agent-multi-profile/src/commands/UploadCommand.js +587 -0
  24. package/.claude/worktrees/agent-multi-profile/src/commands/WatchCommand.js +1342 -0
  25. package/.claude/worktrees/agent-multi-profile/src/commands/WorkerCommand.js +337 -0
  26. package/.claude/worktrees/agent-multi-profile/src/config/config.js +862 -0
  27. package/.claude/worktrees/agent-multi-profile/src/document-type-shared.js +131 -0
  28. package/.claude/worktrees/agent-multi-profile/src/document-types/_pedimento-shared-extractors.js +348 -0
  29. package/.claude/worktrees/agent-multi-profile/src/document-types/doda-pdf.js +121 -0
  30. package/.claude/worktrees/agent-multi-profile/src/document-types/doda-xml.js +118 -0
  31. package/.claude/worktrees/agent-multi-profile/src/document-types/factura-inter-agencia.js +186 -0
  32. package/.claude/worktrees/agent-multi-profile/src/document-types/facturas-comerciales.js +233 -0
  33. package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-completo-xml.js +372 -0
  34. package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-completo.js +108 -0
  35. package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-simplificado.js +76 -0
  36. package/.claude/worktrees/agent-multi-profile/src/document-types/proforma.js +29 -0
  37. package/.claude/worktrees/agent-multi-profile/src/document-types/support-document.js +200 -0
  38. package/.claude/worktrees/agent-multi-profile/src/errors/ErrorHandler.js +278 -0
  39. package/.claude/worktrees/agent-multi-profile/src/errors/ErrorTypes.js +104 -0
  40. package/.claude/worktrees/agent-multi-profile/src/file-detection.js +338 -0
  41. package/.claude/worktrees/agent-multi-profile/src/index.js +890 -0
  42. package/.claude/worktrees/agent-multi-profile/src/scoring/db-matcher-adapter.js +98 -0
  43. package/.claude/worktrees/agent-multi-profile/src/scoring/matchers-seed.js +386 -0
  44. package/.claude/worktrees/agent-multi-profile/src/scoring/scoring-engine.js +251 -0
  45. package/.claude/worktrees/agent-multi-profile/src/services/AdvancedFilterService.js +505 -0
  46. package/.claude/worktrees/agent-multi-profile/src/services/AutoProcessingService.js +749 -0
  47. package/.claude/worktrees/agent-multi-profile/src/services/BenchmarkingService.js +381 -0
  48. package/.claude/worktrees/agent-multi-profile/src/services/DatabaseService.js +2173 -0
  49. package/.claude/worktrees/agent-multi-profile/src/services/DatastageApiService.js +240 -0
  50. package/.claude/worktrees/agent-multi-profile/src/services/ErrorMonitor.js +275 -0
  51. package/.claude/worktrees/agent-multi-profile/src/services/GoogleDriveService.js +217 -0
  52. package/.claude/worktrees/agent-multi-profile/src/services/LoggingService.js +649 -0
  53. package/.claude/worktrees/agent-multi-profile/src/services/MonitoringService.js +401 -0
  54. package/.claude/worktrees/agent-multi-profile/src/services/PerformanceOptimizer.js +511 -0
  55. package/.claude/worktrees/agent-multi-profile/src/services/PipelineApiService.js +274 -0
  56. package/.claude/worktrees/agent-multi-profile/src/services/PipelineJobRunner.js +389 -0
  57. package/.claude/worktrees/agent-multi-profile/src/services/ProfileManager.js +164 -0
  58. package/.claude/worktrees/agent-multi-profile/src/services/ReportingService.js +511 -0
  59. package/.claude/worktrees/agent-multi-profile/src/services/ScanApiService.js +775 -0
  60. package/.claude/worktrees/agent-multi-profile/src/services/SignalHandler.js +255 -0
  61. package/.claude/worktrees/agent-multi-profile/src/services/SmartWatchDatabaseService.js +527 -0
  62. package/.claude/worktrees/agent-multi-profile/src/services/WatchService.js +783 -0
  63. package/.claude/worktrees/agent-multi-profile/src/services/upload/ApiUploadService.js +676 -0
  64. package/.claude/worktrees/agent-multi-profile/src/services/upload/BaseUploadService.js +36 -0
  65. package/.claude/worktrees/agent-multi-profile/src/services/upload/MultiApiUploadService.js +233 -0
  66. package/.claude/worktrees/agent-multi-profile/src/services/upload/SupabaseUploadService.js +148 -0
  67. package/.claude/worktrees/agent-multi-profile/src/services/upload/UploadServiceFactory.js +100 -0
  68. package/.claude/worktrees/agent-multi-profile/src/utils/CleanupManager.js +262 -0
  69. package/.claude/worktrees/agent-multi-profile/src/utils/FileOperations.js +192 -0
  70. package/.claude/worktrees/agent-multi-profile/src/utils/FileSanitizer.js +99 -0
  71. package/.claude/worktrees/agent-multi-profile/src/utils/PathDetector.js +198 -0
  72. package/.claude/worktrees/agent-multi-profile/src/utils/PathNormalizer.js +274 -0
  73. package/.claude/worktrees/agent-multi-profile/src/utils/WatchEventHandler.js +522 -0
  74. package/.claude/worktrees/agent-multi-profile/supabase/migrations/001_create_initial_schema.sql +366 -0
  75. package/.claude/worktrees/agent-multi-profile/supabase/migrations/002_align_with_arela_api_schema.sql +145 -0
  76. package/.claude/worktrees/agent-multi-profile/tests/commands/IdentifyCommand.test.js +570 -0
  77. package/.claude/worktrees/agent-multi-profile/tests/commands/PropagateCommand.test.js +568 -0
  78. package/.claude/worktrees/agent-multi-profile/tests/commands/PushCommand.test.js +754 -0
  79. package/.claude/worktrees/agent-multi-profile/tests/commands/ScanCommand.test.js +382 -0
  80. package/.claude/worktrees/agent-multi-profile/tests/unit/PathAndTableNameGeneration.test.js +1211 -0
  81. package/.claude/worktrees/agent-multi-profile/tests/unit/factura-inter-agencia.test.js +218 -0
  82. package/.claude/worktrees/agent-multi-profile/tests/unit/pedimento-completo-xml-matcher.test.js +271 -0
  83. package/.claude/worktrees/agent-multi-profile/tests/unit/pedimento-simplificado-matcher.test.js +185 -0
  84. package/.claude/worktrees/agent-multi-profile/tests/unit/scoring-engine.test.js +221 -0
  85. package/README.md +65 -0
  86. package/package.json +1 -1
  87. package/src/commands/AgentCommand.js +210 -0
  88. package/src/commands/AgentInitCommand.js +316 -0
  89. package/src/commands/PollWorkerCommand.js +11 -322
  90. package/src/config/config.js +44 -6
  91. package/src/index.js +55 -0
  92. package/src/services/LoggingService.js +35 -0
  93. package/src/services/PipelineApiService.js +7 -1
  94. package/src/services/PipelineJobRunner.js +389 -0
  95. package/src/services/ProfileManager.js +164 -0
@@ -0,0 +1,587 @@
1
+ import cliProgress from 'cli-progress';
2
+ import { globby } from 'globby';
3
+ import mime from 'mime-types';
4
+ import path from 'path';
5
+
6
+ import databaseService from '../services/DatabaseService.js';
7
+ import logger from '../services/LoggingService.js';
8
+ import watchService from '../services/WatchService.js';
9
+ import uploadServiceFactory from '../services/upload/UploadServiceFactory.js';
10
+
11
+ import appConfig from '../config/config.js';
12
+ import ErrorHandler from '../errors/ErrorHandler.js';
13
+ import {
14
+ ConfigurationError,
15
+ FileOperationError,
16
+ } from '../errors/ErrorTypes.js';
17
+ import FileOperations from '../utils/FileOperations.js';
18
+ import fileSanitizer from '../utils/FileSanitizer.js';
19
+ import pathDetector from '../utils/PathDetector.js';
20
+
21
+ /**
22
+ * Upload Command Handler
23
+ * Handles the main upload functionality
24
+ */
25
+ export class UploadCommand {
26
+ constructor() {
27
+ this.errorHandler = new ErrorHandler(logger);
28
+ }
29
+
30
+ /**
31
+ * Execute the upload command
32
+ * @param {Object} options - Command options
33
+ */
34
+ async execute(options) {
35
+ try {
36
+ // Prevent direct uploads while in watch mode
37
+ if (watchService.isWatchActive()) {
38
+ logger.error('āŒ Cannot upload directly while in watch mode');
39
+ logger.info('šŸ’” Files in watch mode are processed automatically');
40
+ logger.info(
41
+ 'šŸ’” Stop watch mode first (Ctrl+C) before using upload command',
42
+ );
43
+ return {
44
+ success: false,
45
+ reason: 'Watch mode is active - cannot upload directly',
46
+ source: null,
47
+ stats: {
48
+ successCount: 0,
49
+ detectedCount: 0,
50
+ organizedCount: 0,
51
+ failureCount: 0,
52
+ skippedCount: 0,
53
+ },
54
+ };
55
+ }
56
+
57
+ // Validate configuration
58
+ this.#validateOptions(options);
59
+
60
+ // Initialize services
61
+ const uploadService = await uploadServiceFactory.getUploadService(
62
+ options.forceSupabase,
63
+ );
64
+ const sources = appConfig.getUploadSources();
65
+ const basePath = appConfig.getBasePath();
66
+
67
+ // Log command start
68
+ logger.info(`Starting upload with ${uploadService.getServiceName()}`);
69
+
70
+ if (options.clearLog) {
71
+ logger.clearLogFile();
72
+ logger.info('Log file cleared');
73
+ }
74
+
75
+ // Process each source with configurable concurrency
76
+ let globalResults = {
77
+ successCount: 0,
78
+ detectedCount: 0,
79
+ organizedCount: 0,
80
+ failureCount: 0,
81
+ skippedCount: 0,
82
+ };
83
+
84
+ // Determine processing strategy based on configuration
85
+ const maxConcurrentSources =
86
+ appConfig.performance?.maxConcurrentSources || 1;
87
+
88
+ if (maxConcurrentSources > 1 && sources.length > 1) {
89
+ // Parallel source processing
90
+ logger.info(
91
+ `Processing ${sources.length} sources with concurrency: ${maxConcurrentSources}`,
92
+ );
93
+
94
+ // Process sources in batches to control concurrency
95
+ for (let i = 0; i < sources.length; i += maxConcurrentSources) {
96
+ const sourceBatch = sources.slice(i, i + maxConcurrentSources);
97
+
98
+ const sourcePromises = sourceBatch.map(async (source) => {
99
+ const sourcePath = path
100
+ .resolve(basePath, source)
101
+ .replace(/\\/g, '/');
102
+ logger.info(`Processing folder: ${sourcePath}`);
103
+
104
+ try {
105
+ const files = await this.#discoverFiles(sourcePath);
106
+ logger.info(`Found ${files.length} files in ${source}`);
107
+
108
+ const result = await this.#processFilesInBatches(
109
+ files,
110
+ options,
111
+ uploadService,
112
+ basePath,
113
+ source,
114
+ sourcePath,
115
+ );
116
+
117
+ this.#logSourceSummary(source, result, options);
118
+ return { success: true, source, result };
119
+ } catch (error) {
120
+ this.errorHandler.handleError(error, { source, sourcePath });
121
+ return { success: false, source, error: error.message };
122
+ }
123
+ });
124
+
125
+ // Wait for this batch of sources to complete
126
+ const results = await Promise.allSettled(sourcePromises);
127
+
128
+ results.forEach((result) => {
129
+ if (result.status === 'fulfilled') {
130
+ const sourceResult = result.value;
131
+ if (sourceResult.success) {
132
+ this.#updateGlobalResults(globalResults, sourceResult.result);
133
+ } else {
134
+ globalResults.failureCount++;
135
+ }
136
+ } else {
137
+ globalResults.failureCount++;
138
+ }
139
+ });
140
+ }
141
+ } else {
142
+ // Sequential source processing (original behavior)
143
+ for (const source of sources) {
144
+ const sourcePath = path.resolve(basePath, source).replace(/\\/g, '/');
145
+ logger.info(`Processing folder: ${sourcePath}`);
146
+
147
+ try {
148
+ const files = await this.#discoverFiles(sourcePath);
149
+ logger.info(`Found ${files.length} files to process`);
150
+
151
+ const result = await this.#processFilesInBatches(
152
+ files,
153
+ options,
154
+ uploadService,
155
+ basePath,
156
+ source,
157
+ sourcePath,
158
+ );
159
+
160
+ this.#updateGlobalResults(globalResults, result);
161
+ this.#logSourceSummary(source, result, options);
162
+ } catch (error) {
163
+ this.errorHandler.handleError(error, { source, sourcePath });
164
+ globalResults.failureCount++;
165
+ }
166
+ }
167
+ }
168
+
169
+ this.#logFinalSummary(globalResults, options, uploadService);
170
+
171
+ // Handle additional phases if requested
172
+ if (options.runAllPhases && options.statsOnly) {
173
+ await this.#runAdditionalPhases(options);
174
+ }
175
+ } catch (error) {
176
+ this.errorHandler.handleFatalError(error, { command: 'upload', options });
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Validate command options
182
+ * @private
183
+ * @param {Object} options - Options to validate
184
+ */
185
+ #validateOptions(options) {
186
+ try {
187
+ appConfig.validateConfiguration(options.forceSupabase);
188
+ } catch (error) {
189
+ throw new ConfigurationError(error.message);
190
+ }
191
+
192
+ if (
193
+ options.batchSize &&
194
+ (options.batchSize < 1 || options.batchSize > 100)
195
+ ) {
196
+ throw new ConfigurationError('Batch size must be between 1 and 100');
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Discover files in a source path
202
+ * @private
203
+ * @param {string} sourcePath - Path to discover files in
204
+ * @returns {Promise<string[]>} Array of file paths
205
+ */
206
+ async #discoverFiles(sourcePath) {
207
+ try {
208
+ if (!FileOperations.fileExists(sourcePath)) {
209
+ throw new FileOperationError(
210
+ `Source path does not exist: ${sourcePath}`,
211
+ );
212
+ }
213
+
214
+ const stats = FileOperations.getFileStats(sourcePath);
215
+
216
+ if (stats?.isDirectory()) {
217
+ return await globby([`${sourcePath}/**/*`], { onlyFiles: true });
218
+ } else {
219
+ return [sourcePath];
220
+ }
221
+ } catch (error) {
222
+ throw new FileOperationError(
223
+ `Failed to discover files in ${sourcePath}`,
224
+ sourcePath,
225
+ { originalError: error.message },
226
+ );
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Process files in batches
232
+ * @private
233
+ * @param {string[]} files - Files to process
234
+ * @param {Object} options - Processing options
235
+ * @param {Object} uploadService - Upload service instance
236
+ * @param {string} basePath - Base path
237
+ * @param {string} source - Source name
238
+ * @param {string} sourcePath - Source path
239
+ * @returns {Promise<Object>} Processing results
240
+ */
241
+ async #processFilesInBatches(
242
+ files,
243
+ options,
244
+ uploadService,
245
+ basePath,
246
+ source,
247
+ sourcePath,
248
+ ) {
249
+ const batchSize =
250
+ parseInt(options.batchSize) || appConfig.performance.batchSize || 50;
251
+ const results = {
252
+ successCount: 0,
253
+ detectedCount: 0,
254
+ organizedCount: 0,
255
+ failureCount: 0,
256
+ skippedCount: 0,
257
+ };
258
+
259
+ // Get processed paths if available
260
+ const processedPaths = options.skipProcessed
261
+ ? databaseService.getProcessedPaths()
262
+ : new Set();
263
+
264
+ // Create progress bar
265
+ const progressBar = new cliProgress.SingleBar({
266
+ format: `šŸ“¤ ${source} |{bar}| {percentage}% | {value}/{total} | Success: {success} | Errors: {errors}`,
267
+ barCompleteChar: 'ā–ˆ',
268
+ barIncompleteChar: 'ā–‘',
269
+ hideCursor: true,
270
+ clearOnComplete: false,
271
+ stopOnComplete: true,
272
+ stream: process.stderr, // Use stderr to separate from stdout logging
273
+ });
274
+
275
+ progressBar.start(files.length, 0, { success: 0, errors: 0 });
276
+
277
+ // Process files in batches
278
+ for (let i = 0; i < files.length; i += batchSize) {
279
+ const batch = files.slice(i, i + batchSize);
280
+
281
+ try {
282
+ const batchResult = await this.#processBatch(
283
+ batch,
284
+ options,
285
+ uploadService,
286
+ basePath,
287
+ processedPaths,
288
+ );
289
+
290
+ this.#updateResults(results, batchResult);
291
+
292
+ progressBar.update(Math.min(i + batchSize, files.length), {
293
+ success: results.successCount,
294
+ errors: results.failureCount,
295
+ });
296
+
297
+ // Delay between batches if configured
298
+ if (appConfig.performance.batchDelay > 0) {
299
+ await new Promise((resolve) =>
300
+ setTimeout(resolve, appConfig.performance.batchDelay),
301
+ );
302
+ }
303
+ } catch (error) {
304
+ this.errorHandler.handleError(error, {
305
+ batch: i / batchSize + 1,
306
+ batchSize,
307
+ });
308
+ results.failureCount += batch.length;
309
+ }
310
+ }
311
+
312
+ progressBar.stop();
313
+ return results;
314
+ }
315
+
316
+ /**
317
+ * Process a batch of files
318
+ * @private
319
+ * @param {string[]} batch - Files in this batch
320
+ * @param {Object} options - Processing options
321
+ * @param {Object} uploadService - Upload service
322
+ * @param {string} basePath - Base path
323
+ * @param {Set} processedPaths - Already processed paths
324
+ * @returns {Promise<Object>} Batch results
325
+ */
326
+ async #processBatch(batch, options, uploadService, basePath, processedPaths) {
327
+ const batchResults = {
328
+ successCount: 0,
329
+ detectedCount: 0,
330
+ organizedCount: 0,
331
+ failureCount: 0,
332
+ skippedCount: 0,
333
+ };
334
+
335
+ if (options.statsOnly) {
336
+ // Stats-only mode: just record file information
337
+ const fileObjects = batch.map((filePath) => ({
338
+ path: filePath,
339
+ originalName: path.basename(filePath),
340
+ stats: FileOperations.getFileStats(filePath),
341
+ }));
342
+
343
+ try {
344
+ const result = await databaseService.insertStatsOnlyToUploaderTable(
345
+ fileObjects,
346
+ {
347
+ ...options,
348
+ quietMode: options.quietMode || false, // Pass through quiet mode flag
349
+ },
350
+ );
351
+ batchResults.successCount = result.totalInserted;
352
+ batchResults.skippedCount = result.totalSkipped;
353
+ } catch (error) {
354
+ throw new Error(`Failed to insert stats: ${error.message}`);
355
+ }
356
+ } else {
357
+ // Upload mode: process files with controlled concurrency to match API replicas
358
+ const maxConcurrentApiCalls =
359
+ appConfig.performance?.maxApiConnections || 10;
360
+
361
+ // Process batch in chunks to respect API replica limits
362
+ const allResults = [];
363
+ for (let i = 0; i < batch.length; i += maxConcurrentApiCalls) {
364
+ const chunk = batch.slice(i, i + maxConcurrentApiCalls);
365
+
366
+ // Process this chunk concurrently (up to API replica count)
367
+ const chunkPromises = chunk.map(async (filePath) => {
368
+ try {
369
+ const result = await this.#processFile(
370
+ filePath,
371
+ options,
372
+ uploadService,
373
+ basePath,
374
+ processedPaths,
375
+ );
376
+ return { success: true, filePath, result };
377
+ } catch (error) {
378
+ this.errorHandler.handleError(error, { filePath });
379
+ return { success: false, filePath, error: error.message };
380
+ }
381
+ });
382
+
383
+ // Wait for this chunk to complete before starting the next
384
+ const chunkResults = await Promise.allSettled(chunkPromises);
385
+ allResults.push(...chunkResults);
386
+
387
+ // Small delay between chunks to prevent overwhelming API
388
+ if (i + maxConcurrentApiCalls < batch.length) {
389
+ await new Promise((resolve) => setTimeout(resolve, 50));
390
+ }
391
+ }
392
+
393
+ // Process all results and update batch results
394
+ allResults.forEach((result) => {
395
+ if (result.status === 'fulfilled') {
396
+ const fileResult = result.value;
397
+ if (fileResult.success) {
398
+ if (fileResult.result && fileResult.result.skipped) {
399
+ batchResults.skippedCount++;
400
+ } else {
401
+ batchResults.successCount++;
402
+ if (fileResult.result && fileResult.result.detectedCount) {
403
+ batchResults.detectedCount += fileResult.result.detectedCount;
404
+ }
405
+ if (fileResult.result && fileResult.result.organizedCount) {
406
+ batchResults.organizedCount += fileResult.result.organizedCount;
407
+ }
408
+ }
409
+ } else {
410
+ batchResults.failureCount++;
411
+ }
412
+ } else {
413
+ batchResults.failureCount++;
414
+ }
415
+ });
416
+ }
417
+
418
+ return batchResults;
419
+ }
420
+
421
+ /**
422
+ * Process a single file
423
+ * @private
424
+ */
425
+ async #processFile(
426
+ filePath,
427
+ options,
428
+ uploadService,
429
+ basePath,
430
+ processedPaths,
431
+ ) {
432
+ // Skip if already processed
433
+ if (processedPaths.has(filePath)) {
434
+ return { skipped: true };
435
+ }
436
+
437
+ // Prepare file for upload
438
+ const sanitizedName = fileSanitizer.sanitizeFileName(
439
+ path.basename(filePath),
440
+ );
441
+ const pathInfo = pathDetector.extractYearAndPedimentoFromPath(
442
+ filePath,
443
+ basePath,
444
+ );
445
+
446
+ let uploadPath = sanitizedName;
447
+ if (pathInfo.detected && options.autoDetectStructure) {
448
+ uploadPath = `${pathInfo.year}/${pathInfo.pedimento}/${sanitizedName}`;
449
+ }
450
+
451
+ const fileObject = {
452
+ path: filePath,
453
+ name: sanitizedName,
454
+ contentType: this.#getMimeType(filePath),
455
+ };
456
+
457
+ // Upload based on service type
458
+ let result = { successCount: 1 };
459
+
460
+ if (uploadService.getServiceName() === 'Arela API') {
461
+ result = await uploadService.upload([fileObject], {
462
+ ...options,
463
+ uploadPath,
464
+ });
465
+ } else {
466
+ // Supabase direct upload
467
+ const uploadResult = await uploadService.upload([fileObject], {
468
+ uploadPath,
469
+ });
470
+
471
+ // Check if upload was successful
472
+ if (!uploadResult.success) {
473
+ throw new Error(`Supabase upload failed: ${uploadResult.error}`);
474
+ }
475
+
476
+ result = { successCount: 1 };
477
+ }
478
+
479
+ logger.info(`SUCCESS: ${path.basename(filePath)} -> ${uploadPath}`);
480
+
481
+ return {
482
+ skipped: false,
483
+ detectedCount: result.detectedCount || 0,
484
+ organizedCount: result.organizedCount || 0,
485
+ };
486
+ }
487
+
488
+ /**
489
+ * Get MIME type for file
490
+ * @private
491
+ */
492
+ #getMimeType(filePath) {
493
+ return mime.lookup(filePath) || 'application/octet-stream';
494
+ }
495
+
496
+ /**
497
+ * Update results object
498
+ * @private
499
+ */
500
+ #updateResults(target, source) {
501
+ target.successCount += source.successCount;
502
+ target.detectedCount += source.detectedCount;
503
+ target.organizedCount += source.organizedCount;
504
+ target.failureCount += source.failureCount;
505
+ target.skippedCount += source.skippedCount;
506
+ }
507
+
508
+ /**
509
+ * Update global results
510
+ * @private
511
+ */
512
+ #updateGlobalResults(global, source) {
513
+ this.#updateResults(global, source);
514
+ }
515
+
516
+ /**
517
+ * Log source summary
518
+ * @private
519
+ */
520
+ #logSourceSummary(source, result, options) {
521
+ console.log(`\nšŸ“¦ Summary for ${source}:`);
522
+ if (options.statsOnly) {
523
+ console.log(` šŸ“Š Stats recorded: ${result.successCount}`);
524
+ console.log(` ā­ļø Duplicates: ${result.skippedCount}`);
525
+ } else {
526
+ console.log(` āœ… Uploaded: ${result.successCount}`);
527
+ if (result.detectedCount)
528
+ console.log(` šŸ” Detected: ${result.detectedCount}`);
529
+ if (result.organizedCount)
530
+ console.log(` šŸ“ Organized: ${result.organizedCount}`);
531
+ console.log(` ā­ļø Skipped: ${result.skippedCount}`);
532
+ }
533
+ console.log(` āŒ Errors: ${result.failureCount}`);
534
+ }
535
+
536
+ /**
537
+ * Log final summary
538
+ * @private
539
+ */
540
+ #logFinalSummary(results, options, uploadService) {
541
+ console.log(`\n${'='.repeat(60)}`);
542
+ if (options.statsOnly) {
543
+ console.log(`šŸ“Š STATS COLLECTION COMPLETED`);
544
+ console.log(` šŸ“Š Total stats recorded: ${results.successCount}`);
545
+ console.log(` ā­ļø Total duplicates: ${results.skippedCount}`);
546
+ } else {
547
+ console.log(
548
+ `šŸŽÆ ${uploadService.getServiceName().toUpperCase()} UPLOAD COMPLETED`,
549
+ );
550
+ console.log(` āœ… Total uploaded: ${results.successCount}`);
551
+ if (results.detectedCount)
552
+ console.log(` šŸ” Total detected: ${results.detectedCount}`);
553
+ if (results.organizedCount)
554
+ console.log(` šŸ“ Total organized: ${results.organizedCount}`);
555
+ console.log(` ā­ļø Total skipped: ${results.skippedCount}`);
556
+ }
557
+ console.log(` āŒ Total errors: ${results.failureCount}`);
558
+ console.log(` šŸ“œ Log file: ${logger.getLogFilePath()}`);
559
+ console.log(`${'='.repeat(60)}\n`);
560
+ }
561
+
562
+ /**
563
+ * Run additional phases
564
+ * @private
565
+ */
566
+ async #runAdditionalPhases(options) {
567
+ try {
568
+ // Phase 2: PDF Detection
569
+ console.log('\nšŸ” === PHASE 2: PDF Detection ===');
570
+ const detectionResult = await databaseService.detectPedimentosInDatabase({
571
+ batchSize:
572
+ parseInt(options.batchSize) || appConfig.performance.batchSize || 50,
573
+ });
574
+ console.log(
575
+ `āœ… Phase 2 Complete: ${detectionResult.detectedCount} detected, ${detectionResult.errorCount} errors`,
576
+ );
577
+
578
+ // Additional phases would be implemented here
579
+ console.log('\nšŸŽ‰ All phases completed successfully!');
580
+ } catch (error) {
581
+ this.errorHandler.handleError(error, { phase: 'additional-phases' });
582
+ throw error;
583
+ }
584
+ }
585
+ }
586
+
587
+ export default UploadCommand;