backend-plus 2.7.0-beta.1 → 2.7.0-beta.12

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.
@@ -29,6 +29,7 @@ var packagejson = require(process.cwd()+'/package.json');
29
29
  var stackTrace = require('stack-trace');
30
30
  var locatePath = require('@upgraded/locate-path');
31
31
  var jsYaml = require('js-yaml');
32
+ var tabPlus = require('tab-plus');
32
33
  var nodemailer = require('nodemailer');
33
34
  var os = require('os');
34
35
  const cors = require('cors');
@@ -62,7 +63,37 @@ var myOwn = require('../for-client/my-things.js');
62
63
 
63
64
  var typeStore = require('type-store');
64
65
  var json4all = require('json4all');
65
- var XLSX = require('xlsx');
66
+ var xlsxNow = require('xlsx-now/node');
67
+ var xlsxNowCore = require('xlsx-now');
68
+ var Big = require('big.js');
69
+
70
+ // Los tipos que las queries devuelven y no son valores nativos de una celda.
71
+ // Sin esto xlsx-now los rechaza, porque un objeto que nadie reclama no tiene
72
+ // una única forma correcta de escribirse.
73
+ var xlsxTypes = xlsxNowCore.defaultTypes;
74
+ // Un timestamp es una fecha: se escribe como tal y no como texto.
75
+ xlsxTypes = xlsxNowCore.withType(xlsxTypes, bestGlobals.Datetime, {
76
+ convert: (value, context) => xlsxNowCore.dateValue(new Date(value.getTime()), context)
77
+ });
78
+ // Igual que en la exportación del cliente: días, con formato de duración.
79
+ xlsxTypes = xlsxNowCore.withType(xlsxTypes, bestGlobals.TimeInterval, {
80
+ convert: (value) => ({v:value.timeInterval.ms?value.timeInterval.ms/(1000*60*60*24):null, numFmt:'[h]:mm:ss'})
81
+ });
82
+ // decimal y hugeint. Como texto cuando el double ya no los representa exacto,
83
+ // para no devolver un número redondeado con apariencia de exacto.
84
+ xlsxTypes = xlsxNowCore.withType(xlsxTypes, Big, {
85
+ convert: function(value){
86
+ var text = value.toString();
87
+ var asNumber = Number(text);
88
+ return Number.isSafeInteger(asNumber) || String(asNumber) === text
89
+ ? {v:asNumber}
90
+ : {v:text, t:'inlineStr'};
91
+ }
92
+ });
93
+ // jsonb, jsona, gpoint, point y cualquier otro objeto sin tipo propio.
94
+ xlsxTypes = xlsxNowCore.withType(xlsxTypes, Object, {
95
+ convert: (value) => ({v:JSON.stringify(value), t:'inlineStr'})
96
+ });
66
97
 
67
98
  var loginPlus = require('login-plus');
68
99
  var dashDashDir=Array.prototype.indexOf.call(process.argv,'--dir')
@@ -73,7 +104,7 @@ if(dashDashDir>0){
73
104
  console.log('cwd',process.cwd());
74
105
  }
75
106
 
76
- /**
107
+ /**
77
108
  * @param {string} text
78
109
  * @return {string}
79
110
  */
@@ -81,14 +112,14 @@ function md5(text){
81
112
  return crypto.createHash('md5').update(text).digest('hex');
82
113
  }
83
114
 
84
- const DEFAULT_ITERATIONS = 4096;
115
+ const DEFAULT_ITERATIONS = 4096;
85
116
  const HASH_ALGORITHM = 'sha256';
86
117
 
87
118
  // primera version de scram sha 256. El Verifier completo tenía 64 bytes.
88
- const LEGACY_KEY_LENGTH = 64;
119
+ const LEGACY_KEY_LENGTH = 64;
89
120
 
90
121
  // Formato nuevo/PG-Compatible: El Client Key tiene 32 bytes (SHA-256).
91
- const SCRAM_KEY_LENGTH = 32;
122
+ const SCRAM_KEY_LENGTH = 32;
92
123
 
93
124
  const bufferToBase64 = (buffer) => buffer.toString('base64');
94
125
 
@@ -96,31 +127,31 @@ const bufferToBase64 = (buffer) => buffer.toString('base64');
96
127
  * Función central para PBKDF2 (Acepta KEY_LEN para compatibilidad).
97
128
  * Devuelve la clave derivada con la longitud especificada.
98
129
  */
