Feat(ADN): Implementado y ejecutado archivar:md para mover Markdowns obsoletos a histórico DB-First
This commit is contained in:
@@ -42,15 +42,45 @@ module.exports = (pool) => {
|
||||
ORDER BY e.inicio DESC
|
||||
`, [bitacora.id]);
|
||||
|
||||
// Obtener gestión asociada a esta bitácora (fija) + activos globales (rollover)
|
||||
const { rows: gestion } = await pool.query(`
|
||||
SELECT g.*, n.nombre as nodo_nombre
|
||||
FROM bitacoras.gestion g
|
||||
LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id
|
||||
WHERE g.bitacora_id = $1 OR g.activo = true
|
||||
ORDER BY g.tipo, g.orden
|
||||
`, [bitacora.id]);
|
||||
|
||||
// Obtener resúmenes por nodo
|
||||
const { rows: resúmenes } = await pool.query(`
|
||||
SELECT r.*, n.nombre as nodo_nombre
|
||||
FROM bitacoras.resumen_nodos r
|
||||
JOIN bitacoras.nodos n ON r.nodo_id = n.id
|
||||
WHERE r.bitacora_id = $1
|
||||
`, [bitacora.id]);
|
||||
|
||||
// Agrupar por nodo
|
||||
const porNodo = {};
|
||||
entradas.forEach(e => {
|
||||
const nodo = e.nodo_nombre || 'sin-nodo';
|
||||
if (!porNodo[nodo]) porNodo[nodo] = { nodo: nodo, ip: e.nodo_ip, entradas: [] };
|
||||
if (!porNodo[nodo]) porNodo[nodo] = { nodo: nodo, ip: e.nodo_ip, entradas: [], resumen: '' };
|
||||
porNodo[nodo].entradas.push(e);
|
||||
});
|
||||
|
||||
res.json({ ...bitacora, nodos: Object.values(porNodo) });
|
||||
// Adjuntar resúmenes a la agrupación por nodo
|
||||
resúmenes.forEach(r => {
|
||||
if (porNodo[r.nodo_nombre]) {
|
||||
porNodo[r.nodo_nombre].resumen = r.resumen;
|
||||
} else {
|
||||
porNodo[r.nodo_nombre] = { nodo: r.nodo_nombre, entradas: [], resumen: r.resumen };
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
...bitacora,
|
||||
nodos: Object.values(porNodo),
|
||||
gestion: gestion
|
||||
});
|
||||
});
|
||||
|
||||
// GET /api/bitacoras/:fecha/export — Exportar a Markdown
|
||||
@@ -61,17 +91,41 @@ module.exports = (pool) => {
|
||||
|
||||
const bitacora = bResult.rows[0];
|
||||
const { rows: entradas } = await pool.query(`
|
||||
SELECT e.*, n.nombre as nodo_nombre
|
||||
FROM bitacoras.entradas e
|
||||
LEFT JOIN bitacoras.nodos n ON e.nodo_id = n.id
|
||||
WHERE e.bitacora_id = $1
|
||||
ORDER BY n.nombre, e.inicio DESC
|
||||
`, [bitacora.id]);
|
||||
SELECT e.*, n.nombre as nodo_nombre FROM bitacoras.entradas e
|
||||
LEFT JOIN bitacoras.nodos n ON e.nodo_id = n.id
|
||||
WHERE e.bitacora_id = $1 ORDER BY n.nombre, e.inicio ASC
|
||||
`, [bitacora.id]);
|
||||
|
||||
const { rows: gestion } = await pool.query(`
|
||||
SELECT g.*, n.nombre as nodo_nombre FROM bitacoras.gestion g
|
||||
LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id
|
||||
WHERE g.bitacora_id = $1 OR g.activo = true ORDER BY g.tipo, g.orden
|
||||
`, [bitacora.id]);
|
||||
|
||||
const { rows: resumenes } = await pool.query(`
|
||||
SELECT r.*, n.nombre as nodo_nombre FROM bitacoras.resumen_nodos r
|
||||
JOIN bitacoras.nodos n ON r.nodo_id = n.id WHERE r.bitacora_id = $1
|
||||
`, [bitacora.id]);
|
||||
|
||||
// Generar Markdown compatible con ADN
|
||||
const [y, m, d] = fecha.split('-');
|
||||
let md = `# Bitácora de Operaciones - ${d}/${m}/${y}\n\n`;
|
||||
md += `## 📂 Actividades Detalladas\n\n`;
|
||||
let md = `# Bitácora ${fecha}\n\n---\n\n## 📋 Control de Gestión\n\n### Pendientes\n\n| ID | Nodo | Detalle |\n| :--- | :--- | :--- |\n`;
|
||||
|
||||
gestion.filter(g => g.tipo === 'pendiente').forEach(g => {
|
||||
md += `| ${g.id} | ${g.nodo_nombre || '—'} | ${g.detalle} |\n`;
|
||||
});
|
||||
|
||||
md += `\n### En Proceso\n\n| ID | Nodo | Detalle |\n| :--- | :--- | :--- |\n`;
|
||||
gestion.filter(g => g.tipo === 'proceso').forEach(g => {
|
||||
md += `| ${g.id} | ${g.nodo_nombre || '—'} | ${g.detalle} |\n`;
|
||||
});
|
||||
|
||||
md += `\n### Resumen de Actividades\n\n| Nodo | Resumen Integral |\n| :--- | :--- |\n`;
|
||||
resumenes.forEach(r => {
|
||||
md += `| ${r.nodo_nombre} | ${r.resumen} |\n`;
|
||||
});
|
||||
|
||||
md += `\n---\n\n## 📝 Actividades Detalladas\n\n`;
|
||||
|
||||
const porNodo = {};
|
||||
entradas.forEach(e => {
|
||||
@@ -82,18 +136,18 @@ module.exports = (pool) => {
|
||||
|
||||
Object.entries(porNodo).forEach(([nodo, entries]) => {
|
||||
md += `### ${nodo}\n\n`;
|
||||
md += `| I | F | Descripción | E |\n`;
|
||||
md += `| :--- | :--- | :--- | :--- |\n`;
|
||||
entries.forEach(e => {
|
||||
// Agrupar por tema si es necesario, o lista simple
|
||||
const inicio = e.inicio ? e.inicio.substring(0, 5) : '-';
|
||||
const fin = e.fin ? e.fin.substring(0, 5) : '-';
|
||||
const ia = e.es_ia ? '(IA) ' : '';
|
||||
const estado = `[${e.modo}] ${e.estado}`;
|
||||
md += `| ${inicio} | ${fin} | ${ia}${e.descripcion} | ${estado} |\n`;
|
||||
md += `| ${inicio} | ${fin} | ${e.descripcion} | ${e.modo} | ${e.estado} |\n`;
|
||||
});
|
||||
md += '\n';
|
||||
});
|
||||
|
||||
md += `\n---\n<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->\n`;
|
||||
|
||||
res.set('Content-Type', 'text/markdown');
|
||||
res.send(md);
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ module.exports = (pool) => {
|
||||
UPDATE bitacoras.entradas SET inicio=$1, fin=$2, descripcion=$3, estado=$4, modo=$5, es_ia=$6
|
||||
WHERE id=$7 RETURNING *
|
||||
`, [inicio, fin, descripcion, estado, modo, es_ia, id]);
|
||||
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'Entrada no encontrada' });
|
||||
res.json(rows[0]);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
module.exports = (pool) => {
|
||||
const router = require('express').Router();
|
||||
|
||||
// GET /api/gestion — Listar activos (para rollover) o por bitácora
|
||||
router.get('/', async (req, res) => {
|
||||
const { bitacora_id, activos } = req.query;
|
||||
let query = 'SELECT g.*, n.nombre as nodo_nombre FROM bitacoras.gestion g LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id';
|
||||
const params = [];
|
||||
|
||||
if (activos === 'true') {
|
||||
query += ' WHERE g.activo = true';
|
||||
} else if (bitacora_id) {
|
||||
query += ' WHERE g.bitacora_id = $1';
|
||||
params.push(bitacora_id);
|
||||
}
|
||||
|
||||
query += ' ORDER BY g.tipo, g.orden, g.created_at DESC';
|
||||
const { rows } = await pool.query(query, params);
|
||||
res.json(rows);
|
||||
});
|
||||
|
||||
// POST /api/gestion — Crear nuevo pendiente/proceso
|
||||
router.post('/', async (req, res) => {
|
||||
const { tipo, nodo_id, detalle, estado, bitacora_id, orden } = req.body;
|
||||
const { rows } = await pool.query(
|
||||
'INSERT INTO bitacoras.gestion (tipo, nodo_id, detalle, estado, bitacora_id, orden) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *',
|
||||
[tipo, nodo_id || null, detalle, estado || '⏳', bitacora_id, orden || 0]
|
||||
);
|
||||
res.status(201).json(rows[0]);
|
||||
});
|
||||
|
||||
// PUT /api/gestion/:id — Actualizar
|
||||
router.put('/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { tipo, nodo_id, detalle, estado, activo, orden } = req.body;
|
||||
const { rows } = await pool.query(
|
||||
'UPDATE bitacoras.gestion SET tipo=$1, nodo_id=$2, detalle=$3, estado=$4, activo=$5, orden=$6, updated_at=CURRENT_TIMESTAMP WHERE id=$7 RETURNING *',
|
||||
[tipo, nodo_id || null, detalle, estado, activo !== undefined ? activo : true, orden || 0, id]
|
||||
);
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'No encontrado' });
|
||||
res.json(rows[0]);
|
||||
});
|
||||
|
||||
// DELETE /api/gestion/:id
|
||||
router.delete('/:id', async (req, res) => {
|
||||
const { rowCount } = await pool.query('DELETE FROM bitacoras.gestion WHERE id = $1', [req.params.id]);
|
||||
if (rowCount === 0) return res.status(404).json({ error: 'No encontrado' });
|
||||
res.json({ message: 'Eliminado' });
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
module.exports = (pool) => {
|
||||
const router = require('express').Router();
|
||||
|
||||
// GET /api/resumen — Listar resúmenes por bitácora
|
||||
router.get('/', async (req, res) => {
|
||||
const { bitacora_id } = req.query;
|
||||
if (!bitacora_id) return res.status(400).json({ error: 'Falta bitacora_id' });
|
||||
|
||||
const { rows } = await pool.query(
|
||||
'SELECT r.*, n.nombre as nodo_nombre FROM bitacoras.resumen_nodos r JOIN bitacoras.nodos n ON r.nodo_id = n.id WHERE r.bitacora_id = $1',
|
||||
[bitacora_id]
|
||||
);
|
||||
res.json(rows);
|
||||
});
|
||||
|
||||
// POST /api/resumen — Upsert resumen por nodo
|
||||
router.post('/', async (req, res) => {
|
||||
const { bitacora_id, nodo_id, resumen } = req.body;
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO bitacoras.resumen_nodos (bitacora_id, nodo_id, resumen)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (bitacora_id, nodo_id)
|
||||
DO UPDATE SET resumen = EXCLUDED.resumen, updated_at = CURRENT_TIMESTAMP
|
||||
RETURNING *`,
|
||||
[bitacora_id, nodo_id, resumen]
|
||||
);
|
||||
res.status(201).json(rows[0]);
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
@@ -29,6 +29,8 @@ app.use('/api/nodos', require('./routes/nodos')(pool));
|
||||
app.use('/api/bitacoras', require('./routes/bitacoras')(pool));
|
||||
app.use('/api/entradas', require('./routes/entradas')(pool));
|
||||
app.use('/api/proyectos', require('./routes/proyectos')(pool));
|
||||
app.use('/api/gestion', require('./routes/gestion')(pool));
|
||||
app.use('/api/resumen', require('./routes/resumen')(pool));
|
||||
|
||||
// Error handler
|
||||
app.use((err, req, res, next) => {
|
||||
|
||||
Reference in New Issue
Block a user