utn-cli 2.0.50 → 2.0.51

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.
@@ -171,7 +171,7 @@ export async function updateBackend(opciones = { cerrarAlFinalizar: true }) {
171
171
  }
172
172
  }
173
173
 
174
- export async function addServiceBackend(nombreServicio = null, opciones = { cerrarAlFinalizar: true }, tituloServicio = null) {
174
+ export async function addServiceBackend(nombreServicio = null, opciones = { cerrarAlFinalizar: true }, tituloServicio = null, tableInfo = null) {
175
175
  let Servicio = nombreServicio;
176
176
  if (!Servicio) {
177
177
  Servicio = await hacerPreguntaTrim('¿Cuál es el nombre del servicio?: ');
@@ -206,12 +206,19 @@ export async function addServiceBackend(nombreServicio = null, opciones = { cerr
206
206
  if (fs.existsSync(rutaRutasJs)) {
207
207
  let contenidoRutasJs = fs.readFileSync(rutaRutasJs, 'utf-8');
208
208
  const nuevaRutaRequire = `const rutasDel${Servicio} = require('./${Servicio}.js');`;
209
- contenidoRutasJs = contenidoRutasJs.replace(/\s*function asignarRutasAExpress\(app\) {/, `
210
- ${nuevaRutaRequire}
209
+
210
+ // Agregar require si no existe
211
+ if (!contenidoRutasJs.includes(nuevaRutaRequire)) {
212
+ contenidoRutasJs = contenidoRutasJs.replace(/function asignarRutasAExpress\(app\) \{/, `${nuevaRutaRequire}\n\nfunction asignarRutasAExpress(app) {`);
213
+ }
211
214
 
212
- function asignarRutasAExpress(app) {`);
213
- contenidoRutasJs = contenidoRutasJs.replace(/app\.use\('\/Servicio1', rutasDelServicio1\);/, `app.use('/Servicio1', rutasDelServicio1);
214
- app.use('/${Servicio}', rutasDel${Servicio});`);
215
+ // Agregar app.use al final de la función
216
+ const lineaAppUse = ` app.use('/${Servicio}', rutasDel${Servicio});`;
217
+ if (!contenidoRutasJs.includes(lineaAppUse)) {
218
+ // Buscamos el final de la función asignarRutasAExpress
219
+ contenidoRutasJs = contenidoRutasJs.replace(/(function asignarRutasAExpress\(app\) \{[\s\S]*?)(\n\})/, `$1\n${lineaAppUse}$2`);
220
+ }
221
+
215
222
  fs.writeFileSync(rutaRutasJs, contenidoRutasJs);
216
223
  } else {
217
224
  console.error(`Error: rutas.js not found in ${rutaRutaDir}`);
@@ -219,6 +226,11 @@ function asignarRutasAExpress(app) {`);
219
226
  process.exit(1);
220
227
  }
221
228
 
229
+ // Si tenemos información de la tabla, generamos el CRUD
230
+ if (tableInfo) {
231
+ generarCRUDBackend(newServicioPath, newRutaPath, Servicio, tableInfo);
232
+ }
233
+
222
234
  // Reemplazar el título amigable si se proporciona
223
235
  if (tituloServicio) {
224
236
  reemplazarContenidoEnArchivo(newServicioPath, "const NombreDelServicio = 'Servicio1';", `const NombreDelServicio = '${tituloServicio}';`);
@@ -227,72 +239,157 @@ function asignarRutasAExpress(app) {`);
227
239
  reemplazarContenidoEnArchivo(newServicioPath, 'Servicio1', Servicio);
228
240
  reemplazarContenidoEnArchivo(newRutaPath, 'Servicio1', Servicio);
229
241
 
230
- // Cross-module interaction with Frontend - this needs a different approach in a unified CLI
231
- // For now, I'll comment out the frontend-related logic as it breaks modularity and
232
- // assumes frontend project structure relative to backend, which might not be true in a monorepo.
233
- // This should be handled by a dedicated 'utn frontend add-service' command or similar.
234
- /*
235
- const rutaArchivoJson = path.join(process.cwd(), 'InformacionDelModulo.json');
236
- const contenidoJson = fs.readFileSync(rutaArchivoJson, 'utf8');
237
- const informacion = JSON.parse(contenidoJson);
238
- const nombreDelFrontend = informacion.NOMBRE_DEL_REPOSITORIO_DE_FRONTEND;
239
- const rutaDelFrontEnd = path.join(process.cwd(), '..', nombreDelFrontend, '/src/app/Paginas'); // This path is problematic
240
- const servicioEnMinuscula = Servicio.toLowerCase();
241
- const rutaOrigen = path.join(rutaDelFrontEnd, '/gestion-tabla');
242
- const nombreCarpetaDestino = `/gestion-tabla-${servicioEnMinuscula}`;
243
- const rutaDestino = path.join(rutaDelFrontEnd, nombreCarpetaDestino);
242
+ console.log(`Servicio ${Servicio} agregado exitosamente.`);
243
+ if (opciones.cerrarAlFinalizar) {
244
+ closeReadLine();
245
+ }
246
+ }
247
+
248
+ function generarCRUDBackend(servicioPath, rutaPath, nombreServicio, tableInfo) {
249
+ const { dbName, tableName, columns, primaryKey } = tableInfo;
250
+ const dbNameSinDB = dbName.endsWith('-db') ? dbName.slice(0, -3) : dbName;
251
+
252
+ // 1. Generar funciones en el Servicio
253
+ let contenidoServicio = fs.readFileSync(servicioPath, 'utf-8');
254
+
255
+ const colNames = columns.map(c => `\`${c.name}\``).join(', ');
256
+ const colPlaceholders = columns.map(() => '?').join(', ');
257
+ const colUpdates = columns.filter(c => c.name !== primaryKey).map(c => `\`${c.name}\` = ?`).join(', ');
258
+ const colUpdateParams = columns.filter(c => c.name !== primaryKey).map(c => `Datos.${c.name}`).join(', ');
259
+ const colInsertParams = columns.map(c => `Datos.${c.name}`).join(', ');
260
+
261
+ const crudFunciones = `
262
+ async listar() {
263
+ return await ejecutarConsulta("SELECT ${colNames} FROM \`${dbNameSinDB}\`.\`${tableName}\` COLLATE utf8mb4_spanish_ci");
264
+ }
265
+
266
+ async agregar(Datos) {
267
+ return ejecutarConsulta("INSERT INTO \`${dbNameSinDB}\`.\`${tableName}\` (${colNames}, \`LastUpdate\`, \`LastUser\`) VALUES (${colPlaceholders}, NOW(4), ?)"
268
+ , [${colInsertParams}, Datos.LastUser]);
269
+ }
270
+
271
+ async borrar(Datos) {
272
+ return ejecutarConsulta("DELETE FROM \`${dbNameSinDB}\`.\`${tableName}\` WHERE \`${primaryKey}\` = ?"
273
+ , [Datos.${primaryKey}]);
274
+ }
275
+
276
+ async actualizar(Datos) {
277
+ return ejecutarConsulta("UPDATE \`${dbNameSinDB}\`.\`${tableName}\` SET ${colUpdates}, \`LastUpdate\` = NOW(4), \`LastUser\` = ? WHERE \`${primaryKey}\` = ?"
278
+ , [${colUpdateParams}, Datos.LastUser, Datos.${primaryKey}]);
279
+ }
280
+
281
+ async detalle(${primaryKey}) {
282
+ return await ejecutarConsulta("SELECT ${colNames} FROM \`${dbNameSinDB}\`.\`${tableName}\` WHERE \`${primaryKey}\` = ?"
283
+ , [${primaryKey}]);
284
+ }
285
+
286
+ async reporte() {
287
+ return await Miscelaneas.convertirACSV(await this.listar());
288
+ }
289
+ `;
290
+
291
+ // Insertar antes del último cierre de llave
292
+ contenidoServicio = contenidoServicio.replace(/\n\}\s*\nmodule\.exports/, crudFunciones + '\n}\n\nmodule.exports');
293
+ fs.writeFileSync(servicioPath, contenidoServicio);
294
+
295
+ // 2. Generar rutas
296
+ let contenidoRutas = fs.readFileSync(rutaPath, 'utf-8');
297
+
298
+ const crudRutas = `
299
+ Router.get('/listar', async (solicitud, respuesta, next) => {
244
300
  try {
245
- fs.cpSync(rutaOrigen, rutaDestino, { recursive: true });
301
+ if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
302
+ try {
303
+ return respuesta.json({ body: await ${nombreServicio}.listar(), error: undefined });
304
+ } catch (error) {
305
+ const MensajeDeError = 'No fue posible listar los registros';
306
+ console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
307
+ return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
308
+ }
309
+ }
310
+ return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
246
311
  } catch (error) {
247
- console.error('Hubo un error al copiar la carpeta:', error);
312
+ next(error);
248
313
  }
249
- const archivos = fs.readdirSync(rutaDestino);
250
- archivos.forEach(archivo => {
251
- if (archivo.startsWith('gestion-tabla.component')) {
252
- const nuevoNombre = archivo.replace('gestion-tabla', `gestion-tabla-${servicioEnMinuscula}`);
253
- const rutaArchivoViejo = path.join(rutaDestino, archivo);
254
- const rutaArchivoNuevo = path.join(rutaDestino, nuevoNombre);
255
- fs.renameSync(rutaArchivoViejo, rutaArchivoNuevo);
314
+ });
315
+
316
+ Router.post('/agregar', async (solicitud, respuesta, next) => {
317
+ try {
318
+ if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
319
+ try {
320
+ const Datos = solicitud.body;
321
+ Datos.LastUser = await Miscelaneo.generarLastUser(solicitud);
322
+ return respuesta.json({ body: await ${nombreServicio}.agregar(Datos), error: undefined });
323
+ } catch (error) {
324
+ const MensajeDeError = 'No fue posible agregar el registro';
325
+ console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
326
+ return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
327
+ }
256
328
  }
257
- });
258
- const rutaArchivoTs = path.join(rutaDestino, `/gestion-tabla-${servicioEnMinuscula}.component.ts`);
259
- let contenidoTs = fs.readFileSync(rutaArchivoTs, 'utf8');
260
- contenidoTs = contenidoTs.replace(
261
- /gestion-tabla\.component/g,
262
- `gestion-tabla-${servicioEnMinuscula}.component`
263
- );
264
- const servicioCapitalizado = Servicio.charAt(0).toUpperCase() + Servicio.slice(1);
265
- contenidoTs = contenidoTs.replace(
266
- /export class GestionTablaComponent/,
267
- `export class GestionTabla${servicioCapitalizado}Component`
268
- );
269
- fs.writeFileSync(rutaArchivoTs, contenidoTs, 'utf8');
270
- const rutaAppRoutes = path.join(rutaDelFrontEnd, '../app.routes.ts');
271
- const nombreComponente = `GestionTabla${servicioCapitalizado}Component`;
272
- const rutaComponente = `./Paginas/gestion-tabla-${servicioEnMinuscula}/gestion-tabla-${servicioEnMinuscula}.component`;
273
- const nuevaLineaImport = `import { ${nombreComponente} } from '${rutaComponente}';`;
274
- const nuevaLineaRuta = `,
275
- { path: '${servicioEnMinuscula}', component: ${nombreComponente} }`;
276
- let contenidoRoutes = fs.readFileSync(rutaAppRoutes, 'utf8');
277
- contenidoRoutes = contenidoRoutes.replace(
278
- /\s*export const routes: Routes = \[/,
279
- `
280
- ${nuevaLineaImport}
281
-
282
- export const routes: Routes = [`
283
- );
284
- contenidoRoutes = contenidoRoutes.replace(
285
- /\s*];/,
286
- `${nuevaLineaRuta}
287
- ];`
288
- );
289
- fs.writeFileSync(rutaAppRoutes, contenidoRoutes, 'utf8');
290
- */
329
+ return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
330
+ } catch (error) {
331
+ next(error);
332
+ }
333
+ });
291
334
 
292
- console.log(`Servicio ${Servicio} agregado exitosamente.`);
293
- if (opciones.cerrarAlFinalizar) {
294
- closeReadLine();
335
+ Router.post('/actualizar', async (solicitud, respuesta, next) => {
336
+ try {
337
+ if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
338
+ try {
339
+ const Datos = solicitud.body;
340
+ Datos.LastUser = await Miscelaneo.generarLastUser(solicitud);
341
+ return respuesta.json({ body: await ${nombreServicio}.actualizar(Datos), error: undefined });
342
+ } catch (error) {
343
+ const MensajeDeError = 'No fue posible actualizar el registro';
344
+ console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
345
+ return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
346
+ }
347
+ }
348
+ return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
349
+ } catch (error) {
350
+ next(error);
295
351
  }
352
+ });
353
+
354
+ Router.post('/borrar', async (solicitud, respuesta, next) => {
355
+ try {
356
+ if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
357
+ try {
358
+ return respuesta.json({ body: await ${nombreServicio}.borrar(solicitud.body), error: undefined });
359
+ } catch (error) {
360
+ const MensajeDeError = 'No fue posible borrar el registro';
361
+ console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
362
+ return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
363
+ }
364
+ }
365
+ return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
366
+ } catch (error) {
367
+ next(error);
368
+ }
369
+ });
370
+
371
+ Router.get('/reporte', async (solicitud, respuesta, next) => {
372
+ try {
373
+ if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
374
+ try {
375
+ respuesta.header('Content-Type', 'text/csv; charset=utf-8');
376
+ respuesta.attachment('Datos.csv');
377
+ return respuesta.send(encodeURI(await ${nombreServicio}.reporte()));
378
+ } catch (error) {
379
+ const MensajeDeError = 'No fue posible generar el reporte';
380
+ console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
381
+ return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
382
+ }
383
+ }
384
+ return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
385
+ } catch (error) {
386
+ next(error);
387
+ }
388
+ });
389
+ `;
390
+
391
+ contenidoRutas = contenidoRutas.replace(/module\.exports = Router;/, crudRutas + '\nmodule.exports = Router;');
392
+ fs.writeFileSync(rutaPath, contenidoRutas);
296
393
  }
297
394
 
298
395
  export function showBackendVersion() {
@@ -7,19 +7,36 @@ import { closeReadLine, hacerPreguntaTrim } from '../utils/index.js';
7
7
  export async function createComponent() {
8
8
  const currentDir = process.cwd();
9
9
 
10
- const nombreServicio = await hacerPreguntaTrim('¿Cuál es el nombre del nuevo servicio/componente?: ');
10
+ const nombreServicio = await hacerPreguntaTrim('¿Cuál es el nombre del nuevo servicio/componente? (Nombre técnico, ej: Incapacidades): ');
11
11
  if (!nombreServicio) {
12
12
  console.error('El nombre es requerido.');
13
13
  closeReadLine();
14
14
  return;
15
15
  }
16
16
 
17
- const tituloCard = await hacerPreguntaTrim('Ingrese el título para la tarjeta del frontend: ');
17
+ const tituloCard = await hacerPreguntaTrim('Ingrese el título para la tarjeta del frontend (ej: Gestión de Incapacidades): ');
18
18
  const descripcionCard = await hacerPreguntaTrim('Ingrese la descripción para la tarjeta del frontend: ');
19
19
 
20
20
  const entries = fs.readdirSync(currentDir, { withFileTypes: true });
21
21
  const directories = entries.filter(entry => entry.isDirectory());
22
22
 
23
+ // Buscar información de la tabla en la base de datos
24
+ let tableInfo = null;
25
+ const dbDir = directories.find(d => d.name.toLowerCase().includes('bd') || d.name.toLowerCase().includes('db'));
26
+
27
+ if (dbDir) {
28
+ const sqlPath = path.join(currentDir, dbDir.name, 'docker-scripts/1-crear estructura.sql');
29
+ if (fs.existsSync(sqlPath)) {
30
+ console.log(`Buscando tabla para el servicio "${tituloCard}" en ${sqlPath}...`);
31
+ tableInfo = findTableByComment(sqlPath, tituloCard);
32
+ if (tableInfo) {
33
+ console.log(`Tabla encontrada: ${tableInfo.tableName} en la base de datos ${tableInfo.dbName}`);
34
+ } else {
35
+ console.log('No se encontró una tabla con un comentario que coincida con el título del servicio.');
36
+ }
37
+ }
38
+ }
39
+
23
40
  let foundBackend = false;
24
41
  let foundFrontend = false;
25
42
 
@@ -31,7 +48,7 @@ export async function createComponent() {
31
48
  console.log(`\nAgregando servicio a Backend en: ${dir.name}`);
32
49
  process.chdir(fullPath);
33
50
  try {
34
- await addServiceBackend(nombreServicio, { cerrarAlFinalizar: false }, tituloCard);
51
+ await addServiceBackend(nombreServicio, { cerrarAlFinalizar: false }, tituloCard, tableInfo);
35
52
  foundBackend = true;
36
53
  } catch (error) {
37
54
  console.error(`Error al agregar servicio en ${dir.name}:`, error);
@@ -41,7 +58,7 @@ export async function createComponent() {
41
58
  console.log(`\nAgregando componente a Frontend en: ${dir.name}`);
42
59
  process.chdir(fullPath);
43
60
  try {
44
- await cloneFrontendComponent(nombreServicio, tituloCard, descripcionCard, { cerrarAlFinalizar: false });
61
+ await cloneFrontendComponent(nombreServicio, tituloCard, descripcionCard, { cerrarAlFinalizar: false }, tableInfo);
45
62
  foundFrontend = true;
46
63
  } catch (error) {
47
64
  console.error(`Error al clonar componente en ${dir.name}:`, error);
@@ -58,3 +75,54 @@ export async function createComponent() {
58
75
 
59
76
  closeReadLine();
60
77
  }
78
+
79
+ function findTableByComment(sqlPath, serviceTitle) {
80
+ const content = fs.readFileSync(sqlPath, 'utf-8');
81
+
82
+ // Regex para encontrar tablas y sus comentarios
83
+ // Buscamos CREATE OR REPLACE TABLE `db`.`table` ... COMMENT = '...serviceTitle...'
84
+ const tableRegex = /CREATE OR REPLACE TABLE `(.+?)`\.`(.+?)` \(([\s\S]+?)\) ENGINE = InnoDB.+?COMMENT = '.*?(?:servicio de |almacena los datos del servicio de |almacena los datos del servicio de: )?([\s\S]+?)';/gi;
85
+
86
+ let match;
87
+ while ((match = tableRegex.exec(content)) !== null) {
88
+ const dbName = match[1];
89
+ const tableName = match[2];
90
+ const tableBody = match[3];
91
+ const tableComment = match[4].trim();
92
+
93
+ // Si el comentario coincide con el título del servicio
94
+ if (tableComment.toLowerCase().includes(serviceTitle.toLowerCase()) || serviceTitle.toLowerCase().includes(tableComment.toLowerCase())) {
95
+ const columns = [];
96
+ let primaryKey = '';
97
+
98
+ // Extraer columnas
99
+ const colRegex = /`(.+?)` (.+?)(?: COMMENT '(.+?)')?,/g;
100
+ let colMatch;
101
+ while ((colMatch = colRegex.exec(tableBody)) !== null) {
102
+ const colName = colMatch[1];
103
+ if (colName !== 'LastUpdate' && colName !== 'LastUser') {
104
+ columns.push({
105
+ name: colName,
106
+ type: colMatch[2],
107
+ alias: colMatch[3] || colName
108
+ });
109
+ }
110
+ }
111
+
112
+ // Extraer Primary Key
113
+ const pkMatch = tableBody.match(/PRIMARY KEY \(`(.+?)`\)/);
114
+ if (pkMatch) {
115
+ primaryKey = pkMatch[1];
116
+ }
117
+
118
+ return {
119
+ dbName,
120
+ tableName,
121
+ columns,
122
+ primaryKey,
123
+ serviceTitle
124
+ };
125
+ }
126
+ }
127
+ return null;
128
+ }
package/commands/db.js CHANGED
@@ -43,6 +43,43 @@ export async function initDb() {
43
43
  closeReadLine();
44
44
  }
45
45
 
46
+ export async function addTableDb() {
47
+ const rutaEstructura = path.join(process.cwd(), 'docker-scripts/1-crear estructura.sql');
48
+
49
+ if (!fs.existsSync(rutaEstructura)) {
50
+ console.error(`No se encontró el archivo de estructura en: ${rutaEstructura}. Asegúrese de estar en la carpeta raíz del proyecto de base de datos.`);
51
+ closeReadLine();
52
+ process.exit(1);
53
+ }
54
+
55
+ const nombreTabla = await hacerPregunta('¿Cuál es el nombre de la tabla?: ');
56
+ const nombreServicio = await hacerPregunta('¿Cuál es el nombre del servicio que almacenará los datos?: ');
57
+
58
+ const contenidoEstructura = fs.readFileSync(rutaEstructura, 'utf-8');
59
+
60
+ // Intentar extraer el nombre del repositorio de la base de datos
61
+ const matchDb = contenidoEstructura.match(/CREATE DATABASE `(.+?)`/);
62
+ let nombreDb = 'NOMBRE_DEL_REPOSITORIO_DE_BASE_DE_DATOS';
63
+
64
+ if (matchDb && matchDb[1]) {
65
+ nombreDb = matchDb[1];
66
+ } else {
67
+ console.warn('No se pudo detectar el nombre de la base de datos en el archivo de estructura. Se usará el marcador por defecto.');
68
+ }
69
+
70
+ const machote = `
71
+ CREATE OR REPLACE TABLE \`${nombreDb}\`.\`${nombreTabla}\` (
72
+ \`LastUpdate\` DATETIME(4) NOT NULL DEFAULT CURRENT_TIMESTAMP(4) ON UPDATE CURRENT_TIMESTAMP(4) COMMENT 'Fecha de la última actualización de la fila',
73
+ \`LastUser\` VARCHAR(1000) COLLATE utf8mb4_spanish_ci NOT NULL DEFAULT '-' COMMENT 'Último usuario que modificó la fila',
74
+ PRIMARY KEY (\`\`)
75
+ ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_spanish_ci COMMENT = 'Almacena los datos del servicio de ${nombreServicio}';
76
+ `;
77
+
78
+ fs.appendFileSync(rutaEstructura, machote);
79
+ console.log(`Tabla \`${nombreTabla}\` agregada exitosamente a ${rutaEstructura}`);
80
+ closeReadLine();
81
+ }
82
+
46
83
  export function showDbVersion() {
47
84
  // Assuming the version for 'db' command comes from the main utn-cli package.json
48
85
  mostrarVersion(path.join(__dirname, '../package.json'));
@@ -62,7 +62,7 @@ export async function updateFrontend(opciones = { cerrarAlFinalizar: true }) {
62
62
  }
63
63
  }
64
64
 
65
- export async function cloneFrontendComponent(nombreComponente = null, tituloCard = null, descripcionCard = null, opciones = { cerrarAlFinalizar: true }) {
65
+ export async function cloneFrontendComponent(nombreComponente = null, tituloCard = null, descripcionCard = null, opciones = { cerrarAlFinalizar: true }, tableInfo = null) {
66
66
  let nombre = nombreComponente;
67
67
  if (!nombre) {
68
68
  nombre = await hacerPreguntaTrim('Ingrese el nombre del nuevo componente (se reemplazará XYZ): ');
@@ -119,8 +119,16 @@ export async function cloneFrontendComponent(nombreComponente = null, tituloCard
119
119
  copiarYReemplazar(rutaFuente, rutaDestino);
120
120
  } else if (entrada.isFile()) {
121
121
  let contenido = fs.readFileSync(rutaFuente, 'utf-8');
122
+
123
+ // Reemplazos base
122
124
  contenido = contenido.replace(/GestionTablaXYZComponent/g, nombreClase);
123
125
  contenido = contenido.replace(/XYZ/g, nombreLower);
126
+
127
+ // Si tenemos información de la tabla, personalizamos el componente
128
+ if (tableInfo && entrada.name.endsWith('.ts')) {
129
+ contenido = personalizarComponenteFrontend(contenido, tableInfo, nombreLower);
130
+ }
131
+
124
132
  fs.writeFileSync(rutaDestino, contenido);
125
133
  }
126
134
  });
@@ -190,6 +198,39 @@ export async function cloneFrontendComponent(nombreComponente = null, tituloCard
190
198
  }
191
199
  }
192
200
 
201
+ function personalizarComponenteFrontend(contenido, tableInfo, nombreServicioLower) {
202
+ const { columns, primaryKey } = tableInfo;
203
+
204
+ // 1. Reemplazar columnas
205
+ const nuevasColumnas = columns.map(col => {
206
+ return ` { Llave: '${col.name}', Alias: '${col.alias}', TipoDeFiltro: 'Tabla' }`;
207
+ }).join(',\n');
208
+
209
+ contenido = contenido.replace(/public columnas = \[\s*[\s\S]*?\];/, `public columnas = [\n${nuevasColumnas}\n ];`);
210
+
211
+ // 2. Reemplazar LicenciaId (la PK de la plantilla) por la PK real
212
+ const pkRegex = new RegExp('LicenciaId', 'g');
213
+ contenido = contenido.replace(pkRegex, primaryKey);
214
+
215
+ // 3. Reemplazar endpoints (ej: licencias/listar por servicio/listar)
216
+ const endpointRegex = new RegExp('licencias/', 'g');
217
+ contenido = contenido.replace(endpointRegex, `${nombreServicioLower}/`);
218
+
219
+ return contenido;
220
+ }
221
+
222
+ console.log('Tarjeta agregada exitosamente.');
223
+ } else {
224
+ console.error('No se encontró un </div> de cierre en el HTML del contenedor principal.');
225
+ }
226
+ } else {
227
+ console.error(`No se encontró el archivo HTML en: ${rutaHtml}`);
228
+ }
229
+ if (opciones.cerrarAlFinalizar) {
230
+ closeReadLine();
231
+ }
232
+ }
233
+
193
234
  export function showFrontendVersion() {
194
235
  mostrarVersion(path.join(__dirname, '../package.json'));
195
236
  closeReadLine();
package/index.js CHANGED
@@ -4,7 +4,7 @@ import { Command } from 'commander';
4
4
  import { mostrarVersion, getPackageInfo, closeReadLine } from './utils/index.js';
5
5
  import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
- import { initDb, showDbVersion } from './commands/db.js';
7
+ import { initDb, addTableDb, showDbVersion } from './commands/db.js';
8
8
  import { initBackend, updateBackend, addServiceBackend, showBackendVersion } from './commands/backend.js';
9
9
  import { initFrontend, updateFrontend, cloneFrontendComponent, showFrontendVersion } from './commands/frontend.js';
10
10
  import { runGlobalUpdate } from './commands/update.js';
@@ -35,14 +35,17 @@ program.command('db')
35
35
  .alias('bd')
36
36
  .description('Comandos para la gestión de plantillas de base de datos.')
37
37
  .option('--init', 'Inicializa un nuevo proyecto de base de datos.')
38
+ .option('--add-table', 'Agrega una nueva tabla al archivo de estructura.')
38
39
  .option('--version', 'Muestra la versión del módulo de base de datos.')
39
40
  .action(async (options) => {
40
41
  if (options.init) {
41
42
  await initDb();
43
+ } else if (options.addTable) {
44
+ await addTableDb();
42
45
  } else if (options.version) {
43
46
  showDbVersion();
44
47
  } else {
45
- console.log('Uso: utn db [--init | --version]');
48
+ console.log('Uso: utn db [--init | --add-table | --version]');
46
49
  closeReadLine();
47
50
  }
48
51
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utn-cli",
3
- "version": "2.0.50",
3
+ "version": "2.0.51",
4
4
  "description": "Herramienta CLI unificada para la gestión de plantillas en SIGU.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  [subAccionesSiempreClickeable] = "subAccionesSiempreClickeable"
20
20
  [subAccionesDinamicas] = "subAccionesDinamicas"
21
21
  [desplegable] = "false"
22
- [atributoDesplegable] = ""
22
+ [atributoDesplegable] = "''"
23
23
  [mostrarElBotonDeGuardado] = "true"
24
24
  [mostrarElBotonDeDescargar] = "true"
25
25
  [mostrarElBotonDeFiltro] = "true"