99
- async function deriveKey(password, saltBuffer, iterations, keyLength) {
130
+ async function deriveKey(password, saltBuffer, iterations, keyLength) {
100
131
  return new Promise((resolve, reject) => {
101
132
  crypto.pbkdf2(
102
- password,
133
+ password,
103
134
  saltBuffer,
104
- iterations,
135
+ iterations,
105
136
  keyLength,
106
- HASH_ALGORITHM,
137
+ HASH_ALGORITHM,
107
138
  (err, derivedKey) => {
108
139
  if (err) return reject(err);
109
- resolve(derivedKey);
140
+ resolve(derivedKey);
110
141
  }
111
142
  );
112
143
  });
113
144
  }
114
145
 
115
146
  async function generateScramVerifier(password) {
116
- const saltBuffer = crypto.randomBytes(16);
147
+ const saltBuffer = crypto.randomBytes(16);
117
148
  const saltBase64 = bufferToBase64(saltBuffer);
118
149
 
119
150
  const Hi = await deriveKey(
120
- password,
121
- saltBuffer,
151
+ password,
152
+ saltBuffer,
122
153
  DEFAULT_ITERATIONS,
123
- SCRAM_KEY_LENGTH
154
+ SCRAM_KEY_LENGTH
124
155
  );
125
156
  const clientKey = crypto.createHmac('sha256', Hi).update('Client Key').digest();
126
157
  const serverKey = crypto.createHmac('sha256', Hi).update('Server Key').digest();
@@ -144,27 +175,27 @@ async function verifyScramPG(password, storedScramString) {
144
175
  if (!storedScramString.startsWith('SCRAM-SHA-256$')) {
145
176
  return false;
146
177
  }
147
-
178
+
148
179
  const parts = storedScramString.split('$');
149
180
  if (parts.length !== 3) {
150
181
  return false;
151
182
  }
152
-
183
+
153
184
  const [storedIterations, saltBase64] = parts[1].split(':');
154
185
  const [storedKeyBase64, serverKeyBase64] = parts[2].split(':'); // Asume StoredKey y ServerKey
155
186
 
156
187
  if (!saltBase64 || !storedKeyBase64 || !serverKeyBase64 || isNaN(parseInt(storedIterations))) {
157
188
  return false;
158
189
  }
159
-
190
+
160
191
  const iterations = parseInt(storedIterations);
161
192
  const saltBuffer = Buffer.from(saltBase64, 'base64');
162
193
  const storedKeyBuffer = Buffer.from(storedKeyBase64, 'base64');
163
194
  const serverKeyBuffer = Buffer.from(serverKeyBase64, 'base64');
164
195
 
165
196
  const Hi = await deriveKey(
166
- password,
167
- saltBuffer,
197
+ password,
198
+ saltBuffer,
168
199
  iterations,
169
200
  SCRAM_KEY_LENGTH
170
201
  );
@@ -203,21 +234,21 @@ async function verifyScramLegacy(password, storedScramString) {
203
234
  if (parts.length !== 3) {
204
235
  throw new Error('Formato SCRAM almacenado inválido.');
205
236
  }
206
-
237
+
207
238
  // Obtiene iteraciones y salt de la segunda parte (ej: '4096:SALT_BASE64')
208
239
  const [storedIterations, storedSalt] = parts[1].split(':');
209
240
  const storedVerifier = parts[2];
210
-
241
+
211
242
  if (!storedSalt || !storedVerifier || isNaN(parseInt(storedIterations))) {
212
243
  throw new Error('Datos de SCRAM incompletos o malformados.');
213
244
  }
214
-
245
+
215
246
  const iterations = parseInt(storedIterations);
216
247
 
217
248
  // Deriva la clave de la contraseña ingresada
218
249
  const generatedVerifierBuffer = await deriveKey(
219
- password,
220
- storedSalt,
250
+ password,
251
+ storedSalt,
221
252
  iterations,
222
253
  LEGACY_KEY_LENGTH
223
254
  );
@@ -313,7 +344,7 @@ AppBackend.prototype.configStaticConfig = function configStaticConfig(){
313
344
  min-version: 12
314
345
  fkOnUpdate: cascade
315
346
  max: 50
316
- log:
347
+ log:
317
348
  db:
318
349
  until: 2001-01-01 00:00
319
350
  last-error: false
@@ -326,7 +357,7 @@ AppBackend.prototype.configStaticConfig = function configStaticConfig(){
326
357
  "":
327
358
  local-path: for-client
328
359
  bin: {}
329
- client-setup:
360
+ client-setup:
330
361
  skin: ""
331
362
  lang: en
332
363
  version: 1.0
@@ -589,7 +620,7 @@ function MemoryPerodicallySaved(session){
589
620
  })}
590
621
 
591
622
  /**
592
- * @param {string} text
623
+ * @param {string} text
593
624
  */
594
625
  AppBackend.prototype.jsonPass = function jsonPass(text){
595
626
  return JSON.stringify(text,null,' ').replace(/\n(.*".*(pass|clave|secret).*":\s*").*(",?)\n/gi,'\n$1********$3\n');
@@ -751,7 +782,7 @@ AppBackend.prototype.start = function start(opts){
751
782
  while(iPosDbDump && iPosDbDump<process.argv.length && !process.argv[iPosDbDump].startsWith('--')){
752
783
  dumps.push(process.argv[iPosDbDump]);
753
784
  iPosDbDump++;
754
- }
785
+ }
755
786
  opts["dump-db"]={
756
787
  complete:!dumps.length,
757
788
  tableNames:dumps instanceof Array?dumps:null,
@@ -888,8 +919,8 @@ AppBackend.prototype.start = function start(opts){
888
919
  }
889
920
  be.db = pg;
890
921
  be.dbUserNameExpr="get_app_user()";
891
- be.dbUserRolExpr=`(select ${be.db.quoteIdent(be.config.login.rolFieldName)}
892
- from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.db.quoteIdent(be.config.login.table)}
922
+ be.dbUserRolExpr=`(select ${be.db.quoteIdent(be.config.login.rolFieldName)}
923
+ from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.db.quoteIdent(be.config.login.table)}
893
924
  where ${be.db.quoteIdent(be.config.login.userFieldName)} = ${be.dbUserNameExpr})`
894
925
  }).then(function(){
895
926
  if(opts["dump-db"]){
@@ -1032,6 +1063,7 @@ AppBackend.prototype.start = function start(opts){
1032
1063
  console.log(err);
1033
1064
  throw err;
1034
1065
  }
1066
+ await pg.setAllTypes(client);
1035
1067
  var data = await client.query("SELECT current_timestamp as cts").fetchUniqueRow();
1036
1068
  if(verboseStartup){
1037
1069
  console.log('NOW in Database',data.row.cts);
@@ -1047,7 +1079,7 @@ AppBackend.prototype.start = function start(opts){
1047
1079
  },function(){ /*OK, login.jade must not be here */ }),
1048
1080
  fs.stat('client/login.jade').then(function(){
1049
1081
  return Path.resolve(be.rootPath,'client/login');
1050
- },function(){
1082
+ },function(){
1051
1083
  return Path.join(__dirname,'../for-client/login');
1052
1084
  }).then(function(loginFile){
1053
1085
  be.config.login.plus.loginPageServe=function(req,res,next){
@@ -1164,7 +1196,7 @@ AppBackend.prototype.start = function start(opts){
1164
1196
  if(verboseStartup){
1165
1197
  console.log('-------------------');
1166
1198
  console.log(
1167
- 'be.config.login',
1199
+ 'be.config.login',
1168
1200
  be.jsonPass(be.config.login)
1169
1201
  );
1170
1202
  }
@@ -1193,7 +1225,7 @@ AppBackend.prototype.start = function start(opts){
1193
1225
  }
1194
1226
  });
1195
1227
  const updatePassword = async ({client, username, password, setUpdateDate, errorIfNoResult}) => {
1196
- const {table, passFieldName, userFieldName, passUpdatedAtFieldName, passAlgorithmFieldName, schema} = be.config.login;
1228
+ const {table, passFieldName, userFieldName, passUpdatedAtFieldName, passAlgorithmFieldName, schema} = be.config.login;
1197
1229
  const hashPass = await generateScramVerifier(password);
1198
1230
  let params = [username, hashPass];
1199
1231
  let setters = [`${be.db.quoteIdent(passFieldName)} = $2`];
@@ -1204,9 +1236,9 @@ AppBackend.prototype.start = function start(opts){
1204
1236
  if(setUpdateDate && passUpdatedAtFieldName){
1205
1237
  setters.push(`${be.db.quoteIdent(passUpdatedAtFieldName)} = current_timestamp`)
1206
1238
  }
1207
-
1239
+
1208
1240
  const result = await client.query(`
1209
- UPDATE ${(schema ? be.db.quoteIdent(schema) + '.' : '') + be.db.quoteIdent(table)}
1241
+ UPDATE ${(schema ? be.db.quoteIdent(schema) + '.' : '') + be.db.quoteIdent(table)}
1210
1242
  SET ${setters.join(', ')}
1211
1243
  WHERE ${be.db.quoteIdent(userFieldName)} = $1
1212
1244
  returning 1 as ok
@@ -1236,7 +1268,7 @@ AppBackend.prototype.start = function start(opts){
1236
1268
  [userFieldName]
1237
1269
  )
1238
1270
  ).concat(passFieldName);
1239
-
1271
+
1240
1272
  const sql = "SELECT "+infoFieldList.map(function(fieldOrPair){ return fieldOrPair.split(' as ').map(function(ident){ return be.db.quoteIdent(ident)}).join(' as '); })+
1241
1273
  ", "+be.config.login.activeClausule+" as active "+
1242
1274
  ", "+be.config.login.lockedClausule+" as locked "+
@@ -1264,7 +1296,7 @@ AppBackend.prototype.start = function start(opts){
1264
1296
  if(await verifyScramPG(password, user[passFieldName])){
1265
1297
  isScramValid = true;
1266
1298
  }
1267
-
1299
+
1268
1300
  // 2. Intento con formato Legacy (64 bytes)
1269
1301
  else if(await verifyScramLegacy(password, user[passFieldName])){
1270
1302
  isScramValid = true;
@@ -1274,7 +1306,7 @@ AppBackend.prototype.start = function start(opts){
1274
1306
  done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
1275
1307
  return
1276
1308
  }
1277
- }else{
1309
+ }else{
1278
1310
  if (md5(password+username.toLowerCase()) === user[passFieldName]){
1279
1311
  needsMigration = true;
1280
1312
  }else{
@@ -1288,7 +1320,7 @@ AppBackend.prototype.start = function start(opts){
1288
1320
  if (be.config.log["pass-migration"]) console.log('Migración completada.');
1289
1321
  }
1290
1322
  }
1291
- //continua validando
1323
+ //continua validando
1292
1324
  if(data.rowCount==1){
1293
1325
  if(!data.row.active){
1294
1326
  done(null,false,{message:be.messages.unlogged.login.inactiveFail});
@@ -1344,7 +1376,7 @@ AppBackend.prototype.start = function start(opts){
1344
1376
  const data = await client.query(
1345
1377
  `SELECT *
1346
1378
  FROM ${(schema ? be.db.quoteIdent(schema) + '.' : '')}${be.db.quoteIdent(table)}
1347
- WHERE ${be.db.quoteIdent(userFieldName)} = $1`,
1379
+ WHERE ${be.db.quoteIdent(userFieldName)} = $1`,
1348
1380
  [username]
1349
1381
  ).fetchOneRowIfExists();
1350
1382
  if (data.rowCount !== 1) {
@@ -1360,7 +1392,7 @@ AppBackend.prototype.start = function start(opts){
1360
1392
  ok = await verifyScramLegacy(oldPassword, storedHash);
1361
1393
  }
1362
1394
  } else { //Intento MD5
1363
- const md5Hash = md5(oldPassword + username.toLowerCase());
1395
+ const md5Hash = md5(oldPassword + username.toLowerCase());
1364
1396
  ok = (md5Hash === storedHash);
1365
1397
  }
1366
1398
  if (!ok) {
@@ -1454,7 +1486,7 @@ AppBackend.prototype.start = function start(opts){
1454
1486
  to: be.config.mailer?.supervise?.to,
1455
1487
  subject: `npm start ${be.config["client-setup"]?.title || packagejson.name} ok ✔️`,
1456
1488
  text:`Inicio del servicio: ${new Date().toJSON()}
1457
-
1489
+
1458
1490
  Contexto: ${os.userInfo().username} ${process.cwd()}
1459
1491
  `
1460
1492
  }, {ignoreNoMailer:true, event:'restart-ok'})
@@ -1462,7 +1494,7 @@ AppBackend.prototype.start = function start(opts){
1462
1494
  if(err.dumping=='ok'){
1463
1495
  console.log('db struct dumped');
1464
1496
  process.exit(0);
1465
- return;
1497
+ return;
1466
1498
  }
1467
1499
  console.log('ERROR',err.stack || err);
1468
1500
  if(err.context){
@@ -1476,11 +1508,11 @@ AppBackend.prototype.start = function start(opts){
1476
1508
  to: be.config.mailer?.supervise?.to,
1477
1509
  subject: `npm start ${be.config["client-setup"]?.title || packagejson.name} fallido 🛑`,
1478
1510
  text:`Falla en el inicio del servicio: ${new Date().toJSON()}
1479
-
1511
+
1480
1512
  Contexto: ${os.userInfo().username} ${process.cwd()}
1481
1513
 
1482
1514
  Mensaje: ${err.message}
1483
-
1515
+
1484
1516
  ${err.stack}
1485
1517
  `
1486
1518
  }, {ignoreNoMailer:true, event:'restart-fail'})
@@ -1549,7 +1581,7 @@ AppBackend.prototype.checkDatabaseStructure = async function checkDatabaseStruct
1549
1581
  throw err;
1550
1582
  }
1551
1583
  try{
1552
- await client.query(`select ${be.db.quoteIdentList(be.config.login.forget.mailFields)}
1584
+ await client.query(`select ${be.db.quoteIdentList(be.config.login.forget.mailFields)}
1553
1585
  from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.config.login.table} limit 1`).fetchOneRowIfExists();
1554
1586
  }catch(err){
1555
1587
  var mensaje = `
@@ -1564,7 +1596,7 @@ AppBackend.prototype.checkDatabaseStructure = async function checkDatabaseStruct
1564
1596
  }
1565
1597
  }
1566
1598
  var bitacoraTableName = be.config.server.bitacoraTableName || 'bitacora';
1567
- var bitacoraId = await client.query(`SELECT data_type
1599
+ var bitacoraId = await client.query(`SELECT data_type
1568
1600
  FROM information_schema.columns
1569
1601
  WHERE /*table_schema = 'his' AND*/ table_name = '${bitacoraTableName}' AND column_name = 'id'
1570
1602
  `).fetchOneRowIfExists();
@@ -1609,9 +1641,9 @@ AppBackend.prototype.postConfig = function postConfig(){
1609
1641
  AppBackend.prototype.getContext = function getContext(req){
1610
1642
  var be = this;
1611
1643
  return {
1612
- be, user:req.user, session:req.session,
1613
- username:req.user && req.user[be.config.login.userFieldName],
1614
- machineId:req.machineId,
1644
+ be, user:req.user, session:req.session,
1645
+ username:req.user && req.user[be.config.login.userFieldName],
1646
+ machineId:req.machineId,
1615
1647
  navigator:(req.userAgent||{}).shortDescription||'?'
1616
1648
  };
1617
1649
  }
@@ -1641,7 +1673,7 @@ AppBackend.prototype.generateInsertSQL = function generateInsertSQL(schemaName,
1641
1673
  var cleanValues = [];
1642
1674
  for (var key in insertElement) {
1643
1675
  cleanKeys.push(db.quoteIdent(key));
1644
- cleanValues.push(db.quoteNullable(insertElement[key]));
1676
+ cleanValues.push(db.quoteNullable(insertElement[key]));
1645
1677
  }
1646
1678
  var sql = `INSERT INTO ${db.quoteIdent(schemaName)}.${db.quoteIdent(tableName)}
1647
1679
  (${cleanKeys.join(',')}) VALUES (${cleanValues.join(',')}) returning id`;
@@ -1659,7 +1691,7 @@ AppBackend.prototype.updateUpdateSQL = function updateUpdateSQL(schemaName, tabl
1659
1691
  for (var key in updateConditions) {
1660
1692
  filterPairs.push(be.db.quoteIdent(key) + " = " + be.db.quoteLiteral(updateConditions[key]));
1661
1693
  };
1662
- var sql = `UPDATE ${db.quoteIdent(schemaName)}.${db.quoteIdent(tableName)}
1694
+ var sql = `UPDATE ${db.quoteIdent(schemaName)}.${db.quoteIdent(tableName)}
1663
1695
  SET ${setPairs.join(',')}
1664
1696
  WHERE ${filterPairs.join(' AND ')}`;
1665
1697
  return sql;
@@ -1667,7 +1699,7 @@ AppBackend.prototype.updateUpdateSQL = function updateUpdateSQL(schemaName, tabl
1667
1699
 
1668
1700
 
1669
1701
  /** @param {boolean} forUnlogged */
1670
- AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnlogged){
1702
+ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnlogged){
1671
1703
  var be = this;
1672
1704
  if(forUnlogged){
1673
1705
  var app = express();
@@ -1685,7 +1717,7 @@ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnl
1685
1717
  be.procedures = defs;
1686
1718
  be.clientSetup.procedure = be.procedure;
1687
1719
  app.get('/client-setup',async function(req, res, next){
1688
- if(forUnlogged && req.user){
1720
+ if(forUnlogged && req.user){
1689
1721
  // este pedido es para unlogged y está logueado, va al próximo
1690
1722
  next();
1691
1723
  }else{
@@ -1715,12 +1747,12 @@ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnl
1715
1747
  if(!isLowerIdent(procedureDef.action)){
1716
1748
  console.error('**** DEPRECATED ***** procedureDef action '+JSON.stringify(procedureDef.action)+' must be a Lower Ident');
1717
1749
  }
1718
- app[procedureDef.method]('/'+procedureDef.action,
1750
+ app[procedureDef.method]('/'+procedureDef.action,
1719
1751
  /**
1720
- *
1721
- * @param {Request} req
1722
- * @param {Response} res
1723
- * @param {import('express').NextFunction} next
1752
+ *
1753
+ * @param {Request} req
1754
+ * @param {Response} res
1755
+ * @param {import('express').NextFunction} next
1724
1756
  */
1725
1757
  async function(req, res, next){
1726
1758
  const BITACORA_SCHEMA = be.config.server.bitacoraSchema;
@@ -1773,9 +1805,9 @@ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnl
1773
1805
  init_date: initDatetimeString,
1774
1806
  };
1775
1807
  var getFinalStatusBitacoraElement = function getFinalStatusBitacoraElement(){
1776
- return {
1777
- end_date: getDatetimeString(),
1778
- end_status: status,
1808
+ return {
1809
+ end_date: getDatetimeString(),
1810
+ end_status: status,
1779
1811
  has_error: hasError
1780
1812
  }
1781
1813
  }
@@ -1805,12 +1837,12 @@ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnl
1805
1837
  var params = getParams();
1806
1838
  if(status){
1807
1839
  //terminó ejecucion
1808
- updateElement[procedureDef.bitacora.targetTableBitacoraFields.end_date] = getDatetimeString();
1840
+ updateElement[procedureDef.bitacora.targetTableBitacoraFields.end_date] = getDatetimeString();
1809
1841
  updateElement[procedureDef.bitacora.targetTableBitacoraFields.end_status] = status;
1810
1842
  updateElement[procedureDef.bitacora.targetTableBitacoraFields.has_error] = hasError;
1811
1843
  }else{
1812
1844
  //empezó ejecucion
1813
- updateElement[procedureDef.bitacora.targetTableBitacoraFields.init_date] = initDatetimeString;
1845
+ updateElement[procedureDef.bitacora.targetTableBitacoraFields.init_date] = initDatetimeString;
1814
1846
  }
1815
1847
  await be.inTransaction(req,async function(client){
1816
1848
  targetTableUpdateFieldsCondition.forEach(function(field){
@@ -2114,7 +2146,7 @@ AppBackend.prototype.optsGenericForFiles = function optsGenericForFiles(req, opt
2114
2146
  return changing(be.optsGenericForAll||{},{
2115
2147
  allowedExts:be.exts.normal,
2116
2148
  jade:{
2117
- skin:skin,
2149
+ skin:skin,
2118
2150
  skinUrl:skinUrl,
2119
2151
  formTitle:title,
2120
2152
  ...(opts?.withFlash ? {flash:req?.flash?.()} : {}),
@@ -2133,8 +2165,8 @@ AppBackend.prototype.unloggedLandPage = function unloggedLandPage(req){
2133
2165
  html.h2({style:'text-align:center; margin-top:15%; font-family:arial, sans-serif'},[be.messages.server.notLoggedIn]),
2134
2166
  html.h2({style:'text-align:center'},[html.code([
2135
2167
  html.a({
2136
- id:'goto-login',
2137
- style:'border:0.5px solid blue; border-radius: 6px; padding: 6px',
2168
+ id:'goto-login',
2169
+ style:'border:0.5px solid blue; border-radius: 6px; padding: 6px',
2138
2170
  href:Path.posix.join(be.config.server["base-url"],(be.config.login.plus.loginUrlPath||'/login'))
2139
2171
  },'login')
2140
2172
  ])]),
@@ -2157,8 +2189,8 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
2157
2189
  var now = bestGlobals.datetime.now();
2158
2190
  var token = crypto.randomUUID();
2159
2191
  var {rows} = await client.query(`select ${be.db.quoteIdent(be.config.login.userFieldName)} as username
2160
- from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.db.quoteIdent(be.config.login.table)}
2161
- where $1 in (${be.db.quoteIdentList(be.config.login.forget.mailFields)})
2192
+ from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.db.quoteIdent(be.config.login.table)}
2193
+ where $1 in (${be.db.quoteIdentList(be.config.login.forget.mailFields)})
2162
2194
  and ${be.config.login.activeClausule}
2163
2195
  and ${be.config.login.lockedClausule} is not true
2164
2196
  `, [req.body.email.toLowerCase()]).fetchAll();
@@ -2266,21 +2298,21 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
2266
2298
  // http://localhost:3033/img/login-logo-icon.png
2267
2299
  mainApp.get(Path.posix.join(baseUrl,'/img/login-logo-icon.png'), async function(req,res,next){
2268
2300
  var buscar = [
2269
- 'unlogged/img/login-logo-icon.svg',
2270
- 'dist/unlogged/img/login-logo-icon.svg',
2271
- 'dist/client/unlogged/img/login-logo-icon.svg',
2272
- 'unlogged/img/login-logo-icon.png',
2273
- 'dist/unlogged/img/login-logo-icon.png',
2274
- 'dist/client/unlogged/img/login-logo-icon.png',
2275
- 'unlogged/img/logo.png',
2276
- 'dist/unlogged/img/logo.png',
2277
- 'dist/client/unlogged/img/logo.png',
2278
- 'client/img/logo.png',
2279
- 'dist/client/img/logo.png',
2280
- 'dist/client/client/img/logo.png',
2301
+ 'unlogged/img/login-logo-icon.svg',
2302
+ 'dist/unlogged/img/login-logo-icon.svg',
2303
+ 'dist/client/unlogged/img/login-logo-icon.svg',
2304
+ 'unlogged/img/login-logo-icon.png',
2305
+ 'dist/unlogged/img/login-logo-icon.png',
2306
+ 'dist/client/unlogged/img/login-logo-icon.png',
2307
+ 'unlogged/img/logo.png',
2308
+ 'dist/unlogged/img/logo.png',
2309
+ 'dist/client/unlogged/img/logo.png',
2310
+ 'client/img/logo.png',
2311
+ 'dist/client/img/logo.png',
2312
+ 'dist/client/client/img/logo.png',
2281
2313
  'unlogged/img/logo-128.png',
2282
2314
  'dist/unlogged/img/logo-128.png',
2283
- 'dist/client/unlogged/img/logo-128.png'
2315
+ 'dist/client/unlogged/img/logo-128.png'
2284
2316
  ];
2285
2317
  buscar = buscar.map(n=>be.rootPath+'/'+n);
2286
2318
  buscar.push(__dirname+'/../for-client/img/login-logo-icon.png');
@@ -2295,7 +2327,7 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
2295
2327
  be.clientIncludesCompleted(null).filter(x => x.module).forEach(function (moduleDef) {
2296
2328
  if(baseUrl=='/'){
2297
2329
  baseUrl='';
2298
- }
2330
+ }
2299
2331
  let baseLib = baseUrl + '/' + (moduleDef.path ? moduleDef.path : be.esJavascript(moduleDef.type)? 'lib': 'css');
2300
2332
  resolve_module_dir(moduleDef.module, moduleDef.modPath, moduleDef.file ?? '.')
2301
2333
  try {
@@ -2319,7 +2351,7 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
2319
2351
  // ----------------------------------------------------
2320
2352
  var skin=be.config['client-setup'].skin;
2321
2353
  var skinUrl=(skin?skin+'/':'');
2322
- var optsGenericForFilesUnlogged=be.optsGenericForFiles();
2354
+ var optsGenericForFilesUnlogged=be.optsGenericForFiles();
2323
2355
  var skinPaths=[Path.join(be.rootPath,'skins')];
2324
2356
  if(be.config.server.skins[skin]['local-path']){
2325
2357
  skinPaths=skinPaths.concat(be.config.server.skins[skin]['local-path']).map(function(path){
@@ -2372,12 +2404,14 @@ AppBackend.prototype.getVisibleMenu = function getVisibleMenu(menu, context){
2372
2404
 
2373
2405
  AppBackend.prototype.clientIncludes = function clientIncludes(req, opts) {
2374
2406
  const hideBEPlusInclusions = opts === true || opts && typeof opts == "object" && opts.hideBEPlusInclusions;
2375
- opts = opts === true ? {} : opts || {};
2407
+ opts = opts === true ? {} : opts || {};
2376
2408
  var list = [];
2377
2409
  if (!hideBEPlusInclusions) {
2378
2410
  list = [
2379
2411
  // { type: 'js', module: 'xlsx', modPath: 'dist', file: 'xlsx.core.min.js' },
2380
- { type: 'js', module: 'xlsx', modPath: 'dist', file: 'xlsx.full.min.js' },
2412
+ { type: 'js', module: 'fflate', modPath: '../umd', file: 'index.js' },
2413
+ { type: 'js', module: 'xlsx-now', modPath: '../../umd', file: 'xlsx-now.umd.js' },
2414
+ { type: 'js', module: 'xlsx-now', modPath: '../../umd', file: 'xlsx-now-browser.umd.js' },
2381
2415
  { type: 'js', module: 'require-bro' },
2382
2416
  { type: 'js', module: 'js-yaml', modPath: 'browser', file: 'js-yaml.umd.min.js' },
2383
2417
  { type: 'js', module: 'cast-error' },
@@ -2463,7 +2497,7 @@ AppBackend.prototype.clientModules = function clientModules(req, opts) {
2463
2497
  }
2464
2498
 
2465
2499
  /**
2466
- * @param {string} tableName
2500
+ * @param {string} tableName
2467
2501
  * @param {(tableDef:typesOpe.TableDefinition, context?:TableContext)=>void} appenderFunction
2468
2502
  */
2469
2503
  AppBackend.prototype.appendToTableDefinition = function appendToTableDefinition(tableName, appenderFunction){
@@ -2520,16 +2554,16 @@ AppBackend.prototype.csss = function csss(hideBEPlusInclusions){
2520
2554
  AppBackend.prototype.scanSkinFiles = function scanSkinFiles() {
2521
2555
  var be = this;
2522
2556
  var skinName = be.config['client-setup'].skin;
2523
- be.activeSkinFiles = new Set();
2557
+ be.activeSkinFiles = new Set();
2524
2558
 
2525
2559
  var skinConfig = be.config.server.skins && be.config.server.skins[skinName];
2526
2560
 
2527
2561
  if (skinName && skinConfig && skinConfig['local-path']) {
2528
2562
  try {
2529
2563
  var skinPath = Path.join(Path.resolve(skinConfig['local-path']), skinName);
2530
-
2564
+
2531
2565
  if (fs.existsSync(skinPath)) {
2532
-
2566
+
2533
2567
  const walk = (currentPath) => {
2534
2568
  const entries = fs.readdirSync(currentPath, { withFileTypes: true });
2535
2569
 
@@ -2544,7 +2578,7 @@ AppBackend.prototype.scanSkinFiles = function scanSkinFiles() {
2544
2578
  var relativePath = Path.relative(skinPath, fullPath)
2545
2579
  .replace(/\\/g, '/')
2546
2580
  .replace(/^\//, '');
2547
-
2581
+
2548
2582
  be.activeSkinFiles.add(relativePath);
2549
2583
  }
2550
2584
  }
@@ -2631,7 +2665,7 @@ AppBackend.prototype.mainPage = function mainPage(req, offlineMode, opts){
2631
2665
 
2632
2666
  var lastDotIndex = css.lastIndexOf('.');
2633
2667
  var cssBase = (lastDotIndex !== -1) ? css.substring(0, lastDotIndex) : css;
2634
-
2668
+
2635
2669
  var existsInSkin = EXTENSIONES_SKIN.some(function(ext) {
2636
2670
  return be.activeSkinFiles.has(cssBase + ext);
2637
2671
  });
@@ -2897,7 +2931,7 @@ AppBackend.prototype.validateBitacora = function validateBitacora(procedureDef){
2897
2931
  var tableDefFields = be.tableStructures[procedureDef.bitacora.targetTable](contextForDump).fields;
2898
2932
  var targetTableBitacoraFields = procedureDef.bitacora.targetTableBitacoraFields;
2899
2933
  for (var fieldForSearch in targetTableBitacoraFields) {
2900
- var searchResult = tableDefFields.find(function findByName(field) {
2934
+ var searchResult = tableDefFields.find(function findByName(field) {
2901
2935
  return field.name === targetTableBitacoraFields[fieldForSearch];
2902
2936
  });
2903
2937
  if(!searchResult){
@@ -2905,12 +2939,12 @@ AppBackend.prototype.validateBitacora = function validateBitacora(procedureDef){
2905
2939
  }
2906
2940
  }
2907
2941
  var targetTableUpdateFieldsCondition = procedureDef.bitacora.targetTableUpdateFieldsCondition || ['init_date','end_date','has_error', 'end_status'];
2908
- if(targetTableUpdateFieldsCondition){
2942
+ if(targetTableUpdateFieldsCondition){
2909
2943
  if(targetTableUpdateFieldsCondition.length == 0){
2910
2944
  throw Error("Bitacora bad definition in core function '" + procedureDef.action + "', targetTableUpdateFieldsCondition must to be defined for table '" + procedureDef.bitacora.targetTable + "'.");
2911
2945
  }
2912
2946
  targetTableUpdateFieldsCondition.forEach(function(fieldName){
2913
- var searchResult = tableDefFields.find(function findByName(field) {
2947
+ var searchResult = tableDefFields.find(function findByName(field) {
2914
2948
  return field.name === fieldName;
2915
2949
  });
2916
2950
  if(!searchResult){
@@ -3040,9 +3074,9 @@ AppBackend.prototype.dumpDbTableFields = function dumpDbTableFields(tableDef, op
3040
3074
  (fieldDef.dataDecimals?','+fieldDef.dataDecimals:'')
3041
3075
  +')':fieldType)+
3042
3076
  ( be.specialSqlDefaultExpressions[fieldDef.defaultDbValue] != null ? ' default ' + be.specialSqlDefaultExpressions[fieldDef.defaultDbValue]
3043
- : fieldDef.defaultDbValue != null ? ' default ' + fieldDef.defaultDbValue
3077
+ : fieldDef.defaultDbValue != null ? ' default ' + fieldDef.defaultDbValue
3044
3078
  : be.specialSqlDefaultExpressions[fieldDef.specialDefaultValue] != null ? ' default ' + be.specialSqlDefaultExpressions[fieldDef.specialDefaultValue]
3045
- : fieldDef.defaultValue != null ? ' default ' + db.quoteLiteral(fieldDef.defaultValue)
3079
+ : fieldDef.defaultValue != null ? ' default ' + db.quoteLiteral(fieldDef.defaultValue)
3046
3080
  : ''
3047
3081
  ) +
3048
3082
  (be.isGeneratedSequence(fieldDef.sequence)?' generated always as identity':'')+
@@ -3195,7 +3229,7 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
3195
3229
  lines.push(');');
3196
3230
  //TODO: REFACTOR: Hacerlo mas sencillo
3197
3231
  // este codigo se encarga de convertir a rights de sql nuestros propios rights
3198
- // por ej (import -> [insert, update])
3232
+ // por ej (import -> [insert, update])
3199
3233
  var allows = tableDef.allow;
3200
3234
  var appToSqlRights = {'import': ['insert', 'update'], 'export': ['select'], 'deleteAll': ['delete']};
3201
3235
  [ 'import', 'export', 'deleteAll'].filter(function(right){
@@ -3252,7 +3286,7 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
3252
3286
  var prefix = 'alter table '+cualQuoteTableName+' add '+
3253
3287
  (cons.consName?'constraint '+db.quoteIdent(cons.consName)+' ':'');
3254
3288
  switch(cons.constraintType){
3255
- case 'unique':
3289
+ case 'unique':
3256
3290
  sql='('+cons.fields.map(function(field){ return db.quoteIdent(field); }).join(', ')+')';
3257
3291
  if(cons.where){
3258
3292
  if(cons.consName == null){
@@ -3264,10 +3298,10 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
3264
3298
  prefix += 'unique ';
3265
3299
  }
3266
3300
  break;
3267
- case 'check':
3301
+ case 'check':
3268
3302
  sql='check ('+cons.expr+')';
3269
3303
  break;
3270
- case 'exclude':
3304
+ case 'exclude':
3271
3305
  sql=`exclude using ${cons.using} (${cons.fields.map(f => typeof f == "string"?`${f} WITH =`:`${f.fieldName} WITH ${f.operator}`).join(', ')})${cons.where ? ` WHERE (${cons.where})`:``}`;
3272
3306
  break;
3273
3307
  default:
@@ -3310,7 +3344,7 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
3310
3344
  (polcom.using? ` USING ( ${polcom.using} )`:'')+
3311
3345
  (polcom.check?` WITH CHECK ( ${polcom.check} )`:'')+';'
3312
3346
  );
3313
- }
3347
+ }
3314
3348
  });
3315
3349
  }
3316
3350
  }else{
@@ -3351,7 +3385,7 @@ begin
3351
3385
  else
3352
3386
  select ${be.config.login.infoFieldList.map(fieldName => db.quoteIdent(fieldName)).join(', ')}
3353
3387
  into ${be.config.login.infoFieldList.map(fieldName => db.quoteIdent('v_'+fieldName)).join(', ')}
3354
-
3388
+
3355
3389
  from ${(be.config.login.from ?? (
3356
3390
  (be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':'')+
3357
3391
  be.db.quoteIdent(be.config.login.table)))}
@@ -3362,7 +3396,7 @@ begin
3362
3396
  set backend_plus._mode = normal;
3363
3397
  end if;
3364
3398
  perform set_config('backend_plus._user', p_username, false);
3365
- end;
3399
+ end;
3366
3400
  $body$;
3367
3401
 
3368
3402
  `)
@@ -3376,7 +3410,7 @@ $body$;
3376
3410
  var allTableContent = await fs.readFile('install/local-dump.psql','utf-8');
3377
3411
  var startIndex = allTableContent.indexOf('-- Data for Name: ');
3378
3412
  console.log('startIndex', startIndex);
3379
- var lastUseful = allTableContent.lastIndexOf('\nSELECT pg_catalog.setval')
3413
+ var lastUseful = allTableContent.lastIndexOf('\nSELECT pg_catalog.setval')
3380
3414
  console.log('lastUseful', lastUseful);
3381
3415
  if (lastUseful == -1) lastUseful = allTableContent.lastIndexOf('\n\\.\n');
3382
3416
  console.log('lastUseful', lastUseful);
@@ -3456,6 +3490,7 @@ $body$;
3456
3490
  });
3457
3491
  rows=lines;
3458
3492
  }else{
3493
+ /* PARSEO DEL ARCHIVO .TAB */
3459
3494
  var lines=content.split(/\r?\n/)
3460
3495
  .filter(line => !(/^[-| ]*$/.test(line)) )
3461
3496
  .map(line => splitRawRowIntoRow(line))
@@ -3489,14 +3524,61 @@ $body$;
3489
3524
  throw Error("no se encuentra la columna "+filteredFieldDef[i]+" en "+tableName);
3490
3525
  }
3491
3526
  return value==='' ? (
3492
- def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
3527
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
3493
3528
  ) : db.quoteNullable(value);
3494
3529
  }).join(', ')+")";
3495
3530
  }).join(',\n')+';\n';
3496
3531
  }
3497
3532
  dataText.push(dataString);
3533
+ if(process.env.TRY_TAB_PLUS){
3534
+ var tabPlusEmptySymbol = Symbol('empty');
3535
+ var parsedTabPlus = tabPlus.parseTab(content, {emptyField: tabPlusEmptySymbol});
3536
+ var tabPlusFields = parsedTabPlus.fields;
3537
+ var tabPlusRows = parsedTabPlus.rows;
3538
+ /* a partir de acá: solo construcción de SQL a partir de {fields,rows}, sin volver a tocar el parseo */
3539
+ var filteredFieldDefTabPlus = tabPlusFields.filter(filterField);
3540
+ var dataStringTabPlus;
3541
+ if(tablesWithStrictSequence[tableName]){
3542
+ dataStringTabPlus="COPY "+db.quoteIdent(tableName)+" ("+
3543
+ filteredFieldDefTabPlus.map(db.quoteIdent).join(', ')+
3544
+ ') FROM stdin;\n'+
3545
+ tabPlusRows.map(function(line){
3546
+ return line.filter(function(_,i){ return filterField(tabPlusFields[i]);}).map(function(value,i){
3547
+ var def = tableDef.field[filteredFieldDefTabPlus[i]];
3548
+ return value===tabPlusEmptySymbol ? (
3549
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? '' : '\\N'
3550
+ ): value.replace(/\\/g,'\\\\').replace(/\t/g,'\\t').replace(/\n/g,'\\n').replace(/\r/g,'\\r');
3551
+ }).join('\t')+'\n';
3552
+ }).join('')+'\\.\n';
3553
+ }else{
3554
+ dataStringTabPlus="insert into "+db.quoteIdent(tableName)+" ("+
3555
+ filteredFieldDefTabPlus.map(db.quoteIdent).join(', ')+
3556
+ ') values\n'+
3557
+ tabPlusRows.map(function(line){
3558
+ return "("+line.filter(function(_,i){ return filterField(tabPlusFields[i]);}).map(function(value,i){
3559
+ var def = tableDef.field[filteredFieldDefTabPlus[i]];
3560
+ if(def == null) {
3561
+ throw Error("no se encuentra la columna "+filteredFieldDefTabPlus[i]+" en "+tableName);
3562
+ }
3563
+ return value===tabPlusEmptySymbol ? (
3564
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
3565
+ ) : db.quoteNullable(value);
3566
+ }).join(', ')+")";
3567
+ }).join(',\n')+';\n';
3568
+ }
3569
+ if(dataStringTabPlus !== dataString){
3570
+ var oldSqlPath = path.replace(/\.tab$/,'')+'-old-local.sql';
3571
+ var newSqlPath = path.replace(/\.tab$/,'')+'-new-local.sql';
3572
+ fs.writeFileSync(oldSqlPath, dataString, {encoding:'UTF8'});
3573
+ fs.writeFileSync(newSqlPath, dataStringTabPlus, {encoding:'UTF8'});
3574
+ if (process.env.TRY_TAB_PLUS == 'ON-ERROR-STOP') {
3575
+ throw new Error('TRY_TAB_PLUS: el parseo con tab-plus difiere del parseo anterior para '+tableName+' ('+path+'). Ver '+oldSqlPath+' y '+newSqlPath);
3576
+ } else {
3577
+ console.log('TRY_TAB_PLUS: el parseo con tab-plus difiere del parseo anterior para '+tableName+' ('+path+'). Ver '+oldSqlPath+' y '+newSqlPath);
3578
+ }
3579
+ }
3580
+ }
3498
3581
  }
3499
- // tablesWithStrictSequence[tableName]
3500
3582
  }
