Files
ap_pos/clients.js
Marco Gallegos 4a841917f8 feat: Add initial setup page and functionality for admin account creation
- Created setup.html for the initial configuration of the admin account.
- Implemented setup.js to handle form submission and validation.
- Added logo images for branding.
- Introduced storage.js for API data handling (load, save, remove).
- Added styles.css for consistent styling across the application.
2025-08-25 08:01:30 -06:00

253 lines
8.8 KiB
JavaScript

import { load, save, remove, KEY_CLIENTS } from './storage.js';
let clients = [];
// --- DOM ELEMENTS ---
const formClient = document.getElementById('formClient');
const tblClientsBody = document.getElementById('tblClients')?.querySelector('tbody');
const searchClientInput = document.getElementById('search-client');
// --- LÓGICA DE NEGOCIO ---
async function saveClient(clientData) {
let clientToSave;
let isUpdate = false;
if (clientData) {
clientToSave = clientData;
} else {
isUpdate = !!document.getElementById('c-id').value;
const id = isUpdate ? document.getElementById('c-id').value : crypto.randomUUID();
clientToSave = {
id: id,
nombre: document.getElementById('c-nombre').value,
telefono: document.getElementById('c-telefono').value,
genero: document.getElementById('c-genero').value,
cumpleaños: document.getElementById('c-cumple').value,
consentimiento: document.getElementById('c-consent').checked,
esOncologico: document.getElementById('c-esOncologico').checked,
oncologoAprueba: document.getElementById('c-oncologoAprueba').checked,
nombreMedico: document.getElementById('c-nombreMedico').value,
telefonoMedico: document.getElementById('c-telefonoMedico').value,
cedulaMedico: document.getElementById('c-cedulaMedico').value,
pruebaAprobacion: document.getElementById('c-pruebaAprobacion').checked,
};
}
await save('clients', { client: clientToSave });
if (isUpdate) {
const index = clients.findIndex(c => c.id === clientToSave.id);
if (index > -1) clients[index] = clientToSave;
} else {
clients.unshift(clientToSave);
}
renderClientsTable();
if (!clientData) {
document.getElementById('formClient').reset();
document.getElementById('c-id').value = '';
document.getElementById('oncologico-fields').classList.add('hidden');
}
}
async function deleteClient(id) {
if (confirm('¿Estás seguro de que quieres eliminar este cliente? Se conservarán sus recibos históricos.')) {
await remove(KEY_CLIENTS, id);
clients = clients.filter(c => c.id !== id);
renderClientsTable();
}
}
function exportClientHistoryCSV(client, history) {
const headers = 'Folio,Fecha,Servicio,Monto';
const rows = history.map(mov => {
const fecha = new Date(mov.fechaISO).toLocaleDateString('es-MX');
const servicio = mov.subtipo ? `${mov.tipo} (${mov.subtipo})` : mov.tipo;
return [
mov.folio,
fecha,
`"${servicio}"`, // Corrected: escaped inner quotes for CSV compatibility
Number(mov.monto).toFixed(2)
].join(',');
});
const csvContent = `data:text/csv;charset=utf-8,${headers}\n${rows.join('\n')}`;
const encodedUri = encodeURI(csvContent);
const link = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute('download', `historial-${client.nombre.replace(/\s+/g, '_')}.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
async function toggleClientHistory(row, client) {
const historyRowId = `history-for-${client.id}`;
const existingHistoryRow = document.getElementById(historyRowId);
if (existingHistoryRow) {
existingHistoryRow.remove();
return;
}
try {
const response = await fetch(`/api/clients/${client.id}/history`);
const history = await response.json();
const historyRow = document.createElement('tr');
historyRow.id = historyRowId;
historyRow.className = 'client-history-row';
let historyHtml = '
<div class="client-history-header">
<h4>Historial de Servicios</h4>
<button class="action-btn" id="btn-export-history-' + client˚.id + '">Exportar CSV</button>
</div>
';
if (history.length > 0) {
historyHtml += '<table><thead><tr><th>Folio</th><th>Fecha</th><th>Servicio</th><th>Monto</th></tr></thead><tbody>';
history.forEach(mov => {
const fecha = new Date(mov.fechaISO).toLocaleDateString('es-MX');
const servicio = mov.subtipo ? `${mov.tipo} (${mov.subtipo})` : mov.tipo;
historyHtml += `<tr><td>${mov.folio}</td><td>${fecha}</td><td>${servicio}</td><td>${Number(mov.monto).toFixed(2)}</td></tr>`;
});
historyHtml += '</tbody></table>';
} else {
historyHtml += '<p>No hay historial de servicios para este cliente.</p>';
}
historyRow.innerHTML = `<td colspan="4"><div class="client-history-content">${historyHtml}</div></td>`;
row.after(historyRow);
const exportButton = document.getElementById(`btn-export-history-${client.id}`);
if (exportButton) {
exportButton.addEventListener('click', (e) => {
e.stopPropagation();
exportClientHistoryCSV(client, history);
});
}
} catch (error) {
console.error('Error al cargar el historial del cliente:', error);
alert('No se pudo cargar el historial.');
}
}
// --- RENDERIZADO ---
function renderClientsTable(clientList = clients) {
if (!tblClientsBody) return;
tblClientsBody.innerHTML = '';
clientList.forEach(c => {
const tr = document.createElement('tr');
tr.dataset.id = c.id;
tr.innerHTML = '
<td>' + c.nombre + '</td>
<td>' + (c.telefono || '') + '</td>
<td>' + (c.esOncologico ? 'Sí' : 'No') + '</td>
<td>
<button class="action-btn" data-id="' + c.id + '" data-action="view-history">Historial</button>
<button class="action-btn" data-id="' + c.id + '" data-action="edit-client">Editar</button>
<button class="action-btn" data-id="' + c.id + '" data-action="delete-client">Eliminar</button>
</td>
';
tblClientsBody.appendChild(tr);
});
}
// --- MANEJADORES DE EVENTOS ---
function handleTableClick(e) {
const target = e.target;
const row = target.closest('tr');
if (!row) return;
const actionBtn = target.closest('.action-btn');
if (actionBtn) {
e.preventDefault();
const id = actionBtn.dataset.id;
const action = actionBtn.dataset.action;
const client = clients.find(c => c.id === id);
if (!client) return;
if (action === 'view-history') {
toggleClientHistory(row, client);
} else if (action === 'edit-client') {
document.getElementById('c-id').value = client.id;
document.getElementById('c-nombre').value = client.nombre;
document.getElementById('c-telefono').value = client.telefono || '';
document.getElementById('c-genero').value = client.genero || '';
document.getElementById('c-cumple').value = client.cumpleaños;
document.getElementById('c-consent').checked = client.consentimiento;
const esOncologicoCheckbox = document.getElementById('c-esOncologico');
const oncologicoFields = document.getElementById('oncologico-fields');
esOncologicoCheckbox.checked = client.esOncologico;
oncologicoFields.classList.toggle('hidden', !client.esOncologico);
document.getElementById('c-oncologoAprueba').checked = client.oncologoAprueba;
document.getElementById('c-nombreMedico').value = client.nombreMedico || '';
document.getElementById('c-telefonoMedico').value = client.telefonoMedico || '';
document.getElementById('c-cedulaMedico').value = client.cedulaMedico || '';
document.getElementById('c-pruebaAprobacion').checked = client.pruebaAprobacion;
} else if (action === 'delete-client') {
deleteClient(id);
}
}
}
async function handleClientForm(e) {
e.preventDefault();
await saveClient();
}
// --- INICIALIZACIÓN ---
async function initializeClientsPage() {
// 1. Verificar autenticación
try {
const response = await fetch('/api/check-auth');
const auth = await response.json();
if (!auth.isAuthenticated) {
window.location.href = '/login.html';
return;
}
} catch (error) {
console.error('Error de autenticación', error);
window.location.href = '/login.html';
return;
}
// 2. Cargar clientes
clients = await load(KEY_CLIENTS, []);
renderClientsTable();
// 3. Añadir manejadores de eventos
formClient?.addEventListener('submit', handleClientForm);
tblClientsBody?.addEventListener('click', handleTableClick);
document.getElementById('btnCancelEditClient')?.addEventListener('click', () => {
formClient.reset();
document.getElementById('c-id').value = '';
document.getElementById('oncologico-fields').classList.add('hidden');
});
document.getElementById('c-esOncologico')?.addEventListener('change', (e) => {
const oncologicoFields = document.getElementById('oncologico-fields');
oncologicoFields.classList.toggle('hidden', !e.target.checked);
});
searchClientInput?.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
const filteredClients = clients.filter(c => c.nombre.toLowerCase().includes(searchTerm));
renderClientsTable(filteredClients);
});
}
document.addEventListener('DOMContentLoaded', initializeClientsPage);