@arela/uploader 1.1.3 → 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 (99) 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/document-type-shared.js +1 -1
  92. package/src/document-types/pedimento-completo.js +8 -1
  93. package/src/file-detection.js +9 -0
  94. package/src/index.js +55 -0
  95. package/src/scoring/scoring-engine.js +1 -1
  96. package/src/services/LoggingService.js +35 -0
  97. package/src/services/PipelineApiService.js +7 -1
  98. package/src/services/PipelineJobRunner.js +389 -0
  99. package/src/services/ProfileManager.js +164 -0
@@ -0,0 +1,233 @@
1
+ import { Agent } from 'http';
2
+ import { Agent as HttpsAgent } from 'https';
3
+ import fetch from 'node-fetch';
4
+
5
+ import appConfig from '../../config/config.js';
6
+ import logger from '../LoggingService.js';
7
+ import { ApiUploadService } from './ApiUploadService.js';
8
+
9
+ /**
10
+ * Multi-API Upload Service
11
+ *
12
+ * Extends ApiUploadService to support uploading to multiple API instances
13
+ * based on RFC/client routing. Uses service discovery from Global API.
14
+ */
15
+ export class MultiApiUploadService extends ApiUploadService {
16
+ constructor() {
17
+ super();
18
+
19
+ // Service discovery configuration
20
+ this.globalApiUrl = process.env.API_GLOBAL_URL || appConfig.api.baseUrl;
21
+ this.serviceCache = new Map(); // RFC -> { url, cachedAt }
22
+ this.cacheTtlMs = parseInt(process.env.SERVICE_CACHE_TTL) || 300000; // 5 minutes
23
+
24
+ logger.info('🌐 MultiApiUploadService initialized');
25
+ logger.info(` Global API: ${this.globalApiUrl}`);
26
+ }
27
+
28
+ /**
29
+ * Discover service URL for a specific RFC
30
+ * @param {string} rfc - The client RFC
31
+ * @returns {Promise<string|null>} Service URL or null
32
+ */
33
+ async discoverServiceByRfc(rfc) {
34
+ const rfcUpper = rfc.toUpperCase();
35
+
36
+ // Check cache first
37
+ const cached = this.serviceCache.get(rfcUpper);
38
+ if (cached && Date.now() - cached.cachedAt < this.cacheTtlMs) {
39
+ logger.debug(
40
+ `📋 Using cached service URL for ${rfcUpper}: ${cached.url}`,
41
+ );
42
+ return cached.url;
43
+ }
44
+
45
+ // Fetch from Global API
46
+ try {
47
+ const response = await fetch(
48
+ `${this.globalApiUrl}/api/service-registry/by-rfc/${rfcUpper}`,
49
+ {
50
+ method: 'GET',
51
+ headers: {
52
+ Authorization: `Bearer ${this.token}`,
53
+ 'Content-Type': 'application/json',
54
+ },
55
+ agent: this.globalApiUrl.startsWith('https')
56
+ ? this.httpsAgent
57
+ : this.httpAgent,
58
+ },
59
+ );
60
+
61
+ if (!response.ok) {
62
+ logger.warn(
63
+ `⚠️ Service discovery failed for ${rfcUpper}: ${response.status}`,
64
+ );
65
+ return null;
66
+ }
67
+
68
+ const data = await response.json();
69
+
70
+ if (data.url) {
71
+ // Cache the result
72
+ this.serviceCache.set(rfcUpper, {
73
+ url: data.url,
74
+ cachedAt: Date.now(),
75
+ });
76
+
77
+ logger.info(`🔍 Discovered service for ${rfcUpper}: ${data.url}`);
78
+ return data.url;
79
+ }
80
+
81
+ return null;
82
+ } catch (error) {
83
+ logger.error(
84
+ `❌ Service discovery error for ${rfcUpper}: ${error.message}`,
85
+ );
86
+ return null;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Clear service cache
92
+ */
93
+ clearCache() {
94
+ this.serviceCache.clear();
95
+ logger.info('🧹 Service cache cleared');
96
+ }
97
+
98
+ /**
99
+ * Clear cache for specific RFC
100
+ * @param {string} rfc - The RFC to clear
101
+ */
102
+ clearRfcCache(rfc) {
103
+ this.serviceCache.delete(rfc.toUpperCase());
104
+ }
105
+
106
+ /**
107
+ * Upload files to the appropriate API based on RFC
108
+ * @param {Array} files - Array of file objects
109
+ * @param {Object} options - Upload options including rfc
110
+ * @returns {Promise<Object>} API response
111
+ */
112
+ async upload(files, options) {
113
+ const { rfc } = options;
114
+
115
+ if (!rfc) {
116
+ // No RFC specified, use default API
117
+ return super.upload(files, options);
118
+ }
119
+
120
+ // Discover the service URL for this RFC
121
+ const serviceUrl = await this.discoverServiceByRfc(rfc);
122
+
123
+ if (serviceUrl) {
124
+ // Override baseUrl for this upload
125
+ const originalBaseUrl = this.baseUrl;
126
+ this.baseUrl = serviceUrl;
127
+
128
+ try {
129
+ logger.info(
130
+ `📤 Uploading ${files.length} files to ${serviceUrl} (RFC: ${rfc})`,
131
+ );
132
+ const result = await super.upload(files, options);
133
+ return result;
134
+ } finally {
135
+ // Restore original baseUrl
136
+ this.baseUrl = originalBaseUrl;
137
+ }
138
+ }
139
+
140
+ // Fallback to default API if service discovery fails
141
+ logger.warn(`⚠️ No specific service found for ${rfc}, using default API`);
142
+ return super.upload(files, options);
143
+ }
144
+
145
+ /**
146
+ * Upload files to multiple RFCs in parallel
147
+ * @param {Object} filesByRfc - Map of RFC -> files array
148
+ * @param {Object} options - Upload options
149
+ * @returns {Promise<Map<string, Object>>} Results by RFC
150
+ */
151
+ async uploadToMultipleRfcs(filesByRfc, options = {}) {
152
+ const results = new Map();
153
+ const rfcs = Object.keys(filesByRfc);
154
+
155
+ logger.info(`📤 Starting multi-RFC upload for ${rfcs.length} RFCs`);
156
+
157
+ // Process RFCs in parallel (with concurrency limit)
158
+ const concurrencyLimit = parseInt(process.env.MULTI_RFC_CONCURRENCY) || 3;
159
+ const chunks = [];
160
+
161
+ for (let i = 0; i < rfcs.length; i += concurrencyLimit) {
162
+ chunks.push(rfcs.slice(i, i + concurrencyLimit));
163
+ }
164
+
165
+ for (const chunk of chunks) {
166
+ await Promise.all(
167
+ chunk.map(async (rfc) => {
168
+ try {
169
+ const files = filesByRfc[rfc];
170
+ const result = await this.upload(files, { ...options, rfc });
171
+ results.set(rfc, { success: true, result });
172
+ } catch (error) {
173
+ logger.error(`❌ Upload failed for RFC ${rfc}: ${error.message}`);
174
+ results.set(rfc, { success: false, error: error.message });
175
+ }
176
+ }),
177
+ );
178
+ }
179
+
180
+ // Log summary
181
+ const successful = [...results.values()].filter((r) => r.success).length;
182
+ const failed = results.size - successful;
183
+ logger.info(
184
+ `✅ Multi-RFC upload complete: ${successful} successful, ${failed} failed`,
185
+ );
186
+
187
+ return results;
188
+ }
189
+
190
+ /**
191
+ * Get all available services from Global API
192
+ * @returns {Promise<Array>} List of services
193
+ */
194
+ async getAllServices() {
195
+ try {
196
+ const response = await fetch(
197
+ `${this.globalApiUrl}/api/service-registry/services`,
198
+ {
199
+ method: 'GET',
200
+ headers: {
201
+ Authorization: `Bearer ${this.token}`,
202
+ 'Content-Type': 'application/json',
203
+ },
204
+ agent: this.globalApiUrl.startsWith('https')
205
+ ? this.httpsAgent
206
+ : this.httpAgent,
207
+ },
208
+ );
209
+
210
+ if (!response.ok) {
211
+ throw new Error(`Failed to fetch services: ${response.status}`);
212
+ }
213
+
214
+ const data = await response.json();
215
+ return data.services || [];
216
+ } catch (error) {
217
+ logger.error(`❌ Failed to get services: ${error.message}`);
218
+ return [];
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Pre-warm cache for a list of RFCs
224
+ * @param {Array<string>} rfcs - List of RFCs to cache
225
+ */
226
+ async prewarmCache(rfcs) {
227
+ logger.info(`🔥 Pre-warming cache for ${rfcs.length} RFCs`);
228
+
229
+ await Promise.all(rfcs.map((rfc) => this.discoverServiceByRfc(rfc)));
230
+
231
+ logger.info('✅ Cache pre-warm complete');
232
+ }
233
+ }
@@ -0,0 +1,148 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+ import fs from 'fs';
3
+ import mime from 'mime-types';
4
+
5
+ import appConfig from '../../config/config.js';
6
+ import { BaseUploadService } from './BaseUploadService.js';
7
+
8
+ /**
9
+ * Supabase Upload Service
10
+ * Handles direct uploads to Supabase Storage
11
+ */
12
+ export class SupabaseUploadService extends BaseUploadService {
13
+ constructor() {
14
+ super();
15
+ this.client = null;
16
+ this.bucket = appConfig.supabase.bucket;
17
+ }
18
+
19
+ /**
20
+ * Initialize Supabase client if not already initialized
21
+ * @private
22
+ */
23
+ async #initializeClient() {
24
+ if (this.client) {
25
+ return;
26
+ }
27
+
28
+ if (!appConfig.supabase.url || !appConfig.supabase.key || !this.bucket) {
29
+ const missing = [];
30
+ if (!appConfig.supabase.url) missing.push('SUPABASE_URL');
31
+ if (!appConfig.supabase.key) missing.push('SUPABASE_KEY');
32
+ if (!this.bucket) missing.push('SUPABASE_BUCKET');
33
+ throw new Error(`Missing Supabase configuration: ${missing.join(', ')}`);
34
+ }
35
+
36
+ console.log(`🔌 Connecting to Supabase: ${appConfig.supabase.url}`);
37
+ console.log(`📦 Using bucket: ${this.bucket}`);
38
+
39
+ this.client = createClient(appConfig.supabase.url, appConfig.supabase.key, {
40
+ db: {
41
+ schema: 'public',
42
+ },
43
+ auth: {
44
+ autoRefreshToken: false,
45
+ persistSession: false,
46
+ },
47
+ global: {
48
+ headers: {
49
+ 'x-client-info': 'arela-uploader',
50
+ },
51
+ },
52
+ // Increase timeout settings to handle large queries
53
+ realtime: {
54
+ timeout: 60000, // 60 seconds
55
+ },
56
+ });
57
+
58
+ // Test connection and bucket access
59
+ console.log(`🧪 Testing bucket access...`);
60
+ const { error } = await this.client.storage
61
+ .from(this.bucket)
62
+ .list('', { limit: 1 });
63
+ if (error) {
64
+ console.error(`❌ Bucket access test failed:`, error.message);
65
+ throw new Error(
66
+ `Error connecting to Supabase bucket '${this.bucket}': ${error.message}`,
67
+ );
68
+ }
69
+
70
+ console.log(`✅ Successfully connected to Supabase bucket: ${this.bucket}`);
71
+ }
72
+
73
+ /**
74
+ * Upload file directly to Supabase
75
+ * @param {Array} files - Array with single file object
76
+ * @param {Object} options - Upload options with uploadPath
77
+ * @returns {Promise<Object>} Upload result
78
+ */
79
+ async upload(files, options) {
80
+ await this.#initializeClient();
81
+
82
+ if (files.length !== 1) {
83
+ throw new Error(
84
+ 'SupabaseUploadService only supports single file uploads',
85
+ );
86
+ }
87
+
88
+ const file = files[0];
89
+ const uploadPath = options.uploadPath;
90
+
91
+ if (!uploadPath) {
92
+ throw new Error('uploadPath is required for Supabase uploads');
93
+ }
94
+
95
+ const content = fs.readFileSync(file.path);
96
+ const contentType = mime.lookup(file.path) || 'application/octet-stream';
97
+
98
+ const normalizedPath = uploadPath.replace(/\\/g, '/');
99
+
100
+ const { data, error } = await this.client.storage
101
+ .from(this.bucket)
102
+ .upload(normalizedPath, content, {
103
+ upsert: true,
104
+ contentType,
105
+ });
106
+
107
+ if (error) {
108
+ console.error(
109
+ `❌ Supabase upload failed for ${normalizedPath}:`,
110
+ error.message,
111
+ );
112
+ return { success: false, error: error.message };
113
+ }
114
+
115
+ console.log(`✅ Supabase upload successful: ${normalizedPath}`);
116
+ return { success: true, data, uploadPath: normalizedPath };
117
+ }
118
+
119
+ /**
120
+ * Check if Supabase service is available
121
+ * @returns {Promise<boolean>} True if available
122
+ */
123
+ async isAvailable() {
124
+ try {
125
+ await this.#initializeClient();
126
+ return true;
127
+ } catch (error) {
128
+ return false;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Get service name
134
+ * @returns {string} Service name
135
+ */
136
+ getServiceName() {
137
+ return 'Supabase';
138
+ }
139
+
140
+ /**
141
+ * Get Supabase client for database operations
142
+ * @returns {Object} Supabase client
143
+ */
144
+ async getClient() {
145
+ await this.#initializeClient();
146
+ return this.client;
147
+ }
148
+ }
@@ -0,0 +1,100 @@
1
+ import { ApiUploadService } from './ApiUploadService.js';
2
+ import { SupabaseUploadService } from './SupabaseUploadService.js';
3
+
4
+ /**
5
+ * Upload Service Factory
6
+ * Manages upload service selection and initialization
7
+ */
8
+ export class UploadServiceFactory {
9
+ constructor() {
10
+ this.apiService = new ApiUploadService();
11
+ this.supabaseService = new SupabaseUploadService();
12
+ // Cache for target-specific API services
13
+ this._targetServices = new Map();
14
+ }
15
+
16
+ /**
17
+ * Get the appropriate upload service based on availability and preferences
18
+ * @param {boolean} forceSupabase - Force Supabase mode
19
+ * @returns {Promise<BaseUploadService>} Upload service instance
20
+ * @throws {Error} If no service is available
21
+ */
22
+ async getUploadService(forceSupabase = false) {
23
+ if (forceSupabase) {
24
+ console.log('🔧 Force Supabase mode enabled - skipping API');
25
+
26
+ if (await this.supabaseService.isAvailable()) {
27
+ console.log(
28
+ `✅ Connected to ${this.supabaseService.getServiceName()} (direct mode)`,
29
+ );
30
+ return this.supabaseService;
31
+ } else {
32
+ throw new Error('Supabase service is not available');
33
+ }
34
+ }
35
+
36
+ // Try API service first
37
+ if (await this.apiService.isAvailable()) {
38
+ console.log(
39
+ '🌐 API mode enabled - files will be uploaded to Arela API with automatic processing',
40
+ );
41
+ console.log(`✅ Connected to ${this.apiService.getServiceName()}`);
42
+ return this.apiService;
43
+ }
44
+
45
+ // Fallback to Supabase
46
+ if (await this.supabaseService.isAvailable()) {
47
+ console.warn(
48
+ '⚠️ API connection failed, falling back to direct Supabase upload',
49
+ );
50
+ console.log(
51
+ `✅ Connected to ${this.supabaseService.getServiceName()} (direct mode)`,
52
+ );
53
+ return this.supabaseService;
54
+ }
55
+
56
+ throw new Error('No upload service is available');
57
+ }
58
+
59
+ /**
60
+ * Get an API service configured for a specific target
61
+ * @param {string} target - API target: 'agencia', 'cliente', 'ktj931117p55', or 'default'
62
+ * @returns {Promise<ApiUploadService>} API service for the specified target
63
+ */
64
+ async getApiServiceForTarget(target) {
65
+ // Import config dynamically to avoid circular dependency
66
+ const { appConfig } = await import('../../config/config.js');
67
+
68
+ const targetConfig = appConfig.getApiConfig(target);
69
+ if (!targetConfig.baseUrl) {
70
+ throw new Error(`No API URL configured for target '${target}'`);
71
+ }
72
+
73
+ // Create a new service instance with specific config
74
+ // Use setExternalConfig to prevent automatic config refresh from overwriting
75
+ const service = new ApiUploadService();
76
+ service.setExternalConfig(targetConfig.baseUrl, targetConfig.token);
77
+
78
+ return service;
79
+ }
80
+
81
+ /**
82
+ * Check if API mode is currently active
83
+ * @returns {Promise<boolean>} True if API service is available
84
+ */
85
+ async isApiModeAvailable() {
86
+ return await this.apiService.isAvailable();
87
+ }
88
+
89
+ /**
90
+ * Get Supabase service for database operations
91
+ * @returns {SupabaseUploadService} Supabase service instance
92
+ */
93
+ getSupabaseService() {
94
+ return this.supabaseService;
95
+ }
96
+ }
97
+
98
+ // Export singleton instance
99
+ export const uploadServiceFactory = new UploadServiceFactory();
100
+ export default uploadServiceFactory;