mirror of
https://github.com/marcogll/ap_pos.git
synced 2026-01-13 21:25:16 +00:00
- Added comprehensive discount detection logic in hasAnyDiscount() - Created extractDiscountInfo() to handle multiple data sources - Updated all discount rendering functions to use new extraction logic - Enhanced support for manual anticipos and applied discounts - Improved fallback detection using subtotal vs monto differences - Added Material Symbols icons to action buttons in table - Fixed discount display issues in PNG receipt generation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
9.0 KiB
9.0 KiB
📱 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
<div class="receipt-wrapper">
<!-- Fondo decorativo -->
<div class="receipt-background" style="background-image: url('/assets/receipt/background.png')">
<!-- Container principal -->
<div class="receipt-container">
<!-- Header con assets -->
<div class="receipt-header">
<img src="/assets/receipt/logo.png" class="logo-img">
<img src="/assets/receipt/business-name.png" class="business-name-img">
<img src="/assets/receipt/tagline.png" class="tagline-img">
</div>
<!-- Contenido sobre rectángulo blanco -->
<div class="receipt-content" style="background-image: url('/assets/receipt/rectangle-white.png')">
<img src="/assets/receipt/comprobante-title.png" class="title-img">
<!-- Todo el contenido dinámico con Montserrat -->
<div class="dynamic-content montserrat">
<!-- Datos generados dinámicamente -->
</div>
</div>
</div>
</div>
</div>
CSS Base
@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
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 tienediscountInfo.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.jspara lógica PNG - Crear archivo
receipt.csspara 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
html2canvasoptimizada 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
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
<!-- En index.html -->
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script>
Google Fonts
@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
<!-- Agregar columna extra en tabla de movimientos -->
<td>
<button onclick="downloadReceiptPNG('${mov.id}')" class="btn-receipt">
📱 Enviar Recibo
</button>
</td>
Función Principal
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
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:
- Cliente termina servicio → Se imprime ticket térmico (como siempre)
- Si cliente quiere recibo digital → Staff hace clic en "📱 Enviar Recibo"
- Se descarga PNG → Staff envía por WhatsApp/email
Estimación Total: 4 horas de desarrollo + testing