From 57dbd5b1fae7c8f5d3b1f6c18ba38a1e267e5f11 Mon Sep 17 00:00:00 2001 From: msaldain Date: Fri, 29 Aug 2025 05:09:44 +0000 Subject: [PATCH] 290825-0209 --- services/manso/src/index.js | 208 +++++--- .../{comandas.html => comandas.html.bak} | 0 .../{dashboard.html => dashboard.html.bak} | 0 services/manso/src/views/comandas.ejs | 333 ++++++++++++ services/manso/src/views/dashboard.ejs | 487 ++++++++++++++++++ services/manso/src/views/estadoComanas.ejs | 18 - services/manso/src/views/estadoComandas.ejs | 291 +++++++++++ services/manso/src/views/layouts/main.ejs | 8 +- services/manso/src/views/partials/_head.ejs | 15 + .../manso/src/views/partials/_sidebar.ejs | 7 + 10 files changed, 1284 insertions(+), 83 deletions(-) rename services/manso/src/pages/{comandas.html => comandas.html.bak} (100%) rename services/manso/src/pages/{dashboard.html => dashboard.html.bak} (100%) create mode 100644 services/manso/src/views/comandas.ejs create mode 100644 services/manso/src/views/dashboard.ejs delete mode 100644 services/manso/src/views/estadoComanas.ejs create mode 100644 services/manso/src/views/estadoComandas.ejs diff --git a/services/manso/src/index.js b/services/manso/src/index.js index b4fd77c..e0b1a01 100644 --- a/services/manso/src/index.js +++ b/services/manso/src/index.js @@ -185,19 +185,31 @@ app.use((req, res, next) => { // ---------------------------------------------------------- app.get("/", (req, res) => { - res.locals.pageTitle = "Inicio"; - res.locals.pageId = "home"; - res.render("estadoComandas"); + res.locals.pageTitle = "Dashboard"; + res.locals.pageId = "home"; // para el sidebar contextual + res.render("dashboard"); +}); + +app.get("/dashboard", (req, res) => { + res.locals.pageTitle = "Dashboard"; + res.locals.pageId = "dashboard"; // <- importante + res.render("dashboard"); }); // app.get('/', (req, res) => { // res.sendFile(path.join(__dirname, 'pages', 'dashboard.html')); // }); -app.get('/comandas', (req, res) => { - res.sendFile(path.join(__dirname, 'pages', 'comandas.html')); +app.get("/comandas", (req, res) => { + res.locals.pageTitle = "Comandas"; + res.locals.pageId = "comandas"; // <- importante para el sidebar contextual + res.render("comandas"); }); +// app.get('/comandas', (req, res) => { +// res.sendFile(path.join(__dirname, 'pages', 'comandas.html')); +// }); + app.get("/estadoComandas", (req, res) => { res.locals.pageTitle = "Estado de Comandas"; res.locals.pageId = "estadoComandas"; @@ -325,85 +337,159 @@ app.post('/api/table/:table', async (req, res) => { } }); -// Listado (con join) y totales por comanda app.get('/api/comandas', async (req, res, next) => { try { - const estado = (req.query.estado || '').trim(); - const limit = Math.min(parseInt(req.query.limit || '200', 10), 1000); - const params = []; - let where = ''; - if (estado) { params.push(estado); where = `WHERE c.estado = $${params.length}`; } - params.push(limit); + const estado = (req.query.estado || '').trim() || null; + const limit = Math.min(parseInt(req.query.limit || '200', 10), 1000); - const sql = ` - WITH items AS ( - SELECT d.id_comanda, - COUNT(*) AS items, - SUM(d.cantidad * d.pre_unitario) AS total - FROM deta_comandas d - GROUP BY d.id_comanda - ) - SELECT - c.id_comanda, c.fec_creacion, c.estado, c.observaciones, - u.id_usuario, u.nombre AS usuario_nombre, u.apellido AS usuario_apellido, - m.id_mesa, m.numero AS mesa_numero, m.apodo AS mesa_apodo, - COALESCE(i.items, 0) AS items, - COALESCE(i.total, 0) AS total - FROM comandas c - JOIN usuarios u ON u.id_usuario = c.id_usuario - JOIN mesas m ON m.id_mesa = c.id_mesa - LEFT JOIN items i ON i.id_comanda = c.id_comanda - ${where} - ORDER BY c.id_comanda DESC - LIMIT $${params.length} - `; - const client = await pool.connect(); - try { - const { rows } = await client.query(sql, params); - res.json(rows); - } finally { client.release(); } + const { rows } = await pool.query( + `SELECT * FROM public.f_comandas_resumen($1, $2)`, + [estado, limit] + ); + res.json(rows); } catch (e) { next(e); } }); + +// app.get('/api/comandas', async (req, res, next) => { +// try { +// const estado = (req.query.estado || '').trim(); +// const limit = Math.min(parseInt(req.query.limit || '200', 10), 1000); +// const params = []; +// let where = ''; +// if (estado) { params.push(estado); where = `WHERE c.estado = $${params.length}`; } +// params.push(limit); + +// const sql = ` +// WITH items AS ( +// SELECT d.id_comanda, +// COUNT(*) AS items, +// SUM(d.cantidad * d.pre_unitario) AS total +// FROM deta_comandas d +// GROUP BY d.id_comanda +// ) +// SELECT +// c.id_comanda, c.fec_creacion, c.estado, c.observaciones, +// u.id_usuario, u.nombre AS usuario_nombre, u.apellido AS usuario_apellido, +// m.id_mesa, m.numero AS mesa_numero, m.apodo AS mesa_apodo, +// COALESCE(i.items, 0) AS items, +// COALESCE(i.total, 0) AS total +// FROM comandas c +// JOIN usuarios u ON u.id_usuario = c.id_usuario +// JOIN mesas m ON m.id_mesa = c.id_mesa +// LEFT JOIN items i ON i.id_comanda = c.id_comanda +// ${where} +// ORDER BY c.id_comanda DESC +// LIMIT $${params.length} +// `; +// const client = await pool.connect(); +// try { +// const { rows } = await client.query(sql, params); +// res.json(rows); +// } finally { client.release(); } +// } catch (e) { next(e); } +// }); + + // Detalle de una comanda (con nombres de productos) + app.get('/api/comandas/:id/detalle', async (req, res, next) => { try { const id = parseInt(req.params.id, 10); - if (!id) return res.status(400).json({ error: 'id inválido' }); + if (!Number.isInteger(id) || id <= 0) { + return res.status(400).json({ error: 'id inválido' }); + } const sql = ` - SELECT d.id_det_comanda, d.id_producto, p.nombre AS producto_nombre, - d.cantidad, d.pre_unitario, (d.cantidad * d.pre_unitario) AS subtotal, - d.observaciones - FROM deta_comandas d - JOIN productos p ON p.id_producto = d.id_producto - WHERE d.id_comanda = $1 - ORDER BY d.id_det_comanda + SELECT + id_det_comanda, id_producto, producto_nombre, + cantidad, pre_unitario, subtotal, observaciones + FROM public.v_comandas_detalle_items + WHERE id_comanda = $1::int + ORDER BY id_det_comanda `; const { rows } = await pool.query(sql, [id]); res.json(rows); } catch (e) { next(e); } }); -// Cambiar estado (abrir/cerrar) -app.post('/api/comandas/:id/estado', async (req, res, next) => { + +// app.get('/api/comandas/:id/detalle', async (req, res, next) => { +// try { +// const id = parseInt(req.params.id, 10); +// if (!id) return res.status(400).json({ error: 'id inválido' }); + +// const sql = ` +// SELECT d.id_det_comanda, d.id_producto, p.nombre AS producto_nombre, +// d.cantidad, d.pre_unitario, (d.cantidad * d.pre_unitario) AS subtotal, +// d.observaciones +// FROM deta_comandas d +// JOIN productos p ON p.id_producto = d.id_producto +// WHERE d.id_comanda = $1 +// ORDER BY d.id_det_comanda +// `; +// const { rows } = await pool.query(sql, [id]); +// res.json(rows); +// } catch (e) { next(e); } +// }); + + + +// Cerrar comanda (setea estado y fec_cierre en DB) +app.post('/api/comandas/:id/cerrar', async (req, res, next) => { try { - const id = parseInt(req.params.id, 10); - let { estado } = req.body || {}; - if (!id) return res.status(400).json({ error: 'id inválido' }); - - const allowed = new Set(['abierta','cerrada','pagada','anulada']); - if (!allowed.has(estado)) return res.status(400).json({ error: 'estado inválido' }); - + const id = Number(req.params.id); + if (!Number.isInteger(id) || id <= 0) { + return res.status(400).json({ error: 'id inválido' }); + } const { rows } = await pool.query( - `UPDATE comandas SET estado = $2 WHERE id_comanda = $1 RETURNING *`, - [id, estado] + `SELECT public.f_cerrar_comanda($1) AS data`, + [id] ); - if (!rows.length) return res.status(404).json({ error: 'comanda no encontrada' }); - res.json({ updated: rows[0] }); - } catch (e) { next(e); } + if (!rows.length || rows[0].data === null) { + return res.status(404).json({ error: 'Comanda no encontrada' }); + } + res.json(rows[0].data); + } catch (err) { next(err); } }); +// Abrir (reabrir) comanda +app.post('/api/comandas/:id/abrir', async (req, res, next) => { + try { + const id = Number(req.params.id); + if (!Number.isInteger(id) || id <= 0) { + return res.status(400).json({ error: 'id inválido' }); + } + const { rows } = await pool.query( + `SELECT public.f_abrir_comanda($1) AS data`, + [id] + ); + if (!rows.length || rows[0].data === null) { + return res.status(404).json({ error: 'Comanda no encontrada' }); + } + res.json(rows[0].data); + } catch (err) { next(err); } +}); + +// // Cambiar estado (abrir/cerrar) +// app.post('/api/comandas/:id/estado', async (req, res, next) => { +// try { +// const id = parseInt(req.params.id, 10); +// let { estado } = req.body || {}; +// if (!id) return res.status(400).json({ error: 'id inválido' }); + +// const allowed = new Set(['abierta','cerrada','pagada','anulada']); +// if (!allowed.has(estado)) return res.status(400).json({ error: 'estado inválido' }); + +// const { rows } = await pool.query( +// `UPDATE comandas SET estado = $2 WHERE id_comanda = $1 RETURNING *`, +// [id, estado] +// ); +// if (!rows.length) return res.status(404).json({ error: 'comanda no encontrada' }); +// res.json({ updated: rows[0] }); +// } catch (e) { next(e); } +// }); + // ---------------------------------------------------------- // Verificación de conexión // ---------------------------------------------------------- diff --git a/services/manso/src/pages/comandas.html b/services/manso/src/pages/comandas.html.bak similarity index 100% rename from services/manso/src/pages/comandas.html rename to services/manso/src/pages/comandas.html.bak diff --git a/services/manso/src/pages/dashboard.html b/services/manso/src/pages/dashboard.html.bak similarity index 100% rename from services/manso/src/pages/dashboard.html rename to services/manso/src/pages/dashboard.html.bak diff --git a/services/manso/src/views/comandas.ejs b/services/manso/src/views/comandas.ejs new file mode 100644 index 0000000..996abb3 --- /dev/null +++ b/services/manso/src/views/comandas.ejs @@ -0,0 +1,333 @@ + +
+

📋 Nueva Comanda

+ /api/* +
+ +
+ +
+
+
+ Productos +
0 ítems
+
+
+
+
+ +
+
+ +
+
+ +
+ +
Cargando…
+
+
+
+
+ + +
+
+
Detalles
+
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ La fecha se completa automáticamente y los estados/activos usan sus valores por defecto. +
+
+
+ +
+
Carrito
+
+
Aún no agregaste productos.
+
+
+
Ítems: 0
+
Total: $ 0.00
+
+ + +
+
+ +
+
+
+ + + diff --git a/services/manso/src/views/dashboard.ejs b/services/manso/src/views/dashboard.ejs new file mode 100644 index 0000000..fb2ae9d --- /dev/null +++ b/services/manso/src/views/dashboard.ejs @@ -0,0 +1,487 @@ + +
+

Dashboard Operativo

+
+ + +
+
+ + +
+
+
+
+
Comandas activas
+
+
+
+
+
+
+
+
Ventas hoy
+
+
+
+
+
+
+
+
Ticket promedio (hoy)
+
+
+
+
+
+
+
+
Productos distintos (hoy)
+
+
+
+
+
+ + +
+
+
+
Top 5 productos (hoy)
+
+
+ +
+
Basado en detalle de comandas de hoy.
+
+
+
+ +
+
+
Comandas por hora (últimas 12 h)
+
+
+ +
+
Se agrupa por hora de creación.
+
+
+
+ +
+
+
Estados de comandas (hoy)
+
+
+ +
+
Distribución por estado.
+
+
+
+ + +
+
+
+ Últimas 10 comandas +
+
+
+
+ + + + + + + + + + + + + + +
#FechaCierre EstadoTotalAcción
Cargando…
+
+
+ +
+
+
+ + + + + diff --git a/services/manso/src/views/estadoComanas.ejs b/services/manso/src/views/estadoComanas.ejs deleted file mode 100644 index 4cbd3aa..0000000 --- a/services/manso/src/views/estadoComanas.ejs +++ /dev/null @@ -1,18 +0,0 @@ -
-

Estado de Comandas

-
- - -
-
- - - - - - - - - - -
IDMesaEstado
15Abierta
diff --git a/services/manso/src/views/estadoComandas.ejs b/services/manso/src/views/estadoComandas.ejs new file mode 100644 index 0000000..b7a9f45 --- /dev/null +++ b/services/manso/src/views/estadoComandas.ejs @@ -0,0 +1,291 @@ + +
+

🧾 Estado de Comandas

+ ➕ Nueva comanda +
+ +
+ +
+
+
+ Listado +
+ + +
+
+
+
+
+ +
+
+ +
+
+ +
+
Cargando…
+
+
+
+
+ + +
+
+
+ Detalle + +
+ +
+
Selecciona una comanda para ver el detalle.
+
+ +
+
ID:
+
Mesa:
+
Total: $ 0.00
+
+ + +
+ +
+
+
+
+
+
+ + diff --git a/services/manso/src/views/layouts/main.ejs b/services/manso/src/views/layouts/main.ejs index 6e29644..2d8aaf9 100644 --- a/services/manso/src/views/layouts/main.ejs +++ b/services/manso/src/views/layouts/main.ejs @@ -1,16 +1,16 @@ - <% include ../partials/_head %> + <%- include('../partials/_head') %> - <% include ../partials/_navbar %> + <%- include('../partials/_navbar') %>
<%- body %>
- <% include ../partials/_sidebar %> - <% include ../partials/_footer %> + <%- include('../partials/_sidebar') %> + <%- include('../partials/_footer') %> diff --git a/services/manso/src/views/partials/_head.ejs b/services/manso/src/views/partials/_head.ejs index c449c6d..f664db4 100644 --- a/services/manso/src/views/partials/_head.ejs +++ b/services/manso/src/views/partials/_head.ejs @@ -19,4 +19,19 @@ .badge-estado-cerrada { border-color:#6c757d; color:#6c757d; } .badge-estado-anulada { border-color:#dc3545; color:#dc3545; } .badge-estado-pagada { border-color:#146c43; color:#146c43; } + + /* Evita crecimiento infinito de los charts */ +.chart-box { + position: relative; + height: 260px; /* altura fija base */ +} +@media (min-width: 992px) { + .chart-box { height: 320px; } /* un poquito más grande en desktop */ +} +.chart-box > canvas { + position: absolute; + inset: 0; + width: 100% !important; + height: 100% !important; /* ocupa todo el alto del contenedor */ +} diff --git a/services/manso/src/views/partials/_sidebar.ejs b/services/manso/src/views/partials/_sidebar.ejs index 06069af..e56db8f 100644 --- a/services/manso/src/views/partials/_sidebar.ejs +++ b/services/manso/src/views/partials/_sidebar.ejs @@ -14,6 +14,13 @@ // Map de opciones por página. Usa body[data-page] o window.scPageId. const SC_SIDEBAR_ITEMS = { // === ejemplos === + "dashboard": [ + { text: "Ver reportes", href: "/reportes" }, + { text: "Actualizar", href: "#", attr: { "data-action": "refresh-list" } }, + { text: "Exportar (CSV)", href: "#", attr: { "data-action": "export-csv" } }, + { text: "Nueva comanda", href: "/comandas" }, + { text: "Ir a Estado", href: "/estadoComandas" } + ], "estadoComandas": [ { text: "➕ Nueva comanda", href: "/comandas" }, { text: "Solo abiertas", href: "#", attr: { "data-action": "toggle-abiertas" } },