diff --git a/PNG_RECEIPT_PROGRESS.md b/PNG_RECEIPT_PROGRESS.md
new file mode 100644
index 0000000..c641438
--- /dev/null
+++ b/PNG_RECEIPT_PROGRESS.md
@@ -0,0 +1,140 @@
+# 📋 PNG Receipt System - Progress Tracker
+
+## 🎯 Estado del Proyecto
+**Iniciado**: 2025-09-14
+**Estado Actual**: 📝 Planificación Completa
+**Próximo**: 🚀 Fase 1 - Setup
+
+---
+
+## ✅ Tasks Completadas
+
+### 📝 Planificación y Documentación
+- [x] **Análisis del sistema actual** - Comprensión completa de `print.js` y generación de tickets
+- [x] **Identificación de casos de uso** - Matriz completa de combinaciones de tickets
+- [x] **Especificación técnica** - HTML/CSS structure y assets strategy
+- [x] **Plan de desarrollo** - 6 fases con estimaciones detalladas
+- [x] **Documentación README** - Especificación completa en `PNG_RECEIPT_README.md`
+- [x] **Setup de tracking** - Este documento de progreso
+
+**Tiempo Total Planificación**: ~1 hora
+
+---
+
+## 🔄 Tasks en Progreso
+
+*Ninguna actualmente - Listo para comenzar desarrollo*
+
+---
+
+## 📋 Tasks Pendientes
+
+### **FASE 1: Setup con Assets (45 min)**
+- [ ] Crear directorio `/assets/receipt/` y subir assets
+- [ ] Agregar librerías CDN: `html2canvas` + `FileSaver.js`
+- [ ] Crear archivo `receipt.js`
+- [ ] Crear archivo `receipt.css` con estilos base
+- [ ] Crear HTML structure base en memoria
+- [ ] Test de carga de assets y CSS
+
+### **FASE 2: Template Engine + Lógica (60 min)**
+- [ ] Implementar `analyzeTicketType()` function
+- [ ] Crear templates dinámicos por caso
+- [ ] Sistema de mapeo: `movement` → `receiptData`
+- [ ] Función `generateReceiptHTML()`
+- [ ] Test básico de renderizado
+
+### **FASE 3: Contenido Dinámico y Médico (45 min)**
+- [ ] Implementar sección consentimiento médico
+- [ ] Sistema de datos oncológicos (médico, tel, cédula)
+- [ ] Formato de información de citas
+- [ ] Función `getConsentText()`
+- [ ] Test con datos médicos completos
+
+### **FASE 4: Notas Anticipo Inteligentes (30 min)**
+- [ ] Lógica `shouldShowAnticipoNotes()`
+- [ ] Templates condicionales para anticipos
+- [ ] Diferenciación: anticipo puro vs aplicado
+- [ ] Test matrix casos anticipo
+
+### **FASE 5: Generación PNG (30 min)**
+- [ ] Configuración `html2canvas` optimizada
+- [ ] Sistema de nombres: `Ticket_{FOLIO}.png`
+- [ ] Función `downloadReceiptPNG()` principal
+- [ ] Integration con tabla de tickets
+- [ ] Test de descarga y calidad
+
+### **FASE 6: Testing Final (45 min)**
+- [ ] Test exhaustivo con todos los casos
+- [ ] Verificación calidad PNG en diferentes devices
+- [ ] Test compatibilidad navegadores
+- [ ] Performance testing
+- [ ] Documentación final de uso
+
+---
+
+## 🧪 Test Matrix Status
+
+| Caso de Uso | Planificado | Implementado | Testeado |
+|-------------|-------------|--------------|----------|
+| Servicio Simple | ✅ | ❌ | ❌ |
+| Servicio + Cita | ✅ | ❌ | ❌ |
+| Anticipo Puro | ✅ | ❌ | ❌ |
+| Servicio + Anticipo Aplicado | ✅ | ❌ | ❌ |
+| Cliente Consentimiento | ✅ | ❌ | ❌ |
+| Paciente Oncológico | ✅ | ❌ | ❌ |
+| Combo Completo | ✅ | ❌ | ❌ |
+
+---
+
+## 📊 Estimaciones vs Tiempo Real
+
+| Fase | Estimado | Real | Diferencia | Estado |
+|------|----------|------|------------|---------|
+| Planificación | 60min | 60min | ✅ 0min | ✅ Completa |
+| Fase 1: Setup | 45min | -min | - | 📋 Pendiente |
+| Fase 2: Templates | 60min | -min | - | 📋 Pendiente |
+| Fase 3: Contenido Médico | 45min | -min | - | 📋 Pendiente |
+| Fase 4: Anticipo Logic | 30min | -min | - | 📋 Pendiente |
+| Fase 5: PNG Generation | 30min | -min | - | 📋 Pendiente |
+| Fase 6: Testing | 45min | -min | - | 📋 Pendiente |
+| **TOTAL** | **4h 15min** | **1h** | - | 🔄 **En progreso** |
+
+---
+
+## 🚨 Issues y Blockers
+
+*Ninguno identificado actualmente*
+
+---
+
+## 📝 Notas de Desarrollo
+
+### Assets Preparados
+- ✅ Background pattern
+- ✅ Logo corporativo
+- ✅ Business name imagen
+- ✅ Tagline imagen
+- ✅ "Comprobante" título
+- ✅ Rectángulo blanco container
+
+### Decisiones Técnicas
+- **Font**: Montserrat para todo el contenido dinámico
+- **Width**: 400px optimizado para móvil
+- **Format**: PNG con transparencia
+- **Quality**: Scale 2x para HD
+- **Integration**: Botón separado en tabla tickets
+
+---
+
+## 🔄 Updates Log
+
+**2025-09-14**
+- ✅ Proyecto iniciado y planificado completamente
+- ✅ README técnico creado
+- ✅ Sistema de tracking establecido
+- 🎯 Listo para Fase 1 cuando assets estén disponibles
+
+---
+
+**Próximo Update**: Después de completar Fase 1
\ No newline at end of file
diff --git a/PNG_RECEIPT_README.md b/PNG_RECEIPT_README.md
new file mode 100644
index 0000000..8b512b6
--- /dev/null
+++ b/PNG_RECEIPT_README.md
@@ -0,0 +1,312 @@
+# 📱 Sistema de Recibos PNG - Especificación Completa
+
+## 🎯 Objetivo
+Crear un sistema paralelo que genere recibos PNG elegantes para enviar a clientes, manteniendo el sistema de impresión térmica actual intacto.
+
+**Archivo de descarga**: `Ticket_{FOLIO}.png` (ej: `Ticket_AP-k8hcg.png`)
+
+---
+
+## 🎨 Assets Structure
+```
+/assets/receipt/
+├── background.png (fondo decorativo completo)
+├── logo.png (logotipo del negocio)
+├── business-name.png (nombre del negocio)
+├── tagline.png (tagline: "Beauty Expert", etc.)
+├── comprobante-title.png (título "COMPROBANTE DE PAGO")
+└── rectangle-white.png (rectángulo contenedor blanco/transparente)
+```
+
+## 🏗️ Arquitectura del Sistema
+
+### HTML Structure
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+```
+
+### CSS Base
+```css
+@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap');
+
+.receipt-wrapper {
+ width: 400px;
+ font-family: 'Montserrat', sans-serif;
+}
+
+.receipt-background {
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+.receipt-content {
+ background-size: contain;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+.montserrat { font-family: 'Montserrat', sans-serif; }
+.bold { font-weight: 600; }
+.extra-bold { font-weight: 700; }
+```
+
+---
+
+## 🎫 Tipos de Tickets y Casos de Uso
+
+### Matriz de Combinaciones
+| Tipo | Cita | Anticipo | Consentimiento | Oncológico | Notas Especiales |
+|------|------|----------|----------------|------------|------------------|
+| **Servicio Simple** | ❌ | ❌ | ❌ | ❌ | - |
+| **Servicio con Cita** | ✅ | ❌ | ❌ | ❌ | Fecha/Hora |
+| **Anticipo Puro** | ✅ | ✅ | ❌ | ❌ | Notas anticipo |
+| **Servicio + Anticipo Aplicado** | ✅ | ✅ | ❌ | ❌ | **SIN** notas anticipo |
+| **Servicio + Consentimiento** | ✅ | ❌ | ✅ | ❌ | Texto consentimiento |
+| **Servicio + Oncológico** | ✅ | ❌ | ✅ | ✅ | Datos médico |
+| **Combo Completo** | ✅ | ✅ | ✅ | ✅ | Todo combinado |
+
+### Lógica de Detección
+```javascript
+function analyzeTicketType(movement) {
+ return {
+ hasAppointment: !!(movement.fechaCita && movement.horaCita),
+ isAnticipo: movement.tipo === 'Anticipo' ||
+ movement.concepto?.toLowerCase().includes('anticipo'),
+ hasAnticipoApplied: movement.discountInfo?.type === 'anticipo',
+ hasConsent: movement.client?.consentimiento || movement.client?.esOncologico,
+ isOncology: movement.client?.esOncologico,
+ needsAnticipoNotes: function() {
+ // Solo mostrar notas si es anticipo PURO (no aplicado a servicio)
+ return this.isAnticipo && !this.hasAnticipoApplied;
+ }
+ };
+}
+```
+
+### Casos Específicos
+
+#### 1. Anticipo Puro
+- **Mostrar**: Notas de compromiso y cancelación
+- **Condición**: `movement.tipo === 'Anticipo'` Y NO tiene `discountInfo.type === 'anticipo'`
+
+#### 2. Servicio + Anticipo Aplicado
+- **NO mostrar**: Notas de anticipo
+- **Condición**: Servicio normal con descuento de anticipo aplicado
+
+#### 3. Paciente Oncológico
+- **Mostrar**: Datos del médico (nombre, teléfono, cédula)
+- **Condición**: `movement.client.esOncologico === true`
+
+#### 4. Consentimiento Médico
+- **Mostrar**: Texto de consentimiento específico
+- **Condición**: `movement.client.consentimiento === true`
+
+---
+
+## 🔄 Plan de Desarrollo
+
+### **FASE 1: Setup con Assets (45 min)**
+- [ ] Agregar librerías: `html2canvas` + `FileSaver.js`
+- [ ] Crear archivo `receipt.js` para lógica PNG
+- [ ] Crear archivo `receipt.css` para estilos móviles
+- [ ] Setup HTML base con assets structure
+- [ ] Test de carga de assets
+
+### **FASE 2: Template Engine + Lógica de Casos (60 min)**
+- [ ] Sistema de detección de tipos de ticket
+- [ ] Templates dinámicos por caso de uso
+- [ ] Mapeo de datos del movimiento a template
+- [ ] Test de renderizado por tipo
+
+### **FASE 3: Contenido Dinámico y Datos Médicos (45 min)**
+- [ ] Sección de consentimiento médico
+- [ ] Datos de pacientes oncológicos
+- [ ] Información de citas (fecha/hora)
+- [ ] Test con datos médicos reales
+
+### **FASE 4: Notas de Anticipo Inteligentes (30 min)**
+- [ ] Lógica para mostrar/ocultar notas de anticipo
+- [ ] Templates condicionales
+- [ ] Test de casos anticipo vs servicio+anticipo
+
+### **FASE 5: Generación PNG y Descarga (30 min)**
+- [ ] Configuración `html2canvas` optimizada para móvil
+- [ ] Sistema de nombres: `Ticket_{FOLIO}.png`
+- [ ] Función de descarga automática
+- [ ] Test de calidad de imagen
+
+### **FASE 6: Testing Exhaustivo (45 min)**
+- [ ] Test matrix con todos los casos
+- [ ] Verificación de calidad PNG
+- [ ] Compatibilidad navegadores
+- [ ] Test de rendimiento
+
+---
+
+## 🧪 Test Scenarios
+
+```javascript
+const testScenarios = [
+ // Básicos
+ {
+ desc: "Servicio simple",
+ data: { tipo: "service", client: null, fechaCita: null },
+ expect: { noAnticipoNotes: true, noMedicalData: true }
+ },
+
+ {
+ desc: "Servicio con cita",
+ data: { tipo: "service", client: "normal", fechaCita: "2025-01-15", horaCita: "10:00" },
+ expect: { hasAppointment: true }
+ },
+
+ // Anticipos
+ {
+ desc: "Anticipo puro",
+ data: { tipo: "Anticipo" },
+ expect: { hasAnticipoNotes: true }
+ },
+
+ {
+ desc: "Servicio + anticipo aplicado",
+ data: { tipo: "service", discountInfo: { type: "anticipo" }},
+ expect: { hasAnticipoNotes: false }
+ },
+
+ // Médicos
+ {
+ desc: "Paciente oncológico completo",
+ data: {
+ client: {
+ esOncologico: true,
+ nombreMedico: "Dr. Juan",
+ telefonoMedico: "123456",
+ cedulaMedico: "ABC123"
+ }
+ },
+ expect: { hasMedicalData: true, hasConsentText: true }
+ }
+];
+```
+
+---
+
+## 📦 Dependencias Requeridas
+
+### Librerías JavaScript
+```html
+
+
+
+```
+
+### Google Fonts
+```css
+@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap');
+```
+
+---
+
+## 🚀 Integración con Sistema Actual
+
+### Botón en Tabla de Tickets
+```html
+
+
+
+ |
+```
+
+### Función Principal
+```javascript
+async function downloadReceiptPNG(movementId) {
+ try {
+ // 1. Obtener datos del movimiento
+ const movement = await getMovementById(movementId);
+
+ // 2. Analizar tipo de ticket
+ const ticketType = analyzeTicketType(movement);
+
+ // 3. Generar HTML del recibo
+ const receiptHTML = generateReceiptHTML(movement, ticketType);
+
+ // 4. Convertir a PNG
+ const canvas = await html2canvas(receiptHTML, pngConfig);
+
+ // 5. Descargar
+ canvas.toBlob(blob => {
+ saveAs(blob, `Ticket_${movement.folio}.png`);
+ });
+
+ } catch (error) {
+ console.error('Error generating receipt:', error);
+ alert('Error al generar el recibo');
+ }
+}
+```
+
+---
+
+## 📏 Configuración PNG
+
+```javascript
+const pngConfig = {
+ scale: 2, // HD quality
+ width: 400, // Mobile optimal width
+ height: 'auto',
+ backgroundColor: 'transparent', // Usar fondo del asset
+ useCORS: true, // Para assets externos
+ allowTaint: true,
+ ignoreElements: (element) => {
+ // Ignorar elementos que no queremos en el PNG
+ return element.classList?.contains('no-png');
+ }
+};
+```
+
+---
+
+## 🎯 Resultado Final
+
+**✅ Sistema que genera recibos PNG:**
+- Diseño profesional idéntico al mockup
+- Maneja TODOS los casos del negocio
+- Descarga automática con nombre correcto
+- No interfiere con sistema de impresión actual
+- Optimizado para móvil y WhatsApp
+
+**📱 Flujo de Usuario:**
+1. Cliente termina servicio → Se imprime ticket térmico (como siempre)
+2. Si cliente quiere recibo digital → Staff hace clic en "📱 Enviar Recibo"
+3. Se descarga PNG → Staff envía por WhatsApp/email
+
+**Estimación Total: 4 horas de desarrollo + testing**
\ No newline at end of file
diff --git a/app.js b/app.js
index 3824f48..28fadf0 100644
--- a/app.js
+++ b/app.js
@@ -1025,23 +1025,35 @@ function renderTable() {
tr.insertCell().textContent = Number(mov.monto).toFixed(2);
const actionsCell = tr.insertCell();
-
+
+ // Botón de descarga PNG
+ const pngButton = document.createElement('button');
+ pngButton.className = 'action-btn btn-success';
+ pngButton.dataset.id = mov.id;
+ pngButton.dataset.action = 'download-png';
+ pngButton.innerHTML = 'payment';
+ pngButton.title = 'Descargar recibo PNG para compartir';
+ pngButton.style.marginRight = '5px';
+ actionsCell.appendChild(pngButton);
+
// Botón de solicitar cancelación para todos los usuarios
const cancelRequestButton = document.createElement('button');
cancelRequestButton.className = 'action-btn btn-warning';
cancelRequestButton.dataset.id = mov.id;
cancelRequestButton.dataset.action = 'request-cancel';
- cancelRequestButton.textContent = 'Solicitar Cancelación';
+ cancelRequestButton.innerHTML = 'cancel';
+ cancelRequestButton.title = 'Solicitar Cancelación';
cancelRequestButton.style.marginRight = '5px';
actionsCell.appendChild(cancelRequestButton);
-
+
// Solo mostrar botón de eliminar para administradores
if (currentUser && currentUser.role === 'admin') {
const deleteButton = document.createElement('button');
deleteButton.className = 'action-btn btn-danger';
deleteButton.dataset.id = mov.id;
deleteButton.dataset.action = 'delete';
- deleteButton.textContent = 'Eliminar';
+ deleteButton.innerHTML = 'delete';
+ deleteButton.title = 'Eliminar permanentemente';
actionsCell.appendChild(deleteButton);
}
});
@@ -1837,7 +1849,7 @@ function handleTableClick(e) {
const id = actionBtn.dataset.id;
const action = actionBtn.dataset.action;
- if (action === 'reprint' || action === 'delete' || action === 'request-cancel') {
+ if (action === 'reprint' || action === 'delete' || action === 'request-cancel' || action === 'download-png') {
const movement = movements.find(m => m.id === id);
if (movement) {
if (action === 'reprint') {
@@ -1847,6 +1859,8 @@ function handleTableClick(e) {
deleteMovement(id);
} else if (action === 'request-cancel') {
showCancellationRequestModal(id, movement);
+ } else if (action === 'download-png') {
+ downloadPNGReceipt(id, movement);
}
}
} else if (action === 'edit-user') {
@@ -3256,6 +3270,44 @@ function setCorrectClearShortcut() {
}
}
+// --- PNG AND PDF DOWNLOAD FUNCTIONS ---
+function downloadPNGReceipt(movementId, movement) {
+ try {
+ console.log('Downloading PNG receipt for movement:', movementId);
+ console.log('Movement data:', movement);
+
+ // Prepare movement data with client info
+ const client = clients.find(c => c.id === movement.clienteId);
+ console.log('Found client:', client);
+
+ const movementWithClient = {
+ ...movement,
+ client: client || null,
+ cliente: client ? client.nombre : 'Cliente General',
+ telefonoCliente: client ? client.telefono : null
+ };
+
+ console.log('Movement with client:', movementWithClient);
+
+ // Check if required libraries are loaded
+ console.log('html2canvas available:', typeof html2canvas !== 'undefined');
+ console.log('saveAs available:', typeof saveAs !== 'undefined');
+ console.log('pngReceiptGenerator available:', typeof window.pngReceiptGenerator !== 'undefined');
+
+ // Use the PNG receipt generator
+ if (typeof window.pngReceiptGenerator !== 'undefined' && window.pngReceiptGenerator) {
+ console.log('Calling pngReceiptGenerator.downloadReceiptPNG...');
+ window.pngReceiptGenerator.downloadReceiptPNG(movementId, movementWithClient);
+ } else {
+ throw new Error('Sistema PNG no disponible - Verificar que receipt.js esté cargado');
+ }
+ } catch (error) {
+ console.error('Error downloading PNG receipt:', error);
+ alert('Error al generar el recibo PNG: ' + error.message);
+ }
+}
+
+
document.addEventListener('DOMContentLoaded', () => {
initializeApp();
setCorrectClearShortcut();
diff --git a/assets/receipt/background.png b/assets/receipt/background.png
new file mode 100644
index 0000000..35cc32f
Binary files /dev/null and b/assets/receipt/background.png differ
diff --git a/assets/receipt/comprobante-title.svg b/assets/receipt/comprobante-title.svg
new file mode 100644
index 0000000..1b21f7f
--- /dev/null
+++ b/assets/receipt/comprobante-title.svg
@@ -0,0 +1,171 @@
+
+
diff --git a/assets/receipt/isotipo.svg b/assets/receipt/isotipo.svg
new file mode 100644
index 0000000..0033bdf
--- /dev/null
+++ b/assets/receipt/isotipo.svg
@@ -0,0 +1,145 @@
+
+
diff --git a/assets/receipt/logo.svg b/assets/receipt/logo.svg
new file mode 100644
index 0000000..06b7967
--- /dev/null
+++ b/assets/receipt/logo.svg
@@ -0,0 +1,387 @@
+
+
diff --git a/index.html b/index.html
index eeb9db0..caec25d 100644
--- a/index.html
+++ b/index.html
@@ -15,8 +15,10 @@
+
+
@@ -775,6 +777,10 @@
+
+
+
+