/**
* Escapa caracteres HTML para prevenir XSS.
* @param {string} str El string a escapar.
* @returns {string} El string escapado.
*/
function esc(str) {
return String(str || '').replace(/[&<>"']/g, c => ({
"&": "&",
"<": "<",
">": ">",
"\"": """,
"'": "'"
}[c]));
}
/**
* Genera el HTML para un ticket de movimiento.
* @param {object} mov El objeto del movimiento.
* @param {object} settings El objeto de configuración.
* @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 montoFormateado = Number(mov.monto).toFixed(2);
const tipoServicio = mov.subtipo === 'Retoque' ? `Retoque de ${mov.tipo}` : mov.tipo;
const lines = [];
lines.push('
');
lines.push('

');
if (settings.negocio) lines.push(`
${esc(settings.negocio)}
`);
if (settings.tagline) lines.push(`
${esc(settings.tagline)}
`);
if (settings.rfc) lines.push(`
RFC: ${esc(settings.rfc)}
`);
if (settings.sucursal) lines.push(`
${esc(settings.sucursal)}
`);
if (settings.tel) lines.push(`
Tel: ${esc(settings.tel)}
`);
lines.push('
');
lines.push(`
Folio:${esc(mov.folio)}
`);
lines.push(`
Fecha:${esc(fechaLocal)}
`);
lines.push('
');
lines.push(`
${esc(tipoServicio)}
`);
if (mov.client) lines.push(`
Cliente: ${esc(mov.client.nombre)}
`);
if (mov.concepto) lines.push(`
Concepto: ${esc(mov.concepto)}
`);
if (mov.staff) lines.push(`
Te atendió: ${esc(mov.staff)}
`);
if (mov.metodo) lines.push(`
Método: ${esc(mov.metodo)}
`);
if (mov.notas) lines.push(`
Notas: ${esc(mov.notas)}
`);
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) {
lines.push('
Consentimiento Oncológico
');
lines.push(`
El cliente declara ser paciente oncológico y que la información de su médico es veraz.
`);
if (mov.client.nombreMedico) lines.push(`
Médico: ${esc(mov.client.nombreMedico)}
`);
if (mov.client.telefonoMedico) lines.push(`
Tel. Médico: ${esc(mov.client.telefonoMedico)}
`);
if (mov.client.cedulaMedico) lines.push(`
Cédula: ${esc(mov.client.cedulaMedico)}
`);
}
lines.push('
');
lines.push(`
Al consentir el servicio, declara que la información médica proporcionada es veraz.
`);
}
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.
');
lines.push('
');
lines.push('
');
lines.push('
');
return lines.join('');
}
/**
* Renderiza el ticket en el DOM, genera el QR y llama a la función de impresión.
* @param {object} mov El objeto del movimiento.
* @param {object} settings El objeto de configuración.
*/
export async function renderTicketAndPrint(mov, settings) {
const printArea = document.getElementById('printArea');
if (!printArea) {
console.error("El área de impresión #printArea no se encontró.");
alert("Error: No se encontró el área de impresión. Contacte al soporte.");
return;
}
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
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) {
console.error("Error al intentar imprimir:", error);
alert(`Ocurrió un error al preparar la impresión: ${error.message}. Revise la consola para más detalles.`);
}
}