Updated ReadMe

This commit is contained in:
Marco Gallegos
2025-08-12 22:43:56 -06:00
parent 576faface2
commit 16fd0f93e8
4 changed files with 109 additions and 13 deletions

7
ap-pos/README.md Normal file
View File

@@ -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.

View File

@@ -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,9 +62,9 @@ async function loadDashboardData() {
};
if (incomeChart) {
incomeChart.data = chartData;
incomeChart.update();
} else {
incomeChart.destroy();
}
incomeChart = new Chart(ctx, {
type: 'pie',
data: chartData,
@@ -72,7 +73,6 @@ async function loadDashboardData() {
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 => {

View File

@@ -178,6 +178,20 @@
Toda la información de tu negocio (clientes, recibos y configuración) se guarda de forma segura en el archivo <strong>ap-pos.db</strong>, ubicado en la misma carpeta que la aplicación. Para hacer un respaldo, simplemente copia este archivo.
</p>
</div>
<div class="section">
<h2>Credenciales de Acceso</h2>
<form id="formCredentials">
<div class="form-grid">
<label>Usuario:</label>
<input type="text" id="s-username" required />
<label>Nueva Contraseña:</label>
<input type="password" id="s-password" placeholder="Dejar en blanco para no cambiar" />
</div>
<div class="form-actions">
<button type="submit">Guardar Credenciales</button>
</div>
</form>
</div>
</div>
</main>

View File

@@ -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 = {