diff --git a/compose.tools.yaml b/compose.tools.yaml
index 49fd8d2..bb692ea 100644
--- a/compose.tools.yaml
+++ b/compose.tools.yaml
@@ -1,4 +1,4 @@
-# compose.tools.yaml
+# $ compose.tools.yaml
name: suitecoffee_tools
services:
@@ -14,7 +14,7 @@ services:
- dbeaver_logs:/opt/cloudbeaver/logs
- dbeaver_workspace:/opt/cloudbeaver/workspace
networks:
- suitecoffee_prod_net: {}
+ # suitecoffee_prod_net: {}
suitecoffee_dev_net: {}
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8978 || exit 1"]
@@ -37,7 +37,7 @@ services:
- npm_data:/data
- npm_letsencrypt:/etc/letsencrypt
networks:
- suitecoffee_prod_net: {}
+ # suitecoffee_prod_net: {}
suitecoffee_dev_net: {}
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:81 || exit 1"]
@@ -50,8 +50,8 @@ services:
networks:
suitecoffee_dev_net:
external: true
- suitecoffee_prod_net:
- external: true
+ # suitecoffee_prod_net:
+ # external: true
volumes:
npm_data:
diff --git a/services/manso/src/index.js b/services/manso/src/index.js
index cb8e9bc..f9d5f48 100644
--- a/services/manso/src/index.js
+++ b/services/manso/src/index.js
@@ -84,7 +84,7 @@ const ALLOWED_TABLES = [
'mate_primas','deta_comp_materias',
'prov_producto','prov_mate_prima',
'receta_producto', 'asistencia_resumen_diario',
- 'asistencia_intervalo'
+ 'asistencia_intervalo', 'vw_compras'
];
const VALID_IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
@@ -243,6 +243,18 @@ app.get('/usuarios', (req, res) => {
res.render('usuarios');
});
+app.get('/reportes', (req, res) => {
+ res.locals.pageTitle = 'Reportes';
+ res.locals.pageId = 'reportes';
+ res.render('reportes');
+});
+
+app.get('/compras', (req, res) => {
+ res.locals.pageTitle = 'Compras';
+ res.locals.pageId = 'compras';
+ res.render('compras');
+});
+
// ----------------------------------------------------------
// API
// ----------------------------------------------------------
@@ -649,6 +661,99 @@ app.post('/api/rpc/asistencia_delete_raw', async (req, res) => {
}
});
+app.post('/api/rpc/report_asistencia', async (req,res)=>{
+ const {desde, hasta} = req.body||{};
+ const sql = 'SELECT * FROM public.report_asistencia($1::date,$2::date)';
+ const {rows} = await pool.query(sql,[desde, hasta]);
+ res.json(rows);
+});
+
+app.post('/api/rpc/report_tickets', async (req, res) => {
+ try {
+ const { year } = req.body || {};
+ const sql = 'SELECT public.report_tickets_year($1::int) AS data';
+ const { rows } = await pool.query(sql, [year]);
+ res.json(rows[0]?.data || {});
+ } catch (e) {
+ console.error(e);
+ res.status(500).json({ error: 'report_tickets failed' });
+ }
+});
+
+
+// Guardar (insert/update)
+app.post('/api/rpc/save_compra', async (req, res) => {
+ try {
+ const { id_compra, id_proveedor, fec_compra, detalles } = req.body || {};
+ const sql = 'SELECT * FROM public.save_compra($1::int,$2::int,$3::timestamptz,$4::jsonb)';
+ const args = [id_compra ?? null, id_proveedor, fec_compra ? new Date(fec_compra) : null, JSON.stringify(detalles)];
+ const { rows } = await pool.query(sql, args);
+ res.json(rows[0]); // { id_compra, total }
+ } catch (e) {
+ console.error('save_compra error:', e);
+ res.status(500).json({ error: 'save_compra failed', message: e.message, detail: e.detail, where: e.where, code: e.code });
+ }
+});
+
+
+// Obtener para editar
+app.post('/api/rpc/get_compra', async (req, res) => {
+ try {
+ const { id_compra } = req.body || {};
+ const sql = `SELECT public.get_compra($1::int) AS data`;
+ const { rows } = await pool.query(sql, [id_compra]);
+ res.json(rows[0]?.data || {});
+ } catch (e) {
+ console.error(e); res.status(500).json({ error: 'get_compra failed' });
+ }
+});
+
+// Eliminar
+app.post('/api/rpc/delete_compra', async (req, res) => {
+ try {
+ const { id_compra } = req.body || {};
+ await pool.query(`SELECT public.delete_compra($1::int)`, [id_compra]);
+ res.json({ ok: true });
+ } catch (e) {
+ console.error(e); res.status(500).json({ error: 'delete_compra failed' });
+ }
+});
+
+
+// POST /api/rpc/report_gastos { year: 2025 }
+app.post('/api/rpc/report_gastos', async (req, res) => {
+ try {
+ const year = parseInt(req.body?.year ?? new Date().getFullYear(), 10);
+ const { rows } = await pool.query(
+ 'SELECT public.report_gastos($1::int) AS j', [year]
+ );
+ res.json(rows[0].j);
+ } catch (e) {
+ console.error('report_gastos error:', e);
+ res.status(500).json({
+ error: 'report_gastos failed',
+ message: e.message, detail: e.detail, code: e.code
+ });
+ }
+});
+
+// (Opcional) GET para probar rápido desde el navegador:
+// /api/rpc/report_gastos?year=2025
+app.get('/api/rpc/report_gastos', async (req, res) => {
+ try {
+ const year = parseInt(req.query.year ?? new Date().getFullYear(), 10);
+ const { rows } = await pool.query(
+ 'SELECT public.report_gastos($1::int) AS j', [year]
+ );
+ res.json(rows[0].j);
+ } catch (e) {
+ console.error('report_gastos error:', e);
+ res.status(500).json({
+ error: 'report_gastos failed',
+ message: e.message, detail: e.detail, code: e.code
+ });
+ }
+});
// ----------------------------------------------------------
diff --git a/services/manso/src/views/compras.ejs b/services/manso/src/views/compras.ejs
new file mode 100644
index 0000000..6111cdd
--- /dev/null
+++ b/services/manso/src/views/compras.ejs
@@ -0,0 +1,361 @@
+<% /* Compras / Gastos */ %>
+
+
+
Compras / Gastos
+
+
+ —
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | # |
+ Proveedor |
+ Fecha |
+ Total |
+ |
+
+
+
+ | Sin datos |
+
+
+
+
+
+
+
+
+
+
diff --git a/services/manso/src/views/partials/_navbar.ejs b/services/manso/src/views/partials/_navbar.ejs
index 6d16359..486666a 100644
--- a/services/manso/src/views/partials/_navbar.ejs
+++ b/services/manso/src/views/partials/_navbar.ejs
@@ -14,6 +14,8 @@
Estado
Productos
Usuarios
+ Reportes
+ Compras
diff --git a/services/manso/src/views/reportes.ejs b/services/manso/src/views/reportes.ejs
new file mode 100644
index 0000000..0716a16
--- /dev/null
+++ b/services/manso/src/views/reportes.ejs
@@ -0,0 +1,548 @@
+<% /* Reportes - Asistencias, Tickets y Gastos */ %>
+
+
+
+
Reportes
+ —
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Los Excel se generan como CSV. Los PDF se generan con “Imprimir área” del navegador.
+
+
+
+
+
+
+
+
+
+
+
+
+ | Documento | Nombre | Apellido | Fecha |
+ Desde | Hasta | Duración |
+
+
+ | Sin datos |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Tickets por mes
+
Cantidad
+
+
+
+
+
+
+
+
+
+ | Mes | Tickets | Importe | Ticket promedio |
+
+ | Sin datos |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Total: —
+ Compras: —
+ Renglones: —
+
+
+
+
+
+
+
+
+
+
+
+
+ | Fecha |
+ Proveedor |
+ Tipo |
+ Ítem |
+ Cantidad |
+ Precio |
+ Subtotal |
+
+
+
+ | Sin datos |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Mes | Ingresos | Gastos | Resultado |
+
+ | Sin datos |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/services/manso/src/views/reportes.ejs.bak b/services/manso/src/views/reportes.ejs.bak
new file mode 100644
index 0000000..7f5ce28
--- /dev/null
+++ b/services/manso/src/views/reportes.ejs.bak
@@ -0,0 +1,402 @@
+<% /* Reportes - Asistencias y Tickets (Comandas) */ %>
+
+
+
+
Reportes
+ —
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Los archivos Excel se generan como CSV (compatibles). Los PDF se generan con “Imprimir área” del navegador.
+
+
+
+
+
+
+
+
+
+
+
+
+ | Documento |
+ Nombre |
+ Apellido |
+ Fecha |
+ Desde |
+ Hasta |
+ Duración |
+
+
+
+ | Sin datos |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Tickets por mes
+
Cantidad
+
+
+
+
+
+
+
+
+
+
+ | Mes |
+ Tickets |
+ Importe |
+ Ticket promedio |
+
+
+
+ | Sin datos |
+
+
+
+
+
+
+
+
+
+
+
+
+
+