utn-cli 2.1.16 → 2.1.18
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.
- package/commands/backend.js +40 -63
- package/package.json +1 -1
- package/templates/backend/servicios/InformacionDelModulo.js +168 -48
- package/templates/backend/servicios/Nucleo/Miscelaneas.js +84 -2433
- package/templates/backend/servicios/Nucleo/MiscelaneasMixins/Archivos.js +329 -0
- package/templates/backend/servicios/Nucleo/MiscelaneasMixins/Autenticacion.js +388 -0
- package/templates/backend/servicios/Nucleo/MiscelaneasMixins/InicializacionDelModulo.js +254 -0
- package/templates/backend/servicios/Nucleo/MiscelaneasMixins/Modulos.js +261 -0
- package/templates/backend/servicios/Nucleo/MiscelaneasMixins/Notificaciones.js +82 -0
- package/templates/backend/servicios/Nucleo/MiscelaneasMixins/Personas.js +93 -0
- package/templates/backend/servicios/Nucleo/MiscelaneasMixins/Reportes.js +370 -0
- package/templates/backend/servicios/Nucleo/MiscelaneasMixins/TareasProgramadas.js +105 -0
- package/templates/frontend/src/app/Componentes/Nucleo/gestion-actividad/gestion-actividad.component.css +16 -1
- package/templates/frontend/src/app/Componentes/Nucleo/gestion-actividad/gestion-actividad.component.html +1 -1
- package/templates/frontend/src/app/Componentes/Nucleo/manual/manual.component.html +17 -16
- package/templates/frontend/src/app/Componentes/Nucleo/manual/manual.component.ts +11 -2
- package/templates/frontend/src/app/Componentes/Nucleo/reporte-de-incidencias/reporte-de-incidencias.component.css +0 -1
- package/templates/frontend/src/app/Componentes/Nucleo/reporte-de-sugerencias/reporte-de-sugerencias.component.css +0 -1
- package/templates/frontend/src/app/Componentes/Nucleo/tabla/tabla.component.css +4 -0
- package/templates/frontend/src/app/Componentes/Nucleo/tarjeta-modulo/tarjeta-modulo.component.css +1 -1
- package/templates/frontend/src/app/Paginas/Nucleo/contenedor-componentes/contenedor-componentes.component.css +26 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
const { ejecutarConsulta, ejecutarConsultaSIGU } = require('../db.js');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
|
|
5
|
+
async generarFirmaHTML(Identificador, FechaDeLaFirma) {
|
|
6
|
+
const ReporteHTML = require('./ReporteHTML.js');
|
|
7
|
+
return await ReporteHTML.generarFirmaHTML(Identificador, FechaDeLaFirma);
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
GenerarReporteHTMLRegistrosVerticales(ElementosParaLaTabla, ParametrosExcluidos = [], ParametrosExtra = [], MostrarEncabezado = true) {
|
|
11
|
+
const ReporteHTML = require('./ReporteHTML.js');
|
|
12
|
+
return ReporteHTML.GenerarReporteHTMLRegistrosVerticales(ElementosParaLaTabla, ParametrosExcluidos, ParametrosExtra, MostrarEncabezado);
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
GenerarReporteHTMLEncabezado(InformacionDeLaDerecha, titulares, marcaDeAgua = '') {
|
|
16
|
+
const ReporteHTML = require('./ReporteHTML.js');
|
|
17
|
+
return ReporteHTML.GenerarReporteHTMLEncabezado(InformacionDeLaDerecha, titulares, marcaDeAgua);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
GenerarReporteHTMLFecha() {
|
|
21
|
+
const ReporteHTML = require('./ReporteHTML.js');
|
|
22
|
+
return ReporteHTML.GenerarReporteHTMLFecha();
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
GenerarReporteHTMLTablas(ElementosParaLaTabla, ParametrosExcluidos = [], ParametrosExtra = []) {
|
|
26
|
+
const ReporteHTML = require('./ReporteHTML.js');
|
|
27
|
+
return ReporteHTML.GenerarReporteHTMLTablas(ElementosParaLaTabla, ParametrosExcluidos, ParametrosExtra);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
GenerarReporteHTMLPie() {
|
|
31
|
+
const ReporteHTML = require('./ReporteHTML.js');
|
|
32
|
+
return ReporteHTML.GenerarReporteHTMLPie();
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
JSONAHTML(input, title = 'Reporte') {
|
|
36
|
+
let obj = input;
|
|
37
|
+
if (typeof input === 'string') {
|
|
38
|
+
try { obj = JSON.parse(input); }
|
|
39
|
+
catch (e) { /* no es JSON, lo tratamos como texto primitivo */ }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const escapeHtml = (s) =>
|
|
43
|
+
String(s)
|
|
44
|
+
.replace(/&/g, '&')
|
|
45
|
+
.replace(/</g, '<')
|
|
46
|
+
.replace(/>/g, '>')
|
|
47
|
+
.replace(/"/g, '"')
|
|
48
|
+
.replace(/'/g, ''');
|
|
49
|
+
|
|
50
|
+
const isPrimitive = v => v === null || v === undefined || typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean';
|
|
51
|
+
|
|
52
|
+
const seen = new WeakSet();
|
|
53
|
+
|
|
54
|
+
function renderValue(v) {
|
|
55
|
+
if (v === null || v === undefined || v === '') return '—';
|
|
56
|
+
if (isPrimitive(v)) return escapeHtml(String(v));
|
|
57
|
+
if (Array.isArray(v)) return renderArray(v);
|
|
58
|
+
return renderObject(v);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function renderObject(o) {
|
|
62
|
+
if (seen.has(o)) return '<em>(referencia circular)</em>';
|
|
63
|
+
seen.add(o);
|
|
64
|
+
const keys = Object.keys(o);
|
|
65
|
+
if (keys.length === 0) {
|
|
66
|
+
seen.delete(o);
|
|
67
|
+
return '—';
|
|
68
|
+
}
|
|
69
|
+
let rows = '';
|
|
70
|
+
for (const k of keys) {
|
|
71
|
+
rows += `
|
|
72
|
+
<tr>
|
|
73
|
+
<th>${escapeHtml(k)}</th>
|
|
74
|
+
<td>${renderValue(o[k])}</td>
|
|
75
|
+
</tr>
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
seen.delete(o);
|
|
79
|
+
return `<table border='1'><tbody>${rows}</tbody></table>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderArray(arr) {
|
|
83
|
+
if (arr.length === 0) return '—';
|
|
84
|
+
if (arr.every(isPrimitive)) {
|
|
85
|
+
return `<div>${arr.map(x => `<span>${escapeHtml(String(x))}</span>`).join(' ')}</div>`;
|
|
86
|
+
}
|
|
87
|
+
if (arr.every(it => typeof it === 'object' && it !== null && !Array.isArray(it))) {
|
|
88
|
+
const columns = Array.from(new Set(arr.flatMap(item => Object.keys(item))));
|
|
89
|
+
const header = columns.map(c => `<th>${escapeHtml(c)}</th>`).join('');
|
|
90
|
+
const body = arr.map(item => {
|
|
91
|
+
const cells = columns.map(c => `<td>${item.hasOwnProperty(c) ? renderValue(item[c]) : ''}</td>`).join('');
|
|
92
|
+
return `<tr>${cells}</tr>`;
|
|
93
|
+
}).join('');
|
|
94
|
+
return `<table border='1'><thead><tr>${header}</tr></thead><tbody>${body}</tbody></table>`;
|
|
95
|
+
}
|
|
96
|
+
return `<div>${arr.map(it => `<div>${renderValue(it)}</div>`).join('')}</div>`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let content = '';
|
|
100
|
+
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj)) {
|
|
101
|
+
const topKeys = Object.keys(obj);
|
|
102
|
+
for (const k of topKeys) {
|
|
103
|
+
content += `
|
|
104
|
+
<section>
|
|
105
|
+
<h2>${escapeHtml(k)}</h2>
|
|
106
|
+
<div>${renderValue(obj[k])}</div>
|
|
107
|
+
</section>
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
content = `<section><div>${renderValue(obj)}</div></section>`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return `<h1>${escapeHtml(title)}</h1>${content}`;
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
convertirACSV(ArregloDeJSON) {
|
|
118
|
+
if (!ArregloDeJSON || ArregloDeJSON.length === 0) return '';
|
|
119
|
+
const intentarParsearJSON = (valor) => {
|
|
120
|
+
if (typeof valor === 'object' && valor !== null && !Array.isArray(valor)) return valor;
|
|
121
|
+
if (typeof valor === 'string' && valor.trim().startsWith('{')) {
|
|
122
|
+
try {
|
|
123
|
+
const objeto = JSON.parse(valor);
|
|
124
|
+
if (typeof objeto === 'object' && objeto !== null && !Array.isArray(objeto)) return objeto;
|
|
125
|
+
} catch (e) { }
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
};
|
|
129
|
+
const columnasOriginales = Object.keys(ArregloDeJSON[0]);
|
|
130
|
+
const mapaColumnasJSON = {};
|
|
131
|
+
columnasOriginales.forEach(col => {
|
|
132
|
+
const todasLasLlaves = new Set();
|
|
133
|
+
let esJSON = false;
|
|
134
|
+
ArregloDeJSON.forEach(fila => {
|
|
135
|
+
const obj = intentarParsearJSON(fila[col]);
|
|
136
|
+
if (obj) {
|
|
137
|
+
esJSON = true;
|
|
138
|
+
Object.keys(obj).forEach(k => todasLasLlaves.add(k));
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
if (esJSON) mapaColumnasJSON[col] = Array.from(todasLasLlaves);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const encabezadosFinales = [];
|
|
145
|
+
columnasOriginales.forEach(col => {
|
|
146
|
+
if (mapaColumnasJSON[col]) {
|
|
147
|
+
mapaColumnasJSON[col].forEach(llave => encabezadosFinales.push(`${col}_${llave}`));
|
|
148
|
+
} else {
|
|
149
|
+
encabezadosFinales.push(col);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const filasCSV = ArregloDeJSON.map(fila => {
|
|
154
|
+
const valoresFila = [];
|
|
155
|
+
columnasOriginales.forEach(col => {
|
|
156
|
+
if (mapaColumnasJSON[col]) {
|
|
157
|
+
const obj = intentarParsearJSON(fila[col]) || {};
|
|
158
|
+
mapaColumnasJSON[col].forEach(llave => {
|
|
159
|
+
let valor = obj[llave];
|
|
160
|
+
if (valor === undefined || valor === null || valor === 'null') valor = '';
|
|
161
|
+
valoresFila.push(JSON.stringify(String(valor)).replace(/,/g, ' '));
|
|
162
|
+
});
|
|
163
|
+
} else {
|
|
164
|
+
let valor = fila[col];
|
|
165
|
+
if (valor === null || valor === 'null') valor = '';
|
|
166
|
+
valoresFila.push(JSON.stringify(String(valor)).replace(/,/g, ' '));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
return valoresFila.join(',');
|
|
170
|
+
});
|
|
171
|
+
return [encabezadosFinales.join(','), ...filasCSV].join('\n');
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
convertirACSVConDetalle(ArregloDeJSON, ColumnaConDetalle) {
|
|
175
|
+
if (!ArregloDeJSON || ArregloDeJSON.length === 0) return '';
|
|
176
|
+
const ejemploConDetalle = ArregloDeJSON.find(e => Array.isArray(e[ColumnaConDetalle]) && e[ColumnaConDetalle].length > 0);
|
|
177
|
+
const camposDetalle = ejemploConDetalle ? Object.keys(ejemploConDetalle[ColumnaConDetalle][0]) : [];
|
|
178
|
+
const columnasBase = Object.keys(ArregloDeJSON[0]).filter(key => key !== ColumnaConDetalle);
|
|
179
|
+
const encabezados = [...columnasBase, ...camposDetalle];
|
|
180
|
+
const filas = ArregloDeJSON.flatMap(fila => {
|
|
181
|
+
const detalles = Array.isArray(fila[ColumnaConDetalle]) ? fila[ColumnaConDetalle] : [];
|
|
182
|
+
if (detalles.length === 0) {
|
|
183
|
+
return [
|
|
184
|
+
encabezados.map(col => {
|
|
185
|
+
const valor = fila[col];
|
|
186
|
+
return formatearValor(valor);
|
|
187
|
+
}).join(',')
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
return detalles.map(detalle => {
|
|
191
|
+
return encabezados.map(col => {
|
|
192
|
+
if (camposDetalle.includes(col)) {
|
|
193
|
+
return formatearValor(detalle[col]);
|
|
194
|
+
} else {
|
|
195
|
+
return formatearValor(fila[col]);
|
|
196
|
+
}
|
|
197
|
+
}).join(',');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
return [encabezados.join(','), ...filas].join('\n');
|
|
201
|
+
function formatearValor(valor) {
|
|
202
|
+
if (valor === null || valor === 'null' || typeof valor === 'undefined') {
|
|
203
|
+
return '""';
|
|
204
|
+
}
|
|
205
|
+
return `"${String(valor).replace(/"/g, '""')}"`;
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
jsonATabla(Datos) {
|
|
210
|
+
if (!Array.isArray(Datos) || Datos.length === 0) {
|
|
211
|
+
return 'El arreglo está vacío o no es válido.';
|
|
212
|
+
}
|
|
213
|
+
const columnas = Object.keys(Datos[0]);
|
|
214
|
+
const anchos = columnas.map(columna => {
|
|
215
|
+
return Math.max(
|
|
216
|
+
columna.length,
|
|
217
|
+
...Datos.map(row => (row[columna] ? row[columna].toString().length : 0))
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
const formatearFila = (fila) => '| ' + fila.map((valor, index) => (valor || '').toString().padEnd(anchos[index])).join(' | ') + ' |';
|
|
221
|
+
const separador = '+-' + anchos.map(ancho => '-'.repeat(ancho)).join('-+-') + '-+';
|
|
222
|
+
const encabezado = formatearFila(columnas);
|
|
223
|
+
const filas = Datos.map(row => formatearFila(columnas.map(col => row[col] || '')));
|
|
224
|
+
return [separador, encabezado, separador, ...filas, separador].join('\n');
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
async DatosParaReporteCSV() {
|
|
228
|
+
return this.convertirACSV(await this.DatosParaGraficoDeBarras());
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
async DatosParaGraficoDeBarras() {
|
|
232
|
+
return await ejecutarConsultaSIGU("SELECT MONTHNAME(`FechaNacimiento`) AS `EjeHorizontal`, `Sexo` AS `Etiqueta`, COUNT(*) AS `Total` FROM `SIGU`.`SIGU_Personas` WHERE MONTH(`FechaNacimiento`) > 0 AND `Sexo` <> '' GROUP BY MONTHNAME(`FechaNacimiento`), `Sexo` ORDER BY MONTH(`FechaNacimiento`)");
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
async DatosParaGraficoDePie() {
|
|
236
|
+
return await ejecutarConsultaSIGU("SELECT `Tipo` AS `Etiqueta`, COUNT(*) AS `Total` FROM `SIGU`.`SIGU_ModulosV2` GROUP BY `Tipo`");
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
async generarObjetoInfoDeUnReportePDF() {
|
|
240
|
+
const UUID = await ejecutarConsulta("SELECT UUID() AS `Dato`");
|
|
241
|
+
const PalabrasClave = {
|
|
242
|
+
"Módulo": this.NombreCanonicoDelModulo,
|
|
243
|
+
"UUID": UUID[0]['Dato']
|
|
244
|
+
};
|
|
245
|
+
return {
|
|
246
|
+
Title: 'Nombre del reporte, como por ejemplo: Boleta de Vacaciones',
|
|
247
|
+
Author: 'Universidad Técnica Nacional',
|
|
248
|
+
Subject: 'Reporte PDF',
|
|
249
|
+
Keywords: JSON.stringify(PalabrasClave),
|
|
250
|
+
Creator: 'SIGU',
|
|
251
|
+
Producer: 'SIGU'
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
agregarMarcaDeAgua(doc, texto = 'Borrador', opciones = {}) {
|
|
256
|
+
const {
|
|
257
|
+
color = '#CCCCCC',
|
|
258
|
+
opacity = 0.3,
|
|
259
|
+
} = opciones;
|
|
260
|
+
|
|
261
|
+
const { width, height } = doc.page;
|
|
262
|
+
|
|
263
|
+
doc.save();
|
|
264
|
+
doc
|
|
265
|
+
.font('Times-Roman', 220)
|
|
266
|
+
.fillColor(color)
|
|
267
|
+
.opacity(opacity)
|
|
268
|
+
.rotate(-55, { origin: [width / 2, height / 2] })
|
|
269
|
+
.text(
|
|
270
|
+
texto,
|
|
271
|
+
-width * 0.2,
|
|
272
|
+
height * 0.4,
|
|
273
|
+
{
|
|
274
|
+
align: 'center',
|
|
275
|
+
valign: 'center',
|
|
276
|
+
width: width * 1.5,
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
doc.restore();
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
agregarTablaElegante(doc, datos, opciones = {}) {
|
|
283
|
+
const { x = 50, y = 50, columnWidth = 100, rowHeight = 20, headerHeight = 25, fontSize = 10 } = opciones;
|
|
284
|
+
|
|
285
|
+
doc.fontSize(fontSize);
|
|
286
|
+
|
|
287
|
+
const encabezados = Object.keys(datos[0]);
|
|
288
|
+
|
|
289
|
+
encabezados.forEach((encabezado, i) => {
|
|
290
|
+
const posX = x + i * columnWidth;
|
|
291
|
+
doc
|
|
292
|
+
.rect(posX, y, columnWidth, headerHeight)
|
|
293
|
+
.fillAndStroke('#d3d3d3', '#000')
|
|
294
|
+
.fillColor('#000')
|
|
295
|
+
.text(encabezado, posX + 5, y + 5, { width: columnWidth - 10, align: 'left' });
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
datos.forEach((fila, filaIndex) => {
|
|
299
|
+
const filaY = y + headerHeight + filaIndex * rowHeight;
|
|
300
|
+
encabezados.forEach((columna, colIndex) => {
|
|
301
|
+
const posX = x + colIndex * columnWidth;
|
|
302
|
+
const texto = fila[columna] !== undefined ? fila[columna].toString() : '';
|
|
303
|
+
doc
|
|
304
|
+
.rect(posX, filaY, columnWidth, rowHeight)
|
|
305
|
+
.stroke()
|
|
306
|
+
.fillColor('#000')
|
|
307
|
+
.text(texto, posX + 5, filaY + 5, { width: columnWidth - 10, align: 'left' });
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return doc;
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
async reportePDFDeEjemplo(Respuesta) {
|
|
315
|
+
Respuesta.setHeader('Content-Type', 'application/pdf');
|
|
316
|
+
Respuesta.setHeader('Content-Disposition', 'inline; filename="reporte.pdf"');
|
|
317
|
+
const PDFDocument = require('pdfkit');
|
|
318
|
+
|
|
319
|
+
const opciones = {
|
|
320
|
+
font: 'Courier',
|
|
321
|
+
size: 'LETTER',
|
|
322
|
+
info: await this.generarObjetoInfoDeUnReportePDF()
|
|
323
|
+
};
|
|
324
|
+
var doc = new PDFDocument(opciones);
|
|
325
|
+
doc.pipe(Respuesta);
|
|
326
|
+
|
|
327
|
+
this.agregarMarcaDeAgua(doc, 'Borrador', {
|
|
328
|
+
fontSize: 80,
|
|
329
|
+
opacity: 0.2,
|
|
330
|
+
color: '#FF0000',
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
doc.fontSize(25).text('Here is some vector graphics...', 100, 80);
|
|
334
|
+
|
|
335
|
+
doc.save()
|
|
336
|
+
.moveTo(100, 150)
|
|
337
|
+
.lineTo(100, 250)
|
|
338
|
+
.lineTo(200, 250)
|
|
339
|
+
.fill('#FF3300');
|
|
340
|
+
|
|
341
|
+
doc.circle(280, 200, 50).fill('#6600FF');
|
|
342
|
+
|
|
343
|
+
doc.scale(0.6)
|
|
344
|
+
.translate(470, 130)
|
|
345
|
+
.path('M 250,75 L 323,301 131,161 369,161 177,301 z')
|
|
346
|
+
.fill('red', 'even-odd')
|
|
347
|
+
.restore();
|
|
348
|
+
|
|
349
|
+
doc.text('And here is some wrapped text...', 100, 300)
|
|
350
|
+
.font('Times-Roman', 13)
|
|
351
|
+
.moveDown()
|
|
352
|
+
.text("lorem", {
|
|
353
|
+
width: 412,
|
|
354
|
+
align: 'justify',
|
|
355
|
+
indent: 30,
|
|
356
|
+
columns: 2,
|
|
357
|
+
height: 300,
|
|
358
|
+
ellipsis: true
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const datos = [
|
|
362
|
+
{ Nombre: 'Juan', Edad: 28, Ciudad: 'San José', LugarDeTrabajo: 'UTN' },
|
|
363
|
+
{ Nombre: 'Ana', Edad: 34 },
|
|
364
|
+
{ Nombre: 'Luis', Edad: 25, Ciudad: 'Alajuela' },
|
|
365
|
+
];
|
|
366
|
+
this.agregarTablaElegante(doc, datos, { x: 0, y: 400, columnWidth: 150, fontSize: 12 });
|
|
367
|
+
doc.end();
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const { ejecutarConsultaSIGU } = require('../db.js');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
|
|
5
|
+
async crearTareaProgramada(Tarea) {
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
return await ejecutarConsultaSIGU("INSERT INTO `SIGU`.`SIGU_ModulosTareasProgramadas` VALUES\
|
|
8
|
+
(?, ?, 'Ejecutada', NOW(4), ?) ON DUPLICATE KEY UPDATE `NombreDelEquipo` = ?"
|
|
9
|
+
, [this.NombreDelRepositorioDelBackend, Tarea.name, os.hostname(), os.hostname()]);
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async pseudoEjecutarTareaProgramada(Tarea) {
|
|
13
|
+
const os = require('node:os');
|
|
14
|
+
const Resultado = await ejecutarConsultaSIGU("UPDATE `SIGU`.`SIGU_ModulosTareasProgramadas` SET `Estado` = 'Procesando'\
|
|
15
|
+
, `NombreDelEquipo` = ?, `FechaYHoraDeLaUltimaEjecucion` = NOW(4)\
|
|
16
|
+
WHERE `Repositorio` = ? AND `TareaProgramada` = ? AND `Estado` IN ('Ejecutada', 'Cancelada', 'Fallida')"
|
|
17
|
+
, [os.hostname(), this.NombreDelRepositorioDelBackend, Tarea]);
|
|
18
|
+
return Resultado['affectedRows'];
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async finalizarTareaProgramada(Tarea) {
|
|
22
|
+
const os = require('node:os');
|
|
23
|
+
const Resultado = await ejecutarConsultaSIGU("UPDATE `SIGU`.`SIGU_ModulosTareasProgramadas` SET `Estado` = 'Ejecutada'\
|
|
24
|
+
, `NombreDelEquipo` = ?, `FechaYHoraDeLaUltimaEjecucion` = NOW(4)\
|
|
25
|
+
WHERE `Repositorio` = ? AND `TareaProgramada` = ? AND `Estado` IN ('Procesando')"
|
|
26
|
+
, [os.hostname(), this.NombreDelRepositorioDelBackend, Tarea]);
|
|
27
|
+
return Resultado['affectedRows'];
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
async ejecucionCadaXMinutos(callback, intervaloMinutos) {
|
|
31
|
+
while (true) {
|
|
32
|
+
try {
|
|
33
|
+
await this.crearTareaProgramada(callback);
|
|
34
|
+
break;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error al ejecutar crearTareaProgramada:', error);
|
|
37
|
+
console.log('Reintentando en 5 segundos...');
|
|
38
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const intervaloMilisegundos = intervaloMinutos * 60 * 1000;
|
|
42
|
+
while (true) {
|
|
43
|
+
const inicio = Date.now();
|
|
44
|
+
try {
|
|
45
|
+
await callback();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(`Error en la función "${callback.name}":`, error?.message || error);
|
|
48
|
+
}
|
|
49
|
+
const duracion = Date.now() - inicio;
|
|
50
|
+
const tiempoRestante = intervaloMilisegundos - duracion;
|
|
51
|
+
if (tiempoRestante > 0) {
|
|
52
|
+
await new Promise(resolve => setTimeout(resolve, tiempoRestante));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
async ejecutarEnHoraEspecifica(hora, minuto, segundo, callback) {
|
|
58
|
+
while (true) {
|
|
59
|
+
try {
|
|
60
|
+
await this.crearTareaProgramada(callback);
|
|
61
|
+
break;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Error al ejecutar crearTareaProgramada:', error);
|
|
64
|
+
console.log('Reintentando en 5 segundos...');
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function programarEjecucion() {
|
|
69
|
+
const ahora = new Date();
|
|
70
|
+
const proximaEjecucion = new Date(ahora);
|
|
71
|
+
proximaEjecucion.setHours(hora, minuto, segundo, 0);
|
|
72
|
+
let tiempoEspera = proximaEjecucion - ahora;
|
|
73
|
+
if (tiempoEspera < 0) {
|
|
74
|
+
proximaEjecucion.setDate(proximaEjecucion.getDate() + 1);
|
|
75
|
+
tiempoEspera = proximaEjecucion - ahora;
|
|
76
|
+
}
|
|
77
|
+
console.log(`Se ha programado a '${callback.name}' para ejecución a las ${proximaEjecucion.toLocaleTimeString()}`);
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
callback();
|
|
80
|
+
programarEjecucion();
|
|
81
|
+
}, tiempoEspera);
|
|
82
|
+
}
|
|
83
|
+
programarEjecucion();
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async ejecucionDiferida(callback) {
|
|
87
|
+
const stackLines = new Error().stack.split('\n');
|
|
88
|
+
const lineaCaller = stackLines[2] ?? '';
|
|
89
|
+
const match = lineaCaller.match(/\((.+?):\d+:\d+\)/) ?? lineaCaller.match(/at (.+?):\d+:\d+/);
|
|
90
|
+
const archivo = match ? require('path').basename(match[1]) : null;
|
|
91
|
+
while (true) {
|
|
92
|
+
try {
|
|
93
|
+
this._archivoFuenteActual = archivo;
|
|
94
|
+
await callback();
|
|
95
|
+
break;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(`Error en la función "${callback.name}": `, error.message);
|
|
98
|
+
console.log('Reintentando en 5 segundos...');
|
|
99
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
this._archivoFuenteActual = null;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
};
|
|
@@ -120,7 +120,8 @@
|
|
|
120
120
|
flex: 1;
|
|
121
121
|
display: flex;
|
|
122
122
|
flex-direction: column;
|
|
123
|
-
min-width: 0;
|
|
123
|
+
min-width: 0;
|
|
124
|
+
overflow: hidden;
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
.item-titulo {
|
|
@@ -162,6 +163,20 @@
|
|
|
162
163
|
white-space: nowrap;
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
.detalle-browser {
|
|
167
|
+
min-width: 0;
|
|
168
|
+
max-width: 100%;
|
|
169
|
+
overflow: hidden;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.texto-browser {
|
|
173
|
+
overflow: hidden;
|
|
174
|
+
text-overflow: ellipsis;
|
|
175
|
+
white-space: nowrap;
|
|
176
|
+
min-width: 0;
|
|
177
|
+
flex: 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
165
180
|
.item-detalles mat-icon {
|
|
166
181
|
font-size: 14px;
|
|
167
182
|
width: 14px;
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<mat-icon>{{ item.Dispositivo === 'Móvil' ? 'smartphone' : (item.Dispositivo === 'Tablet' ? 'tablet' : 'laptop') }}</mat-icon>
|
|
41
41
|
{{ item.Dispositivo }}
|
|
42
42
|
</span>
|
|
43
|
-
<span class="detalle-browser"><mat-icon>public</mat-icon>
|
|
43
|
+
<span class="detalle-browser"><mat-icon>public</mat-icon><span class="texto-browser">{{ item.Navegador }}</span></span>
|
|
44
44
|
<span class="detalle-ip"><mat-icon>lan</mat-icon> {{ item.IP }}</span>
|
|
45
45
|
<span class="detalle-pais"><mat-icon>location_on</mat-icon> {{ item.Pais }}</span>
|
|
46
46
|
</div>
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
<nav>
|
|
7
7
|
<ul class="toc-lista">
|
|
8
8
|
@for (entrada of toc; track entrada.id) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
<li [class]="'toc-nivel-' + entrada.nivel">
|
|
10
|
+
<button class="toc-enlace" (click)="irA(entrada.id)" [title]="entrada.texto">
|
|
11
|
+
{{ entrada.texto }}
|
|
12
|
+
</button>
|
|
13
|
+
</li>
|
|
14
14
|
}
|
|
15
15
|
</ul>
|
|
16
16
|
</nav>
|
|
@@ -20,24 +20,25 @@
|
|
|
20
20
|
<main class="manual-main" role="main" aria-label="Manual de usuario">
|
|
21
21
|
|
|
22
22
|
@if (cargando) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
<div class="manual-estado" role="status" aria-live="polite" aria-busy="true">
|
|
24
|
+
<p>Cargando manual…</p>
|
|
25
|
+
</div>
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
@if (error) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
<div class="manual-estado manual-error" role="alert">
|
|
30
|
+
<p>
|
|
31
|
+
No se pudo cargar el manual. Verifique que el archivo
|
|
32
|
+
<code>Manual.md</code> esté en la carpeta <code>public/</code>.
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
@if (!cargando && !error) {
|
|
38
|
-
|
|
38
|
+
<article #contenidoManual class="manual-articulo" [innerHTML]="contenido" (click)="manejarClicEnContenido($event)">
|
|
39
|
+
</article>
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
</main>
|
|
42
43
|
|
|
43
|
-
</div>
|
|
44
|
+
</div>
|
|
@@ -44,7 +44,7 @@ export class ManualComponent implements OnInit, OnDestroy {
|
|
|
44
44
|
|
|
45
45
|
doc.querySelectorAll('h1, h2, h3').forEach((heading) => {
|
|
46
46
|
const texto = heading.textContent ?? '';
|
|
47
|
-
const id =
|
|
47
|
+
const id = texto
|
|
48
48
|
.toLowerCase()
|
|
49
49
|
.normalize('NFD')
|
|
50
50
|
.replace(DIACRITICOS, '')
|
|
@@ -72,6 +72,15 @@ export class ManualComponent implements OnInit, OnDestroy {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
irA(id: string): void {
|
|
75
|
-
|
|
75
|
+
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
manejarClicEnContenido(event: MouseEvent): void {
|
|
79
|
+
const anchor = (event.target as Element).closest('a');
|
|
80
|
+
if (!anchor) return;
|
|
81
|
+
const href = anchor.getAttribute('href') ?? '';
|
|
82
|
+
if (!href.startsWith('#')) return;
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
this.irA(href.slice(1));
|
|
76
85
|
}
|
|
77
86
|
}
|
|
@@ -237,6 +237,8 @@ tr.example-element-row:hover {
|
|
|
237
237
|
display: block !important;
|
|
238
238
|
background: transparent !important;
|
|
239
239
|
border: none !important;
|
|
240
|
+
width: 100% !important;
|
|
241
|
+
min-width: 0 !important;
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
/* Ocultar el encabezado por completo */
|
|
@@ -254,6 +256,8 @@ tr.example-element-row:hover {
|
|
|
254
256
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important;
|
|
255
257
|
height: auto !important;
|
|
256
258
|
border: 1px solid #eee !important;
|
|
259
|
+
width: 100% !important;
|
|
260
|
+
box-sizing: border-box !important;
|
|
257
261
|
}
|
|
258
262
|
|
|
259
263
|
/* 3. Convertir cada CELDA en una línea con espacio entre nombre y valor */
|
|
@@ -199,12 +199,38 @@
|
|
|
199
199
|
width: 100%;
|
|
200
200
|
min-width: 0;
|
|
201
201
|
}
|
|
202
|
+
|
|
203
|
+
.pie {
|
|
204
|
+
flex-wrap: wrap;
|
|
205
|
+
font-size: 13px;
|
|
206
|
+
gap: 4px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.pie-col.izquierda {
|
|
210
|
+
font-size: 11px;
|
|
211
|
+
overflow: hidden;
|
|
212
|
+
text-overflow: ellipsis;
|
|
213
|
+
white-space: nowrap;
|
|
214
|
+
}
|
|
202
215
|
}
|
|
203
216
|
|
|
204
217
|
@media (max-width: 480px) {
|
|
205
218
|
.encabezado {
|
|
206
219
|
font-size: 18px;
|
|
207
220
|
}
|
|
221
|
+
|
|
222
|
+
.pie-col.izquierda {
|
|
223
|
+
display: none;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.pie-col.centro {
|
|
227
|
+
flex: 1;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.pie-col.derecha {
|
|
232
|
+
flex: 0 1 auto;
|
|
233
|
+
}
|
|
208
234
|
}
|
|
209
235
|
|
|
210
236
|
/* Pie */
|