valtech-components 4.0.167 → 4.0.168

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
56
56
  * Current version of valtech-components.
57
57
  * This is automatically updated during the publish process.
58
58
  */
59
- const VERSION = '4.0.167';
59
+ const VERSION = '4.0.168';
60
60
 
61
61
  // Control de estado de refresco (singleton a nivel de módulo)
62
62
  let isRefreshing = false;
@@ -61516,14 +61516,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
61516
61516
  // Use LocaleService for language management instead
61517
61517
 
61518
61518
  const CANVAS_WIDTH = 560;
61519
- const HEADER_H = 160;
61520
- const QR_ZONE_H = 380;
61521
- const FOOTER_H = 240;
61522
- const CANVAS_HEIGHT = HEADER_H + QR_ZONE_H + FOOTER_H;
61523
- const QR_SIZE = 280;
61519
+ const HEADER_H = 210;
61520
+ const QR_ZONE_H = 340;
61521
+ const PERF_H = 28;
61522
+ const FOOTER_H = 220;
61523
+ const CANVAS_HEIGHT = HEADER_H + QR_ZONE_H + PERF_H + FOOTER_H;
61524
+ const QR_SIZE = 260;
61524
61525
  const DEFAULT_BRAND_COLOR = '#9b1fde';
61525
61526
  const DEFAULT_BRAND_COLOR_TO = '#c93075';
