@javalabs/prisma-client 1.0.27 → 1.0.29

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 (50) hide show
  1. package/.github/CODEOWNERS +1 -1
  2. package/README.md +269 -269
  3. package/migration-config.json +63 -63
  4. package/migration-config.json.bk +95 -95
  5. package/migrations/add_reserved_amount.sql +7 -7
  6. package/package.json +44 -44
  7. package/prisma/migrations/add_uuid_to_transactions.sql +13 -13
  8. package/prisma/schema.prisma +609 -601
  9. package/src/index.ts +23 -23
  10. package/src/prisma-factory.service.ts +40 -40
  11. package/src/prisma.module.ts +9 -9
  12. package/src/prisma.service.ts +16 -16
  13. package/src/scripts/add-uuid-to-table.ts +138 -138
  14. package/src/scripts/create-tenant-schemas.ts +145 -145
  15. package/src/scripts/data-migration/batch-migrator.ts +248 -248
  16. package/src/scripts/data-migration/data-transformer.ts +426 -426
  17. package/src/scripts/data-migration/db-connector.ts +120 -120
  18. package/src/scripts/data-migration/dependency-resolver.ts +174 -174
  19. package/src/scripts/data-migration/entity-discovery.ts +196 -196
  20. package/src/scripts/data-migration/foreign-key-manager.ts +277 -277
  21. package/src/scripts/data-migration/migration-config.json +63 -63
  22. package/src/scripts/data-migration/migration-tool.ts +509 -509
  23. package/src/scripts/data-migration/schema-utils.ts +248 -248
  24. package/src/scripts/data-migration/tenant-migrator.ts +201 -201
  25. package/src/scripts/data-migration/typecast-manager.ts +193 -193
  26. package/src/scripts/data-migration/types.ts +113 -113
  27. package/src/scripts/database-initializer.ts +49 -49
  28. package/src/scripts/drop-database.ts +104 -104
  29. package/src/scripts/dump-source-db.sh +61 -61
  30. package/src/scripts/encrypt-user-passwords.ts +36 -36
  31. package/src/scripts/error-handler.ts +117 -117
  32. package/src/scripts/fix-data-types.ts +241 -241
  33. package/src/scripts/fix-enum-values.ts +357 -357
  34. package/src/scripts/fix-schema-discrepancies.ts +317 -317
  35. package/src/scripts/fix-table-indexes.ts +601 -601
  36. package/src/scripts/migrate-schema-structure.ts +90 -90
  37. package/src/scripts/migrate-uuid.ts +76 -76
  38. package/src/scripts/post-migration-validator.ts +526 -526
  39. package/src/scripts/pre-migration-validator.ts +610 -610
  40. package/src/scripts/reset-database.ts +263 -263
  41. package/src/scripts/retry-failed-migrations.ts +416 -416
  42. package/src/scripts/run-migration.ts +707 -707
  43. package/src/scripts/schema-sync.ts +128 -128
  44. package/src/scripts/sequence-sync-cli.ts +416 -416
  45. package/src/scripts/sequence-synchronizer.ts +127 -127
  46. package/src/scripts/sync-enum-types.ts +170 -170
  47. package/src/scripts/sync-enum-values.ts +563 -563
  48. package/src/scripts/truncate-database.ts +123 -123
  49. package/src/scripts/verify-migration-setup.ts +135 -135
  50. package/tsconfig.json +17 -17
