@javalabs/prisma-client 1.0.16 → 1.0.19

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 (106) hide show
  1. package/.dockerignore +14 -0
  2. package/Dockerfile +23 -0
  3. package/README.md +269 -269
  4. package/dist/index.d.ts +1 -1
  5. package/dist/prisma.service.d.ts +1 -1
  6. package/dist/scripts/add-uuid-to-table.js +32 -32
  7. package/dist/scripts/data-migration/batch-migrator.js +12 -12
  8. package/dist/scripts/data-migration/data-transformer.js +14 -14
  9. package/dist/scripts/data-migration/dependency-resolver.js +23 -23
  10. package/dist/scripts/data-migration/entity-discovery.js +68 -68
  11. package/dist/scripts/data-migration/foreign-key-manager.js +23 -23
  12. package/dist/scripts/data-migration/migration-tool.js +5 -5
  13. package/dist/scripts/data-migration/schema-utils.js +74 -74
  14. package/dist/scripts/data-migration/typecast-manager.js +4 -4
  15. package/dist/scripts/database-initializer.js +5 -5
  16. package/dist/scripts/drop-database.js +5 -5
  17. package/dist/scripts/fix-data-types.js +53 -53
  18. package/dist/scripts/fix-enum-values.js +34 -34
  19. package/dist/scripts/fix-schema-discrepancies.js +40 -40
  20. package/dist/scripts/fix-table-indexes.js +81 -81
  21. package/dist/scripts/migrate-schema-structure.js +4 -4
  22. package/dist/scripts/migrate-uuid.js +19 -19
  23. package/dist/scripts/post-migration-validator.js +49 -49
  24. package/dist/scripts/pre-migration-validator.js +107 -107
  25. package/dist/scripts/reset-database.js +21 -21
  26. package/dist/scripts/retry-failed-migrations.js +28 -28
  27. package/dist/scripts/run-migration.js +5 -5
  28. package/dist/scripts/schema-sync.js +18 -18
  29. package/dist/scripts/sequence-sync-cli.js +55 -55
  30. package/dist/scripts/sequence-synchronizer.js +20 -20
  31. package/dist/scripts/sync-enum-types.js +30 -30
  32. package/dist/scripts/sync-enum-values.js +52 -52
  33. package/dist/scripts/truncate-database.js +10 -10
  34. package/dist/scripts/verify-migration-setup.js +10 -10
  35. package/dist/tsconfig.tsbuildinfo +1 -1
  36. package/migration-config.json +63 -63
  37. package/migration-config.json.bk +95 -95
  38. package/package.json +44 -44
  39. package/prisma/migrations/add_accepts_partial_payments_to_users.sql +19 -0
  40. package/prisma/migrations/add_amount_received_to_manual_payments.sql +19 -0
  41. package/prisma/migrations/add_commission_fields.sql +33 -0
  42. package/prisma/migrations/add_uuid_to_transactions.sql +13 -13
  43. package/prisma/migrations/complete_partial_payments_migration.sql +53 -0
  44. package/prisma/migrations/create_settlements_table.sql +60 -0
  45. package/prisma/schema.prisma +56 -4
  46. package/src/index.ts +23 -23
  47. package/src/prisma-factory.service.ts +40 -40
  48. package/src/prisma.module.ts +9 -9
  49. package/src/prisma.service.ts +16 -16
  50. package/src/scripts/add-uuid-to-table.ts +138 -138
  51. package/src/scripts/create-tenant-schemas.ts +145 -145
  52. package/src/scripts/data-migration/batch-migrator.ts +248 -248
  53. package/src/scripts/data-migration/data-transformer.ts +426 -426
  54. package/src/scripts/data-migration/db-connector.ts +120 -120
  55. package/src/scripts/data-migration/dependency-resolver.ts +174 -174
  56. package/src/scripts/data-migration/entity-discovery.ts +196 -196
  57. package/src/scripts/data-migration/foreign-key-manager.ts +277 -277
  58. package/src/scripts/data-migration/migration-config.json +63 -63
  59. package/src/scripts/data-migration/migration-tool.ts +509 -509
  60. package/src/scripts/data-migration/schema-utils.ts +248 -248
  61. package/src/scripts/data-migration/tenant-migrator.ts +201 -201
  62. package/src/scripts/data-migration/typecast-manager.ts +193 -193
  63. package/src/scripts/data-migration/types.ts +113 -113
  64. package/src/scripts/database-initializer.ts +49 -49
  65. package/src/scripts/drop-database.ts +104 -104
  66. package/src/scripts/dump-source-db.sh +61 -61
  67. package/src/scripts/encrypt-user-passwords.ts +36 -36
  68. package/src/scripts/error-handler.ts +117 -117
  69. package/src/scripts/fix-data-types.ts +241 -241
  70. package/src/scripts/fix-enum-values.ts +357 -357
  71. package/src/scripts/fix-schema-discrepancies.ts +317 -317
  72. package/src/scripts/fix-table-indexes.ts +601 -601
  73. package/src/scripts/migrate-schema-structure.ts +90 -90
  74. package/src/scripts/migrate-uuid.ts +76 -76
  75. package/src/scripts/post-migration-validator.ts +526 -526
  76. package/src/scripts/pre-migration-validator.ts +610 -610
  77. package/src/scripts/reset-database.ts +263 -263
  78. package/src/scripts/retry-failed-migrations.ts +416 -416
  79. package/src/scripts/run-migration.ts +707 -707
  80. package/src/scripts/schema-sync.ts +128 -128
  81. package/src/scripts/sequence-sync-cli.ts +416 -416
  82. package/src/scripts/sequence-synchronizer.ts +127 -127
  83. package/src/scripts/sync-enum-types.ts +170 -170
  84. package/src/scripts/sync-enum-values.ts +563 -563
  85. package/src/scripts/truncate-database.ts +123 -123
  86. package/src/scripts/verify-migration-setup.ts +135 -135
  87. package/tsconfig.json +17 -17
  88. package/dist/scripts/data-migration/dependency-manager.d.ts +0 -9
  89. package/dist/scripts/data-migration/dependency-manager.js +0 -86
  90. package/dist/scripts/data-migration/dependency-manager.js.map +0 -1
  91. package/dist/scripts/data-migration/migration-config.json +0 -63
  92. package/dist/scripts/data-migration/migration-phases.d.ts +0 -5
  93. package/dist/scripts/data-migration/migration-phases.js +0 -55
  94. package/dist/scripts/data-migration/migration-phases.js.map +0 -1
  95. package/dist/scripts/data-migration/multi-source-migrator.d.ts +0 -17
  96. package/dist/scripts/data-migration/multi-source-migrator.js +0 -130
  97. package/dist/scripts/data-migration/multi-source-migrator.js.map +0 -1
  98. package/dist/scripts/data-migration/phase-generator.d.ts +0 -15
  99. package/dist/scripts/data-migration/phase-generator.js +0 -187
  100. package/dist/scripts/data-migration/phase-generator.js.map +0 -1
  101. package/dist/scripts/data-migration.d.ts +0 -22
  102. package/dist/scripts/data-migration.js +0 -593
  103. package/dist/scripts/data-migration.js.map +0 -1
  104. package/dist/scripts/multi-db-migration.d.ts +0 -1
  105. package/dist/scripts/multi-db-migration.js +0 -55
  106. package/dist/scripts/multi-db-migration.js.map +0 -1
@@ -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
+ }