61526
- const FONT_STACK = 'Khula, Arial, sans-serif';
61527
+ const FONT_STACK = '"Plus Jakarta Sans", Khula, Arial, sans-serif';
61527
61528
  class TicketCardImageService {
61528
61529
  async generate(config) {
61529
61530
  const width = config.width ?? CANVAS_WIDTH;
@@ -61541,6 +61542,7 @@ class TicketCardImageService {
61541
61542
  }
61542
61543
  await this.drawHeader(ctx, config, width);
61543
61544
  await this.drawQrZone(ctx, config, width);
61545
+ this.drawPerforated(ctx, config, width);
61544
61546
  this.drawFooter(ctx, config, width);
61545
61547
  if (canvas instanceof OffscreenCanvas) {
61546
61548
  return canvas.convertToBlob({ type: 'image/png' });
@@ -61594,31 +61596,41 @@ class TicketCardImageService {
61594
61596
  grad.addColorStop(1, colorTo);
61595
61597
  ctx.fillStyle = grad;
61596
61598
  ctx.fillRect(0, 0, width, HEADER_H);
61597
- const logoH = 44;
61598
- const logoPad = 28;
61599
- let textY = HEADER_H / 2;
61599
+ const logoH = 48;
61600
+ const logoPadTop = 28;
61601
+ let cursorY = logoPadTop;
61600
61602
  if (config.logoUrl) {
61601
61603
  try {
61602
61604
  const logoImg = await this.loadImage(config.logoUrl);
61603
61605
  const ratio = logoImg.width / logoImg.height;
61604
61606
  const logoW = logoH * ratio;
61605
- const logoX = logoPad;
61606
- const logoY = (HEADER_H - logoH) / 2;
61607
- ctx.drawImage(logoImg, logoX, logoY, logoW, logoH);
61608
- if (config.brandName) {
61609
- textY = logoY + logoH + 14;
61610
- }
61607
+ const logoX = (width - logoW) / 2;
61608
+ ctx.drawImage(logoImg, logoX, cursorY, logoW, logoH);
61609
+ cursorY += logoH + 14;
61611
61610
  }
61612
61611
  catch {
61613
- // logo failed — render brand name centered as fallback
61612
+ // logo failed — continue without it
61614
61613
  }
61615
61614
  }
61616
61615
  if (config.brandName) {
61617
61616
  ctx.fillStyle = '#ffffff';
61618
- ctx.font = `bold 1.75rem ${FONT_STACK}`;
61617
+ ctx.font = `bold 1.625rem ${FONT_STACK}`;
61618
+ ctx.textAlign = 'center';
61619
+ ctx.textBaseline = 'top';
61620
+ ctx.fillText(config.brandName, width / 2, cursorY, width - 48);
61621
+ cursorY += 32;
61622
+ }
61623
+ const metaParts = [];
61624
+ if (config.eventDate)
61625
+ metaParts.push(config.eventDate);
61626
+ if (config.eventLocation)
61627
+ metaParts.push(config.eventLocation);
61628
+ if (metaParts.length > 0) {
61629
+ ctx.fillStyle = 'rgba(255,255,255,0.78)';
61630
+ ctx.font = `0.875rem ${FONT_STACK}`;
61619
61631
  ctx.textAlign = 'center';
61620
- ctx.textBaseline = 'middle';
61621
- ctx.fillText(config.brandName, width / 2, textY, width - logoPad * 2);
61632
+ ctx.textBaseline = 'top';
61633
+ ctx.fillText(metaParts.join(' · '), width / 2, cursorY, width - 48);
61622
61634
  }
61623
61635
  }
61624
61636
  async drawQrZone(ctx, config, width) {
@@ -61626,7 +61638,7 @@ class TicketCardImageService {
61626
61638
  ctx.fillStyle = '#ffffff';
61627
61639
  ctx.fillRect(0, zoneTop, width, QR_ZONE_H);
61628
61640
  const qrX = (width - QR_SIZE) / 2;
61629
- const qrY = zoneTop + (QR_ZONE_H - QR_SIZE - 28) / 2;
61641
+ const qrY = zoneTop + (QR_ZONE_H - QR_SIZE - 40) / 2;
61630
61642
  try {
61631
61643
  const qrImg = await this.loadImage(config.qrDataUrl);
61632
61644
  ctx.drawImage(qrImg, qrX, qrY, QR_SIZE, QR_SIZE);
@@ -61635,42 +61647,106 @@ class TicketCardImageService {
61635
61647
  ctx.strokeStyle = '#e0e0e0';
61636
61648
  ctx.strokeRect(qrX, qrY, QR_SIZE, QR_SIZE);
61637
61649
  }
61638
- ctx.fillStyle = '#2f2a36';
61639
- ctx.font = `0.9375rem ${FONT_STACK}`;
61650
+ const hint = config.scanHint ?? 'Muestra este QR en la puerta para canjear tu entrada.';
61651
+ ctx.fillStyle = '#6b6675';
61652
+ ctx.font = `0.875rem ${FONT_STACK}`;
61640
61653
  ctx.textAlign = 'center';
61641
61654
  ctx.textBaseline = 'top';
61642
- ctx.fillText('Escanea para verificar', width / 2, qrY + QR_SIZE + 12);
61655
+ const hintY = qrY + QR_SIZE + 14;
61656
+ this.fillWrappedText(ctx, hint, width / 2, hintY, width - 80, 22);
61657
+ }
61658
+ drawPerforated(ctx, config, width) {
61659
+ const y = HEADER_H + QR_ZONE_H;
61660
+ const notchR = PERF_H / 2;
61661
+ const colorFrom = config.brandColor ?? DEFAULT_BRAND_COLOR;
61662
+ ctx.fillStyle = '#ffffff';
61663
+ ctx.fillRect(0, y, width, PERF_H);
61664
+ // Left notch (semicircle cut from edge)
61665
+ ctx.fillStyle = '#f8f8f8';
61666
+ ctx.beginPath();
61667
+ ctx.arc(-1, y + notchR, notchR + 1, -Math.PI / 2, Math.PI / 2);
61668
+ ctx.fill();
61669
+ // Right notch
61670
+ ctx.beginPath();
61671
+ ctx.arc(width + 1, y + notchR, notchR + 1, Math.PI / 2, -Math.PI / 2);
61672
+ ctx.fill();
61673
+ // Dashed line in middle
61674
+ ctx.setLineDash([8, 6]);
61675
+ ctx.strokeStyle = colorFrom + '55';
61676
+ ctx.lineWidth = 1.5;
61677
+ ctx.beginPath();
61678
+ ctx.moveTo(notchR * 2 + 8, y + notchR);
61679
+ ctx.lineTo(width - notchR * 2 - 8, y + notchR);
61680
+ ctx.stroke();
61681
+ ctx.setLineDash([]);
61643
61682
  }
61644
61683
  drawFooter(ctx, config, width) {
61645
- const footerTop = HEADER_H + QR_ZONE_H;
61684
+ const footerTop = HEADER_H + QR_ZONE_H + PERF_H;
61685
+ const colorFrom = config.brandColor ?? DEFAULT_BRAND_COLOR;
61646
61686
  ctx.fillStyle = '#ffffff';
61647
61687
  ctx.fillRect(0, footerTop, width, FOOTER_H);
61648
- const colorFrom = config.brandColor ?? DEFAULT_BRAND_COLOR;
61649
- ctx.fillStyle = colorFrom;
61650
- ctx.fillRect(0, footerTop, width, 3);
61651
- let contentY = footerTop + 40;
61688
+ let contentY = footerTop + 32;
61652
61689
  if (config.folio !== undefined && config.folio !== null && config.folio !== '') {
61653
- const label = config.folioLabel ?? 'Entrada';
61654
- const folioText = `${label} #${config.folio}`;
61655
- ctx.fillStyle = '#1a0b2e';
61656
- ctx.font = `bold 3rem ${FONT_STACK}`;
61690
+ const label = (config.folioLabel ?? 'Cartón').toUpperCase();
61691
+ // Label small brand color, letter-spaced
61692
+ ctx.fillStyle = colorFrom;
61693
+ ctx.font = `600 0.8125rem ${FONT_STACK}`;
61657
61694
  ctx.textAlign = 'center';
61658
61695
  ctx.textBaseline = 'top';
61659
- ctx.fillText(folioText, width / 2, contentY, width - 40);
61660
- contentY += 56;
61696
+ this.fillLetterSpaced(ctx, label, width / 2, contentY, 3);
61697
+ contentY += 24;
61698
+ // Folio number — large, bold, brand color
61699
+ ctx.fillStyle = colorFrom;
61700
+ ctx.font = `800 3.5rem ${FONT_STACK}`;
61701
+ ctx.textAlign = 'center';
61702
+ ctx.textBaseline = 'top';
61703
+ ctx.fillText(`#${config.folio}`, width / 2, contentY, width - 40);
61704
+ contentY += 68;
61661
61705
  }
61662
61706
  if (config.buyerName) {
61663
- ctx.fillStyle = '#2f2a36';
61664
- ctx.font = `1.25rem ${FONT_STACK}`;
61707
+ ctx.fillStyle = '#6b6675';
61708
+ ctx.font = `0.9375rem ${FONT_STACK}`;
61665
61709
  ctx.textAlign = 'center';
61666
61710
  ctx.textBaseline = 'top';
61667
61711
  ctx.fillText(config.buyerName, width / 2, contentY, width - 40);
61668
61712
  }
61669
- ctx.fillStyle = '#6b6675';
61670
- ctx.font = `0.75rem ${FONT_STACK}`;
61671
- ctx.textAlign = 'right';
61713
+ // Copyright
61714
+ ctx.fillStyle = '#b0a8bc';
61715
+ ctx.font = `0.6875rem ${FONT_STACK}`;
61716
+ ctx.textAlign = 'center';
61672
61717
  ctx.textBaseline = 'bottom';
61673
- ctx.fillText('Valtech', width - 16, footerTop + FOOTER_H - 12);
61718
+ ctx.fillText('© 2026 Valtech. Todos los derechos reservados.', width / 2, footerTop + FOOTER_H - 14);
61719
+ }
61720
+ fillWrappedText(ctx, text, x, y, maxWidth, lineHeight) {
61721
+ const words = text.split(' ');
61722
+ let line = '';
61723
+ let curY = y;
61724
+ for (const word of words) {
61725
+ const test = line ? `${line} ${word}` : word;
61726
+ if (ctx.measureText(test).width > maxWidth && line) {
61727
+ ctx.fillText(line, x, curY);
61728
+ line = word;
61729
+ curY += lineHeight;
61730
+ }
61731
+ else {
61732
+ line = test;
61733
+ }
61734
+ }
61735
+ if (line)
61736
+ ctx.fillText(line, x, curY);
61737
+ }
61738
+ fillLetterSpaced(ctx, text, cx, y, spacing) {
61739
+ // Measure total width with spacing and center
61740
+ const chars = text.split('');
61741
+ const charWidths = chars.map(c => ctx.measureText(c).width);
61742
+ const totalW = charWidths.reduce((s, w) => s + w, 0) + spacing * (chars.length - 1);
61743
+ let x = cx - totalW / 2;
61744
+ ctx.textAlign = 'left';
61745
+ for (let i = 0; i < chars.length; i++) {
61746
+ ctx.fillText(chars[i], x, y);
61747
+ x += charWidths[i] + spacing;
61748
+ }
61749
+ ctx.textAlign = 'center';
61674
61750
  }
61675
61751
  loadImage(src) {
61676
61752
  return new Promise((resolve, reject) => {
@@ -70432,6 +70508,9 @@ class TicketCardComponent {
70432
70508
  folio: p.folio,
70433
70509
  folioLabel: p.shareFolioLabel,
70434
70510
  buyerName: p.buyerName,
70511
+ eventDate: p.shareEventDate,
70512
+ eventLocation: p.shareEventLocation,
70513
+ scanHint: p.shareScanHint,
70435
70514
  };
70436
70515
  }
70437
70516
  async copyToClipboard(config, p) {