From 16fd0f93e84aa516af87e7e0ed87127933dc2d6a Mon Sep 17 00:00:00 2001
From: Marco Gallegos
Date: Tue, 12 Aug 2025 22:43:56 -0600
Subject: [PATCH] Updated ReadMe
---
ap-pos/README.md | 7 ++++++
ap-pos/app.js | 60 +++++++++++++++++++++++++++++++++++++----------
ap-pos/index.html | 14 +++++++++++
ap-pos/server.js | 41 ++++++++++++++++++++++++++++++++
4 files changed, 109 insertions(+), 13 deletions(-)
create mode 100644 ap-pos/README.md
diff --git a/ap-pos/README.md b/ap-pos/README.md
new file mode 100644
index 0000000..0b396dc
--- /dev/null
+++ b/ap-pos/README.md
@@ -0,0 +1,7 @@
+# AP-POS WebApp
+
+Este es un sistema de punto de venta simple basado en la web.
+
+## Futuras Implementaciones
+
+Se tiene la intención de que esta aplicación se pueda ejecutar en un contenedor de Docker. Además, se buscará que la aplicación tenga la capacidad de interactuar con una impresora de tickets conectada vía USB en un entorno de macOS.
diff --git a/ap-pos/app.js b/ap-pos/app.js
index 9223602..ebb4783 100644
--- a/ap-pos/app.js
+++ b/ap-pos/app.js
@@ -29,6 +29,7 @@ const btnTestTicket = document.getElementById('btnTestTicket');
const formClient = document.getElementById('formClient');
const tblClientsBody = document.getElementById('tblClients')?.querySelector('tbody');
const clientDatalist = document.getElementById('client-list');
+const formCredentials = document.getElementById('formCredentials');
// --- LÓGICA DE NEGOCIO ---
@@ -61,18 +62,17 @@ async function loadDashboardData() {
};
if (incomeChart) {
- incomeChart.data = chartData;
- incomeChart.update();
- } else {
- incomeChart = new Chart(ctx, {
- type: 'pie',
- data: chartData,
- options: {
- responsive: true,
- maintainAspectRatio: false,
- }
- });
+ incomeChart.destroy();
}
+
+ incomeChart = new Chart(ctx, {
+ type: 'pie',
+ data: chartData,
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ }
+ });
} catch (error) {
console.error('Error loading dashboard:', error);
}
@@ -254,6 +254,35 @@ async function handleSaveSettings(e) {
alert('Configuración guardada.');
}
+async function handleSaveCredentials(e) {
+ e.preventDefault();
+ const username = document.getElementById('s-username').value;
+ const password = document.getElementById('s-password').value;
+
+ const body = { username };
+ if (password) {
+ body.password = password;
+ }
+
+ try {
+ const response = await fetch('/api/user', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body)
+ });
+
+ if (response.ok) {
+ alert('Credenciales actualizadas.');
+ document.getElementById('s-password').value = '';
+ } else {
+ const error = await response.json();
+ alert(`Error: ${error.error}`);
+ }
+ } catch (error) {
+ alert('Error de conexión al guardar credenciales.');
+ }
+}
+
async function handleNewMovement(e) {
e.preventDefault();
const form = e.target;
@@ -393,6 +422,7 @@ async function initializeApp() {
const btnLogout = document.getElementById('btnLogout');
formSettings?.addEventListener('submit', handleSaveSettings);
+ formCredentials?.addEventListener('submit', handleSaveCredentials);
formMove?.addEventListener('submit', handleNewMovement);
tblMovesBody?.addEventListener('click', handleTableClick);
tblClientsBody?.addEventListener('click', handleTableClick);
@@ -414,13 +444,17 @@ async function initializeApp() {
Promise.all([
load(KEY_SETTINGS, DEFAULT_SETTINGS),
load(KEY_DATA, []),
- load(KEY_CLIENTS, [])
+ load(KEY_CLIENTS, []),
+ fetch('/api/user').then(res => res.json())
]).then(values => {
- [settings, movements, clients] = values;
+ [settings, movements, clients, user] = values;
renderSettings();
renderTable();
renderClientsTable();
updateClientDatalist();
+ if (user) {
+ document.getElementById('s-username').value = user.username;
+ }
// Cargar datos del dashboard al inicio
loadDashboardData();
}).catch(error => {
diff --git a/ap-pos/index.html b/ap-pos/index.html
index dc6bc9c..453d32c 100644
--- a/ap-pos/index.html
+++ b/ap-pos/index.html
@@ -178,6 +178,20 @@
Toda la información de tu negocio (clientes, recibos y configuración) se guarda de forma segura en el archivo ap-pos.db, ubicado en la misma carpeta que la aplicación. Para hacer un respaldo, simplemente copia este archivo.
+
+
Credenciales de Acceso
+
+
diff --git a/ap-pos/server.js b/ap-pos/server.js
index d547ee0..0f3df14 100644
--- a/ap-pos/server.js
+++ b/ap-pos/server.js
@@ -233,6 +233,47 @@ apiRouter.delete('/movements/:id', (req, res) => {
// Registrar el router de la API protegida
app.use('/api', apiRouter);
+// --- User Management ---
+apiRouter.get('/user', (req, res) => {
+ db.get("SELECT id, username FROM users WHERE id = ?", [req.session.userId], (err, row) => {
+ if (err) {
+ res.status(500).json({ error: err.message });
+ return;
+ }
+ res.json(row);
+ });
+});
+
+apiRouter.post('/user', (req, res) => {
+ const { username, password } = req.body;
+ if (!username) {
+ return res.status(400).json({ error: 'Username is required' });
+ }
+
+ if (password) {
+ // Si se proporciona una nueva contraseña, hashearla y actualizar todo
+ bcrypt.hash(password, SALT_ROUNDS, (err, hash) => {
+ if (err) {
+ return res.status(500).json({ error: 'Error hashing password' });
+ }
+ db.run('UPDATE users SET username = ?, password = ? WHERE id = ?', [username, hash, req.session.userId], function(err) {
+ if (err) {
+ return res.status(500).json({ error: err.message });
+ }
+ res.json({ message: 'User credentials updated successfully' });
+ });
+ });
+ } else {
+ // Si no se proporciona contraseña, solo actualizar el nombre de usuario
+ db.run('UPDATE users SET username = ? WHERE id = ?', [username, req.session.userId], function(err) {
+ if (err) {
+ return res.status(500).json({ error: err.message });
+ }
+ res.json({ message: 'Username updated successfully' });
+ });
+ }
+});
+
// --- Dashboard Route ---
apiRouter.get('/dashboard', (req, res) => {
const queries = {