From c9b4b4871d3b681fe6405e03752d500792e7482c Mon Sep 17 00:00:00 2001 From: msaldain Date: Sat, 30 Aug 2025 04:49:59 +0000 Subject: [PATCH] =?UTF-8?q?Creaci=C3=B3n=20de=20secci=C3=B3n=20Usuarios=20?= =?UTF-8?q?para=20administrar=20las=20entradas=20y=20salidas=20del=20perso?= =?UTF-8?q?nal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/manso/src/index.js | 75 +- services/manso/src/views/comandas.ejs | 137 ++- services/manso/src/views/partials/_navbar.ejs | 4 +- .../manso/src/views/partials/_sidebar.ejs | 3 + services/manso/src/views/usuarios.ejs | 1030 +++++++++++++++++ 5 files changed, 1196 insertions(+), 53 deletions(-) create mode 100644 services/manso/src/views/usuarios.ejs diff --git a/services/manso/src/index.js b/services/manso/src/index.js index 3401511..cb8e9bc 100644 --- a/services/manso/src/index.js +++ b/services/manso/src/index.js @@ -83,7 +83,8 @@ const ALLOWED_TABLES = [ 'proveedores','compras','deta_comp_producto', 'mate_primas','deta_comp_materias', 'prov_producto','prov_mate_prima', - 'receta_producto' + 'receta_producto', 'asistencia_resumen_diario', + 'asistencia_intervalo' ]; const VALID_IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*$/; @@ -235,6 +236,13 @@ app.get("/productos", (req, res) => { res.locals.pageId = "productos"; res.render("productos"); }); + +app.get('/usuarios', (req, res) => { + res.locals.pageTitle = 'Usuarios'; + res.locals.pageId = 'usuarios'; + res.render('usuarios'); +}); + // ---------------------------------------------------------- // API // ---------------------------------------------------------- @@ -578,6 +586,71 @@ app.post('/api/rpc/save_materia', async (req, res) => { } }); +// POST /api/rpc/find_usuarios_por_documentos { docs: ["12345678","09123456", ...] } +app.post('/api/rpc/find_usuarios_por_documentos', async (req, res) => { + try { + const docs = Array.isArray(req.body?.docs) ? req.body.docs : []; + const sql = 'SELECT public.find_usuarios_por_documentos($1::jsonb) AS data'; + const { rows } = await pool.query(sql, [JSON.stringify(docs)]); + res.json(rows[0]?.data || {}); + } catch (e) { + console.error(e); + res.status(500).json({ error: 'find_usuarios_por_documentos failed' }); + } +}); + +// POST /api/rpc/import_asistencia { registros: [...], origen?: "AGL_001.txt" } +app.post('/api/rpc/import_asistencia', async (req, res) => { + try { + const registros = Array.isArray(req.body?.registros) ? req.body.registros : []; + const origen = req.body?.origen || null; + const sql = 'SELECT public.import_asistencia($1::jsonb,$2) AS data'; + const { rows } = await pool.query(sql, [JSON.stringify(registros), origen]); + res.json(rows[0]?.data || {}); + } catch (e) { + console.error(e); + res.status(500).json({ error: 'import_asistencia failed' }); + } +}); + +// Consultar datos de asistencia (raw + pares) para un usuario y rango +app.post('/api/rpc/asistencia_get', async (req, res) => { + try { + const { doc, desde, hasta } = req.body || {}; + const sql = 'SELECT public.asistencia_get($1::text,$2::date,$3::date) AS data'; + const { rows } = await pool.query(sql, [doc, desde, hasta]); + res.json(rows[0]?.data || {}); + } catch (e) { + console.error(e); res.status(500).json({ error: 'asistencia_get failed' }); + } +}); + +// Editar un registro crudo y recalcular pares +app.post('/api/rpc/asistencia_update_raw', async (req, res) => { + try { + const { id_raw, fecha, hora, modo } = req.body || {}; + const sql = 'SELECT public.asistencia_update_raw($1::bigint,$2::date,$3::text,$4::text) AS data'; + const { rows } = await pool.query(sql, [id_raw, fecha, hora, modo ?? null]); + res.json(rows[0]?.data || {}); + } catch (e) { + console.error(e); res.status(500).json({ error: 'asistencia_update_raw failed' }); + } +}); + +// Eliminar un registro crudo y recalcular pares +app.post('/api/rpc/asistencia_delete_raw', async (req, res) => { + try { + const { id_raw } = req.body || {}; + const sql = 'SELECT public.asistencia_delete_raw($1::bigint) AS data'; + const { rows } = await pool.query(sql, [id_raw]); + res.json(rows[0]?.data || {}); + } catch (e) { + console.error(e); res.status(500).json({ error: 'asistencia_delete_raw failed' }); + } +}); + + + // ---------------------------------------------------------- // Verificación de conexión // ---------------------------------------------------------- diff --git a/services/manso/src/views/comandas.ejs b/services/manso/src/views/comandas.ejs index df979d5..ae564e9 100644 --- a/services/manso/src/views/comandas.ejs +++ b/services/manso/src/views/comandas.ejs @@ -86,21 +86,58 @@ productos: [], mesas: [], usuarios: [], - carrito: [], // [{id_producto, nombre, pre_unitario, cantidad}] + categorias: [], // <--- NUEVO + carrito: [], filtro: '' }; + function norm(s='') { + return s.toString().toLowerCase() + .normalize('NFD').replace(/\p{Diacritic}/gu,''); // "café" -> "cafe" + } + + function isTakeaway(apodo) { + return /^takeaway$/i.test(String(apodo || '').trim()); + } + + function groupOrderByCatName(catName='') { + const n = norm(catName); + if (n.includes('bar')) return 1; + if (n.includes('cafe')) return 2; + if (n.includes('cafeter')) return 3; + if (n.includes('trago') || n.includes('refresc')) return 4; + return 99; // otros + } + // Genera el HTML del ticket de cocina (80mm aprox) function buildKitchenTicketHTML(data) { - const mesaTxt = `Mesa #${data.mesa_numero ?? '—'}${data.mesa_apodo ? ' · ' + data.mesa_apodo : ''}`; + const apodo = String(data.mesa_apodo ?? '').trim(); + const numero = data.mesa_numero ?? ''; + const take = isTakeaway(apodo); + + const mesaTxt = take ? apodo.toUpperCase() : `Mesa #${numero}${apodo ? ' · ' + apodo : ''}`; + // const isTakeaway = /Takeaway/i.test(String(data.mesa_apodo ?? '')) || /Takeaway/i.test(String(data.mesa_numero ?? '')); + const mesaClass = take ? 'bigline' : 'mesa-medium'; const obs = (data.observaciones && data.observaciones.trim()) ? data.observaciones.trim() : ''; - const productosHtml = data.productos.map(p => ` -
-
x${p.cantidad}
-
${p.nombre}
-
- `).join(''); + + + // Productos ya vienen con su "g" (grupo numérico 1..4/99) y cantidad formateada + const items = data.productos.slice().sort((a,b)=> (a.g||99) - (b.g||99)); + + let productosHtml = ''; + let prevG = null; + for (const p of items) { + if (prevG !== null && p.g !== prevG) { + productosHtml += `
`; // separación punteada entre grupos + } + productosHtml += ` +
+
x${p.cantidad}
+
${p.nombre}
+
`; + prevG = p.g; + } return ` @@ -109,66 +146,54 @@ Ticket Cocina
-
COMANDA COCINA
-
#${data.id_comanda}
-
+ +
#${data.id_comanda}
+
${mesaTxt}
-
Fecha: ${data.fecha} ${data.hora}
-
${mesaTxt}
-
Mozo: ${data.usuario || '—'}
- - ${obs ? `
OBSERVACIONES:
${obs}
` : ''} +
Fecha: ${data.fecha} ${data.hora}
+
Mozo: ${data.usuario || '—'}
+ ${obs ? `
Obs: ${obs}
` : ''}
-
PRODUCTOS
${productosHtml}
-
Ítems: ${data.items} · Unidades: ${data.units}
- +
Ítems: ${data.items} · Unidades: ${data.units}
— fin —