diff --git a/.gitignore b/.gitignore
index 28b7f83..64b7d7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@ dev-tasks.md
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+devtastks
diff --git a/app.js b/app.js
index 6258ac0..5542217 100644
--- a/app.js
+++ b/app.js
@@ -14,7 +14,18 @@ function escapeHTML(str) {
.replace(/'/g, ''');
}
-const APP_VERSION = '1.0.0';
+function formatDate(dateString) {
+ if (!dateString) return '';
+ const date = new Date(dateString);
+ const userTimezoneOffset = date.getTimezoneOffset() * 60000;
+ const adjustedDate = new Date(date.getTime() + userTimezoneOffset);
+ const day = String(adjustedDate.getDate()).padStart(2, '0');
+ const month = String(adjustedDate.getMonth() + 1).padStart(2, '0');
+ const year = adjustedDate.getFullYear();
+ return `${day}/${month}/${year}`;
+}
+
+const APP_VERSION = '1.3.0';
// --- ESTADO Y DATOS ---
const DEFAULT_SETTINGS = {
@@ -62,7 +73,6 @@ let isDashboardLoading = false;
// --- LÓGICA DE NEGOCIO ---
async function loadDashboardData() {
- // Guardia para prevenir ejecuciones múltiples y re-entradas.
if (currentUser.role !== 'admin' || isDashboardLoading) {
return;
}
@@ -76,45 +86,39 @@ async function loadDashboardData() {
} else {
throw new Error('Falló la carga de datos del dashboard');
}
- return; // Salir aquí después de manejar el error
+ return;
}
const data = await response.json();
- // Antes de actualizar, verificar que el dashboard sigue activo.
const dashboardTab = document.getElementById('tab-dashboard');
if (!dashboardTab.classList.contains('active')) {
return;
}
- // Actualizar tarjetas de estadísticas
document.getElementById('stat-total-income').textContent = `${Number(data.totalIncome || 0).toFixed(2)}`;
document.getElementById('stat-total-movements').textContent = data.totalMovements || 0;
- // Actualizar datos del gráfico de ingresos
if (incomeChart) {
incomeChart.data.labels = data.incomeByService.map(item => item.tipo);
incomeChart.data.datasets[0].data = data.incomeByService.map(item => item.total);
incomeChart.update('none');
}
- // Actualizar datos del gráfico de método de pago
if (paymentMethodChart) {
paymentMethodChart.data.labels = data.incomeByPaymentMethod.map(item => item.metodo);
paymentMethodChart.data.datasets[0].data = data.incomeByPaymentMethod.map(item => item.total);
paymentMethodChart.update('none');
}
- // Renderizar próximas citas
if (appointmentsList) {
appointmentsList.innerHTML = '';
if (data.upcomingAppointments.length > 0) {
data.upcomingAppointments.forEach(appt => {
const item = document.createElement('div');
item.className = 'appointment-item';
- const fechaCita = new Date(appt.fechaCita + 'T00:00:00').toLocaleDateString('es-MX', { day: '2-digit', month: 'long' });
item.innerHTML = `
${appt.clienteNombre}
- ${fechaCita} - ${appt.horaCita}
+ ${formatDate(appt.fechaCita)} - ${appt.horaCita}
`;
appointmentsList.appendChild(item);
});
@@ -126,7 +130,6 @@ async function loadDashboardData() {
} catch (error) {
console.error('Error al cargar el dashboard:', error);
} finally {
- // Asegurar que el bloqueo se libere sin importar el resultado.
isDashboardLoading = false;
}
}
@@ -182,12 +185,11 @@ async function saveClient(clientData) {
await save('clients', { client: clientToSave });
- // Optimización: en lugar de recargar, actualizamos el estado local.
if (isUpdate) {
const index = clients.findIndex(c => c.id === clientToSave.id);
if (index > -1) clients[index] = clientToSave;
} else {
- clients.unshift(clientToSave); // Añadir al principio para que aparezca primero
+ clients.unshift(clientToSave);
}
renderClientsTable();
@@ -251,7 +253,6 @@ function renderTable() {
const client = clients.find(c => c.id === mov.clienteId);
const tr = tblMovesBody.insertRow();
- const fechaCita = mov.fechaCita ? new Date(mov.fechaCita + 'T00:00:00').toLocaleDateString('es-MX') : '';
const tipoServicio = mov.subtipo ? `${escapeHTML(mov.tipo)} (${escapeHTML(mov.subtipo)})` : escapeHTML(mov.tipo);
const folioCell = tr.insertCell();
@@ -263,8 +264,8 @@ function renderTable() {
folioLink.textContent = mov.folio;
folioCell.appendChild(folioLink);
- tr.insertCell().textContent = new Date(mov.fechaISO).toLocaleDateString('es-MX');
- tr.insertCell().textContent = `${fechaCita} ${mov.horaCita || ''}`;
+ tr.insertCell().textContent = formatDate(mov.fechaISO);
+ tr.insertCell().textContent = `${formatDate(mov.fechaCita)} ${mov.horaCita || ''}`.trim();
tr.insertCell().textContent = client ? escapeHTML(client.nombre) : 'Cliente Eliminado';
tr.insertCell().textContent = tipoServicio;
tr.insertCell().textContent = Number(mov.monto).toFixed(2);
@@ -416,7 +417,7 @@ async function handleSaveCredentials(e) {
if (response.ok) {
alert('Credenciales actualizadas.');
- currentUser.name = name; // Actualizar el nombre en el estado local
+ currentUser.name = name;
currentUser.username = username;
document.getElementById('s-password').value = '';
} else {
@@ -546,9 +547,6 @@ async function deleteProduct(id) {
if (response.ok) {
products = products.filter(p => p.id !== id);
renderProductTables();
- } else {
- const error = await response.json();
- alert(`Error: ${error.error}`);
}
} catch (error) {
alert('Error de conexión al eliminar el producto.');
@@ -680,7 +678,7 @@ async function handleNewMovement(e) {
monto: Number(monto.toFixed(2)),
metodo: document.getElementById('m-metodo').value,
concepto: document.getElementById('m-articulo').value,
- staff: currentUser.name, // Usar el nombre del usuario actual
+ staff: currentUser.name,
notas: document.getElementById('m-notas').value,
fechaCita: document.getElementById('m-fecha-cita').value,
horaCita: document.getElementById('m-hora-cita').value,
@@ -696,12 +694,11 @@ async function handleNewMovement(e) {
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}"`,
+ formatDate(mov.fechaISO),
+ `"${servicio}"`,
Number(mov.monto).toFixed(2)
].join(',');
});
@@ -732,11 +729,10 @@ async function showClientRecord(clientId) {
const clientHistoryTableBody = document.getElementById('client-history-table').querySelector('tbody');
const clientCoursesContainer = document.getElementById('client-courses-history-container');
- // Sanitize client details before rendering
clientDetails.innerHTML = `
Nombre: ${escapeHTML(client.nombre)}
Teléfono: ${escapeHTML(client.telefono || 'N/A')}
- Cumpleaños: ${escapeHTML(client.cumpleaños ? new Date(client.cumpleaños + 'T00:00:00').toLocaleDateString('es-MX') : 'N/A')}
+ Cumpleaños: ${escapeHTML(formatDate(client.cumpleaños) || 'N/A')}
Género: ${escapeHTML(client.genero || 'N/A')}
Oncológico: ${client.esOncologico ? 'Sí' : 'No'}
`;
@@ -754,10 +750,9 @@ async function showClientRecord(clientId) {
if (history.length > 0) {
history.forEach(mov => {
const tr = clientHistoryTableBody.insertRow();
- const fecha = new Date(mov.fechaISO).toLocaleDateString('es-MX');
const servicio = mov.subtipo ? `${escapeHTML(mov.tipo)} (${escapeHTML(mov.subtipo)})` : escapeHTML(mov.tipo);
tr.insertCell().textContent = mov.folio;
- tr.insertCell().textContent = fecha;
+ tr.insertCell().textContent = formatDate(mov.fechaISO);
tr.insertCell().textContent = servicio;
tr.insertCell().textContent = Number(mov.monto).toFixed(2);
});
@@ -784,7 +779,7 @@ async function showClientRecord(clientId) {
${courses.map(course => `
${escapeHTML(course.course_name)}
- ${escapeHTML(course.fecha_curso)}
+ ${escapeHTML(formatDate(course.fecha_curso))}
${escapeHTML(course.score_general)}
${course.completo_presencial ? 'Sí' : 'No'}
${course.completo_online ? 'Sí' : 'No'}
@@ -876,18 +871,15 @@ function handleTableClick(e) {
async function handleClientForm(e) {
e.preventDefault();
await saveClient();
- // Después de guardar, cambiar a la pestaña de consulta
activateClientSubTab('sub-tab-consult');
}
function activateClientSubTab(subTabId) {
if (!subTabId) return;
- // Desactivar todas las sub-pestañas y contenidos de clientes
document.querySelectorAll('#tab-clients .sub-tab-link').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('#tab-clients .sub-tab-content').forEach(content => content.classList.remove('active'));
- // Activar la sub-pestaña y el contenido correctos
const tabButton = document.querySelector(`[data-subtab="${subTabId}"]`);
const tabContent = document.getElementById(subTabId);
@@ -910,11 +902,9 @@ function handleClientTabChange(e) {
function activateTab(tabId) {
if (!tabId) return;
- // Desactivar todas las pestañas y contenidos
document.querySelectorAll('.tab-link').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
- // Activar la pestaña y el contenido correctos
const tabButton = document.querySelector(`[data-tab="${tabId}"]`);
const tabContent = document.getElementById(tabId);
@@ -925,9 +915,7 @@ function activateTab(tabId) {
tabContent.classList.add('active');
}
- // Cargar datos dinámicos si es la pestaña del dashboard
if (tabId === 'tab-dashboard' && currentUser.role === 'admin') {
- // Si es la primera vez que se visita la pestaña, inicializar el gráfico
if (!incomeChart) {
const ctx = document.getElementById('incomeChart').getContext('2d');
incomeChart = new Chart(ctx, {
@@ -966,7 +954,6 @@ function activateTab(tabId) {
}
});
}
- // Cargar (o recargar) los datos del dashboard
loadDashboardData();
}
}
@@ -975,13 +962,11 @@ function handleTabChange(e) {
const tabButton = e.target.closest('.tab-link');
if (!tabButton) return;
- // Solo prevenir el comportamiento por defecto si es un botón para cambiar de pestaña
if (tabButton.dataset.tab) {
e.preventDefault();
const tabId = tabButton.dataset.tab;
activateTab(tabId);
}
- // Si no tiene data-tab (es un enlace normal como el de Clientes), no hacer nada y permitir la navegación.
}
function handleTestTicket() {
@@ -1011,11 +996,13 @@ function setupUIForRole(role) {
const settingsTab = document.querySelector('[data-tab="settings"]');
const userManagementSection = document.getElementById('user-management-section');
const staffInput = document.getElementById('m-staff');
+ const dbInfoIcon = document.getElementById('db-info-icon');
if (role === 'admin') {
if (dashboardTab) dashboardTab.style.display = 'block';
if (settingsTab) settingsTab.style.display = 'block';
if (userManagementSection) userManagementSection.style.display = 'block';
+ if (dbInfoIcon) dbInfoIcon.style.display = 'inline-block';
fetch('/api/users')
.then(res => {
@@ -1031,6 +1018,7 @@ function setupUIForRole(role) {
if (dashboardTab) dashboardTab.style.display = 'none';
if (settingsTab) settingsTab.style.display = 'none';
if (userManagementSection) userManagementSection.style.display = 'none';
+ if (dbInfoIcon) dbInfoIcon.style.display = 'none';
}
if (staffInput) {
@@ -1043,7 +1031,7 @@ function populateFooter() {
const versionElement = document.getElementById('footer-version');
if (dateElement) {
- dateElement.textContent = new Date().toLocaleDateString('es-MX', { year: 'numeric', month: 'long', day: 'numeric' });
+ dateElement.textContent = formatDate(new Date().toISOString());
}
if (versionElement) {
versionElement.textContent = `Versión ${APP_VERSION}`;
@@ -1054,17 +1042,14 @@ function populateFooter() {
// --- INICIALIZACIÓN ---
async function initializeApp() {
- // 1. Verificar autenticación y obtener datos del usuario.
let userResponse;
try {
userResponse = await fetch('/api/user');
if (!userResponse.ok) {
- // Si la respuesta no es 2xx, el usuario no está autenticado o hay un error.
window.location.href = '/login.html';
return;
}
- // Verificar que la respuesta sea JSON antes de procesarla.
const contentType = userResponse.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
console.error('La respuesta del servidor no es JSON. Redirigiendo al login.');
@@ -1072,22 +1057,20 @@ async function initializeApp() {
return;
}
- // 2. Procesar datos del usuario.
currentUser = await userResponse.json();
} catch (error) {
- // Si hay un error de red, es probable que el servidor no esté corriendo.
console.error('Error de conexión al verificar la autenticación. Redirigiendo al login.', error);
window.location.href = '/login.html';
return;
}
- // 3. Añadir manejadores de eventos.
const tabs = document.querySelector('.tabs');
const btnLogout = document.getElementById('btnLogout');
const btnCancelEditUser = document.getElementById('btnCancelEditUser');
const tipoServicioSelect = document.getElementById('m-tipo');
const clientSubTabs = document.querySelector('#tab-clients .sub-tabs');
+ const dbInfoIcon = document.getElementById('db-info-icon');
formSettings?.addEventListener('submit', handleSaveSettings);
formCredentials?.addEventListener('submit', handleSaveCredentials);
@@ -1107,6 +1090,9 @@ async function initializeApp() {
if (currentUser.role === 'admin') {
formAddUser?.addEventListener('submit', handleAddOrUpdateUser);
tblUsersBody?.addEventListener('click', handleTableClick);
+ dbInfoIcon?.addEventListener('click', () => {
+ document.getElementById('db-instructions').classList.toggle('hidden');
+ });
}
btnLogout?.addEventListener('click', async () => {
@@ -1193,7 +1179,6 @@ async function initializeApp() {
showAddCourseModal(clientId);
});
- // 4. Cargar el resto de los datos de la aplicación.
Promise.all([
load(KEY_SETTINGS, DEFAULT_SETTINGS),
load(KEY_DATA, []),
@@ -1213,7 +1198,6 @@ async function initializeApp() {
renderProductTables();
console.log('Updating client datalist...');
updateClientDatalist();
- // Initial population of the articulo dropdown
populateArticuloDropdown(document.getElementById('m-categoria').value);
if (currentUser) {
@@ -1234,7 +1218,7 @@ async function initializeApp() {
}
console.log('Activating client sub-tab...');
- activateClientSubTab('sub-tab-consult');
+ activateClientSubTab('sub-tab-register');
console.log('Clearing client record...');
clearClientRecord();
console.log('Populating footer...');
@@ -1247,5 +1231,4 @@ async function initializeApp() {
});
}
-
-document.addEventListener('DOMContentLoaded', initializeApp);
\ No newline at end of file
+document.addEventListener('DOMContentLoaded', initializeApp);
diff --git a/clients.js b/clients.js
index cefc315..eb03e1b 100644
--- a/clients.js
+++ b/clients.js
@@ -2,6 +2,18 @@ import { load, save, remove, KEY_CLIENTS } from './storage.js';
let clients = [];
+// --- UTILITIES ---
+function formatDate(dateString) {
+ if (!dateString) return '';
+ const date = new Date(dateString);
+ const userTimezoneOffset = date.getTimezoneOffset() * 60000;
+ const adjustedDate = new Date(date.getTime() + userTimezoneOffset);
+ const day = String(adjustedDate.getDate()).padStart(2, '0');
+ const month = String(adjustedDate.getMonth() + 1).padStart(2, '0');
+ const year = adjustedDate.getFullYear();
+ return `${day}/${month}/${year}`;
+}
+
// --- DOM ELEMENTS ---
const formClient = document.getElementById('formClient');
const tblClientsBody = document.getElementById('tblClients')?.querySelector('tbody');
@@ -64,11 +76,10 @@ async function deleteClient(id) {
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,
+ formatDate(mov.fechaISO),
`"${servicio}"`, // Corrected: escaped inner quotes for CSV compatibility
Number(mov.monto).toFixed(2)
].join(',');
@@ -101,19 +112,18 @@ async function toggleClientHistory(row, client) {
historyRow.id = historyRowId;
historyRow.className = 'client-history-row';
- let historyHtml = '
+ let historyHtml = `
- ';
+ `;
if (history.length > 0) {
historyHtml += 'Folio Fecha Servicio Monto ';
history.forEach(mov => {
- const fecha = new Date(mov.fechaISO).toLocaleDateString('es-MX');
const servicio = mov.subtipo ? `${mov.tipo} (${mov.subtipo})` : mov.tipo;
- historyHtml += `${mov.folio} ${fecha} ${servicio} ${Number(mov.monto).toFixed(2)} `;
+ historyHtml += `${mov.folio} ${formatDate(mov.fechaISO)} ${servicio} ${Number(mov.monto).toFixed(2)} `;
});
historyHtml += '
';
} else {
@@ -145,16 +155,16 @@ function renderClientsTable(clientList = clients) {
clientList.forEach(c => {
const tr = document.createElement('tr');
tr.dataset.id = c.id;
- tr.innerHTML = '
- ' + c.nombre + '
- ' + (c.telefono || '') + '
- ' + (c.esOncologico ? 'Sí' : 'No') + '
+ tr.innerHTML = `
+ ${c.nombre}
+ ${c.telefono || ''}
+ ${c.esOncologico ? 'Sí' : 'No'}
- Historial
- Editar
- Eliminar
+ Historial
+ Editar
+ Eliminar
- ';
+ `;
tblClientsBody.appendChild(tr);
});
}
@@ -209,7 +219,6 @@ async function handleClientForm(e) {
// --- INICIALIZACIÓN ---
async function initializeClientsPage() {
- // 1. Verificar autenticación
try {
const response = await fetch('/api/check-auth');
const auth = await response.json();
@@ -223,11 +232,9 @@ async function initializeClientsPage() {
return;
}
- // 2. Cargar clientes
clients = await load(KEY_CLIENTS, []);
renderClientsTable();
- // 3. Añadir manejadores de eventos
formClient?.addEventListener('submit', handleClientForm);
tblClientsBody?.addEventListener('click', handleTableClick);
@@ -249,4 +256,4 @@ async function initializeClientsPage() {
});
}
-document.addEventListener('DOMContentLoaded', initializeClientsPage);
+document.addEventListener('DOMContentLoaded', initializeClientsPage);
\ No newline at end of file
diff --git a/index.html b/index.html
index de7be27..d347eb4 100644
--- a/index.html
+++ b/index.html
@@ -372,12 +372,6 @@
-
-
Ubicación de los Datos
-
- 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.
-
-
Mis Credenciales
+
+
Ubicación y Exportación de Datos
+ help_outline
+
+
+
Toda la información de tu negocio (clientes, recibos, configuración) se guarda localmente. Para respaldar tus datos, simplemente copia el archivo correspondiente.
+
Para usuarios avanzados, la base de datos (ap-pos.db) se encuentra dentro del contenedor de la aplicación. Puedes acceder a ella usando los comandos estándar de Docker para copiar el archivo.
+
+
+
diff --git a/print.js b/print.js
index 41fe9ce..0bc5721 100644
--- a/print.js
+++ b/print.js
@@ -4,15 +4,28 @@
* @returns {string} El string escapado.
*/
function esc(str) {
- return String(str || '').replace(/[&<>"']/g, c => ({
+ return String(str || '').replace(/[&<>"'/]/g, c => ({
"&": "&",
"<": "<",
">": ">",
- "\"": """,
+ '"': '"',
"'": "'"
}[c]));
}
+function formatDate(dateString) {
+ if (!dateString) return '';
+ const date = new Date(dateString);
+ const userTimezoneOffset = date.getTimezoneOffset() * 60000;
+ const adjustedDate = new Date(date.getTime() + userTimezoneOffset);
+ const day = String(adjustedDate.getDate()).padStart(2, '0');
+ const month = String(adjustedDate.getMonth() + 1).padStart(2, '0');
+ const year = adjustedDate.getFullYear();
+ const hours = String(adjustedDate.getHours()).padStart(2, '0');
+ const minutes = String(adjustedDate.getMinutes()).padStart(2, '0');
+ return `${day}/${month}/${year} ${hours}:${minutes}`;
+}
+
/**
* Genera el HTML para un ticket de movimiento.
* @param {object} mov El objeto del movimiento.
@@ -20,8 +33,7 @@ function esc(str) {
* @returns {string} El HTML del ticket.
*/
function templateTicket(mov, settings) {
- const dt = new Date(mov.fechaISO || Date.now());
- const fechaLocal = dt.toLocaleString('es-MX', { dateStyle: 'medium', timeStyle: 'short' });
+ const fechaLocal = formatDate(mov.fechaISO || Date.now());
const montoFormateado = Number(mov.monto).toFixed(2);
const tipoServicio = mov.subtipo === 'Retoque' ? `Retoque de ${mov.tipo}` : mov.tipo;
@@ -50,7 +62,6 @@ function templateTicket(mov, settings) {
lines.push('
');
lines.push(`Total ${montoFormateado}
`);
- // Sección de consentimientos
if (mov.client && (mov.client.esOncologico || mov.client.consentimiento)) {
lines.push('
');
if (mov.client.esOncologico) {
@@ -66,7 +77,6 @@ function templateTicket(mov, settings) {
if (settings.leyenda) lines.push(``);
- // Sección de Encuesta con QR
lines.push('');
lines.push('
¡Tu opinión es muy importante!
');
lines.push('
Escanea el código QR para darnos tu feedback.
');
@@ -91,24 +101,18 @@ export async function renderTicketAndPrint(mov, settings) {
}
try {
- // 1. Renderizar la estructura HTML del ticket
printArea.innerHTML = templateTicket(mov, settings);
- // 2. Encontrar el elemento canvas para el QR
const canvas = document.getElementById('qr-canvas');
if (!canvas) {
console.error("El canvas del QR #qr-canvas no se encontró. Se imprimirá sin QR.");
- window.print(); // Imprimir sin QR de inmediato
+ window.print();
return;
}
- // 3. Generar el código QR
const qrUrl = 'http://vanityexperience.mx/qr';
await QRCode.toCanvas(canvas, qrUrl, { width: 140, margin: 1 });
- // 4. Llamar a la impresión.
- // El `await` anterior asegura que el QR ya está renderizado.
- // Un pequeño timeout puede seguir siendo útil para asegurar que el navegador "pinte" el canvas en la pantalla.
requestAnimationFrame(() => window.print());
} catch (error) {
@@ -116,3 +120,31 @@ export async function renderTicketAndPrint(mov, settings) {
alert(`Ocurrió un error al preparar la impresión: ${error.message}. Revise la consola para más detalles.`);
}
}
+
+
+document.addEventListener('DOMContentLoaded', () => {
+ const btnTestTicket = document.getElementById('btnTestTicket');
+ if (btnTestTicket) {
+ btnTestTicket.addEventListener('click', () => {
+ const demoMovement = {
+ id: 'demo',
+ folio: 'DEMO-000001',
+ fechaISO: new Date().toISOString(),
+ client: {
+ nombre: 'Cliente de Prueba',
+ esOncologico: true,
+ nombreMedico: 'Dr. Juan Pérez',
+ telefonoMedico: '5512345678',
+ cedulaMedico: '1234567'
+ },
+ tipo: 'Pago',
+ monto: 123.45,
+ metodo: 'Efectivo',
+ concepto: 'Producto de demostración',
+ staff: 'Admin',
+ notas: 'Esta es una impresión de prueba.'
+ };
+ renderTicketAndPrint(demoMovement, window.settings || {});
+ });
+ }
+});
\ No newline at end of file
diff --git a/styles.css b/styles.css
index 77b2e1f..2a73c62 100644
--- a/styles.css
+++ b/styles.css
@@ -154,39 +154,6 @@ button:hover {
border-radius: 5px;
}
-#tblMoves, #tblClients, #tblUsers {
- width: 100%;
- border-collapse: collapse;
- margin: 0;
-}
-
-#tblMoves th, #tblMoves td,
-#tblClients th, #tblClients td,
-#tblUsers th, #tblUsers td {
- border-bottom: 1px solid #dee2e6;
- padding: 12px 15px;
- text-align: left;
- white-space: nowrap;
-}
-#tblMoves td:last-child, #tblMoves th:last-child,
-#tblClients td:last-child, #tblClients th:last-child,
-#tblUsers td:last-child, #tblUsers th:last-child {
- text-align: center;
-}
-
-#tblMoves th, #tblClients th, #tblUsers th {
- background-color: #f8f9fa;
- font-family: 'Montserrat', sans-serif;
- font-weight: 500;
- border-bottom-width: 2px;
-}
-
-#tblMoves tbody tr:last-child td,
-#tblClients tbody tr:last-child td,
-#tblUsers tbody tr:last-child td {
- border-bottom: none;
-}
-
.action-btn {
background: none;
border: none;
@@ -530,6 +497,17 @@ table tbody tr:hover {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}
+.info-icon {
+ font-size: 18px;
+ color: #6c757d;
+ cursor: pointer;
+ vertical-align: middle;
+ margin-left: 8px;
+}
+.info-icon:hover {
+ color: #343a40;
+}
+
.sub-section {
margin-top: 30px;
padding-top: 20px;