3501
3583
  }).catch(function(err){
3502
3584
  if(err.code=='ENOENT'){
@@ -3524,7 +3606,7 @@ $body$;
3524
3606
  ]
3525
3607
  .map(async function(fileNames){
3526
3608
  if (!fileNames) return '';
3527
- var i = 0;
3609
+ var i = 0;
3528
3610
  return (await Promise.all(fileNames.map(async fileName => {
3529
3611
  var content;
3530
3612
  do {
@@ -3576,7 +3658,7 @@ $body$;
3576
3658
  '\n-- functions\n' + functionLines.join('\n')+
3577
3659
  '\n-- lines \n' + lines.join('\n')+
3578
3660
  (complete? ('\n\n-- pre-ADAPTs\n'+texts[1]+'\n\n') : '' )+
3579
- (complete? ('\n\n-- DATA\n'+ dataText.join('\n')) : '' )+
3661
+ (complete? ('\n\n-- DATA\n'+ dataText.join('\n')) : '' )+
3580
3662
  (complete? ('\n\n-- ADAPTs\n'+ texts[2]+'\n\n') : '' )+
3581
3663
  '\n-- conss\n' + consLines.join('\n')+
3582
3664
  '\n-- FKs\n' + fkLines.join('\n')+
@@ -3606,14 +3688,17 @@ AppBackend.prototype.getDbFunctions = async function (){
3606
3688
  AppBackend.prototype.dumpDbSchema = async function dumpDbSchema(opts){
3607
3689
  var be = this;
3608
3690
  var {mainSql,enancePart} = await be.dumpDbSchemaPartial(
3609
- opts.complete?be.tableStructures:likeAr(be.tableStructures).filter((_, name)=>opts.tableNames.includes(name)),
3691
+ opts.complete?be.tableStructures:likeAr(be.tableStructures).filter((_, name)=>opts.tableNames.includes(name)),
3610
3692
  opts
3611
3693
  )
3612
3694
  mainSql=be.config.install.dump.db.extensions.map(function(extension){
3613
3695
  return ({
3614
- gist: "create extension if not exists btree_gist;",
3615
- pg_trgm: "create extension if not exists pg_trgm;",
3616
- pgcrypto: "create extension if not exists pgcrypto;"
3696
+ cube : "create extension if not exists cube ;",
3697
+ earthdistance : "create extension if not exists earthdistance ;",
3698
+ gist : "create extension if not exists btree_gist ;",
3699
+ pg_trgm : "create extension if not exists pg_trgm ;",
3700
+ pgcrypto : "create extension if not exists pgcrypto ;",
3701
+ postgis : "create extension if not exists postgis ;",
3617
3702
  }[extension]||('--unknown exension '+extension))+'\n';
3618
3703
  }).join('')+mainSql;
3619
3704
  await fs.writeFile('local-db-dump.sql', mainSql);
@@ -3686,7 +3771,7 @@ AppBackend.prototype.transformInput = function transformInput(fieldDef, value){
3686
3771
  return value;
3687
3772
  }
3688
3773
 
3689
- /**
3774
+ /**
3690
3775
  * xxxparam {{ action:string, parameters:any, conRegistro:boolean, conPadron:boolean, fileName?:string, csvFileName?:string, csvSeparator?:string, queries:{titulo:string, sql:string, params:string[]}[] }}
3691
3776
  * @param {{title:string, rows:Record<string, any>[]}[]} result
3692
3777
  * @returns {Promise<void>}
@@ -3767,24 +3852,19 @@ AppBackend.prototype.exportacionesGenerico = async function exportacionesGeneric
3767
3852
  }
3768
3853
  if(fileName){
3769
3854
  context.informProgress({message:'armando XLSX'})
3770
- var wb = XLSX.utils.book_new();
3771
- result.map(({title, rows})=>{
3855
+ var sheets = result.map(({title, rows})=>{
3772
3856
  var rowsA = rows.map(row=>Object.values(row));
3773
3857
  if(rows[0]){
3774
- rowsA.unshift(Object.keys(rows[0]));
3858
+ rowsA.unshift({'#line': 'array', values:Object.keys(rows[0]), s: {bold: true}});
3775
3859
  }
3776
- var ws = XLSX.utils.aoa_to_sheet(rowsA);
3777
- XLSX.utils.book_append_sheet(wb, ws, title);
3778
- return ws;
3860
+ return {name:title, rows:rowsA};
3779
3861
  })
3780
3862
  context.informProgress({message:`guardando XLSX: ${fileName}`})
3781
3863
  await bestGlobals.sleep(100);
3782
- await new Promise((resolve,reject)=>XLSX.writeFileAsync(`dist/client/${fileName}`,wb,{},
3783
- (err)=>err?reject(err):resolve()
3784
- ))
3864
+ await xlsxNow.writeXlsxFile(`dist/client/${fileName}`, {sheets, types:xlsxTypes, autoWidthMax:50, freezeRows:1});
3785
3865
  }
3786
3866
  return [
3787
- ...(fileName?[{url:fileName, label:csvFileName?'xlsx de control':fileName}]:[]),
3867
+ ...(fileName?[{url:fileName, label:csvFileName?'xlsx de control':fileName}]:[]),
3788
3868
  ...(csvFileName?[{url:csvFileName, label:fileName?'csv (formato UTF-8)':csvFileName}]:[]),
3789
3869
  ];
3790
3870
  }