Created
November 25, 2025 23:13
-
-
Save iDavidMorales/9994fb39bc8a342ebcf1ed24eef68ba8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <title>Cupones Digitales - Vista de marca</title> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <!-- Opcional: iconos --> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css"> | |
| <style> | |
| :root{ | |
| --brand:#00C2CB; | |
| --brand-dark:#028499; | |
| --accent:#FF4D5A; | |
| --accent-soft:#FFE4E6; | |
| --bg:#F3F4F6; | |
| --card:#FFFFFF; | |
| --border:#E5E7EB; | |
| --text:#111827; | |
| --muted:#6B7280; | |
| --radius-lg:24px; | |
| --radius-md:18px; | |
| } | |
| *{ | |
| box-sizing:border-box; | |
| } | |
| body{ | |
| margin:0; | |
| font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; | |
| background:var(--bg); | |
| color:var(--text); | |
| } | |
| .page{ | |
| max-width:1120px; | |
| margin:0 auto; | |
| padding:16px 16px 32px; | |
| } | |
| /* NAVBAR */ | |
| .navbar{ | |
| display:flex; | |
| align-items:center; | |
| justify-content:space-between; | |
| gap:12px; | |
| padding:8px 12px; | |
| margin-bottom:16px; | |
| border-radius:999px; | |
| background:#FFFFFF; | |
| border:1px solid var(--border); | |
| box-shadow:0 4px 18px rgba(15,23,42,0.06); | |
| position:sticky; | |
| top:8px; | |
| z-index:10; | |
| } | |
| .nav-left{ | |
| display:flex; | |
| align-items:center; | |
| gap:10px; | |
| min-width:0; | |
| } | |
| .nav-logo{ | |
| width:32px; | |
| height:32px; | |
| border-radius:999px; | |
| background:radial-gradient(circle at 30% 30%, #FFFFFF 0, #FFFFFF 40%, var(--brand) 90%); | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| color:#0f172a; | |
| font-size:18px; | |
| flex-shrink:0; | |
| box-shadow:0 0 0 1px rgba(15,23,42,0.05); | |
| } | |
| .nav-title{ | |
| display:flex; | |
| flex-direction:column; | |
| min-width:0; | |
| } | |
| .nav-title strong{ | |
| font-size:14px; | |
| white-space:nowrap; | |
| overflow:hidden; | |
| text-overflow:ellipsis; | |
| } | |
| .nav-title span{ | |
| font-size:12px; | |
| color:var(--muted); | |
| white-space:nowrap; | |
| overflow:hidden; | |
| text-overflow:ellipsis; | |
| } | |
| .nav-btn{ | |
| font-size:13px; | |
| padding:6px 12px; | |
| border-radius:999px; | |
| border:1px solid var(--border); | |
| background:#F9FAFB; | |
| color:#374151; | |
| display:inline-flex; | |
| align-items:center; | |
| gap:6px; | |
| cursor:pointer; | |
| white-space:nowrap; | |
| } | |
| .nav-btn i{ | |
| font-size:14px; | |
| } | |
| /* HERO */ | |
| .hero{ | |
| margin-top:12px; | |
| margin-bottom:24px; | |
| border-radius:var(--radius-lg); | |
| background: | |
| linear-gradient(120deg, #FDF2F8 0, #E0F2FE 45%, #EFF6FF 100%); | |
| overflow:hidden; | |
| padding:24px 28px; | |
| display:flex; | |
| gap:18px; | |
| position:relative; | |
| align-items:center; | |
| } | |
| .hero::after{ | |
| content:""; | |
| position:absolute; | |
| inset:0; | |
| background:url("https://images.pexels.com/photos/302899/pexels-photo-302899.jpeg?auto=compress&cs=tinysrgb&w=1200") center/cover no-repeat; | |
| opacity:0.12; | |
| mix-blend-mode:multiply; | |
| pointer-events:none; | |
| } | |
| .hero-content{ | |
| position:relative; | |
| max-width:480px; | |
| z-index:1; | |
| } | |
| .hero-kicker{ | |
| font-size:11px; | |
| letter-spacing:0.16em; | |
| text-transform:uppercase; | |
| color:#B91C1C; | |
| margin-bottom:4px; | |
| font-weight:600; | |
| } | |
| .hero-title{ | |
| font-size:28px; | |
| margin:0 0 4px; | |
| } | |
| .hero-subtitle{ | |
| font-size:14px; | |
| color:#374151; | |
| margin:0 0 16px; | |
| } | |
| .hero-actions{ | |
| display:flex; | |
| flex-wrap:wrap; | |
| gap:10px; | |
| } | |
| .btn-primary{ | |
| border:none; | |
| border-radius:999px; | |
| background:linear-gradient(135deg,var(--brand),var(--brand-dark)); | |
| color:#FFFFFF; | |
| padding:8px 16px; | |
| font-size:13px; | |
| font-weight:600; | |
| display:inline-flex; | |
| gap:8px; | |
| align-items:center; | |
| cursor:pointer; | |
| box-shadow:0 10px 25px rgba(8,47,73,0.25); | |
| } | |
| .btn-secondary{ | |
| border-radius:999px; | |
| border:1px solid var(--border); | |
| background:#FFFFFF; | |
| color:#111827; | |
| padding:8px 16px; | |
| font-size:13px; | |
| display:inline-flex; | |
| gap:8px; | |
| align-items:center; | |
| cursor:pointer; | |
| } | |
| .hero-meta{ | |
| margin-top:10px; | |
| font-size:11px; | |
| color:#6B7280; | |
| } | |
| .hero-chip{ | |
| display:inline-flex; | |
| align-items:center; | |
| gap:4px; | |
| padding:2px 8px; | |
| border-radius:999px; | |
| border:1px solid rgba(148,163,184,0.8); | |
| background:rgba(255,255,255,0.8); | |
| font-size:11px; | |
| margin-right:4px; | |
| } | |
| .hero-chip i{ | |
| font-size:13px; | |
| color:var(--accent); | |
| } | |
| /* SECTION TITLE */ | |
| .section-header{ | |
| display:flex; | |
| justify-content:space-between; | |
| align-items:flex-end; | |
| gap:10px; | |
| margin-bottom:12px; | |
| } | |
| .section-header h2{ | |
| margin:0; | |
| font-size:18px; | |
| } | |
| .section-header p{ | |
| margin:4px 0 0; | |
| font-size:13px; | |
| color:var(--muted); | |
| } | |
| .section-meta{ | |
| font-size:12px; | |
| color:var(--muted); | |
| text-align:right; | |
| } | |
| /* GRID DE CUPONES */ | |
| .grid{ | |
| display:grid; | |
| grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); | |
| gap:18px; | |
| } | |
| .coupon-card{ | |
| background:var(--card); | |
| border-radius:var(--radius-md); | |
| overflow:hidden; | |
| border:1px solid var(--border); | |
| box-shadow:0 6px 18px rgba(15,23,42,0.06); | |
| display:flex; | |
| flex-direction:column; | |
| min-height:100%; | |
| } | |
| .coupon-media{ | |
| position:relative; | |
| overflow:hidden; | |
| } | |
| .coupon-media::before{ | |
| content:""; | |
| display:block; | |
| padding-top:60%; /* 5:3 */ | |
| } | |
| .coupon-media img{ | |
| position:absolute; | |
| inset:0; | |
| width:100%; | |
| height:100%; | |
| object-fit:cover; | |
| transition:transform .35s ease-out; | |
| } | |
| .coupon-card:hover .coupon-media img{ | |
| transform:scale(1.04); | |
| } | |
| .coupon-badges{ | |
| position:absolute; | |
| top:8px; | |
| left:8px; | |
| display:flex; | |
| flex-wrap:wrap; | |
| gap:4px; | |
| } | |
| .badge-pill{ | |
| font-size:11px; | |
| padding:2px 7px; | |
| border-radius:999px; | |
| background:rgba(15,23,42,0.9); | |
| color:#F9FAFB; | |
| display:inline-flex; | |
| align-items:center; | |
| gap:4px; | |
| } | |
| .badge-pill.badge-main{ | |
| background:var(--accent); | |
| color:#fff; | |
| } | |
| .badge-pill.badge-new{ | |
| background:#22C55E; | |
| } | |
| .badge-pill i{ | |
| font-size:11px; | |
| } | |
| .coupon-footer-logo{ | |
| position:absolute; | |
| bottom:8px; | |
| left:8px; | |
| width:26px; | |
| height:26px; | |
| border-radius:999px; | |
| overflow:hidden; | |
| border:2px solid #fff; | |
| background:#F9FAFB; | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| font-size:13px; | |
| color:#111827; | |
| } | |
| .coupon-body{ | |
| padding:12px 14px 12px; | |
| display:flex; | |
| flex-direction:column; | |
| gap:6px; | |
| flex:1; | |
| } | |
| .coupon-title{ | |
| font-size:14px; | |
| font-weight:600; | |
| margin:0; | |
| } | |
| .coupon-desc{ | |
| font-size:12px; | |
| color:var(--muted); | |
| margin:0; | |
| max-height:3em; | |
| overflow:hidden; | |
| text-overflow:ellipsis; | |
| } | |
| .coupon-meta-line{ | |
| font-size:11px; | |
| color:var(--muted); | |
| margin-top:2px; | |
| } | |
| .coupon-meta-line span{ | |
| margin-right:8px; | |
| } | |
| .coupon-stats{ | |
| display:flex; | |
| flex-wrap:wrap; | |
| gap:6px; | |
| margin-top:4px; | |
| } | |
| .chip-stat{ | |
| font-size:11px; | |
| padding:2px 8px; | |
| border-radius:999px; | |
| background:#F3F4F6; | |
| color:#4B5563; | |
| display:inline-flex; | |
| align-items:center; | |
| gap:4px; | |
| } | |
| .chip-stat i{ | |
| font-size:11px; | |
| color:#9CA3AF; | |
| } | |
| .chip-stat strong{ | |
| font-weight:600; | |
| } | |
| .coupon-actions{ | |
| display:flex; | |
| justify-content:space-between; | |
| align-items:center; | |
| margin-top:8px; | |
| } | |
| .coupon-link{ | |
| font-size:12px; | |
| padding:6px 10px; | |
| border-radius:999px; | |
| border:1px solid var(--border); | |
| background:#FFFFFF; | |
| color:#111827; | |
| text-decoration:none; | |
| display:inline-flex; | |
| align-items:center; | |
| gap:6px; | |
| } | |
| .coupon-link i{ | |
| font-size:13px; | |
| } | |
| .coupon-tag{ | |
| font-size:11px; | |
| color:var(--muted); | |
| } | |
| /* ESTADOS */ | |
| .empty-state{ | |
| padding:32px 16px; | |
| text-align:center; | |
| font-size:14px; | |
| color:var(--muted); | |
| } | |
| .error-box{ | |
| margin-top:12px; | |
| padding:10px 12px; | |
| border-radius:12px; | |
| background:#FEE2E2; | |
| color:#B91C1C; | |
| font-size:13px; | |
| border:1px solid #FCA5A5; | |
| display:none; | |
| } | |
| @media (max-width:640px){ | |
| .navbar{ | |
| border-radius:16px; | |
| } | |
| .hero{ | |
| padding:18px 18px 20px; | |
| } | |
| .hero-title{ | |
| font-size:22px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="page"> | |
| <!-- NAVBAR --> | |
| <nav class="navbar"> | |
| <div class="nav-left"> | |
| <div class="nav-logo"> | |
| <i class="bi bi-ticket-perforated"></i> | |
| </div> | |
| <div class="nav-title"> | |
| <strong>Cupones Digitales</strong> | |
| <span id="nav-subtitle">Descubre ofertas locales personalizadas para tu comunidad.</span> | |
| </div> | |
| </div> | |
| <button class="nav-btn" type="button"> | |
| <i class="bi bi-sliders"></i> | |
| Configurar marca | |
| </button> | |
| </nav> | |
| <!-- HERO --> | |
| <section class="hero"> | |
| <div class="hero-content"> | |
| <div class="hero-kicker" id="hero-kicker">CAFÉ ARTESANAL</div> | |
| <h1 class="hero-title">Cupones Digitales</h1> | |
| <p class="hero-subtitle"> | |
| Descubre ofertas locales personalizadas para tu comunidad y comparte tus promociones en un solo enlace. | |
| </p> | |
| <div class="hero-actions"> | |
| <button type="button" class="btn-primary" onclick="scrollToPromos()"> | |
| <i class="bi bi-stars"></i> | |
| Ver cupones destacados | |
| </button> | |
| <button type="button" class="btn-secondary"> | |
| <i class="bi bi-people"></i> | |
| Personalizar experiencia | |
| </button> | |
| </div> | |
| <div class="hero-meta" id="hero-meta"> | |
| <span class="hero-chip"> | |
| <i class="bi bi-activity"></i> | |
| Esperando datos de la API... | |
| </span> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- HEADER LISTA --> | |
| <section id="promos"> | |
| <div class="section-header"> | |
| <div> | |
| <h2 id="section-title">Promociones de tu marca</h2> | |
| <p>Comparte estas ofertas con tu comunidad y aumenta tus canjes digitales.</p> | |
| </div> | |
| <div class="section-meta" id="section-meta"> | |
| 0 cupones · 0 vistas totales | |
| </div> | |
| </div> | |
| <div id="error-box" class="error-box"></div> | |
| <div id="coupon-grid" class="grid"> | |
| <div class="empty-state" id="empty-state"> | |
| Cargando cupones desde la API... | |
| </div> | |
| </div> | |
| </section> | |
| </div> | |
| <script> | |
| const qs = new URLSearchParams(window.location.search); | |
| const apiPublic = qs.get('api_public') || ''; | |
| const brandName = qs.get('brand') || 'Café Artesanal'; | |
| const HERO_KICKER = document.getElementById('hero-kicker'); | |
| const HERO_META = document.getElementById('hero-meta'); | |
| const NAV_SUBTITLE = document.getElementById('nav-subtitle'); | |
| const SECTION_TITLE = document.getElementById('section-title'); | |
| const SECTION_META = document.getElementById('section-meta'); | |
| const GRID = document.getElementById('coupon-grid'); | |
| const EMPTY_STATE = document.getElementById('empty-state'); | |
| const ERROR_BOX = document.getElementById('error-box'); | |
| HERO_KICKER.textContent = brandName.toUpperCase(); | |
| SECTION_TITLE.textContent = `Promociones de ${brandName}`; | |
| NAV_SUBTITLE.textContent = `Cupones digitales de ${brandName} para tu comunidad.`; | |
| function scrollToPromos(){ | |
| document.getElementById('promos').scrollIntoView({behavior:'smooth', block:'start'}); | |
| } | |
| function shortText(text, max){ | |
| if(!text) return ''; | |
| text = String(text); | |
| if(text.length <= max) return text; | |
| return text.slice(0,max-1) + '…'; | |
| } | |
| function buildBadgeLabel(c){ | |
| // Heurística para texto principal del badge | |
| const title = (c.titulo || c.text || '').toUpperCase(); | |
| if(title.includes('2X1')) return '2X1'; | |
| if(title.includes('%')) return title.match(/\d+%/g)?.[0] + ' OFF' || 'OFERTA'; | |
| if((c.condiciones || '').toLowerCase().includes('gratis')) return 'GRATIS'; | |
| if((c.tipo || '').toLowerCase() === 'promocion') return 'PROMO'; | |
| return 'OFERTA'; | |
| } | |
| function isNewCoupon(c){ | |
| if(!c.fecha_create) return false; | |
| const created = new Date(c.fecha_create.replace(' ','T')); | |
| if(isNaN(created.getTime())) return false; | |
| const now = new Date(); | |
| const diffDays = (now - created) / (1000*60*60*24); | |
| return diffDays <= 14; // últimos 14 días | |
| } | |
| function renderCoupons(data){ | |
| const apiUsage = data.api_usage || {}; | |
| const stats = data.stats || {}; | |
| const cupones = Array.isArray(data.cupones) ? data.cupones : []; | |
| // Meta hero | |
| const totalViews = (stats.totals && stats.totals.vistosx) || 0; | |
| const totalScan = (stats.totals && stats.totals.scan) || 0; | |
| const records = stats.records || cupones.length; | |
| HERO_META.innerHTML = ` | |
| <span class="hero-chip"> | |
| <i class="bi bi-activity"></i> | |
| ${records} cupones activos | |
| </span> | |
| <span class="hero-chip"> | |
| <i class="bi bi-eye"></i> | |
| ${totalViews.toLocaleString('es-MX')} vistas totales | |
| </span> | |
| <span class="hero-chip"> | |
| <i class="bi bi-upc-scan"></i> | |
| ${totalScan.toLocaleString('es-MX')} scans acumulados | |
| </span> | |
| `; | |
| SECTION_META.textContent = | |
| `${records} cupones · ${totalViews.toLocaleString('es-MX')} vistas totales`; | |
| if(!cupones.length){ | |
| GRID.innerHTML = ''; | |
| const div = document.createElement('div'); | |
| div.className = 'empty-state'; | |
| div.textContent = 'No hay cupones vigentes para este token público.'; | |
| GRID.appendChild(div); | |
| return; | |
| } | |
| GRID.innerHTML = ''; | |
| cupones.forEach(c => { | |
| const card = document.createElement('article'); | |
| card.className = 'coupon-card'; | |
| const media = document.createElement('div'); | |
| media.className = 'coupon-media'; | |
| const img = document.createElement('img'); | |
| img.src = c.foto || c.foto_post || 'https://i.imgur.com/L3DeKNK.png'; | |
| img.alt = c.titulo || c.text || 'Cupón'; | |
| const badges = document.createElement('div'); | |
| badges.className = 'coupon-badges'; | |
| const mainBadge = document.createElement('span'); | |
| mainBadge.className = 'badge-pill badge-main'; | |
| mainBadge.innerHTML = `<i class="bi bi-ticket-perforated"></i>${buildBadgeLabel(c)}`; | |
| badges.appendChild(mainBadge); | |
| if(isNewCoupon(c)){ | |
| const newBadge = document.createElement('span'); | |
| newBadge.className = 'badge-pill badge-new'; | |
| newBadge.innerHTML = `<i class="bi bi-stars"></i>NUEVO`; | |
| badges.appendChild(newBadge); | |
| } | |
| const footerLogo = document.createElement('div'); | |
| footerLogo.className = 'coupon-footer-logo'; | |
| footerLogo.innerHTML = `<i class="bi bi-shop"></i>`; | |
| media.appendChild(img); | |
| media.appendChild(badges); | |
| media.appendChild(footerLogo); | |
| const body = document.createElement('div'); | |
| body.className = 'coupon-body'; | |
| const title = document.createElement('h3'); | |
| title.className = 'coupon-title'; | |
| title.textContent = shortText(c.titulo || c.text || 'Cupón sin título', 70); | |
| const desc = document.createElement('p'); | |
| desc.className = 'coupon-desc'; | |
| const baseDesc = (c.descripcion && c.descripcion !== '0') ? c.descripcion : (c.condiciones || ''); | |
| desc.textContent = shortText(baseDesc || 'Sin descripción definida.', 140); | |
| const metaLine = document.createElement('div'); | |
| metaLine.className = 'coupon-meta-line'; | |
| metaLine.innerHTML = ` | |
| <span>Válido hasta <strong>${c.fecha_final || c.fecha || '—'}</strong></span> | |
| <span>Tipo: ${(c.tipo || 'promo').toString()}</span> | |
| `; | |
| const statsRow = document.createElement('div'); | |
| statsRow.className = 'coupon-stats'; | |
| const vistasChip = document.createElement('div'); | |
| vistasChip.className = 'chip-stat'; | |
| vistasChip.innerHTML = `<i class="bi bi-eye"></i><strong>${(c.vistosx||0).toLocaleString('es-MX')}</strong> vistas`; | |
| const scanChip = document.createElement('div'); | |
| scanChip.className = 'chip-stat'; | |
| scanChip.innerHTML = `<i class="bi bi-upc-scan"></i><strong>${(c.scan||0).toLocaleString('es-MX')}</strong> scans`; | |
| const usadoChip = document.createElement('div'); | |
| usadoChip.className = 'chip-stat'; | |
| usadoChip.innerHTML = `<i class="bi bi-check2-circle"></i><strong>${(c.usado||0).toLocaleString('es-MX')}</strong> usos`; | |
| statsRow.appendChild(vistasChip); | |
| statsRow.appendChild(scanChip); | |
| statsRow.appendChild(usadoChip); | |
| const actions = document.createElement('div'); | |
| actions.className = 'coupon-actions'; | |
| const urlCupon = c.link || `https://routicket.com/cupon/?id_cupon=${c.id}`; | |
| const linkBtn = document.createElement('a'); | |
| linkBtn.href = urlCupon; | |
| linkBtn.target = '_blank'; | |
| linkBtn.className = 'coupon-link'; | |
| linkBtn.innerHTML = `Ver cupón <i class="bi bi-arrow-up-right"></i>`; | |
| const tag = document.createElement('span'); | |
| tag.className = 'coupon-tag'; | |
| tag.textContent = `ID ${c.id} · empresa ${c.id_empresa || 0}`; | |
| actions.appendChild(linkBtn); | |
| actions.appendChild(tag); | |
| body.appendChild(title); | |
| body.appendChild(desc); | |
| body.appendChild(metaLine); | |
| body.appendChild(statsRow); | |
| body.appendChild(actions); | |
| card.appendChild(media); | |
| card.appendChild(body); | |
| GRID.appendChild(card); | |
| }); | |
| // Info de errores limpia | |
| ERROR_BOX.style.display = 'none'; | |
| } | |
| async function init(){ | |
| if(!apiPublic){ | |
| EMPTY_STATE.textContent = 'Agrega ?api_public=TU_TOKEN a la URL para cargar los cupones.'; | |
| return; | |
| } | |
| const apiUrl = `https://routicket.com/cuponera/cancun/api_cupones.php?api_public=${encodeURIComponent(apiPublic)}`; | |
| try{ | |
| const res = await fetch(apiUrl); | |
| if(!res.ok){ | |
| throw new Error('Error HTTP ' + res.status); | |
| } | |
| const data = await res.json(); | |
| renderCoupons(data); | |
| }catch(err){ | |
| console.error(err); | |
| ERROR_BOX.style.display = 'block'; | |
| ERROR_BOX.textContent = 'No se pudieron cargar los cupones desde api_cupones.php: ' + err.message; | |
| EMPTY_STATE.textContent = 'Error al cargar cupones.'; | |
| } | |
| } | |
| init(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment