'use client'; import React, { useState, useEffect } from 'react'; import { Tag, Ticket, Percent, DollarSign, Calendar, Clock, CheckCircle2, AlertCircle, Plus, Search, Filter, Trash2, Edit2, Copy, Check, Share2, ExternalLink, ShieldCheck, Sparkles, RefreshCw, Power, BarChart3, TrendingUp, Flame, } from 'lucide-react'; import { Coupon } from '@/types'; import { formatCOP } from '@/lib/pricing-engine'; export default function AdminCuponesPage() { const [coupons, setCoupons] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive' | 'expired' | 'exhausted'>('all'); // Modal State const [isModalOpen, setIsModalOpen] = useState(false); const [editingCoupon, setEditingCoupon] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [copiedCode, setCopiedCode] = useState(null); const [copiedLink, setCopiedLink] = useState(null); // Form Fields const [code, setCode] = useState(''); const [description, setDescription] = useState(''); const [discountType, setDiscountType] = useState<'percentage' | 'fixed' | 'per_item'>('percentage'); const [discountValue, setDiscountValue] = useState(10); const [minSpend, setMinSpend] = useState(0); const [maxDiscount, setMaxDiscount] = useState(0); // Expiration config const [hasExpiry, setHasExpiry] = useState(true); const [expiresAt, setExpiresAt] = useState(''); // Quantity limit config const [hasUsageLimit, setHasUsageLimit] = useState(true); const [usageLimit, setUsageLimit] = useState(50); const [isActive, setIsActive] = useState(true); const [formError, setFormError] = useState(null); const fetchCoupons = async () => { setLoading(true); try { const res = await fetch('/api/coupons'); const data = await res.json(); if (data.success && Array.isArray(data.coupons)) { setCoupons(data.coupons); } } catch (err) { console.error('Error cargando cupones:', err); } finally { setLoading(false); } }; useEffect(() => { fetchCoupons(); }, []); const openCreateModal = () => { setEditingCoupon(null); setCode(''); setDescription(''); setDiscountType('percentage'); setDiscountValue(10); setMinSpend(0); setMaxDiscount(0); setHasUsageLimit(true); setUsageLimit(50); // Default expiration: in 15 days setHasExpiry(true); const in15Days = new Date(); in15Days.setDate(in15Days.getDate() + 15); in15Days.setHours(23, 59, 0, 0); setExpiresAt(in15Days.toISOString().slice(0, 16)); setIsActive(true); setFormError(null); setIsModalOpen(true); }; const openEditModal = (c: Coupon) => { setEditingCoupon(c); setCode(c.code); setDescription(c.description || ''); setDiscountType(c.discountType); setDiscountValue(c.discountValue); setMinSpend(c.minSpend || 0); setMaxDiscount(c.maxDiscount || 0); if (c.usageLimit && c.usageLimit > 0) { setHasUsageLimit(true); setUsageLimit(c.usageLimit); } else { setHasUsageLimit(false); setUsageLimit(0); } if (c.expiresAt) { setHasExpiry(true); try { const d = new Date(c.expiresAt); setExpiresAt(d.toISOString().slice(0, 16)); } catch (_) { setExpiresAt(''); } } else { setHasExpiry(false); setExpiresAt(''); } setIsActive(c.isActive !== false); setFormError(null); setIsModalOpen(true); }; const handleSetExpiryPreset = (days: number) => { setHasExpiry(true); const target = new Date(); target.setDate(target.getDate() + days); target.setHours(23, 59, 0, 0); setExpiresAt(target.toISOString().slice(0, 16)); }; const handleSetEndOfMonth = () => { setHasExpiry(true); const now = new Date(); const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0); lastDay.setHours(23, 59, 0, 0); setExpiresAt(lastDay.toISOString().slice(0, 16)); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setFormError(null); const cleanCode = code.toUpperCase().trim(); if (!cleanCode) { setFormError('El código del cupón es obligatorio.'); return; } if (discountValue <= 0) { setFormError('El valor del descuento debe ser mayor a 0.'); return; } if (discountType === 'percentage' && discountValue > 100) { setFormError('El porcentaje de descuento no puede ser superior al 100%.'); return; } if (hasExpiry && !expiresAt) { setFormError('Debes seleccionar una fecha y hora de expiración.'); return; } if (hasUsageLimit && usageLimit <= 0) { setFormError('El límite de usos debe ser al menos 1.'); return; } setIsSubmitting(true); const payload: Partial = { code: cleanCode, description, discountType, discountValue: Number(discountValue), minSpend: Number(minSpend) || 0, maxDiscount: Number(maxDiscount) || 0, usageLimit: hasUsageLimit ? Number(usageLimit) : 0, expiresAt: hasExpiry && expiresAt ? new Date(expiresAt).toISOString() : undefined, isActive, }; try { if (editingCoupon) { const res = await fetch('/api/coupons', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: editingCoupon.id, ...payload }), }); const data = await res.json(); if (!data.success) { throw new Error(data.error || 'Error al actualizar cupón.'); } } else { const res = await fetch('/api/coupons', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await res.json(); if (!data.success) { throw new Error(data.error || 'Error al crear cupón.'); } } setIsModalOpen(false); await fetchCoupons(); } catch (err: any) { setFormError(err.message || 'Error guardando cupón.'); } finally { setIsSubmitting(false); } }; const handleToggleActive = async (c: Coupon) => { try { const nextState = !c.isActive; // Optimistic update setCoupons((prev) => prev.map((item) => (item.id === c.id ? { ...item, isActive: nextState } : item)) ); await fetch('/api/coupons', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: c.id, isActive: nextState }), }); } catch (err) { console.error('Error alternando estado del cupón:', err); fetchCoupons(); } }; const handleDelete = async (c: Coupon) => { if (!confirm(`¿Estás seguro de eliminar el cupón "${c.code}"? Esta acción no se puede deshacer.`)) { return; } try { setCoupons((prev) => prev.filter((item) => item.id !== c.id)); await fetch(`/api/coupons?id=${c.id}`, { method: 'DELETE' }); } catch (err) { console.error('Error eliminando cupón:', err); fetchCoupons(); } }; const handleCopyCode = (couponCode: string) => { navigator.clipboard.writeText(couponCode); setCopiedCode(couponCode); setTimeout(() => setCopiedCode(null), 2500); }; const handleCopyLink = (couponCode: string) => { const origin = typeof window !== 'undefined' ? window.location.origin : 'https://drip-zone.online'; const link = `${origin}/checkout?coupon=${couponCode}`; navigator.clipboard.writeText(link); setCopiedLink(couponCode); setTimeout(() => setCopiedLink(null), 2500); }; // Helper status calculations const isCouponExpired = (c: Coupon) => { if (!c.expiresAt) return false; return new Date(c.expiresAt).getTime() < Date.now(); }; const isCouponExhausted = (c: Coupon) => { if (!c.usageLimit || c.usageLimit <= 0) return false; return (c.usageCount || 0) >= c.usageLimit; }; const filteredCoupons = coupons.filter((c) => { const matchesSearch = c.code.toLowerCase().includes(searchTerm.toLowerCase()) || (c.description || '').toLowerCase().includes(searchTerm.toLowerCase()); if (!matchesSearch) return false; const expired = isCouponExpired(c); const exhausted = isCouponExhausted(c); if (statusFilter === 'active') return c.isActive && !expired && !exhausted; if (statusFilter === 'inactive') return !c.isActive; if (statusFilter === 'expired') return expired; if (statusFilter === 'exhausted') return exhausted; return true; }); // KPI Calculations const totalCoupons = coupons.length; const activeCoupons = coupons.filter((c) => c.isActive && !isCouponExpired(c) && !isCouponExhausted(c)).length; const totalRedemptions = coupons.reduce((sum, c) => sum + (c.usageCount || 0), 0); const expiringSoonCount = coupons.filter((c) => { if (!c.expiresAt || !c.isActive || isCouponExpired(c)) return false; const diffDays = (new Date(c.expiresAt).getTime() - Date.now()) / (1000 * 3600 * 24); return diffDays <= 7; }).length; return (
{/* Header */}

Cupones de Descuento & Promociones

Administra campañas promocionales para redes sociales, WhatsApp y fechas especiales. Configura límites de canje por cantidad de usos o fecha de vencimiento.

{/* KPI Cards */}
Total Cupones

{totalCoupons}

Campañas configuradas

Cupones Activos

{activeCoupons}

Listos para canjear en la tienda

Canjes Totales

{totalRedemptions}

Usos aplicados en pedidos

Por Vencer (7 días)

{expiringSoonCount}

Campañas próximas a caducar

{/* Filters & Search */}
setSearchTerm(e.target.value)} placeholder="Buscar por código (ej. DRIPZONE10) o campaña..." className="w-full bg-neutral-950 border border-neutral-800 focus:border-purple-500 rounded-xl pl-9 pr-4 py-2 text-xs text-white placeholder:text-neutral-500 outline-none transition-all" />
{[ { id: 'all', label: 'Todos' }, { id: 'active', label: 'Activos' }, { id: 'inactive', label: 'Pausados' }, { id: 'expired', label: 'Vencidos' }, { id: 'exhausted', label: 'Agotados' }, ].map((tab) => ( ))}
{/* Coupons List */} {loading ? (

Cargando cupones de promoción...

) : filteredCoupons.length === 0 ? (

No se encontraron cupones

{searchTerm || statusFilter !== 'all' ? 'No hay cupones que coincidan con los filtros aplicados.' : 'Aún no has creado campañas ni cupones de descuento. Crea el primero para tus promociones.'}

) : (
{filteredCoupons.map((coupon) => { const expired = isCouponExpired(coupon); const exhausted = isCouponExhausted(coupon); const isFullyActive = coupon.isActive && !expired && !exhausted; const usagePercent = coupon.usageLimit && coupon.usageLimit > 0 ? Math.min(100, Math.round(((coupon.usageCount || 0) / coupon.usageLimit) * 100)) : null; return (
{/* Top Bar: Code and Status */}
{coupon.code}

{coupon.description || 'Promoción general de la tienda'}

{/* Status Pill */} {!coupon.isActive ? 'Pausado' : expired ? 'Expirado' : exhausted ? 'Agotado' : 'Activo'}
{/* Discount Value Badge */}
{coupon.discountType === 'percentage' ? '%' : coupon.discountType === 'per_item' ? '📦' : '$'}

{coupon.discountType === 'percentage' ? `${coupon.discountValue}% de Descuento` : coupon.discountType === 'per_item' ? `${formatCOP(coupon.discountValue)} / cada producto` : `${formatCOP(coupon.discountValue)} de Descuento`}

{coupon.discountType === 'per_item' ? `Descuenta ${formatCOP(coupon.discountValue)} por cada producto al saldo contraentrega` : coupon.minSpend && coupon.minSpend > 0 ? `Compra mín: ${formatCOP(coupon.minSpend)}` : 'Sin monto mínimo'} {coupon.maxDiscount && coupon.maxDiscount > 0 ? ` • Tope: ${formatCOP(coupon.maxDiscount)}` : ''}

{/* Quantity Limit Progress */}
Límite de Canjes: {coupon.usageLimit && coupon.usageLimit > 0 ? ( <> {coupon.usageCount || 0} / {coupon.usageLimit}{' '} ({usagePercent}%) ) : ( {coupon.usageCount || 0} canjes (Ilimitado) )}
{usagePercent !== null && (
= 100 ? 'bg-amber-500' : usagePercent >= 75 ? 'bg-purple-400' : 'bg-emerald-500' }`} style={{ width: `${usagePercent}%` }} />
)}
{/* Expiration Details */}
{coupon.expiresAt ? ( expired ? ( Expiró el{' '} {new Date(coupon.expiresAt).toLocaleDateString('es-CO', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', })} ) : ( Vence el{' '} {new Date(coupon.expiresAt).toLocaleDateString('es-CO', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', })} ) ) : ( Sin fecha de caducidad (Indefinido) )}
{/* Bottom Actions */}
); })}
)} {/* Modal: Crear / Editar Cupón */} {isModalOpen && (

{editingCoupon ? `Editar Cupón: ${editingCoupon.code}` : 'Crear Nuevo Cupón de Descuento'}

{formError && (
{formError}
)} {/* Código y Nombre */}
setCode(e.target.value.toUpperCase().replace(/\s/g, ''))} placeholder="Ej. LANZAMIENTO20" className="w-full bg-neutral-950 border border-neutral-700 focus:border-purple-500 rounded-xl px-3 py-2.5 font-mono font-bold tracking-wider text-white uppercase outline-none" />

Sin espacios, en mayúsculas.

setDescription(e.target.value)} placeholder="Ej. Promoción Black Friday Instagram" className="w-full bg-neutral-950 border border-neutral-700 focus:border-purple-500 rounded-xl px-3 py-2.5 text-white outline-none" />
{/* Tipo de Descuento */}
{discountType === 'per_item' && (

⚡ Descuento Progresivo por Unidades:

Si el cliente compra 1 par descuenta {formatCOP(discountValue || 10000)}, si compra 2 pares descuenta {formatCOP((discountValue || 10000) * 2)}, si compra 3 pares {formatCOP((discountValue || 10000) * 3)} y así sucesivamente.

🛡️ El descuento se aplica 100% sobre el saldo pendiente por pagar (contraentrega), protegiendo intacto el anticipo de separación.

)}
{/* Valor del Descuento y Atajos */}
setDiscountValue(Number(e.target.value))} className="w-full bg-neutral-950 border border-neutral-700 focus:border-purple-500 rounded-xl px-3 py-2.5 text-white font-mono font-bold text-sm outline-none" /> {discountType === 'percentage' ? '%' : 'COP'}
{/* Quick Presets */}
Atajos: {discountType === 'percentage' ? [5, 10, 15, 20, 25, 30].map((v) => ( )) : discountType === 'per_item' ? [5000, 10000, 15000, 20000, 25000, 30000].map((v) => ( )) : [10000, 15000, 20000, 30000, 50000].map((v) => ( ))}
{/* CONDICIONES CLAVE: VENCIMIENTO Y CANTIDAD */}

Control de Vigencia y Límite de Usos

{/* 1. TIEMPO DE CADUCIDAD */}

El cupón dejará de funcionar automáticamente después de esta fecha.

setHasExpiry(e.target.checked)} className="w-4 h-4 accent-purple-600 rounded cursor-pointer" />
{hasExpiry && (
setExpiresAt(e.target.value)} className="w-full bg-neutral-900 border border-neutral-700 focus:border-purple-500 rounded-xl px-3 py-2 text-white font-mono text-xs outline-none" />
Vence en:
)}
{/* 2. LÍMITE POR CANTIDAD DE USOS */}

Cantidad máxima de clientes o compras que pueden redimir este cupón.

setHasUsageLimit(e.target.checked)} className="w-4 h-4 accent-purple-600 rounded cursor-pointer" />
{hasUsageLimit && (
setUsageLimit(Number(e.target.value))} placeholder="Ej. 50" className="w-full bg-neutral-900 border border-neutral-700 focus:border-purple-500 rounded-xl px-3 py-2 text-white font-mono text-xs outline-none" /> Canjes máximos
Cupo: {[10, 25, 50, 100, 200].map((lim) => ( ))}
)}
{/* Condiciones Opcionales: Compra Mínima y Tope de Descuento */}
setMinSpend(Number(e.target.value))} placeholder="0 = Sin mínimo" className="w-full bg-neutral-950 border border-neutral-700 focus:border-purple-500 rounded-xl px-3 py-2 text-white font-mono outline-none" />

Ej. $100.000 para pedidos mayores.

{discountType === 'percentage' && (
setMaxDiscount(Number(e.target.value))} placeholder="0 = Sin tope" className="w-full bg-neutral-950 border border-neutral-700 focus:border-purple-500 rounded-xl px-3 py-2 text-white font-mono outline-none" />

Tope máximo en pesos para cuidar márgenes.

)}
{/* Estado Activo */}
Estado del Cupón {isActive ? 'El cupón estará activo y listo para ser redimido.' : 'El cupón estará pausado.'}
setIsActive(e.target.checked)} className="w-5 h-5 accent-emerald-500 rounded cursor-pointer" />
{/* Submit Buttons */}
)}
); }