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/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ La fecha se completa automáticamente y los estados/activos usan sus valores por defecto.
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Ticket promedio (hoy)
+
—
+
+
+
+
+
+
+
Productos distintos (hoy)
+
—
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Basado en detalle de comandas de hoy.
+
+
+
+
+
+
+
+
+
+
+
+
Se agrupa por hora de creación.
+
+
+
+
+
+
+
+
+
+
+
+
Distribución por estado.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | # |
+ Fecha |
+ Cierre |
+ Estado |
+ Total |
+ Acció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
-
-
-
-
-
-
-
-
- | ID | Mesa | Estado |
-
-
- | 1 | 5 |
- Abierta |
-
-
-
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
+
+
+
+
+
+
+
+
+
+
+
+
+
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" } },