@@ -1,426 +1,426 @@
1
- import { Logger } from "@nestjs/common";
2
- import { ColumnSchema, EnumCastValue } from "./types";
3
- import { SchemaUtils } from "./schema-utils";
4
- import {
5
- DatabaseConnections,
6
- MigrationConfig,
7
- MigrationOptions,
8
- } from "./types";
9
- import { ForeignKeyManager } from "./foreign-key-manager";
10
- import { TypecastManager } from "./typecast-manager";
11
-
12
- export class DataTransformer {
13
- private readonly logger = new Logger("DataTransformer");
14
- private readonly schemaUtils: SchemaUtils;
15
- private readonly sourceConnection: any;
16
- private connections: DatabaseConnections;
17
- private migrationConfig: MigrationConfig;
18
- private options: MigrationOptions;
19
- private foreignKeyManager: ForeignKeyManager;
20
- private readonly typecastManager: TypecastManager;
21
-
22
- constructor(
23
- schemaUtils: SchemaUtils,
24
- sourceConnection: any,
25
- connections: DatabaseConnections
26
- ) {
27
- this.schemaUtils = schemaUtils;
28
- this.sourceConnection = sourceConnection;
29
- this.connections = connections;
30
- this.typecastManager = new TypecastManager();
31
- }
32
-
33
- async validateEnumValue(
34
- tableName: string,
35
- columnName: string,
36
- value: string | null,
37
- isNullable: boolean = true
38
- ): Promise<string | null> {
39
- try {
40
- if (value === null) {
41
- return isNullable
42
- ? null
43
- : await this.getDefaultEnumValue(tableName, columnName);
44
- }
45
-
46
- const enumTypeName = await this.getEnumTypeName(tableName, columnName);
47
- const normalizedValue = value.toString().trim().toLowerCase();
48
-
49
- // Obtener valores válidos del enum desde la base de datos
50
- const query = `SELECT unnest(enum_range(NULL::${enumTypeName})) as enum_value`;
51
- const validValues = await this.connections.sourcePool.query(query);
52
-
53
- if (!validValues || !validValues.rows) {
54
- this.logger.warn(
55
- `No se pudieron obtener valores válidos para el enum ${enumTypeName}`
56
- );
57
- return await this.getDefaultEnumValue(enumTypeName);
58
- }
59
-
60
- const enumValues = validValues.rows.map((row: any) =>
61
- row.enum_value.toLowerCase()
62
- );
63
-
64
- // Verificar coincidencia exacta
65
- if (enumValues.includes(normalizedValue)) {
66
- return value.toString().trim();
67
- }
68
-
69
- // Verificar coincidencia parcial
70
- const partialMatch = enumValues.find(
71
- (enumValue) =>
72
- enumValue.includes(normalizedValue) ||
73
- normalizedValue.includes(enumValue)
74
- );
75
-
76
- if (partialMatch) {
77
- this.logger.debug(
78
- `Valor enum corregido: ${value} -> ${partialMatch} para ${tableName}.${columnName}`
79
- );
80
- return partialMatch;
81
- }
82
-
83
- // Si no se encuentra coincidencia, usar valor por defecto
84
- const defaultValue = await this.getDefaultEnumValue(enumTypeName);
85
- if (defaultValue) {
86
- this.logger.warn(
87
- `Usando valor por defecto ${defaultValue} para ${tableName}.${columnName}`
88
- );
89
- return defaultValue;
90
- }
91
-
92
- if (!isNullable) {
93
- throw new Error(
94
- `No se pudo encontrar un valor válido para el enum ${enumTypeName}`
95
- );
96
- }
97
-
98
- return null;
99
- } catch (error) {
100
- this.logger.error(
101
- `Error validating enum value for ${tableName}.${columnName}: ${error.message}`
102
- );
103
- if (!isNullable) {
104
- return await this.getDefaultEnumValue(tableName, columnName);
105
- }
106
- return null;
107
- }
108
- }
109
-
110
- private async getEnumTypeName(
111
- tableName: string,
112
- columnName: string
113
- ): Promise<string> {
114
- const result = await this.connections.sourcePool.query(
115
- `
116
- SELECT udt_name
117
- FROM information_schema.columns
118
- WHERE table_name = $1
119
- AND column_name = $2
120
- AND table_schema = 'public'
121
- `,
122
- [tableName.replace(/^public\./, ""), columnName]
123
- );
124
-
125
- if (result.rows.length === 0) {
126
- throw new Error(
127
- `No se encontró el tipo de enum para ${tableName}.${columnName}`
128
- );
129
- }
130
-
131
- return result.rows[0].udt_name;
132
- }
133
-
134
- transformToNumeric(value: any): number | null {
135
- if (value === null || value === "") {
136
- return null;
137
- }
138
-
139
- const numericValue = parseFloat(value);
140
- if (isNaN(numericValue)) {
141
- return null;
142
- }
143
-
144
- return numericValue;
145
- }
146
-
147
- private async getDefaultEnumValue(
148
- enumType: string,
149
- tenantId: string = "public"
150
- ): Promise<string | null> {
151
- try {
152
- // Mapeo específico para tipos de enum conocidos
153
- const enumDefaults = {
154
- enum_users_role: "user",
155
- enum_account_type: "savings",
156
- enum_transaction_type: "payin",
157
- enum_transaction_status: "pending",
158
- };
159
-
160
- if (enumDefaults[enumType]) {
161
- return enumDefaults[enumType];
162
- }
163
-
164
- // Si no hay un valor por defecto específico, obtener el primer valor del enum
165
- const query = `
166
- SELECT e.enumlabel
167
- FROM pg_type t
168
- JOIN pg_enum e ON t.oid = e.enumtypid
169
- JOIN pg_namespace n ON n.oid = t.typnamespace
170
- WHERE t.typname = $1 AND n.nspname = $2
171
- ORDER BY e.enumsortorder
172
- LIMIT 1
173
- `;
174
-
175
- const result = await this.schemaUtils.queryTargetDb(query, [
176
- enumType,
177
- tenantId,
178
- ]);
179
-
180
- if (result.rows.length > 0) {
181
- return result.rows[0].enumlabel;
182
- }
183
-
184
- return null;
185
- } catch (error) {
186
- this.logger.error(
187
- `Error getting default enum value for ${enumType}: ${error.message}`
188
- );
189
- return null;
190
- }
191
- }
192
-
193
- async prepareEnumValue(
194
- tenantId: string,
195
- enumType: string,
196
- value: string
197
- ): Promise<EnumCastValue | null> {
198
- if (value === null || value === undefined || value === "") {
199
- return null;
200
- }
201
-
202
- const validatedValue = await this.validateEnumValue(
203
- tenantId,
204
- enumType,
205
- value
206
- );
207
-
208
- if (validatedValue === null) {
209
- // Obtener valor por defecto dinámicamente
210
- const defaultValue = await this.getDefaultEnumValue(enumType, tenantId);
211
- if (defaultValue) {
212
- this.logger.warn(
213
- `Using first enum value '${defaultValue}' as default for invalid value '${value}' of type ${enumType}`
214
- );
215
- return {
216
- needsEnumCast: true,
217
- value: defaultValue,
218
- enumType,
219
- };
220
- }
221
- return null;
222
- }
223
-
224
- return {
225
- needsEnumCast: true,
226
- value: validatedValue,
227
- enumType,
228
- };
229
- }
230
-
231
- async transformColumnValue(
232
- value: any,
233
- columnName: string,
234
- targetColumn: ColumnSchema & { source_type?: string },
235
- tenantId: string
236
- ): Promise<any> {
237
- if (value === null || value === undefined) {
238
- return targetColumn.is_nullable === "YES"
239
- ? null
240
- : this.getDefaultValue(targetColumn);
241
- }
242
-
243
- try {
244
- // Si tenemos el tipo de origen, usar el TypecastManager
245
- if (targetColumn.source_type) {
246
- return this.typecastManager.castValue(
247
- value,
248
- targetColumn.source_type,
249
- targetColumn.data_type
250
- );
251
- }
252
-
253
- // Si no tenemos el tipo de origen, intentar convertir basado en el tipo destino
254
- switch (targetColumn.data_type.toLowerCase()) {
255
- case "integer":
256
- case "bigint":
257
- case "smallint":
258
- return typeof value === "string" ? parseInt(value, 10) : value;
259
- case "numeric":
260
- case "decimal":
261
- case "real":
262
- case "double precision":
263
- return typeof value === "string" ? parseFloat(value) : value;
264
- case "boolean":
265
- if (typeof value === "string") {
266
- return value.toLowerCase() === "true" || value === "1";
267
- }
268
- return Boolean(value);
269
- case "json":
270
- case "jsonb":
271
- if (typeof value === "string") {
272
- try {
273
- return JSON.parse(value);
274
- } catch {
275
- return value;
276
- }
277
- }
278
- return value;
279
- case "timestamp":
280
- case "timestamptz":
281
- case "timestamp with time zone":
282
- case "timestamp without time zone":
283
- if (value instanceof Date) {
284
- return value.toISOString();
285
- }
286
- if (typeof value === "string" || typeof value === "number") {
287
- return new Date(value).toISOString();
288
- }
289
- return value;
290
- case "date":
291
- if (value instanceof Date) {
292
- return value.toISOString().split("T")[0];
293
- }
294
- if (typeof value === "string" || typeof value === "number") {
295
- return new Date(value).toISOString().split("T")[0];
296
- }
297
- return value;
298
- case "text":
299
- case "character varying":
300
- case "varchar":
301
- case "char":
302
- return String(value);
303
- default:
304
- return value;
305
- }
306
- } catch (error) {
307
- this.logger.warn(
308
- `Error transformando valor para columna ${columnName}: ${error.message}`
309
- );
310
- return targetColumn.is_nullable === "YES"
311
- ? null
312
- : this.getDefaultValue(targetColumn);
313
- }
314
- }
315
-
316
- private getDefaultValue(column: ColumnSchema): any {
317
- switch (column.data_type.toLowerCase()) {
318
- case "integer":
319
- case "bigint":
320
- case "smallint":
321
- return 0;
322
- case "numeric":
323
- case "decimal":
324
- case "real":
325
- case "double precision":
326
- return 0.0;
327
- case "boolean":
328
- return false;
329
- case "text":
330
- case "character varying":
331
- case "varchar":
332
- case "char":
333
- return "";
334
- case "json":
335
- case "jsonb":
336
- return "{}";
337
- case "timestamp":
338
- case "timestamptz":
339
- case "timestamp with time zone":
340
- case "timestamp without time zone":
341
- return new Date().toISOString();
342
- case "date":
343
- return new Date().toISOString().split("T")[0];
344
- default:
345
- return null;
346
- }
347
- }
348
-
349
- private extractEnumValueFromObject(value: any): string | null {
350
- if (typeof value === "object" && value !== null) {
351
- // Intentar extraer el valor de propiedades comunes que podrían contener el valor enum
352
- const possibleProperties = ["value", "type", "name", "code"];
353
- for (const prop of possibleProperties) {
354
- if (value[prop] && typeof value[prop] === "string") {
355
- return value[prop].trim();
356
- }
357
- }
358
- }
359
- return null;
360
- }
361
-
362
- private async handleEnumType(
363
- value: any,
364
- columnName: string,
365
- targetColumn: ColumnSchema,
366
- tenantId: string
367
- ): Promise<string | null> {
368
- // Si el valor es null y la columna permite null
369
- if (value === null && targetColumn.is_nullable === "YES") {
370
- return null;
371
- }
372
-
373
- // Obtener el tipo específico de enum
374
- const enumType = targetColumn.udt_name;
375
-
376
- // Validar el valor según el tipo específico de enum
377
- const validatedValue = await this.validateEnumValue(
378
- tenantId,
379
- columnName,
380
- value,
381
- targetColumn.is_nullable === "YES"
382
- );
383
-
384
- return validatedValue;
385
- }
386
-
387
- async transformRecord(
388
- record: any,
389
- sourceSchema: ColumnSchema[],
390
- targetSchema: ColumnSchema[],
391
- tenantId: string
392
- ): Promise<any> {
393
- const transformedData: any = {};
394
-
395
- for (const [key, value] of Object.entries(record)) {
396
- // Primero intentar encontrar una coincidencia exacta
397
- let targetColumn = targetSchema.find((col) => col.column_name === key);
398
-
399
- // Si no se encuentra coincidencia exacta, intentar case-insensitive
400
- if (!targetColumn) {
401
- targetColumn = targetSchema.find(
402
- (col) => col.column_name.toLowerCase() === key.toLowerCase()
403
- );
404
- }
405
-
406
- if (!targetColumn) {
407
- this.logger.warn(`Column ${key} not found in target schema, skipping`);
408
- continue;
409
- }
410
-
411
- // Usar el nombre exacto de la columna del schema destino
412
- const columnName = targetColumn.column_name;
413
-
414
- const transformedValue = await this.transformColumnValue(
415
- value,
416
- columnName,
417
- targetColumn,
418
- tenantId
419
- );
420
-
421
- transformedData[columnName] = transformedValue;
422
- }
423
-
424
- return transformedData;
425
- }
426
- }
1
+ import { Logger } from "@nestjs/common";
2
+ import { ColumnSchema, EnumCastValue } from "./types";
3
+ import { SchemaUtils } from "./schema-utils";
4
+ import {
5
+ DatabaseConnections,
6
+ MigrationConfig,
7
+ MigrationOptions,
8
+ } from "./types";
9
+ import { ForeignKeyManager } from "./foreign-key-manager";
10
+ import { TypecastManager } from "./typecast-manager";
11
+
12
+ export class DataTransformer {
13
+ private readonly logger = new Logger("DataTransformer");
14
+ private readonly schemaUtils: SchemaUtils;
15
+ private readonly sourceConnection: any;
16
+ private connections: DatabaseConnections;
17
+ private migrationConfig: MigrationConfig;
18
+ private options: MigrationOptions;
19
+ private foreignKeyManager: ForeignKeyManager;
20
+ private readonly typecastManager: TypecastManager;
21
+
22
+ constructor(
23
+ schemaUtils: SchemaUtils,
24
+ sourceConnection: any,
25
+ connections: DatabaseConnections
26
+ ) {
27
+ this.schemaUtils = schemaUtils;
28
+ this.sourceConnection = sourceConnection;
29
+ this.connections = connections;
30
+ this.typecastManager = new TypecastManager();
31
+ }
32
+
33
+ async validateEnumValue(
34
+ tableName: string,
35
+ columnName: string,
36
+ value: string | null,
37
+ isNullable: boolean = true
38
+ ): Promise<string | null> {
39
+ try {
40
+ if (value === null) {
41
+ return isNullable
42
+ ? null
43
+ : await this.getDefaultEnumValue(tableName, columnName);
44
+ }
45
+
46
+ const enumTypeName = await this.getEnumTypeName(tableName, columnName);
47
+ const normalizedValue = value.toString().trim().toLowerCase();
48
+
49
+ // Obtener valores válidos del enum desde la base de datos
50
+ const query = `SELECT unnest(enum_range(NULL::${enumTypeName})) as enum_value`;
51
+ const validValues = await this.connections.sourcePool.query(query);
52
+
53
+ if (!validValues || !validValues.rows) {
54
+ this.logger.warn(
55
+ `No se pudieron obtener valores válidos para el enum ${enumTypeName}`
56
+ );
57
+ return await this.getDefaultEnumValue(enumTypeName);
58
+ }
59
+
60
+ const enumValues = validValues.rows.map((row: any) =>
61
+ row.enum_value.toLowerCase()
62
+ );
63
+
64
+ // Verificar coincidencia exacta
65
+ if (enumValues.includes(normalizedValue)) {
66
+ return value.toString().trim();
67
+ }
68
+
69
+ // Verificar coincidencia parcial
70
+ const partialMatch = enumValues.find(
71
+ (enumValue) =>
72
+ enumValue.includes(normalizedValue) ||
73
+ normalizedValue.includes(enumValue)
74
+ );
75
+
76
+ if (partialMatch) {
77
+ this.logger.debug(
78
+ `Valor enum corregido: ${value} -> ${partialMatch} para ${tableName}.${columnName}`
79
+ );
80
+ return partialMatch;
81
+ }
82
+
83
+ // Si no se encuentra coincidencia, usar valor por defecto
84
+ const defaultValue = await this.getDefaultEnumValue(enumTypeName);
85
+ if (defaultValue) {
86
+ this.logger.warn(
87
+ `Usando valor por defecto ${defaultValue} para ${tableName}.${columnName}`
88
+ );
89
+ return defaultValue;
90
+ }
91
+
92
+ if (!isNullable) {
93
+ throw new Error(
94
+ `No se pudo encontrar un valor válido para el enum ${enumTypeName}`
95
+ );
96
+ }
97
+
98
+ return null;
99
+ } catch (error) {
100
+ this.logger.error(
101
+ `Error validating enum value for ${tableName}.${columnName}: ${error.message}`
102
+ );
103
+ if (!isNullable) {
104
+ return await this.getDefaultEnumValue(tableName, columnName);
105
+ }
106
+ return null;
107
+ }
108
+ }
109
+
110
+ private async getEnumTypeName(
111
+ tableName: string,
112
+ columnName: string
113
+ ): Promise<string> {
114
+ const result = await this.connections.sourcePool.query(
115
+ `
116
+ SELECT udt_name
117
+ FROM information_schema.columns
118
+ WHERE table_name = $1
119
+ AND column_name = $2
120
+ AND table_schema = 'public'
121
+ `,
122
+ [tableName.replace(/^public\./, ""), columnName]
123
+ );
124
+
125
+ if (result.rows.length === 0) {
126
+ throw new Error(
127
+ `No se encontró el tipo de enum para ${tableName}.${columnName}`
128
+ );
129
+ }
130
+
131
+ return result.rows[0].udt_name;
132
+ }
133
+
134
+ transformToNumeric(value: any): number | null {
135
+ if (value === null || value === "") {
136
+ return null;
137
+ }
138
+
139
+ const numericValue = parseFloat(value);
140
+ if (isNaN(numericValue)) {
141
+ return null;
142
+ }
143
+
144
+ return numericValue;
145
+ }
146
+
147
+ private async getDefaultEnumValue(
148
+ enumType: string,
149
+ tenantId: string = "public"
150
+ ): Promise<string | null> {
151
+ try {
152
+ // Mapeo específico para tipos de enum conocidos
153
+ const enumDefaults = {
154
+ enum_users_role: "user",
155
+ enum_account_type: "savings",
156
+ enum_transaction_type: "payin",
157
+ enum_transaction_status: "pending",
158
+ };
159
+
160
+ if (enumDefaults[enumType]) {
161
+ return enumDefaults[enumType];
162
+ }
163
+
164
+ // Si no hay un valor por defecto específico, obtener el primer valor del enum
165
+ const query = `
166
+ SELECT e.enumlabel
167
+ FROM pg_type t
168
+ JOIN pg_enum e ON t.oid = e.enumtypid
169
+ JOIN pg_namespace n ON n.oid = t.typnamespace
170
+ WHERE t.typname = $1 AND n.nspname = $2
171
+ ORDER BY e.enumsortorder
172
+ LIMIT 1
173
+ `;
174
+
175
+ const result = await this.schemaUtils.queryTargetDb(query, [
176
+ enumType,
177
+ tenantId,
178
+ ]);
179
+
180
+ if (result.rows.length > 0) {
181
+ return result.rows[0].enumlabel;
182
+ }
183
+
184
+ return null;
185
+ } catch (error) {
186
+ this.logger.error(
187
+ `Error getting default enum value for ${enumType}: ${error.message}`
188
+ );
189
+ return null;
190
+ }
191
+ }
192
+
193
+ async prepareEnumValue(
194
+ tenantId: string,
195
+ enumType: string,
196
+ value: string
197
+ ): Promise<EnumCastValue | null> {
198
+ if (value === null || value === undefined || value === "") {
199
+ return null;
200
+ }
201
+
202
+ const validatedValue = await this.validateEnumValue(
203
+ tenantId,
204
+ enumType,
205
+ value
206
+ );
207
+
208
+ if (validatedValue === null) {
209
+ // Obtener valor por defecto dinámicamente
210
+ const defaultValue = await this.getDefaultEnumValue(enumType, tenantId);
211
+ if (defaultValue) {
212
+ this.logger.warn(
213
+ `Using first enum value '${defaultValue}' as default for invalid value '${value}' of type ${enumType}`
214
+ );
215
+ return {
216
+ needsEnumCast: true,
217
+ value: defaultValue,
218
+ enumType,
219
+ };
220
+ }
221
+ return null;
222
+ }
223
+
224
+ return {
225
+ needsEnumCast: true,
226
+ value: validatedValue,
227
+ enumType,
228
+ };
229
+ }
230
+
231
+ async transformColumnValue(
232
+ value: any,
233
+ columnName: string,
234
+ targetColumn: ColumnSchema & { source_type?: string },
235
+ tenantId: string
236
+ ): Promise<any> {
237
+ if (value === null || value === undefined) {
238
+ return targetColumn.is_nullable === "YES"
239
+ ? null
240
+ : this.getDefaultValue(targetColumn);
241
+ }
242
+
243
+ try {
244
+ // Si tenemos el tipo de origen, usar el TypecastManager
245
+ if (targetColumn.source_type) {
246
+ return this.typecastManager.castValue(
247
+ value,
248
+ targetColumn.source_type,
249
+ targetColumn.data_type
250
+ );
251
+ }
252
+
253
+ // Si no tenemos el tipo de origen, intentar convertir basado en el tipo destino
254
+ switch (targetColumn.data_type.toLowerCase()) {
255
+ case "integer":
256
+ case "bigint":
257
+ case "smallint":
258
+ return typeof value === "string" ? parseInt(value, 10) : value;
259
+ case "numeric":
260
+ case "decimal":
261
+ case "real":
262
+ case "double precision":
263
+ return typeof value === "string" ? parseFloat(value) : value;
264
+ case "boolean":
265
+ if (typeof value === "string") {
266
+ return value.toLowerCase() === "true" || value === "1";
267
+ }
268
+ return Boolean(value);
269
+ case "json":
270
+ case "jsonb":
271
+ if (typeof value === "string") {
272
+ try {
273
+ return JSON.parse(value);
274
+ } catch {
275
+ return value;
276
+ }
277
+ }
278
+ return value;
279
+ case "timestamp":
280
+ case "timestamptz":
281
+ case "timestamp with time zone":
282
+ case "timestamp without time zone":
283
+ if (value instanceof Date) {
284
+ return value.toISOString();
285
+ }
286
+ if (typeof value === "string" || typeof value === "number") {
287
+ return new Date(value).toISOString();
288
+ }
289
+ return value;
290
+ case "date":
291
+ if (value instanceof Date) {
292
+ return value.toISOString().split("T")[0];
293
+ }
294
+ if (typeof value === "string" || typeof value === "number") {
295
+ return new Date(value).toISOString().split("T")[0];
296
+ }
297
+ return value;
298
+ case "text":
299
+ case "character varying":
300
+ case "varchar":
301
+ case "char":
302
+ return String(value);
303
+ default:
304
+ return value;
305
+ }
306
+ } catch (error) {
307
+ this.logger.warn(
308
+ `Error transformando valor para columna ${columnName}: ${error.message}`
309
+ );
310
+ return targetColumn.is_nullable === "YES"
311
+ ? null
312
+ : this.getDefaultValue(targetColumn);
313
+ }
314
+ }
315
+
316
+ private getDefaultValue(column: ColumnSchema): any {
317
+ switch (column.data_type.toLowerCase()) {
318
+ case "integer":
319
+ case "bigint":
320
+ case "smallint":
321
+ return 0;
322
+ case "numeric":
323
+ case "decimal":
324
+ case "real":
325
+ case "double precision":
326
+ return 0.0;
327
+ case "boolean":
328
+ return false;
329
+ case "text":
330
+ case "character varying":
331
+ case "varchar":
332
+ case "char":
333
+ return "";
334
+ case "json":
335
+ case "jsonb":
336
+ return "{}";
337
+ case "timestamp":
338
+ case "timestamptz":
339
+ case "timestamp with time zone":
340
+ case "timestamp without time zone":
341
+ return new Date().toISOString();
342
+ case "date":
343
+ return new Date().toISOString().split("T")[0];
344
+ default:
345
+ return null;
346
+ }
347
+ }
348
+
349
+ private extractEnumValueFromObject(value: any): string | null {
350
+ if (typeof value === "object" && value !== null) {
351
+ // Intentar extraer el valor de propiedades comunes que podrían contener el valor enum
352
+ const possibleProperties = ["value", "type", "name", "code"];
353
+ for (const prop of possibleProperties) {
354
+ if (value[prop] && typeof value[prop] === "string") {
355
+ return value[prop].trim();
356
+ }
357
+ }
358
+ }
359
+ return null;
360
+ }
361
+
362
+ private async handleEnumType(
363
+ value: any,
364
+ columnName: string,
365
+ targetColumn: ColumnSchema,
366
+ tenantId: string
367
+ ): Promise<string | null> {
368
+ // Si el valor es null y la columna permite null
369
+ if (value === null && targetColumn.is_nullable === "YES") {
370
+ return null;
371
+ }
372
+
373
+ // Obtener el tipo específico de enum
374
+ const enumType = targetColumn.udt_name;
375
+
376
+ // Validar el valor según el tipo específico de enum
377
+ const validatedValue = await this.validateEnumValue(
378
+ tenantId,
379
+ columnName,
380
+ value,
381
+ targetColumn.is_nullable === "YES"
382
+ );
383
+
384
+ return validatedValue;
385
+ }
386
+
387
+ async transformRecord(
388
+ record: any,
389
+ sourceSchema: ColumnSchema[],
390
+ targetSchema: ColumnSchema[],
391
+ tenantId: string
392
+ ): Promise<any> {
393
+ const transformedData: any = {};
394
+
395
+ for (const [key, value] of Object.entries(record)) {
396
+ // Primero intentar encontrar una coincidencia exacta
397
+ let targetColumn = targetSchema.find((col) => col.column_name === key);
398
+
399
+ // Si no se encuentra coincidencia exacta, intentar case-insensitive
400
+ if (!targetColumn) {
401
+ targetColumn = targetSchema.find(
402
+ (col) => col.column_name.toLowerCase() === key.toLowerCase()
403
+ );
404
+ }
405
+
406
+ if (!targetColumn) {
407
+ this.logger.warn(`Column ${key} not found in target schema, skipping`);
408
+ continue;
409
+ }
410
+
411
+ // Usar el nombre exacto de la columna del schema destino
412
+ const columnName = targetColumn.column_name;
413
+
414
+ const transformedValue = await this.transformColumnValue(
415
+ value,
416
+ columnName,
417
+ targetColumn,
418
+ tenantId
419
+ );
420
+
421
+ transformedData[columnName] = transformedValue;
422
+ }
423
+
424
+ return transformedData;
425
+ }
426
+ }