feat(logistics): consolidado de compras por proveedor y hojas de ruta con navegacion Waze para mensajeros

This commit is contained in:
AI Bot 2026-08-26 07:57:20 -05:00
parent e49317f221
commit d562598b01
5 changed files with 2144 additions and 715 deletions

View File

@ -1,14 +1,36 @@
'use client';
import React, { useState, useEffect } from 'react';
import { ShoppingBag, Truck, CheckCircle2, Clock, Phone, MapPin, Search, MessageCircle, Send, ShieldCheck, UserCheck } from 'lucide-react';
import {
ShoppingBag,
Truck,
CheckCircle2,
Clock,
Phone,
MapPin,
Search,
MessageCircle,
Send,
ShieldCheck,
Building2,
Navigation,
ExternalLink,
DollarSign,
Layers,
Sparkles,
Compass,
Check,
AlertCircle
} from 'lucide-react';
import { Order, OrderItem } from '@/types';
import { formatCOP } from '@/lib/pricing-engine';
export default function AdminOrdersPage() {
const [orders, setOrders] = useState<Order[]>([]);
const [activeTab, setActiveTab] = useState<'individual' | 'suppliers' | 'routes'>('individual');
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState<string>('all');
const [toastMessage, setToastMessage] = useState<string | null>(null);
useEffect(() => {
fetch('/api/orders')
@ -22,14 +44,19 @@ export default function AdminOrdersPage() {
prev.map((o) => (o.id === orderId ? { ...o, status: newStatus } : o))
);
await fetch('/api/orders', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: orderId, status: newStatus }),
}).catch(() => {});
try {
await fetch('/api/orders', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: orderId, status: newStatus }),
});
setToastMessage(`Estado de la orden #${orderId} actualizado a "${newStatus}".`);
} catch (e) {
console.error(e);
}
};
const handleNotifySupplier = (order: Order, item: OrderItem) => {
const handleNotifySingleSupplier = (order: Order, item: OrderItem) => {
const supplierPhone = item.supplierPhone || '573134848203';
const supplierName = item.supplierName || 'Proveedor Mayorista';
const supplierCost = item.supplierPrice ? formatCOP(item.supplierPrice) : 'Precio Mayorista';
@ -39,12 +66,55 @@ export default function AdminOrdersPage() {
const url = `https://wa.me/${supplierPhone}?text=${encodeURIComponent(message)}`;
window.open(url, '_blank');
// Auto-update status to supplier_notified if still pending
if (order.status === 'pending' || order.status === 'deposit_received') {
handleStatusChange(order.id, 'supplier_notified');
}
};
const handleSendSupplierBatchManifest = (supplierName: string, supplierPhone: string, itemsList: { order: Order; item: OrderItem }[]) => {
const totalPairs = itemsList.reduce((acc, curr) => acc + curr.item.quantity, 0);
const totalCost = itemsList.reduce((acc, curr) => acc + (curr.item.supplierPrice || 0) * curr.item.quantity, 0);
let text = `📦 *MANIFIESTO CONSOLIDADO DE SEPARACIÓN*\n`;
text += `🏢 *Proveedor:* ${supplierName}\n`;
text += `📅 *Fecha:* ${new Date().toLocaleDateString('es-CO')} | *Total Pares:* ${totalPairs}\n`;
text += `💰 *Valor Total a Pagar Proveedor:* ${formatCOP(totalCost)}\n\n`;
text += `👟 *DETALLE DE PARES A SEPARAR:*\n`;
itemsList.forEach((entry, idx) => {
text += `\n${idx + 1}. *${entry.item.productName}*\n`;
text += ` • Talla: *${entry.item.size}* (x${entry.item.quantity})\n`;
text += ` • Costo Base: ${formatCOP(entry.item.supplierPrice || 0)}\n`;
text += ` • Orden Cliente: #${entry.order.id} (${entry.order.cityName})\n`;
});
text += `\n🤝 *Por favor confirmar disponibilidad y empaque para recogida del mensajero.* ¡Muchas gracias!`;
const cleanPhone = supplierPhone.startsWith('57') ? supplierPhone : `57${supplierPhone}`;
window.open(`https://wa.me/${cleanPhone}?text=${encodeURIComponent(text)}`, '_blank');
setToastMessage(`📲 Manifiesto de ${totalPairs} pares enviado a ${supplierName}.`);
};
const handleOpenWaze = (address: string, city: string) => {
const query = encodeURIComponent(`${address}, ${city}, Colombia`);
const wazeUrl = `https://waze.com/ul?q=${query}&navigate=yes`;
window.open(wazeUrl, '_blank');
};
const handleOpenGoogleMaps = (address: string, city: string) => {
const query = encodeURIComponent(`${address}, ${city}, Colombia`);
const mapsUrl = `https://www.google.com/maps/search/?api=1&query=${query}`;
window.open(mapsUrl, '_blank');
};
const handleNotifyCustomerOnTheWay = (order: Order) => {
const cleanPhone = order.customerPhone.startsWith('57') ? order.customerPhone : `57${order.customerPhone}`;
const text = `👋 ¡Hola *${order.customerName}*!\n\n🛵 Tu pedido ya va *EN CAMINO* con nuestro mensajero.\n\n📍 *Dirección de Entrega:* ${order.address}\n🏙️ *Ciudad:* ${order.cityName}\n💵 *Saldo a Pagar Contraentrega:* *${formatCOP(order.balanceDue)}*\n💳 *Medio de Pago:* Efectivo o Transferencia Nequi\n\nPor favor estar atento a la portería o celular. ¡Gracias por tu compra! 🔥`;
window.open(`https://wa.me/${cleanPhone}?text=${encodeURIComponent(text)}`, '_blank');
handleStatusChange(order.id, 'dispatched');
};
const getStatusBadge = (status: Order['status']) => {
switch (status) {
case 'pending':
@ -52,9 +122,9 @@ export default function AdminOrdersPage() {
case 'deposit_received':
return <span className="bg-blue-500/20 text-blue-400 border border-blue-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">Separación $10k Pagada</span>;
case 'supplier_notified':
return <span className="bg-cyan-500/20 text-cyan-300 border border-cyan-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><CheckCircle2 className="w-3 h-3 text-cyan-400" /> Proveedor Notificado</span>;
return <span className="bg-purple-500/20 text-purple-300 border border-purple-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><CheckCircle2 className="w-3 h-3 text-purple-400" /> Proveedor Notificado</span>;
case 'dispatched':
return <span className="bg-purple-500/20 text-purple-400 border border-purple-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">En Ruta con Mensajero</span>;
return <span className="bg-fuchsia-500/20 text-fuchsia-400 border border-fuchsia-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><Truck className="w-3 h-3" /> En Ruta con Mensajero</span>;
case 'delivered':
return <span className="bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">Entregado & Cobrado</span>;
case 'cancelled':
@ -66,6 +136,7 @@ export default function AdminOrdersPage() {
const matchesSearch =
o.customerName.toLowerCase().includes(search.toLowerCase()) ||
o.customerPhone.includes(search) ||
o.address.toLowerCase().includes(search.toLowerCase()) ||
o.cityName.toLowerCase().includes(search.toLowerCase()) ||
o.id.toLowerCase().includes(search.toLowerCase());
@ -74,208 +145,440 @@ export default function AdminOrdersPage() {
return true;
});
const supplierGroups = orders.reduce((acc, order) => {
order.items.forEach((item) => {
const suppName = item.supplierName || 'DIFUSION JOVIC STORE';
const suppPhone = item.supplierPhone || '573134848203';
if (!acc[suppName]) {
acc[suppName] = {
name: suppName,
phone: suppPhone,
entries: [],
totalPairs: 0,
totalCost: 0,
};
}
acc[suppName].entries.push({ order, item });
acc[suppName].totalPairs += item.quantity;
acc[suppName].totalCost += (item.supplierPrice || 0) * item.quantity;
});
return acc;
}, {} as Record<string, { name: string; phone: string; entries: { order: Order; item: OrderItem }[]; totalPairs: number; totalCost: number }>);
const pendingDeliveries = orders.filter((o) => o.status !== 'delivered' && o.status !== 'cancelled');
const totalBalanceToCollect = pendingDeliveries.reduce((acc, curr) => acc + curr.balanceDue, 0);
return (
<div className="space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-black text-white flex items-center gap-2">
<ShoppingBag className="w-6 h-6 text-cyan-400" />
<span>Gestión de Pedidos & Separación con Proveedores ({orders.length})</span>
</h1>
<p className="text-xs text-slate-400 mt-1">
Control logístico de pedidos contraentrega, cobro de anticipos de $10.000 COP y <strong>notificación inmediata de separación al proveedor</strong> vía WhatsApp.
</p>
</div>
{/* Toolbar */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="flex items-center space-x-1.5 bg-slate-900 border border-slate-800 p-1 rounded-2xl overflow-x-auto">
<button
onClick={() => setStatusFilter('all')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'all' ? 'bg-white text-slate-950 shadow' : 'text-slate-400 hover:text-white'
}`}
>
Todos ({orders.length})
</button>
<button
onClick={() => setStatusFilter('pending')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'pending' ? 'bg-amber-500/30 text-amber-300' : 'text-slate-400'
}`}
>
Pendientes
</button>
<button
onClick={() => setStatusFilter('supplier_notified')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'supplier_notified' ? 'bg-cyan-500/30 text-cyan-300' : 'text-slate-400'
}`}
>
Proveedor Notificado
</button>
<button
onClick={() => setStatusFilter('dispatched')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'dispatched' ? 'bg-purple-500/30 text-purple-300' : 'text-slate-400'
}`}
>
En Ruta
</button>
</div>
<div className="max-w-xs w-full">
<input
type="text"
placeholder="Buscar por cliente, ciudad o # orden..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-slate-900 border border-slate-800 rounded-xl px-3.5 py-2 text-xs text-white outline-hidden focus:border-cyan-400"
/>
</div>
</div>
{filteredOrders.length === 0 ? (
<div className="bg-slate-900 border border-slate-800 rounded-3xl p-12 text-center space-y-3">
<div className="w-16 h-16 rounded-full bg-slate-950 text-slate-500 flex items-center justify-center mx-auto border border-slate-800">
<ShoppingBag className="w-8 h-8 text-cyan-400" />
</div>
<h3 className="font-bold text-white text-base">No hay pedidos en esta sección</h3>
<p className="text-xs text-slate-400 max-w-sm mx-auto">
Cuando los clientes hagan un pedido en la tienda web o por WhatsApp, aparecerán aquí con el botón para separar inmediatamente con el proveedor correspondiente.
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-black text-white flex items-center gap-2">
<ShoppingBag className="w-6 h-6 text-purple-400" />
<span>Centro Logístico: Pedidos, Proveedores & Rutas Waze</span>
</h1>
<p className="text-xs text-neutral-400 mt-1">
Consolidación automática para <strong>separación con proveedores</strong> y <strong>hoja de ruta para mensajeros con navegación Waze en 1 clic</strong>.
</p>
</div>
) : (
</div>
{toastMessage && (
<div className="bg-purple-950/80 border border-purple-500 text-purple-300 px-4 py-3 rounded-2xl text-xs flex items-center justify-between animate-in fade-in shadow-md">
<span>{toastMessage}</span>
<button onClick={() => setToastMessage(null)} className="font-bold text-white cursor-pointer ml-3">
✕
</button>
</div>
)}
{/* Main Tabs Navigation */}
<div className="grid grid-cols-1 sm:grid-cols-3 p-1 bg-neutral-900 border border-neutral-800 rounded-2xl gap-1">
<button
onClick={() => setActiveTab('individual')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'individual'
? 'bg-purple-600 text-white shadow-lg shadow-purple-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Layers className="w-4 h-4" />
<span>1. Todos los Pedidos ({orders.length})</span>
</button>
<button
onClick={() => setActiveTab('suppliers')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'suppliers'
? 'bg-purple-600 text-white shadow-lg shadow-purple-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Building2 className="w-4 h-4 text-fuchsia-300" />
<span>2. Consolidado por Proveedor ({Object.keys(supplierGroups).length})</span>
</button>
<button
onClick={() => setActiveTab('routes')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'routes'
? 'bg-gradient-to-r from-purple-600 to-pink-600 text-white shadow-lg shadow-pink-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Compass className="w-4 h-4 text-cyan-300" />
<span>3. Rutas de Entrega & Waze ({pendingDeliveries.length})</span>
</button>
</div>
{/* TAB 1: INDIVIDUAL ORDERS LIST */}
{activeTab === 'individual' && (
<div className="space-y-4">
{filteredOrders.map((order) => (
<div
key={order.id}
className="bg-slate-900 border border-slate-800 rounded-3xl p-5 space-y-4 shadow-xl"
>
{/* Top Order bar */}
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-slate-800 pb-3">
<div className="flex items-center space-x-2">
<span className="font-black text-white text-sm">Orden #{order.id}</span>
<span className="text-xs text-slate-500">• {new Date(order.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
</div>
{getStatusBadge(order.status)}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="flex items-center space-x-1.5 bg-neutral-900 border border-neutral-800 p-1 rounded-2xl overflow-x-auto">
{['all', 'pending', 'deposit_received', 'supplier_notified', 'dispatched', 'delivered'].map((st) => (
<button
key={st}
onClick={() => setStatusFilter(st)}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === st ? 'bg-white text-neutral-950 shadow' : 'text-neutral-400 hover:text-white'
}`}
>
{st === 'all' ? 'Todos' : st === 'pending' ? 'Pendientes' : st === 'deposit_received' ? 'Anticipo Pagado' : st === 'supplier_notified' ? 'Prov. Notificado' : st === 'dispatched' ? 'En Ruta' : 'Entregados'}
</button>
))}
</div>
<div className="max-w-xs w-full">
<input
type="text"
placeholder="Buscar por cliente, celular, ciudad o #..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-neutral-900 border border-neutral-800 rounded-xl px-3.5 py-2 text-xs text-white outline-hidden focus:border-purple-400"
/>
</div>
</div>
<div className="space-y-4">
{filteredOrders.length === 0 ? (
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl p-12 text-center text-neutral-500">
No hay pedidos que coincidan con los filtros.
</div>
) : (
filteredOrders.map((order) => (
<div
key={order.id}
className="bg-neutral-900 border border-neutral-800 hover:border-purple-500/40 rounded-3xl p-5 shadow-xl transition-all space-y-4"
>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 border-b border-neutral-800 pb-3">
<div className="flex items-center space-x-3">
<span className="font-black text-white text-base">Orden #{order.id}</span>
{getStatusBadge(order.status)}
<span className="text-[11px] text-neutral-500 font-mono">{order.createdAt}</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
{/* Customer Details */}
<div className="bg-slate-950/60 border border-slate-800/80 rounded-2xl p-3.5 space-y-1.5">
<span className="text-[10px] text-cyan-400 font-bold uppercase block tracking-wider">
Cliente & Entrega (24 Horas)
</span>
<p className="font-bold text-white text-sm">{order.customerName}</p>
<p className="text-slate-300 flex items-center gap-1.5">
<Phone className="w-3.5 h-3.5 text-cyan-400" /> {order.customerPhone}
</p>
<p className="text-slate-300 flex items-center gap-1.5">
<MapPin className="w-3.5 h-3.5 text-cyan-400" /> {order.address} ({order.cityName})
</p>
</div>
<div className="flex items-center space-x-2">
<span className="text-xs text-neutral-400 font-bold">Estado:</span>
<select
value={order.status}
onChange={(e) => handleStatusChange(order.id, e.target.value as any)}
className="bg-neutral-950 border border-neutral-800 text-xs text-white rounded-xl px-3 py-1.5 outline-hidden font-bold cursor-pointer"
>
<option value="pending">⏳ Pendiente Confirmar</option>
<option value="deposit_received">💳 Separación $10k Pagada</option>
<option value="supplier_notified">🏢 Proveedor Notificado</option>
<option value="dispatched">🛵 En Ruta con Mensajero</option>
<option value="delivered">✅ Entregado & Cobrado</option>
<option value="cancelled">❌ Cancelado</option>
</select>
</div>
</div>
{/* Items and Supplier Separation Action */}
<div className="bg-slate-950/60 border border-slate-800/80 rounded-2xl p-3.5 space-y-2">
<span className="text-[10px] text-slate-400 font-bold uppercase block tracking-wider">
Zapatillas & Proveedor Asignado:
</span>
<div className="space-y-2.5">
{order.items.map((it, idx) => {
const suppName = it.supplierName || 'DIFUSION JOVIC STORE';
return (
<div key={idx} className="bg-slate-900 border border-slate-800 rounded-xl p-2.5 space-y-2">
<div className="flex items-center justify-between gap-2">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
<div className="space-y-1 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Cliente & Destino</p>
<p className="text-white font-bold text-sm">{order.customerName}</p>
<p className="text-emerald-400 font-mono font-semibold flex items-center gap-1">
<Phone className="w-3 h-3" /> {order.customerPhone}
</p>
<p className="text-neutral-300 flex items-start gap-1">
<MapPin className="w-3 h-3 shrink-0 text-purple-400 mt-0.5" />
<span>{order.address} ({order.cityName})</span>
</p>
</div>
<div className="space-y-1 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Resumen de Cobro</p>
<div className="flex justify-between text-neutral-300">
<span>Total Venta:</span>
<strong className="text-white">{formatCOP(order.totalAmount)}</strong>
</div>
<div className="flex justify-between text-neutral-300">
<span>Anticipo / Separación:</span>
<strong className="text-blue-400">-{formatCOP(order.depositAmount)}</strong>
</div>
<div className="flex justify-between text-neutral-300 pt-1 border-t border-neutral-800 font-bold">
<span className="text-purple-300">Saldo Contraentrega:</span>
<span className="text-emerald-400 text-sm">{formatCOP(order.balanceDue)}</span>
</div>
</div>
<div className="space-y-2 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80 flex flex-col justify-center">
<button
onClick={() => handleOpenWaze(order.address, order.cityName)}
className="w-full bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-1.5 px-3 rounded-xl flex items-center justify-center gap-1.5 cursor-pointer shadow transition-all text-xs"
>
<Compass className="w-3.5 h-3.5" />
<span>Navegar con Waze</span>
</button>
<button
onClick={() => handleNotifyCustomerOnTheWay(order)}
className="w-full bg-emerald-600 hover:bg-emerald-500 text-white font-bold py-1.5 px-3 rounded-xl flex items-center justify-center gap-1.5 cursor-pointer shadow transition-all text-xs"
>
<MessageCircle className="w-3.5 h-3.5" />
<span>Avisar "Voy en camino"</span>
</button>
</div>
</div>
<div className="space-y-2 pt-2">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Pares en esta orden:</p>
<div className="space-y-2">
{order.items.map((item, idx) => (
<div
key={idx}
className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 bg-neutral-950 p-3 rounded-2xl border border-neutral-800"
>
<div className="flex items-center space-x-3">
<img src={item.image} alt="" className="w-12 h-12 rounded-xl object-cover border border-neutral-800 shrink-0" />
<div>
<p className="font-bold text-white text-xs">{it.productName}</p>
<p className="text-[11px] text-slate-400">
Talla <strong className="text-cyan-400 font-bold">{it.size}</strong> • Cant: {it.quantity}
<p className="font-bold text-white text-xs">{item.productName}</p>
<p className="text-[11px] text-neutral-400">
Talla: <strong className="text-purple-400">{item.size}</strong> | Cantidad: <strong>{item.quantity}</strong> | Proveedor: <strong className="text-purple-300">{item.supplierName || 'Bodega Principal'}</strong>
</p>
</div>
<span className="text-[10px] bg-slate-950 text-slate-300 border border-slate-800 px-2 py-0.5 rounded font-mono">
Prov: {suppName}
</span>
</div>
{/* 1-Click WhatsApp Separation Button */}
<button
onClick={() => handleNotifySupplier(order, it)}
className="w-full bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-300 border border-emerald-500/30 font-bold text-xs py-1.5 px-2.5 rounded-lg flex items-center justify-center space-x-1.5 transition-all cursor-pointer shadow-xs"
title="Enviar mensaje preformateado al WhatsApp del proveedor para separar este par"
onClick={() => handleNotifySingleSupplier(order, item)}
className="bg-purple-600 hover:bg-purple-500 text-white font-bold text-xs px-3.5 py-2 rounded-xl flex items-center space-x-1.5 shadow cursor-pointer shrink-0 self-end sm:self-auto"
>
<MessageCircle className="w-3.5 h-3.5 text-emerald-400" />
<span>📲 Separar con {suppName} (WhatsApp)</span>
<Send className="w-3.5 h-3.5" />
<span>Separar con Proveedor</span>
</button>
</div>
);
})}
))}
</div>
</div>
</div>
))
)}
</div>
</div>
)}
{/* TAB 2: CONSOLIDADO AGRUPADO POR PROVEEDOR */}
{activeTab === 'suppliers' && (
<div className="space-y-6 animate-in fade-in">
<div className="bg-purple-950/40 border border-purple-500/30 rounded-3xl p-5 text-xs text-neutral-300 space-y-2 shadow-lg">
<div className="flex items-center space-x-2 text-purple-300 font-bold text-sm">
<Building2 className="w-5 h-5 text-purple-400" />
<span>Consolidado Automático de Compras & Separaciones</span>
</div>
<p>
Aquí se agrupan todos los pares pendientes clasificados por cada <strong>Proveedor Mayorista</strong>. Puedes enviar el <strong>manifiesto consolidado en 1 solo mensaje de WhatsApp</strong> para que el proveedor prepare y empaque todos los pedidos de la jornada juntos.
</p>
</div>
<div className="grid grid-cols-1 gap-6">
{Object.values(supplierGroups).map((group) => (
<div
key={group.name}
className="bg-neutral-900 border border-neutral-800 rounded-3xl p-6 shadow-xl space-y-4"
>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 border-b border-neutral-800 pb-4">
<div>
<span className="bg-purple-500/20 text-purple-300 border border-purple-500/40 text-[10px] font-bold px-2.5 py-0.5 rounded-full uppercase tracking-wider">
Proveedor Mayorista
</span>
<h3 className="text-xl font-black text-white mt-1">{group.name}</h3>
<p className="text-xs text-neutral-400 flex items-center gap-1 mt-0.5">
<Phone className="w-3.5 h-3.5 text-emerald-400" />
<span>WhatsApp Proveedor: <strong>{group.phone}</strong></span>
</p>
</div>
<div className="flex items-center gap-3">
<div className="bg-neutral-950 border border-neutral-800 px-4 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Total Pares</p>
<p className="text-lg font-black text-purple-400">{group.totalPairs} par(es)</p>
</div>
<div className="bg-neutral-950 border border-neutral-800 px-4 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Total a Pagar</p>
<p className="text-lg font-black text-emerald-400">{formatCOP(group.totalCost)}</p>
</div>
</div>
</div>
{/* Payment Breakdown */}
<div className="bg-slate-950/60 border border-slate-800/80 rounded-2xl p-3.5 space-y-2">
<span className="text-[10px] text-slate-400 font-bold uppercase block tracking-wider">
Cobro & Pagos Contraentrega
</span>
<div className="space-y-1 text-xs">
<p className="text-slate-400 flex justify-between">
<span>Total de la Orden:</span>
<strong className="text-white font-mono">{formatCOP(order.totalAmount)}</strong>
</p>
{order.depositAmount > 0 ? (
<>
<p className="text-amber-300 flex justify-between">
<span>Anticipo / Separación:</span>
<strong className="font-mono">{formatCOP(order.depositAmount)}</strong>
</p>
<p className="text-emerald-400 font-bold flex justify-between pt-1 border-t border-slate-800">
<span>Saldo al Mensajero:</span>
<strong className="font-mono">{formatCOP(order.balanceDue)}</strong>
</p>
</>
) : (
<p className="text-emerald-400 font-bold pt-1 border-t border-slate-800">
100% Contraentrega al recibir
</p>
)}
</div>
<div className="overflow-x-auto">
<table className="w-full text-left text-xs text-neutral-300">
<thead className="bg-neutral-950 text-neutral-400 font-bold uppercase text-[10px]">
<tr>
<th className="py-2.5 px-3">Modelo / Zapatilla</th>
<th className="py-2.5 px-3">Talla</th>
<th className="py-2.5 px-3">Cant.</th>
<th className="py-2.5 px-3">Costo Base</th>
<th className="py-2.5 px-3">Orden Cliente</th>
<th className="py-2.5 px-3">Destino</th>
<th className="py-2.5 px-3">Estado Orden</th>
</tr>
</thead>
<tbody className="divide-y divide-neutral-800">
{group.entries.map((entry, idx) => (
<tr key={idx} className="hover:bg-neutral-800/40">
<td className="py-2.5 px-3 font-bold text-white flex items-center gap-2">
<img src={entry.item.image} alt="" className="w-8 h-8 rounded-lg object-cover border border-neutral-800" />
<span>{entry.item.productName}</span>
</td>
<td className="py-2.5 px-3 font-bold text-purple-400 font-mono">{entry.item.size}</td>
<td className="py-2.5 px-3 font-bold">{entry.item.quantity}</td>
<td className="py-2.5 px-3 font-mono font-semibold">{formatCOP(entry.item.supplierPrice || 0)}</td>
<td className="py-2.5 px-3 font-mono text-neutral-400">#{entry.order.id}</td>
<td className="py-2.5 px-3">{entry.order.cityName}</td>
<td className="py-2.5 px-3">{getStatusBadge(entry.order.status)}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="pt-2 flex justify-end">
<button
onClick={() => handleSendSupplierBatchManifest(group.name, group.phone, group.entries)}
className="bg-gradient-to-r from-purple-600 via-fuchsia-600 to-pink-600 hover:opacity-95 text-white font-black text-xs px-5 py-3 rounded-2xl shadow-lg flex items-center gap-2 cursor-pointer transition-all active:scale-95"
>
<MessageCircle className="w-4 h-4" />
<span>Enviar Manifiesto de {group.totalPairs} Pares por WhatsApp a {group.name}</span>
</button>
</div>
</div>
))}
</div>
</div>
)}
{/* Order Status Action Buttons */}
<div className="flex flex-wrap items-center justify-between gap-2 pt-2 border-t border-slate-800">
<span className="text-[11px] text-slate-500">Método: {order.paymentMethodName}</span>
{/* TAB 3: RUTAS DE ENTREGA ORGANIZADAS POR DIRECCIONES & WAZE */}
{activeTab === 'routes' && (
<div className="space-y-6 animate-in fade-in">
<div className="bg-gradient-to-r from-neutral-900 via-neutral-900 to-purple-950/60 border border-purple-500/30 rounded-3xl p-6 shadow-xl flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<div className="flex items-center space-x-2 text-cyan-300 font-bold text-sm">
<Compass className="w-5 h-5 text-cyan-400" />
<span>Hoja de Ruta & Navegación GPS (Waze / Google Maps)</span>
</div>
<p className="text-xs text-neutral-300 mt-1 max-w-xl">
Entregas contraentrega secuenciadas por zona geográfica. El mensajero puede presionar <strong>"Abrir en Waze"</strong> para iniciar la navegación instantánea a cada portería/casa.
</p>
</div>
<div className="flex flex-wrap gap-2">
<button
onClick={() => handleStatusChange(order.id, 'deposit_received')}
className="bg-blue-600/20 hover:bg-blue-600/30 text-blue-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-blue-500/20"
>
Confirmar $10k
</button>
<button
onClick={() => handleStatusChange(order.id, 'supplier_notified')}
className="bg-cyan-600/20 hover:bg-cyan-600/30 text-cyan-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-cyan-500/20"
>
Marcar Proveedor Notificado
</button>
<button
onClick={() => handleStatusChange(order.id, 'dispatched')}
className="bg-purple-600/20 hover:bg-purple-600/30 text-purple-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-purple-500/20"
>
Despachar en Ruta
</button>
<button
onClick={() => handleStatusChange(order.id, 'delivered')}
className="bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-emerald-500/20"
>
Marcar Entregado
</button>
</div>
<div className="flex items-center gap-3">
<div className="bg-neutral-950 border border-neutral-800 p-3 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Paradas / Entregas</p>
<p className="text-xl font-black text-white">{pendingDeliveries.length} Paradas</p>
</div>
<div className="bg-neutral-950 border border-emerald-500/30 p-3 rounded-2xl text-right">
<p className="text-[10px] text-emerald-400 uppercase font-bold">Total a Recaudar</p>
<p className="text-xl font-black text-emerald-400">{formatCOP(totalBalanceToCollect)}</p>
</div>
</div>
))}
</div>
<div className="space-y-3">
{pendingDeliveries.length === 0 ? (
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl p-12 text-center text-neutral-500">
🎉 No hay entregas pendientes por despachar en este momento.
</div>
) : (
pendingDeliveries.map((delivery, index) => (
<div
key={delivery.id}
className="bg-neutral-900 border border-neutral-800 hover:border-cyan-500/40 rounded-3xl p-5 shadow-lg transition-all flex flex-col lg:flex-row lg:items-center justify-between gap-4"
>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 rounded-2xl bg-cyan-500/20 border border-cyan-500/40 flex flex-col items-center justify-center text-cyan-300 font-black shrink-0 shadow-sm">
<span className="text-[10px] uppercase font-bold text-neutral-400 leading-none">Parada</span>
<span className="text-lg leading-tight">#{index + 1}</span>
</div>
<div className="space-y-1">
<div className="flex items-center space-x-2">
<span className="font-black text-white text-base">{delivery.customerName}</span>
<span className="text-xs text-neutral-400">• Orden #{delivery.id}</span>
{getStatusBadge(delivery.status)}
</div>
<p className="text-xs font-bold text-white flex items-center gap-1.5 bg-neutral-950 px-3 py-1 rounded-xl border border-neutral-800 w-fit">
<MapPin className="w-4 h-4 text-cyan-400 shrink-0" />
<span>{delivery.address} ({delivery.cityName})</span>
</p>
<div className="flex items-center space-x-3 text-xs text-neutral-400 pt-0.5">
<span className="flex items-center gap-1 text-emerald-400 font-semibold">
<Phone className="w-3 h-3" /> {delivery.customerPhone}
</span>
<span>•</span>
<span>
{delivery.items.map((i) => `${i.productName} (Talla ${i.size})`).join(', ')}
</span>
</div>
{delivery.notes && (
<p className="text-[11px] text-amber-300/90 italic bg-amber-500/10 px-2.5 py-0.5 rounded-lg border border-amber-500/20 w-fit">
Nota: {delivery.notes}
</p>
)}
</div>
</div>
<div className="flex items-center gap-3 self-end lg:self-auto shrink-0">
<div className="bg-neutral-950 border border-neutral-800 px-3.5 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 font-bold uppercase">Cobrar Contraentrega</p>
<p className="text-base font-black text-emerald-400">{formatCOP(delivery.balanceDue)}</p>
</div>
<button
onClick={() => handleOpenWaze(delivery.address, delivery.cityName)}
className="bg-cyan-600 hover:bg-cyan-500 text-white font-black text-xs px-4 py-3 rounded-2xl shadow-lg flex items-center gap-1.5 cursor-pointer active:scale-95 transition-all"
title="Abrir ubicación directamente en Waze"
>
<Compass className="w-4 h-4" />
<span>Waze</span>
</button>
<button
onClick={() => handleOpenGoogleMaps(delivery.address, delivery.cityName)}
className="bg-neutral-800 hover:bg-neutral-700 text-white font-bold text-xs p-3 rounded-2xl shadow cursor-pointer transition-all"
title="Abrir en Google Maps"
>
<Navigation className="w-4 h-4 text-emerald-400" />
</button>
<button
onClick={() => handleNotifyCustomerOnTheWay(delivery)}
className="bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs px-3.5 py-3 rounded-2xl shadow flex items-center gap-1.5 cursor-pointer active:scale-95 transition-all"
title="Enviar mensaje de WhatsApp notificando que el mensajero va en camino"
>
<MessageCircle className="w-4 h-4" />
<span className="hidden sm:inline">En Camino</span>
</button>
</div>
</div>
))
)}
</div>
</div>
)}
</div>

View File

@ -1,14 +1,36 @@
'use client';
import React, { useState, useEffect } from 'react';
import { ShoppingBag, Truck, CheckCircle2, Clock, Phone, MapPin, Search, MessageCircle, Send, ShieldCheck, UserCheck } from 'lucide-react';
import {
ShoppingBag,
Truck,
CheckCircle2,
Clock,
Phone,
MapPin,
Search,
MessageCircle,
Send,
ShieldCheck,
Building2,
Navigation,
ExternalLink,
DollarSign,
Layers,
Sparkles,
Compass,
Check,
AlertCircle
} from 'lucide-react';
import { Order, OrderItem } from '@/types';
import { formatCOP } from '@/lib/pricing-engine';
export default function AdminOrdersPage() {
const [orders, setOrders] = useState<Order[]>([]);
const [activeTab, setActiveTab] = useState<'individual' | 'suppliers' | 'routes'>('individual');
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState<string>('all');
const [toastMessage, setToastMessage] = useState<string | null>(null);
useEffect(() => {
fetch('/api/orders')
@ -22,14 +44,20 @@ export default function AdminOrdersPage() {
prev.map((o) => (o.id === orderId ? { ...o, status: newStatus } : o))
);
await fetch('/api/orders', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: orderId, status: newStatus }),
}).catch(() => {});
try {
await fetch('/api/orders', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: orderId, status: newStatus }),
});
setToastMessage(`Estado de la orden #${orderId} actualizado a "${newStatus}".`);
} catch (e) {
console.error(e);
}
};
const handleNotifySupplier = (order: Order, item: OrderItem) => {
// 1. Notificar separación individual a proveedor
const handleNotifySingleSupplier = (order: Order, item: OrderItem) => {
const supplierPhone = item.supplierPhone || '573134848203';
const supplierName = item.supplierName || 'Proveedor Mayorista';
const supplierCost = item.supplierPrice ? formatCOP(item.supplierPrice) : 'Precio Mayorista';
@ -44,6 +72,55 @@ export default function AdminOrdersPage() {
}
};
// 2. Enviar Manifiesto Consolidado por Proveedor vía WhatsApp
const handleSendSupplierBatchManifest = (supplierName: string, supplierPhone: string, itemsList: { order: Order; item: OrderItem }[]) => {
const totalPairs = itemsList.reduce((acc, curr) => acc + curr.item.quantity, 0);
const totalCost = itemsList.reduce((acc, curr) => acc + (curr.item.supplierPrice || 0) * curr.item.quantity, 0);
let text = `📦 *MANIFIESTO CONSOLIDADO DE SEPARACIÓN*\n`;
text += `🏢 *Proveedor:* ${supplierName}\n`;
text += `📅 *Fecha:* ${new Date().toLocaleDateString('es-CO')} | *Total Pares:* ${totalPairs}\n`;
text += `💰 *Valor Total a Pagar Proveedor:* ${formatCOP(totalCost)}\n\n`;
text += `👟 *DETALLE DE PARES A SEPARAR:*\n`;
itemsList.forEach((entry, idx) => {
text += `\n${idx + 1}. *${entry.item.productName}*\n`;
text += ` • Talla: *${entry.item.size}* (x${entry.item.quantity})\n`;
text += ` • Costo Base: ${formatCOP(entry.item.supplierPrice || 0)}\n`;
text += ` • Orden Cliente: #${entry.order.id} (${entry.order.cityName})\n`;
});
text += `\n🤝 *Por favor confirmar disponibilidad y empaque para recogida del mensajero.* ¡Muchas gracias!`;
const cleanPhone = supplierPhone.startsWith('57') ? supplierPhone : `57${supplierPhone}`;
window.open(`https://wa.me/${cleanPhone}?text=${encodeURIComponent(text)}`, '_blank');
setToastMessage(`📲 Manifiesto de ${totalPairs} pares enviado a ${supplierName}.`);
};
// 3. Abrir en Waze
const handleOpenWaze = (address: string, city: string) => {
const query = encodeURIComponent(`${address}, ${city}, Colombia`);
// Waze Universal Deep Link
const wazeUrl = `https://waze.com/ul?q=${query}&navigate=yes`;
window.open(wazeUrl, '_blank');
};
// 4. Abrir en Google Maps
const handleOpenGoogleMaps = (address: string, city: string) => {
const query = encodeURIComponent(`${address}, ${city}, Colombia`);
const mapsUrl = `https://www.google.com/maps/search/?api=1&query=${query}`;
window.open(mapsUrl, '_blank');
};
// 5. Avisar al cliente que el mensajero va en camino
const handleNotifyCustomerOnTheWay = (order: Order) => {
const cleanPhone = order.customerPhone.startsWith('57') ? order.customerPhone : `57${order.customerPhone}`;
const text = `👋 ¡Hola *${order.customerName}*!\n\n🛵 Tu pedido de *DripZone* ya va *EN CAMINO* con nuestro mensajero.\n\n📍 *Dirección de Entrega:* ${order.address}\n🏙️ *Ciudad:* ${order.cityName}\n💵 *Saldo a Pagar Contraentrega:* *${formatCOP(order.balanceDue)}*\n💳 *Medio de Pago:* Efectivo o Transferencia Nequi\n\nPor favor estar atento a la portería o celular. ¡Gracias por tu compra! 🔥`;
window.open(`https://wa.me/${cleanPhone}?text=${encodeURIComponent(text)}`, '_blank');
handleStatusChange(order.id, 'dispatched');
};
const getStatusBadge = (status: Order['status']) => {
switch (status) {
case 'pending':
@ -53,7 +130,7 @@ export default function AdminOrdersPage() {
case 'supplier_notified':
return <span className="bg-purple-500/20 text-purple-300 border border-purple-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><CheckCircle2 className="w-3 h-3 text-purple-400" /> Proveedor Notificado</span>;
case 'dispatched':
return <span className="bg-purple-500/20 text-purple-400 border border-purple-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">En Ruta con Mensajero</span>;
return <span className="bg-fuchsia-500/20 text-fuchsia-400 border border-fuchsia-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><Truck className="w-3 h-3" /> En Ruta con Mensajero</span>;
case 'delivered':
return <span className="bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">Entregado & Cobrado</span>;
case 'cancelled':
@ -61,10 +138,12 @@ export default function AdminOrdersPage() {
}
};
// Filtrado de pedidos
const filteredOrders = orders.filter((o) => {
const matchesSearch =
o.customerName.toLowerCase().includes(search.toLowerCase()) ||
o.customerPhone.includes(search) ||
o.address.toLowerCase().includes(search.toLowerCase()) ||
o.cityName.toLowerCase().includes(search.toLowerCase()) ||
o.id.toLowerCase().includes(search.toLowerCase());
@ -73,201 +152,464 @@ export default function AdminOrdersPage() {
return true;
});
// Agrupación por Proveedor
const supplierGroups = orders.reduce((acc, order) => {
order.items.forEach((item) => {
const suppName = item.supplierName || 'DIFUSION JOVIC STORE';
const suppPhone = item.supplierPhone || '573134848203';
if (!acc[suppName]) {
acc[suppName] = {
name: suppName,
phone: suppPhone,
entries: [],
totalPairs: 0,
totalCost: 0,
};
}
acc[suppName].entries.push({ order, item });
acc[suppName].totalPairs += item.quantity;
acc[suppName].totalCost += (item.supplierPrice || 0) * item.quantity;
});
return acc;
}, {} as Record<string, { name: string; phone: string; entries: { order: Order; item: OrderItem }[]; totalPairs: number; totalCost: number }>);
// Agrupación de Pendientes por Entregar (Zonas / Direcciones)
const pendingDeliveries = orders.filter((o) => o.status !== 'delivered' && o.status !== 'cancelled');
const totalBalanceToCollect = pendingDeliveries.reduce((acc, curr) => acc + curr.balanceDue, 0);
return (
<div className="space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-black text-white flex items-center gap-2">
<ShoppingBag className="w-6 h-6 text-purple-400" />
<span>Gestión de Pedidos & Separación con Proveedores ({orders.length})</span>
</h1>
<p className="text-xs text-neutral-400 mt-1">
Control logístico de pedidos contraentrega, cobro de anticipos de $10.000 COP y <strong>notificación inmediata de separación al proveedor</strong> vía WhatsApp.
</p>
</div>
{/* Toolbar */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="flex items-center space-x-1.5 bg-neutral-900 border border-neutral-800 p-1 rounded-2xl overflow-x-auto">
<button
onClick={() => setStatusFilter('all')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'all' ? 'bg-white text-neutral-950 shadow' : 'text-neutral-400 hover:text-white'
}`}
>
Todos ({orders.length})
</button>
<button
onClick={() => setStatusFilter('pending')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'pending' ? 'bg-amber-500/30 text-amber-300' : 'text-neutral-400'
}`}
>
Pendientes
</button>
<button
onClick={() => setStatusFilter('supplier_notified')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'supplier_notified' ? 'bg-purple-500/30 text-purple-300' : 'text-neutral-400'
}`}
>
Proveedor Notificado
</button>
<button
onClick={() => setStatusFilter('dispatched')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'dispatched' ? 'bg-purple-500/30 text-purple-300' : 'text-neutral-400'
}`}
>
En Ruta
</button>
</div>
<div className="max-w-xs w-full">
<input
type="text"
placeholder="Buscar por cliente, ciudad o # orden..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-neutral-900 border border-neutral-800 rounded-xl px-3.5 py-2 text-xs text-white outline-hidden focus:border-purple-400"
/>
</div>
</div>
{filteredOrders.length === 0 ? (
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl p-12 text-center space-y-3">
<div className="w-16 h-16 rounded-full bg-neutral-950 text-neutral-500 flex items-center justify-center mx-auto border border-neutral-800">
<ShoppingBag className="w-8 h-8 text-purple-400" />
</div>
<h3 className="font-bold text-white text-base">No hay pedidos en esta sección</h3>
<p className="text-xs text-neutral-400 max-w-sm mx-auto">
Cuando los clientes hagan un pedido en la tienda web o por WhatsApp, aparecerán aquí con el botón para separar inmediatamente con el proveedor correspondiente.
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-black text-white flex items-center gap-2">
<ShoppingBag className="w-6 h-6 text-purple-400" />
<span>Centro Logístico: Pedidos, Proveedores & Rutas Waze</span>
</h1>
<p className="text-xs text-neutral-400 mt-1">
Consolidación automática para <strong>separación con proveedores</strong> y <strong>hoja de ruta para mensajeros con navegación Waze en 1 clic</strong>.
</p>
</div>
) : (
</div>
{toastMessage && (
<div className="bg-purple-950/80 border border-purple-500 text-purple-300 px-4 py-3 rounded-2xl text-xs flex items-center justify-between animate-in fade-in shadow-md">
<span>{toastMessage}</span>
<button onClick={() => setToastMessage(null)} className="font-bold text-white cursor-pointer ml-3">
✕
</button>
</div>
)}
{/* Main Tabs Navigation */}
<div className="grid grid-cols-1 sm:grid-cols-3 p-1 bg-neutral-900 border border-neutral-800 rounded-2xl gap-1">
<button
onClick={() => setActiveTab('individual')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'individual'
? 'bg-purple-600 text-white shadow-lg shadow-purple-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Layers className="w-4 h-4" />
<span>1. Todos los Pedidos ({orders.length})</span>
</button>
<button
onClick={() => setActiveTab('suppliers')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'suppliers'
? 'bg-purple-600 text-white shadow-lg shadow-purple-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Building2 className="w-4 h-4 text-fuchsia-300" />
<span>2. Consolidado por Proveedor ({Object.keys(supplierGroups).length})</span>
</button>
<button
onClick={() => setActiveTab('routes')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'routes'
? 'bg-gradient-to-r from-purple-600 to-pink-600 text-white shadow-lg shadow-pink-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Compass className="w-4 h-4 text-cyan-300" />
<span>3. Rutas de Entrega & Waze ({pendingDeliveries.length})</span>
</button>
</div>
{/* TAB 1: INDIVIDUAL ORDERS LIST */}
{activeTab === 'individual' && (
<div className="space-y-4">
{filteredOrders.map((order) => (
<div
key={order.id}
className="bg-neutral-900 border border-neutral-800 rounded-3xl p-5 space-y-4 shadow-xl"
>
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-neutral-800 pb-3">
<div className="flex items-center space-x-2">
<span className="font-black text-white text-sm">Orden #{order.id}</span>
<span className="text-xs text-neutral-500">• {new Date(order.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
</div>
{getStatusBadge(order.status)}
{/* Toolbar */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="flex items-center space-x-1.5 bg-neutral-900 border border-neutral-800 p-1 rounded-2xl overflow-x-auto">
{['all', 'pending', 'deposit_received', 'supplier_notified', 'dispatched', 'delivered'].map((st) => (
<button
key={st}
onClick={() => setStatusFilter(st)}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === st ? 'bg-white text-neutral-950 shadow' : 'text-neutral-400 hover:text-white'
}`}
>
{st === 'all' ? 'Todos' : st === 'pending' ? 'Pendientes' : st === 'deposit_received' ? 'Anticipo Pagado' : st === 'supplier_notified' ? 'Prov. Notificado' : st === 'dispatched' ? 'En Ruta' : 'Entregados'}
</button>
))}
</div>
<div className="max-w-xs w-full">
<input
type="text"
placeholder="Buscar por cliente, celular, ciudad o #..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-neutral-900 border border-neutral-800 rounded-xl px-3.5 py-2 text-xs text-white outline-hidden focus:border-purple-400"
/>
</div>
</div>
{/* Orders Cards Grid */}
<div className="space-y-4">
{filteredOrders.length === 0 ? (
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl p-12 text-center text-neutral-500">
No hay pedidos que coincidan con los filtros.
</div>
) : (
filteredOrders.map((order) => (
<div
key={order.id}
className="bg-neutral-900 border border-neutral-800 hover:border-purple-500/40 rounded-3xl p-5 shadow-xl transition-all space-y-4"
>
{/* Top Bar: Order ID, Status, Actions */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 border-b border-neutral-800 pb-3">
<div className="flex items-center space-x-3">
<span className="font-black text-white text-base">Orden #{order.id}</span>
{getStatusBadge(order.status)}
<span className="text-[11px] text-neutral-500 font-mono">{order.createdAt}</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
<div className="bg-neutral-950/60 border border-neutral-800/80 rounded-2xl p-3.5 space-y-1.5">
<span className="text-[10px] text-purple-400 font-bold uppercase block tracking-wider">
Cliente & Entrega (24 Horas)
</span>
<p className="font-bold text-white text-sm">{order.customerName}</p>
<p className="text-neutral-300 flex items-center gap-1.5">
<Phone className="w-3.5 h-3.5 text-purple-400" /> {order.customerPhone}
</p>
<p className="text-neutral-300 flex items-center gap-1.5">
<MapPin className="w-3.5 h-3.5 text-purple-400" /> {order.address} ({order.cityName})
</p>
</div>
{/* Status Dropdown */}
<div className="flex items-center space-x-2">
<span className="text-xs text-neutral-400 font-bold">Estado:</span>
<select
value={order.status}
onChange={(e) => handleStatusChange(order.id, e.target.value as any)}
className="bg-neutral-950 border border-neutral-800 text-xs text-white rounded-xl px-3 py-1.5 outline-hidden font-bold cursor-pointer"
>
<option value="pending">⏳ Pendiente Confirmar</option>
<option value="deposit_received">💳 Separación $10k Pagada</option>
<option value="supplier_notified">🏢 Proveedor Notificado</option>
<option value="dispatched">🛵 En Ruta con Mensajero</option>
<option value="delivered">✅ Entregado & Cobrado</option>
<option value="cancelled">❌ Cancelado</option>
</select>
</div>
</div>
<div className="bg-neutral-950/60 border border-neutral-800/80 rounded-2xl p-3.5 space-y-2">
<span className="text-[10px] text-neutral-400 font-bold uppercase block tracking-wider">
Zapatillas & Proveedor Asignado:
</span>
<div className="space-y-2.5">
{order.items.map((it, idx) => {
const suppName = it.supplierName || 'DIFUSION JOVIC STORE';
return (
<div key={idx} className="bg-neutral-900 border border-neutral-800 rounded-xl p-2.5 space-y-2">
<div className="flex items-center justify-between gap-2">
{/* Customer Info & Financials */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
{/* Customer */}
<div className="space-y-1 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Cliente & Destino</p>
<p className="text-white font-bold text-sm">{order.customerName}</p>
<p className="text-emerald-400 font-mono font-semibold flex items-center gap-1">
<Phone className="w-3 h-3" /> {order.customerPhone}
</p>
<p className="text-neutral-300 flex items-start gap-1">
<MapPin className="w-3 h-3 shrink-0 text-purple-400 mt-0.5" />
<span>{order.address} ({order.cityName})</span>
</p>
</div>
{/* Financial Balance */}
<div className="space-y-1 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Resumen de Cobro</p>
<div className="flex justify-between text-neutral-300">
<span>Total Venta:</span>
<strong className="text-white">{formatCOP(order.totalAmount)}</strong>
</div>
<div className="flex justify-between text-neutral-300">
<span>Anticipo / Separación:</span>
<strong className="text-blue-400">-{formatCOP(order.depositAmount)}</strong>
</div>
<div className="flex justify-between text-neutral-300 pt-1 border-t border-neutral-800 font-bold">
<span className="text-purple-300">Saldo Contraentrega:</span>
<span className="text-emerald-400 text-sm">{formatCOP(order.balanceDue)}</span>
</div>
</div>
{/* Quick Dispatch Actions */}
<div className="space-y-2 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80 flex flex-col justify-center">
<button
onClick={() => handleOpenWaze(order.address, order.cityName)}
className="w-full bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-1.5 px-3 rounded-xl flex items-center justify-center gap-1.5 cursor-pointer shadow transition-all text-xs"
>
<Compass className="w-3.5 h-3.5" />
<span>Navegar con Waze</span>
</button>
<button
onClick={() => handleNotifyCustomerOnTheWay(order)}
className="w-full bg-emerald-600 hover:bg-emerald-500 text-white font-bold py-1.5 px-3 rounded-xl flex items-center justify-center gap-1.5 cursor-pointer shadow transition-all text-xs"
>
<MessageCircle className="w-3.5 h-3.5" />
<span>Avisar "Voy en camino"</span>
</button>
</div>
</div>
{/* Items breakdown with Supplier notification button */}
<div className="space-y-2 pt-2">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Pares en esta orden:</p>
<div className="space-y-2">
{order.items.map((item, idx) => (
<div
key={idx}
className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 bg-neutral-950 p-3 rounded-2xl border border-neutral-800"
>
<div className="flex items-center space-x-3">
<img src={item.image} alt="" className="w-12 h-12 rounded-xl object-cover border border-neutral-800 shrink-0" />
<div>
<p className="font-bold text-white text-xs">{it.productName}</p>
<p className="font-bold text-white text-xs">{item.productName}</p>
<p className="text-[11px] text-neutral-400">
Talla <strong className="text-purple-400 font-bold">{it.size}</strong> • Cant: {it.quantity}
Talla: <strong className="text-purple-400">{item.size}</strong> | Cantidad: <strong>{item.quantity}</strong> | Proveedor: <strong className="text-purple-300">{item.supplierName || 'Bodega Principal'}</strong>
</p>
</div>
<span className="text-[10px] bg-neutral-950 text-neutral-300 border border-neutral-800 px-2 py-0.5 rounded font-mono">
Prov: {suppName}
</span>
</div>
<button
onClick={() => handleNotifySupplier(order, it)}
className="w-full bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-300 border border-emerald-500/30 font-bold text-xs py-1.5 px-2.5 rounded-lg flex items-center justify-center space-x-1.5 transition-all cursor-pointer shadow-xs"
onClick={() => handleNotifySingleSupplier(order, item)}
className="bg-purple-600 hover:bg-purple-500 text-white font-bold text-xs px-3.5 py-2 rounded-xl flex items-center space-x-1.5 shadow cursor-pointer shrink-0 self-end sm:self-auto"
>
<MessageCircle className="w-3.5 h-3.5 text-emerald-400" />
<span>📲 Separar con {suppName} (WhatsApp)</span>
<Send className="w-3.5 h-3.5" />
<span>Separar con Proveedor</span>
</button>
</div>
);
})}
))}
</div>
</div>
</div>
))
)}
</div>
</div>
)}
{/* TAB 2: CONSOLIDADO AGRUPADO POR PROVEEDOR */}
{activeTab === 'suppliers' && (
<div className="space-y-6 animate-in fade-in">
<div className="bg-purple-950/40 border border-purple-500/30 rounded-3xl p-5 text-xs text-neutral-300 space-y-2 shadow-lg">
<div className="flex items-center space-x-2 text-purple-300 font-bold text-sm">
<Building2 className="w-5 h-5 text-purple-400" />
<span>Consolidado Automático de Compras & Separaciones</span>
</div>
<p>
Aquí se agrupan todos los pares pendientes clasificados por cada <strong>Proveedor Mayorista</strong>. Puedes enviar el <strong>manifiesto consolidado en 1 solo mensaje de WhatsApp</strong> para que el proveedor prepare y empaque todos los pedidos de la jornada juntos.
</p>
</div>
<div className="grid grid-cols-1 gap-6">
{Object.values(supplierGroups).map((group) => (
<div
key={group.name}
className="bg-neutral-900 border border-neutral-800 rounded-3xl p-6 shadow-xl space-y-4"
>
{/* Supplier Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 border-b border-neutral-800 pb-4">
<div>
<span className="bg-purple-500/20 text-purple-300 border border-purple-500/40 text-[10px] font-bold px-2.5 py-0.5 rounded-full uppercase tracking-wider">
Proveedor Mayorista
</span>
<h3 className="text-xl font-black text-white mt-1">{group.name}</h3>
<p className="text-xs text-neutral-400 flex items-center gap-1 mt-0.5">
<Phone className="w-3.5 h-3.5 text-emerald-400" />
<span>WhatsApp Proveedor: <strong>{group.phone}</strong></span>
</p>
</div>
<div className="flex items-center gap-3">
<div className="bg-neutral-950 border border-neutral-800 px-4 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Total Pares</p>
<p className="text-lg font-black text-purple-400">{group.totalPairs} par(es)</p>
</div>
<div className="bg-neutral-950 border border-neutral-800 px-4 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Total a Pagar</p>
<p className="text-lg font-black text-emerald-400">{formatCOP(group.totalCost)}</p>
</div>
</div>
</div>
<div className="bg-neutral-950/60 border border-neutral-800/80 rounded-2xl p-3.5 space-y-2">
<span className="text-[10px] text-neutral-400 font-bold uppercase block tracking-wider">
Cobro & Pagos Contraentrega
</span>
<div className="space-y-1 text-xs">
<p className="text-neutral-400 flex justify-between">
<span>Total de la Orden:</span>
<strong className="text-white font-mono">{formatCOP(order.totalAmount)}</strong>
</p>
{order.depositAmount > 0 ? (
<>
<p className="text-amber-300 flex justify-between">
<span>Anticipo / Separación:</span>
<strong className="font-mono">{formatCOP(order.depositAmount)}</strong>
</p>
<p className="text-emerald-400 font-bold flex justify-between pt-1 border-t border-neutral-800">
<span>Saldo al Mensajero:</span>
<strong className="font-mono">{formatCOP(order.balanceDue)}</strong>
</p>
</>
) : (
<p className="text-emerald-400 font-bold pt-1 border-t border-neutral-800">
100% Contraentrega al recibir
</p>
)}
</div>
{/* Items Table for this supplier */}
<div className="overflow-x-auto">
<table className="w-full text-left text-xs text-neutral-300">
<thead className="bg-neutral-950 text-neutral-400 font-bold uppercase text-[10px]">
<tr>
<th className="py-2.5 px-3">Modelo / Zapatilla</th>
<th className="py-2.5 px-3">Talla</th>
<th className="py-2.5 px-3">Cant.</th>
<th className="py-2.5 px-3">Costo Base</th>
<th className="py-2.5 px-3">Orden Cliente</th>
<th className="py-2.5 px-3">Destino</th>
<th className="py-2.5 px-3">Estado Orden</th>
</tr>
</thead>
<tbody className="divide-y divide-neutral-800">
{group.entries.map((entry, idx) => (
<tr key={idx} className="hover:bg-neutral-800/40">
<td className="py-2.5 px-3 font-bold text-white flex items-center gap-2">
<img src={entry.item.image} alt="" className="w-8 h-8 rounded-lg object-cover border border-neutral-800" />
<span>{entry.item.productName}</span>
</td>
<td className="py-2.5 px-3 font-bold text-purple-400 font-mono">{entry.item.size}</td>
<td className="py-2.5 px-3 font-bold">{entry.item.quantity}</td>
<td className="py-2.5 px-3 font-mono font-semibold">{formatCOP(entry.item.supplierPrice || 0)}</td>
<td className="py-2.5 px-3 font-mono text-neutral-400">#{entry.order.id}</td>
<td className="py-2.5 px-3">{entry.order.cityName}</td>
<td className="py-2.5 px-3">{getStatusBadge(entry.order.status)}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Send Batch WhatsApp Manifest Button */}
<div className="pt-2 flex justify-end">
<button
onClick={() => handleSendSupplierBatchManifest(group.name, group.phone, group.entries)}
className="bg-gradient-to-r from-purple-600 via-fuchsia-600 to-pink-600 hover:opacity-95 text-white font-black text-xs px-5 py-3 rounded-2xl shadow-lg flex items-center gap-2 cursor-pointer transition-all active:scale-95"
>
<MessageCircle className="w-4 h-4" />
<span>Enviar Manifiesto de {group.totalPairs} Pares por WhatsApp a {group.name}</span>
</button>
</div>
</div>
))}
</div>
</div>
)}
<div className="flex flex-wrap items-center justify-between gap-2 pt-2 border-t border-neutral-800">
<span className="text-[11px] text-neutral-500">Método: {order.paymentMethodName}</span>
{/* TAB 3: RUTAS DE ENTREGA ORGANIZADAS POR DIRECCIONES & WAZE */}
{activeTab === 'routes' && (
<div className="space-y-6 animate-in fade-in">
{/* KPI Route Header */}
<div className="bg-gradient-to-r from-neutral-900 via-neutral-900 to-purple-950/60 border border-purple-500/30 rounded-3xl p-6 shadow-xl flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<div className="flex items-center space-x-2 text-cyan-300 font-bold text-sm">
<Compass className="w-5 h-5 text-cyan-400" />
<span>Hoja de Ruta & Navegación GPS (Waze / Google Maps)</span>
</div>
<p className="text-xs text-neutral-300 mt-1 max-w-xl">
Entregas contraentrega secuenciadas por zona geográfica. El mensajero puede presionar <strong>"Abrir en Waze"</strong> para iniciar la navegación instantánea a cada portería/casa.
</p>
</div>
<div className="flex flex-wrap gap-2">
<button
onClick={() => handleStatusChange(order.id, 'deposit_received')}
className="bg-blue-600/20 hover:bg-blue-600/30 text-blue-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-blue-500/20"
>
Confirmar $10k
</button>
<button
onClick={() => handleStatusChange(order.id, 'supplier_notified')}
className="bg-purple-600/20 hover:bg-purple-600/30 text-purple-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-purple-500/20"
>
Marcar Proveedor Notificado
</button>
<button
onClick={() => handleStatusChange(order.id, 'dispatched')}
className="bg-purple-600/20 hover:bg-purple-600/30 text-purple-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-purple-500/20"
>
Despachar en Ruta
</button>
<button
onClick={() => handleStatusChange(order.id, 'delivered')}
className="bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-emerald-500/20"
>
Marcar Entregado
</button>
</div>
<div className="flex items-center gap-3">
<div className="bg-neutral-950 border border-neutral-800 p-3 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Paradas / Entregas</p>
<p className="text-xl font-black text-white">{pendingDeliveries.length} Paradas</p>
</div>
<div className="bg-neutral-950 border border-emerald-500/30 p-3 rounded-2xl text-right">
<p className="text-[10px] text-emerald-400 uppercase font-bold">Total a Recaudar</p>
<p className="text-xl font-black text-emerald-400">{formatCOP(totalBalanceToCollect)}</p>
</div>
</div>
))}
</div>
{/* Delivery Stops Sequential List */}
<div className="space-y-3">
{pendingDeliveries.length === 0 ? (
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl p-12 text-center text-neutral-500">
🎉 No hay entregas pendientes por despachar en este momento.
</div>
) : (
pendingDeliveries.map((delivery, index) => (
<div
key={delivery.id}
className="bg-neutral-900 border border-neutral-800 hover:border-cyan-500/40 rounded-3xl p-5 shadow-lg transition-all flex flex-col lg:flex-row lg:items-center justify-between gap-4"
>
{/* Left: Sequence Number & Stop Details */}
<div className="flex items-start space-x-4">
{/* Stop Number Badge */}
<div className="w-12 h-12 rounded-2xl bg-cyan-500/20 border border-cyan-500/40 flex flex-col items-center justify-center text-cyan-300 font-black shrink-0 shadow-sm">
<span className="text-[10px] uppercase font-bold text-neutral-400 leading-none">Parada</span>
<span className="text-lg leading-tight">#{index + 1}</span>
</div>
<div className="space-y-1">
<div className="flex items-center space-x-2">
<span className="font-black text-white text-base">{delivery.customerName}</span>
<span className="text-xs text-neutral-400">• Orden #{delivery.id}</span>
{getStatusBadge(delivery.status)}
</div>
{/* Exact Address Highlight */}
<p className="text-xs font-bold text-white flex items-center gap-1.5 bg-neutral-950 px-3 py-1 rounded-xl border border-neutral-800 w-fit">
<MapPin className="w-4 h-4 text-cyan-400 shrink-0" />
<span>{delivery.address} ({delivery.cityName})</span>
</p>
<div className="flex items-center space-x-3 text-xs text-neutral-400 pt-0.5">
<span className="flex items-center gap-1 text-emerald-400 font-semibold">
<Phone className="w-3 h-3" /> {delivery.customerPhone}
</span>
<span>•</span>
<span>
{delivery.items.map((i) => `${i.productName} (Talla ${i.size})`).join(', ')}
</span>
</div>
{delivery.notes && (
<p className="text-[11px] text-amber-300/90 italic bg-amber-500/10 px-2.5 py-0.5 rounded-lg border border-amber-500/20 w-fit">
Nota: {delivery.notes}
</p>
)}
</div>
</div>
{/* Right: Balance & 1-Click Waze / WhatsApp Actions */}
<div className="flex items-center gap-3 self-end lg:self-auto shrink-0">
{/* Cash to collect badge */}
<div className="bg-neutral-950 border border-neutral-800 px-3.5 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 font-bold uppercase">Cobrar Contraentrega</p>
<p className="text-base font-black text-emerald-400">{formatCOP(delivery.balanceDue)}</p>
</div>
{/* Waze GPS Button */}
<button
onClick={() => handleOpenWaze(delivery.address, delivery.cityName)}
className="bg-cyan-600 hover:bg-cyan-500 text-white font-black text-xs px-4 py-3 rounded-2xl shadow-lg flex items-center gap-1.5 cursor-pointer active:scale-95 transition-all"
title="Abrir ubicación directamente en Waze"
>
<Compass className="w-4 h-4" />
<span>Waze</span>
</button>
{/* Google Maps GPS Button */}
<button
onClick={() => handleOpenGoogleMaps(delivery.address, delivery.cityName)}
className="bg-neutral-800 hover:bg-neutral-700 text-white font-bold text-xs p-3 rounded-2xl shadow cursor-pointer transition-all"
title="Abrir en Google Maps"
>
<Navigation className="w-4 h-4 text-emerald-400" />
</button>
{/* WhatsApp Alert */}
<button
onClick={() => handleNotifyCustomerOnTheWay(delivery)}
className="bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs px-3.5 py-3 rounded-2xl shadow flex items-center gap-1.5 cursor-pointer active:scale-95 transition-all"
title="Enviar mensaje de WhatsApp notificando que el mensajero va en camino"
>
<MessageCircle className="w-4 h-4" />
<span className="hidden sm:inline">En Camino</span>
</button>
</div>
</div>
))
)}
</div>
</div>
)}
</div>

View File

@ -2138,12 +2138,174 @@ export const INITIAL_MONITORED_GROUPS: MonitoredWhatsAppGroup[] = [
},
];
export const INITIAL_ORDERS: Order[] = [
{
id: 'ORD-8921',
customerName: 'Mateo Valencia',
customerPhone: '3168945120',
address: 'Av 6N # 24N-32 Edificio Granada Real',
cityId: 'city-cali',
cityName: 'Cali (Norte - Granada)',
items: [
{
productId: 'snk-jordan4-1',
productName: 'Nike Air Jordan 4 Retro White Cement',
brand: 'Jordan',
size: '41',
quantity: 1,
price: 135000,
supplierPrice: 95000,
supplierName: '💎 2️⃣ DIFUSIÓN JOVIC STORE COLOMBIA🍀',
supplierPhone: '573134848203',
image: 'https://images.unsplash.com/photo-1584735935682-2f2b69dff9d2?auto=format&fit=crop&w=800&q=80',
},
],
subtotal: 135000,
shippingCost: 0,
totalAmount: 135000,
depositAmount: 10000,
balanceDue: 125000,
paymentMethodId: 'pay-contraentrega',
paymentMethodName: 'Contraentrega Efectivo / Nequi',
status: 'pending',
createdAt: 'Hoy, 06:45 AM',
notes: 'Timbrar en apto 302. Paga con billete de 100 y 50.',
},
{
id: 'ORD-8922',
customerName: 'Valentina Restrepo',
customerPhone: '3157829401',
address: 'Calle 52N # 4BN-15 Casa 4 La Flora',
cityId: 'city-cali',
cityName: 'Cali (Norte - La Flora)',
items: [
{
productId: 'snk-vomero5-1',
productName: 'Nike Zoom Vomero 5 Photon Dust Metallic Silver',
brand: 'Nike',
size: '39',
quantity: 1,
price: 130000,
supplierPrice: 90000,
supplierName: '💎 2️⃣ DIFUSIÓN JOVIC STORE COLOMBIA🍀',
supplierPhone: '573134848203',
image: 'https://images.unsplash.com/photo-1600185365926-3a2ce3cdb9eb?auto=format&fit=crop&w=800&q=80',
},
],
subtotal: 130000,
shippingCost: 0,
totalAmount: 130000,
depositAmount: 10000,
balanceDue: 120000,
paymentMethodId: 'pay-contraentrega',
paymentMethodName: 'Contraentrega Nequi',
status: 'pending',
createdAt: 'Hoy, 07:15 AM',
notes: 'Dejar en portería si no contesta.',
},
{
id: 'ORD-8923',
customerName: 'Santiago Henao',
customerPhone: '3186523910',
address: 'Cra 105 # 14-45 Condominio Portal de la Viga',
cityId: 'city-cali',
cityName: 'Cali (Sur - Ciudad Jardín)',
items: [
{
productId: 'snk-campus-1',
productName: 'Adidas Campus 00s Core Black White',
brand: 'Adidas',
size: '40',
quantity: 1,
price: 125000,
supplierPrice: 85000,
supplierName: 'EXCLUSIVE CC MÍO 3B',
supplierPhone: '573159876543',
image: 'https://images.unsplash.com/photo-1525966222134-fcfa99b8ae77?auto=format&fit=crop&w=800&q=80',
},
],
subtotal: 125000,
shippingCost: 0,
totalAmount: 125000,
depositAmount: 10000,
balanceDue: 115000,
paymentMethodId: 'pay-contraentrega',
paymentMethodName: 'Contraentrega',
status: 'pending',
createdAt: 'Hoy, 07:30 AM',
notes: 'Recibe en horas de la tarde después de las 2 PM.',
},
{
id: 'ORD-8924',
customerName: 'Camila Ospina',
customerPhone: '3114958203',
address: 'Cra 85E # 14-80 Apto 501 El Ingenio',
cityId: 'city-cali',
cityName: 'Cali (Sur - El Ingenio)',
items: [
{
productId: 'snk-coach-1',
productName: 'Coach Low Top Monogram Signature Brown',
brand: 'Coach',
size: '38',
quantity: 1,
price: 145000,
supplierPrice: 100000,
supplierName: 'EXCLUSIVE CC MÍO 3B',
supplierPhone: '573159876543',
image: 'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80',
},
],
subtotal: 145000,
shippingCost: 0,
totalAmount: 145000,
depositAmount: 10000,
balanceDue: 135000,
paymentMethodId: 'pay-contraentrega',
paymentMethodName: 'Separación Pagada Bold',
status: 'deposit_received',
createdAt: 'Hoy, 08:00 AM',
notes: 'Cliente ya pagó $10.000 de anticipo.',
},
{
id: 'ORD-8925',
customerName: 'Daniel Caicedo',
customerPhone: '3174829105',
address: 'Cra 10 # 3-45 San Antonio',
cityId: 'city-cali',
cityName: 'Cali (Oeste - San Antonio)',
items: [
{
productId: 'snk-af1-1',
productName: 'Nike Air Force 1 07 Low Triple White',
brand: 'Nike',
size: '42',
quantity: 1,
price: 120000,
supplierPrice: 80000,
supplierName: '💎 2️⃣ DIFUSIÓN JOVIC STORE COLOMBIA🍀',
supplierPhone: '573134848203',
image: 'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80',
},
],
subtotal: 120000,
shippingCost: 0,
totalAmount: 120000,
depositAmount: 10000,
balanceDue: 110000,
paymentMethodId: 'pay-contraentrega',
paymentMethodName: 'Contraentrega',
status: 'pending',
createdAt: 'Hoy, 08:20 AM',
},
];
class DataService {
private products: Product[] = [...INITIAL_PRODUCTS];
private cities: City[] = [...INITIAL_CITIES];
private paymentMethods: PaymentMethod[] = [...INITIAL_PAYMENT_METHODS];
private suppliers: Supplier[] = [...INITIAL_SUPPLIERS];
private orders: Order[] = [];
private orders: Order[] = [...INITIAL_ORDERS];
private supplierPosts: SupplierPost[] = [...INITIAL_SUPPLIER_POSTS];
getProducts(): Product[] {

View File

@ -1,14 +1,36 @@
'use client';
import React, { useState, useEffect } from 'react';
import { ShoppingBag, Truck, CheckCircle2, Clock, Phone, MapPin, Search, MessageCircle, Send, ShieldCheck, UserCheck } from 'lucide-react';
import {
ShoppingBag,
Truck,
CheckCircle2,
Clock,
Phone,
MapPin,
Search,
MessageCircle,
Send,
ShieldCheck,
Building2,
Navigation,
ExternalLink,
DollarSign,
Layers,
Sparkles,
Compass,
Check,
AlertCircle
} from 'lucide-react';
import { Order, OrderItem } from '@/types';
import { formatCOP } from '@/lib/pricing-engine';
export default function AdminOrdersPage() {
const [orders, setOrders] = useState<Order[]>([]);
const [activeTab, setActiveTab] = useState<'individual' | 'suppliers' | 'routes'>('individual');
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState<string>('all');
const [toastMessage, setToastMessage] = useState<string | null>(null);
useEffect(() => {
fetch('/api/orders')
@ -22,14 +44,19 @@ export default function AdminOrdersPage() {
prev.map((o) => (o.id === orderId ? { ...o, status: newStatus } : o))
);
await fetch('/api/orders', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: orderId, status: newStatus }),
}).catch(() => {});
try {
await fetch('/api/orders', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: orderId, status: newStatus }),
});
setToastMessage(`Estado de la orden #${orderId} actualizado a "${newStatus}".`);
} catch (e) {
console.error(e);
}
};
const handleNotifySupplier = (order: Order, item: OrderItem) => {
const handleNotifySingleSupplier = (order: Order, item: OrderItem) => {
const supplierPhone = item.supplierPhone || '573134848203';
const supplierName = item.supplierName || 'Proveedor Mayorista';
const supplierCost = item.supplierPrice ? formatCOP(item.supplierPrice) : 'Precio Mayorista';
@ -44,6 +71,50 @@ export default function AdminOrdersPage() {
}
};
const handleSendSupplierBatchManifest = (supplierName: string, supplierPhone: string, itemsList: { order: Order; item: OrderItem }[]) => {
const totalPairs = itemsList.reduce((acc, curr) => acc + curr.item.quantity, 0);
const totalCost = itemsList.reduce((acc, curr) => acc + (curr.item.supplierPrice || 0) * curr.item.quantity, 0);
let text = `📦 *MANIFIESTO CONSOLIDADO DE SEPARACIÓN*\n`;
text += `🏢 *Proveedor:* ${supplierName}\n`;
text += `📅 *Fecha:* ${new Date().toLocaleDateString('es-CO')} | *Total Pares:* ${totalPairs}\n`;
text += `💰 *Valor Total a Pagar Proveedor:* ${formatCOP(totalCost)}\n\n`;
text += `👟 *DETALLE DE PARES A SEPARAR:*\n`;
itemsList.forEach((entry, idx) => {
text += `\n${idx + 1}. *${entry.item.productName}*\n`;
text += ` • Talla: *${entry.item.size}* (x${entry.item.quantity})\n`;
text += ` • Costo Base: ${formatCOP(entry.item.supplierPrice || 0)}\n`;
text += ` • Orden Cliente: #${entry.order.id} (${entry.order.cityName})\n`;
});
text += `\n🤝 *Por favor confirmar disponibilidad y empaque para recogida del mensajero.* ¡Muchas gracias!`;
const cleanPhone = supplierPhone.startsWith('57') ? supplierPhone : `57${supplierPhone}`;
window.open(`https://wa.me/${cleanPhone}?text=${encodeURIComponent(text)}`, '_blank');
setToastMessage(`📲 Manifiesto de ${totalPairs} pares enviado a ${supplierName}.`);
};
const handleOpenWaze = (address: string, city: string) => {
const query = encodeURIComponent(`${address}, ${city}, Colombia`);
const wazeUrl = `https://waze.com/ul?q=${query}&navigate=yes`;
window.open(wazeUrl, '_blank');
};
const handleOpenGoogleMaps = (address: string, city: string) => {
const query = encodeURIComponent(`${address}, ${city}, Colombia`);
const mapsUrl = `https://www.google.com/maps/search/?api=1&query=${query}`;
window.open(mapsUrl, '_blank');
};
const handleNotifyCustomerOnTheWay = (order: Order) => {
const cleanPhone = order.customerPhone.startsWith('57') ? order.customerPhone : `57${order.customerPhone}`;
const text = `👋 ¡Hola *${order.customerName}*!\n\n🛵 Tu pedido ya va *EN CAMINO* con nuestro mensajero.\n\n📍 *Dirección de Entrega:* ${order.address}\n🏙️ *Ciudad:* ${order.cityName}\n💵 *Saldo a Pagar Contraentrega:* *${formatCOP(order.balanceDue)}*\n💳 *Medio de Pago:* Efectivo o Transferencia Nequi\n\nPor favor estar atento a la portería o celular. ¡Gracias por tu compra! 🔥`;
window.open(`https://wa.me/${cleanPhone}?text=${encodeURIComponent(text)}`, '_blank');
handleStatusChange(order.id, 'dispatched');
};
const getStatusBadge = (status: Order['status']) => {
switch (status) {
case 'pending':
@ -51,9 +122,9 @@ export default function AdminOrdersPage() {
case 'deposit_received':
return <span className="bg-blue-500/20 text-blue-400 border border-blue-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">Separación $10k Pagada</span>;
case 'supplier_notified':
return <span className="bg-orange-500/20 text-orange-300 border border-orange-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><CheckCircle2 className="w-3 h-3 text-orange-400" /> Proveedor Notificado</span>;
return <span className="bg-purple-500/20 text-purple-300 border border-purple-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><CheckCircle2 className="w-3 h-3 text-purple-400" /> Proveedor Notificado</span>;
case 'dispatched':
return <span className="bg-purple-500/20 text-purple-400 border border-purple-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">En Ruta con Mensajero</span>;
return <span className="bg-fuchsia-500/20 text-fuchsia-400 border border-fuchsia-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><Truck className="w-3 h-3" /> En Ruta con Mensajero</span>;
case 'delivered':
return <span className="bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">Entregado & Cobrado</span>;
case 'cancelled':
@ -65,6 +136,7 @@ export default function AdminOrdersPage() {
const matchesSearch =
o.customerName.toLowerCase().includes(search.toLowerCase()) ||
o.customerPhone.includes(search) ||
o.address.toLowerCase().includes(search.toLowerCase()) ||
o.cityName.toLowerCase().includes(search.toLowerCase()) ||
o.id.toLowerCase().includes(search.toLowerCase());
@ -73,201 +145,440 @@ export default function AdminOrdersPage() {
return true;
});
const supplierGroups = orders.reduce((acc, order) => {
order.items.forEach((item) => {
const suppName = item.supplierName || 'DIFUSION JOVIC STORE';
const suppPhone = item.supplierPhone || '573134848203';
if (!acc[suppName]) {
acc[suppName] = {
name: suppName,
phone: suppPhone,
entries: [],
totalPairs: 0,
totalCost: 0,
};
}
acc[suppName].entries.push({ order, item });
acc[suppName].totalPairs += item.quantity;
acc[suppName].totalCost += (item.supplierPrice || 0) * item.quantity;
});
return acc;
}, {} as Record<string, { name: string; phone: string; entries: { order: Order; item: OrderItem }[]; totalPairs: number; totalCost: number }>);
const pendingDeliveries = orders.filter((o) => o.status !== 'delivered' && o.status !== 'cancelled');
const totalBalanceToCollect = pendingDeliveries.reduce((acc, curr) => acc + curr.balanceDue, 0);
return (
<div className="space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-black text-white flex items-center gap-2">
<ShoppingBag className="w-6 h-6 text-orange-500" />
<span>Gestión de Pedidos & Separación con Proveedores ({orders.length})</span>
</h1>
<p className="text-xs text-gray-400 mt-1">
Control logístico de pedidos contraentrega, cobro de anticipos de $10.000 COP y <strong>notificación inmediata de separación al proveedor</strong> vía WhatsApp.
</p>
</div>
{/* Toolbar */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="flex items-center space-x-1.5 bg-gray-900 border border-gray-800 p-1 rounded-2xl overflow-x-auto">
<button
onClick={() => setStatusFilter('all')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'all' ? 'bg-white text-gray-950 shadow' : 'text-gray-400 hover:text-white'
}`}
>
Todos ({orders.length})
</button>
<button
onClick={() => setStatusFilter('pending')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'pending' ? 'bg-amber-500/30 text-amber-300' : 'text-gray-400'
}`}
>
Pendientes
</button>
<button
onClick={() => setStatusFilter('supplier_notified')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'supplier_notified' ? 'bg-orange-500/30 text-orange-300' : 'text-gray-400'
}`}
>
Proveedor Notificado
</button>
<button
onClick={() => setStatusFilter('dispatched')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'dispatched' ? 'bg-purple-500/30 text-purple-300' : 'text-gray-400'
}`}
>
En Ruta
</button>
</div>
<div className="max-w-xs w-full">
<input
type="text"
placeholder="Buscar por cliente, ciudad o # orden..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-gray-900 border border-gray-800 rounded-xl px-3.5 py-2 text-xs text-white outline-hidden focus:border-orange-500"
/>
</div>
</div>
{filteredOrders.length === 0 ? (
<div className="bg-gray-900 border border-gray-800 rounded-3xl p-12 text-center space-y-3">
<div className="w-16 h-16 rounded-full bg-gray-950 text-gray-500 flex items-center justify-center mx-auto border border-gray-800">
<ShoppingBag className="w-8 h-8 text-orange-500" />
</div>
<h3 className="font-bold text-white text-base">No hay pedidos en esta sección</h3>
<p className="text-xs text-gray-400 max-w-sm mx-auto">
Cuando los clientes hagan un pedido en la tienda web o por WhatsApp, aparecerán aquí con el botón para separar inmediatamente con el proveedor correspondiente.
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-black text-white flex items-center gap-2">
<ShoppingBag className="w-6 h-6 text-purple-400" />
<span>Centro Logístico: Pedidos, Proveedores & Rutas Waze</span>
</h1>
<p className="text-xs text-neutral-400 mt-1">
Consolidación automática para <strong>separación con proveedores</strong> y <strong>hoja de ruta para mensajeros con navegación Waze en 1 clic</strong>.
</p>
</div>
) : (
</div>
{toastMessage && (
<div className="bg-purple-950/80 border border-purple-500 text-purple-300 px-4 py-3 rounded-2xl text-xs flex items-center justify-between animate-in fade-in shadow-md">
<span>{toastMessage}</span>
<button onClick={() => setToastMessage(null)} className="font-bold text-white cursor-pointer ml-3">
✕
</button>
</div>
)}
{/* Main Tabs Navigation */}
<div className="grid grid-cols-1 sm:grid-cols-3 p-1 bg-neutral-900 border border-neutral-800 rounded-2xl gap-1">
<button
onClick={() => setActiveTab('individual')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'individual'
? 'bg-purple-600 text-white shadow-lg shadow-purple-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Layers className="w-4 h-4" />
<span>1. Todos los Pedidos ({orders.length})</span>
</button>
<button
onClick={() => setActiveTab('suppliers')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'suppliers'
? 'bg-purple-600 text-white shadow-lg shadow-purple-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Building2 className="w-4 h-4 text-fuchsia-300" />
<span>2. Consolidado por Proveedor ({Object.keys(supplierGroups).length})</span>
</button>
<button
onClick={() => setActiveTab('routes')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'routes'
? 'bg-gradient-to-r from-purple-600 to-pink-600 text-white shadow-lg shadow-pink-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Compass className="w-4 h-4 text-cyan-300" />
<span>3. Rutas de Entrega & Waze ({pendingDeliveries.length})</span>
</button>
</div>
{/* TAB 1: INDIVIDUAL ORDERS LIST */}
{activeTab === 'individual' && (
<div className="space-y-4">
{filteredOrders.map((order) => (
<div
key={order.id}
className="bg-gray-900 border border-gray-800 rounded-3xl p-5 space-y-4 shadow-xl"
>
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-gray-800 pb-3">
<div className="flex items-center space-x-2">
<span className="font-black text-white text-sm">Orden #{order.id}</span>
<span className="text-xs text-gray-500">• {new Date(order.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
</div>
{getStatusBadge(order.status)}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="flex items-center space-x-1.5 bg-neutral-900 border border-neutral-800 p-1 rounded-2xl overflow-x-auto">
{['all', 'pending', 'deposit_received', 'supplier_notified', 'dispatched', 'delivered'].map((st) => (
<button
key={st}
onClick={() => setStatusFilter(st)}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === st ? 'bg-white text-neutral-950 shadow' : 'text-neutral-400 hover:text-white'
}`}
>
{st === 'all' ? 'Todos' : st === 'pending' ? 'Pendientes' : st === 'deposit_received' ? 'Anticipo Pagado' : st === 'supplier_notified' ? 'Prov. Notificado' : st === 'dispatched' ? 'En Ruta' : 'Entregados'}
</button>
))}
</div>
<div className="max-w-xs w-full">
<input
type="text"
placeholder="Buscar por cliente, celular, ciudad o #..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-neutral-900 border border-neutral-800 rounded-xl px-3.5 py-2 text-xs text-white outline-hidden focus:border-purple-400"
/>
</div>
</div>
<div className="space-y-4">
{filteredOrders.length === 0 ? (
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl p-12 text-center text-neutral-500">
No hay pedidos que coincidan con los filtros.
</div>
) : (
filteredOrders.map((order) => (
<div
key={order.id}
className="bg-neutral-900 border border-neutral-800 hover:border-purple-500/40 rounded-3xl p-5 shadow-xl transition-all space-y-4"
>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 border-b border-neutral-800 pb-3">
<div className="flex items-center space-x-3">
<span className="font-black text-white text-base">Orden #{order.id}</span>
{getStatusBadge(order.status)}
<span className="text-[11px] text-neutral-500 font-mono">{order.createdAt}</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
<div className="bg-gray-950/60 border border-gray-800/80 rounded-2xl p-3.5 space-y-1.5">
<span className="text-[10px] text-orange-400 font-bold uppercase block tracking-wider">
Cliente & Entrega (24 Horas)
</span>
<p className="font-bold text-white text-sm">{order.customerName}</p>
<p className="text-gray-300 flex items-center gap-1.5">
<Phone className="w-3.5 h-3.5 text-orange-400" /> {order.customerPhone}
</p>
<p className="text-gray-300 flex items-center gap-1.5">
<MapPin className="w-3.5 h-3.5 text-orange-400" /> {order.address} ({order.cityName})
</p>
</div>
<div className="flex items-center space-x-2">
<span className="text-xs text-neutral-400 font-bold">Estado:</span>
<select
value={order.status}
onChange={(e) => handleStatusChange(order.id, e.target.value as any)}
className="bg-neutral-950 border border-neutral-800 text-xs text-white rounded-xl px-3 py-1.5 outline-hidden font-bold cursor-pointer"
>
<option value="pending">⏳ Pendiente Confirmar</option>
<option value="deposit_received">💳 Separación $10k Pagada</option>
<option value="supplier_notified">🏢 Proveedor Notificado</option>
<option value="dispatched">🛵 En Ruta con Mensajero</option>
<option value="delivered">✅ Entregado & Cobrado</option>
<option value="cancelled">❌ Cancelado</option>
</select>
</div>
</div>
<div className="bg-gray-950/60 border border-gray-800/80 rounded-2xl p-3.5 space-y-2">
<span className="text-[10px] text-gray-400 font-bold uppercase block tracking-wider">
Zapatillas & Proveedor Asignado:
</span>
<div className="space-y-2.5">
{order.items.map((it, idx) => {
const suppName = it.supplierName || 'DIFUSION JOVIC STORE';
return (
<div key={idx} className="bg-gray-900 border border-gray-800 rounded-xl p-2.5 space-y-2">
<div className="flex items-center justify-between gap-2">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
<div className="space-y-1 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Cliente & Destino</p>
<p className="text-white font-bold text-sm">{order.customerName}</p>
<p className="text-emerald-400 font-mono font-semibold flex items-center gap-1">
<Phone className="w-3 h-3" /> {order.customerPhone}
</p>
<p className="text-neutral-300 flex items-start gap-1">
<MapPin className="w-3 h-3 shrink-0 text-purple-400 mt-0.5" />
<span>{order.address} ({order.cityName})</span>
</p>
</div>
<div className="space-y-1 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Resumen de Cobro</p>
<div className="flex justify-between text-neutral-300">
<span>Total Venta:</span>
<strong className="text-white">{formatCOP(order.totalAmount)}</strong>
</div>
<div className="flex justify-between text-neutral-300">
<span>Anticipo / Separación:</span>
<strong className="text-blue-400">-{formatCOP(order.depositAmount)}</strong>
</div>
<div className="flex justify-between text-neutral-300 pt-1 border-t border-neutral-800 font-bold">
<span className="text-purple-300">Saldo Contraentrega:</span>
<span className="text-emerald-400 text-sm">{formatCOP(order.balanceDue)}</span>
</div>
</div>
<div className="space-y-2 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80 flex flex-col justify-center">
<button
onClick={() => handleOpenWaze(order.address, order.cityName)}
className="w-full bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-1.5 px-3 rounded-xl flex items-center justify-center gap-1.5 cursor-pointer shadow transition-all text-xs"
>
<Compass className="w-3.5 h-3.5" />
<span>Navegar con Waze</span>
</button>
<button
onClick={() => handleNotifyCustomerOnTheWay(order)}
className="w-full bg-emerald-600 hover:bg-emerald-500 text-white font-bold py-1.5 px-3 rounded-xl flex items-center justify-center gap-1.5 cursor-pointer shadow transition-all text-xs"
>
<MessageCircle className="w-3.5 h-3.5" />
<span>Avisar "Voy en camino"</span>
</button>
</div>
</div>
<div className="space-y-2 pt-2">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Pares en esta orden:</p>
<div className="space-y-2">
{order.items.map((item, idx) => (
<div
key={idx}
className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 bg-neutral-950 p-3 rounded-2xl border border-neutral-800"
>
<div className="flex items-center space-x-3">
<img src={item.image} alt="" className="w-12 h-12 rounded-xl object-cover border border-neutral-800 shrink-0" />
<div>
<p className="font-bold text-white text-xs">{it.productName}</p>
<p className="text-[11px] text-gray-400">
Talla <strong className="text-orange-400 font-bold">{it.size}</strong> • Cant: {it.quantity}
<p className="font-bold text-white text-xs">{item.productName}</p>
<p className="text-[11px] text-neutral-400">
Talla: <strong className="text-purple-400">{item.size}</strong> | Cantidad: <strong>{item.quantity}</strong> | Proveedor: <strong className="text-purple-300">{item.supplierName || 'Bodega Principal'}</strong>
</p>
</div>
<span className="text-[10px] bg-gray-950 text-gray-300 border border-gray-800 px-2 py-0.5 rounded font-mono">
Prov: {suppName}
</span>
</div>
<button
onClick={() => handleNotifySupplier(order, it)}
className="w-full bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-300 border border-emerald-500/30 font-bold text-xs py-1.5 px-2.5 rounded-lg flex items-center justify-center space-x-1.5 transition-all cursor-pointer shadow-xs"
onClick={() => handleNotifySingleSupplier(order, item)}
className="bg-purple-600 hover:bg-purple-500 text-white font-bold text-xs px-3.5 py-2 rounded-xl flex items-center space-x-1.5 shadow cursor-pointer shrink-0 self-end sm:self-auto"
>
<MessageCircle className="w-3.5 h-3.5 text-emerald-400" />
<span>📲 Separar con {suppName} (WhatsApp)</span>
<Send className="w-3.5 h-3.5" />
<span>Separar con Proveedor</span>
</button>
</div>
);
})}
))}
</div>
</div>
</div>
))
)}
</div>
</div>
)}
{/* TAB 2: CONSOLIDADO AGRUPADO POR PROVEEDOR */}
{activeTab === 'suppliers' && (
<div className="space-y-6 animate-in fade-in">
<div className="bg-purple-950/40 border border-purple-500/30 rounded-3xl p-5 text-xs text-neutral-300 space-y-2 shadow-lg">
<div className="flex items-center space-x-2 text-purple-300 font-bold text-sm">
<Building2 className="w-5 h-5 text-purple-400" />
<span>Consolidado Automático de Compras & Separaciones</span>
</div>
<p>
Aquí se agrupan todos los pares pendientes clasificados por cada <strong>Proveedor Mayorista</strong>. Puedes enviar el <strong>manifiesto consolidado en 1 solo mensaje de WhatsApp</strong> para que el proveedor prepare y empaque todos los pedidos de la jornada juntos.
</p>
</div>
<div className="grid grid-cols-1 gap-6">
{Object.values(supplierGroups).map((group) => (
<div
key={group.name}
className="bg-neutral-900 border border-neutral-800 rounded-3xl p-6 shadow-xl space-y-4"
>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 border-b border-neutral-800 pb-4">
<div>
<span className="bg-purple-500/20 text-purple-300 border border-purple-500/40 text-[10px] font-bold px-2.5 py-0.5 rounded-full uppercase tracking-wider">
Proveedor Mayorista
</span>
<h3 className="text-xl font-black text-white mt-1">{group.name}</h3>
<p className="text-xs text-neutral-400 flex items-center gap-1 mt-0.5">
<Phone className="w-3.5 h-3.5 text-emerald-400" />
<span>WhatsApp Proveedor: <strong>{group.phone}</strong></span>
</p>
</div>
<div className="flex items-center gap-3">
<div className="bg-neutral-950 border border-neutral-800 px-4 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Total Pares</p>
<p className="text-lg font-black text-purple-400">{group.totalPairs} par(es)</p>
</div>
<div className="bg-neutral-950 border border-neutral-800 px-4 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Total a Pagar</p>
<p className="text-lg font-black text-emerald-400">{formatCOP(group.totalCost)}</p>
</div>
</div>
</div>
<div className="bg-gray-950/60 border border-gray-800/80 rounded-2xl p-3.5 space-y-2">
<span className="text-[10px] text-gray-400 font-bold uppercase block tracking-wider">
Cobro & Pagos Contraentrega
</span>
<div className="space-y-1 text-xs">
<p className="text-gray-400 flex justify-between">
<span>Total de la Orden:</span>
<strong className="text-white font-mono">{formatCOP(order.totalAmount)}</strong>
</p>
{order.depositAmount > 0 ? (
<>
<p className="text-amber-300 flex justify-between">
<span>Anticipo / Separación:</span>
<strong className="font-mono">{formatCOP(order.depositAmount)}</strong>
</p>
<p className="text-emerald-400 font-bold flex justify-between pt-1 border-t border-gray-800">
<span>Saldo al Mensajero:</span>
<strong className="font-mono">{formatCOP(order.balanceDue)}</strong>
</p>
</>
) : (
<p className="text-emerald-400 font-bold pt-1 border-t border-gray-800">
100% Contraentrega al recibir
</p>
)}
</div>
<div className="overflow-x-auto">
<table className="w-full text-left text-xs text-neutral-300">
<thead className="bg-neutral-950 text-neutral-400 font-bold uppercase text-[10px]">
<tr>
<th className="py-2.5 px-3">Modelo / Zapatilla</th>
<th className="py-2.5 px-3">Talla</th>
<th className="py-2.5 px-3">Cant.</th>
<th className="py-2.5 px-3">Costo Base</th>
<th className="py-2.5 px-3">Orden Cliente</th>
<th className="py-2.5 px-3">Destino</th>
<th className="py-2.5 px-3">Estado Orden</th>
</tr>
</thead>
<tbody className="divide-y divide-neutral-800">
{group.entries.map((entry, idx) => (
<tr key={idx} className="hover:bg-neutral-800/40">
<td className="py-2.5 px-3 font-bold text-white flex items-center gap-2">
<img src={entry.item.image} alt="" className="w-8 h-8 rounded-lg object-cover border border-neutral-800" />
<span>{entry.item.productName}</span>
</td>
<td className="py-2.5 px-3 font-bold text-purple-400 font-mono">{entry.item.size}</td>
<td className="py-2.5 px-3 font-bold">{entry.item.quantity}</td>
<td className="py-2.5 px-3 font-mono font-semibold">{formatCOP(entry.item.supplierPrice || 0)}</td>
<td className="py-2.5 px-3 font-mono text-neutral-400">#{entry.order.id}</td>
<td className="py-2.5 px-3">{entry.order.cityName}</td>
<td className="py-2.5 px-3">{getStatusBadge(entry.order.status)}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="pt-2 flex justify-end">
<button
onClick={() => handleSendSupplierBatchManifest(group.name, group.phone, group.entries)}
className="bg-gradient-to-r from-purple-600 via-fuchsia-600 to-pink-600 hover:opacity-95 text-white font-black text-xs px-5 py-3 rounded-2xl shadow-lg flex items-center gap-2 cursor-pointer transition-all active:scale-95"
>
<MessageCircle className="w-4 h-4" />
<span>Enviar Manifiesto de {group.totalPairs} Pares por WhatsApp a {group.name}</span>
</button>
</div>
</div>
))}
</div>
</div>
)}
<div className="flex flex-wrap items-center justify-between gap-2 pt-2 border-t border-gray-800">
<span className="text-[11px] text-gray-500">Método: {order.paymentMethodName}</span>
{/* TAB 3: RUTAS DE ENTREGA ORGANIZADAS POR DIRECCIONES & WAZE */}
{activeTab === 'routes' && (
<div className="space-y-6 animate-in fade-in">
<div className="bg-gradient-to-r from-neutral-900 via-neutral-900 to-purple-950/60 border border-purple-500/30 rounded-3xl p-6 shadow-xl flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<div className="flex items-center space-x-2 text-cyan-300 font-bold text-sm">
<Compass className="w-5 h-5 text-cyan-400" />
<span>Hoja de Ruta & Navegación GPS (Waze / Google Maps)</span>
</div>
<p className="text-xs text-neutral-300 mt-1 max-w-xl">
Entregas contraentrega secuenciadas por zona geográfica. El mensajero puede presionar <strong>"Abrir en Waze"</strong> para iniciar la navegación instantánea a cada portería/casa.
</p>
</div>
<div className="flex flex-wrap gap-2">
<button
onClick={() => handleStatusChange(order.id, 'deposit_received')}
className="bg-blue-600/20 hover:bg-blue-600/30 text-blue-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-blue-500/20"
>
Confirmar $10k
</button>
<button
onClick={() => handleStatusChange(order.id, 'supplier_notified')}
className="bg-orange-600/20 hover:bg-orange-600/30 text-orange-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-orange-500/20"
>
Marcar Proveedor Notificado
</button>
<button
onClick={() => handleStatusChange(order.id, 'dispatched')}
className="bg-purple-600/20 hover:bg-purple-600/30 text-purple-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-purple-500/20"
>
Despachar en Ruta
</button>
<button
onClick={() => handleStatusChange(order.id, 'delivered')}
className="bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-emerald-500/20"
>
Marcar Entregado
</button>
</div>
<div className="flex items-center gap-3">
<div className="bg-neutral-950 border border-neutral-800 p-3 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Paradas / Entregas</p>
<p className="text-xl font-black text-white">{pendingDeliveries.length} Paradas</p>
</div>
<div className="bg-neutral-950 border border-emerald-500/30 p-3 rounded-2xl text-right">
<p className="text-[10px] text-emerald-400 uppercase font-bold">Total a Recaudar</p>
<p className="text-xl font-black text-emerald-400">{formatCOP(totalBalanceToCollect)}</p>
</div>
</div>
))}
</div>
<div className="space-y-3">
{pendingDeliveries.length === 0 ? (
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl p-12 text-center text-neutral-500">
🎉 No hay entregas pendientes por despachar en este momento.
</div>
) : (
pendingDeliveries.map((delivery, index) => (
<div
key={delivery.id}
className="bg-neutral-900 border border-neutral-800 hover:border-cyan-500/40 rounded-3xl p-5 shadow-lg transition-all flex flex-col lg:flex-row lg:items-center justify-between gap-4"
>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 rounded-2xl bg-cyan-500/20 border border-cyan-500/40 flex flex-col items-center justify-center text-cyan-300 font-black shrink-0 shadow-sm">
<span className="text-[10px] uppercase font-bold text-neutral-400 leading-none">Parada</span>
<span className="text-lg leading-tight">#{index + 1}</span>
</div>
<div className="space-y-1">
<div className="flex items-center space-x-2">
<span className="font-black text-white text-base">{delivery.customerName}</span>
<span className="text-xs text-neutral-400">• Orden #{delivery.id}</span>
{getStatusBadge(delivery.status)}
</div>
<p className="text-xs font-bold text-white flex items-center gap-1.5 bg-neutral-950 px-3 py-1 rounded-xl border border-neutral-800 w-fit">
<MapPin className="w-4 h-4 text-cyan-400 shrink-0" />
<span>{delivery.address} ({delivery.cityName})</span>
</p>
<div className="flex items-center space-x-3 text-xs text-neutral-400 pt-0.5">
<span className="flex items-center gap-1 text-emerald-400 font-semibold">
<Phone className="w-3 h-3" /> {delivery.customerPhone}
</span>
<span>•</span>
<span>
{delivery.items.map((i) => `${i.productName} (Talla ${i.size})`).join(', ')}
</span>
</div>
{delivery.notes && (
<p className="text-[11px] text-amber-300/90 italic bg-amber-500/10 px-2.5 py-0.5 rounded-lg border border-amber-500/20 w-fit">
Nota: {delivery.notes}
</p>
)}
</div>
</div>
<div className="flex items-center gap-3 self-end lg:self-auto shrink-0">
<div className="bg-neutral-950 border border-neutral-800 px-3.5 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 font-bold uppercase">Cobrar Contraentrega</p>
<p className="text-base font-black text-emerald-400">{formatCOP(delivery.balanceDue)}</p>
</div>
<button
onClick={() => handleOpenWaze(delivery.address, delivery.cityName)}
className="bg-cyan-600 hover:bg-cyan-500 text-white font-black text-xs px-4 py-3 rounded-2xl shadow-lg flex items-center gap-1.5 cursor-pointer active:scale-95 transition-all"
title="Abrir ubicación directamente en Waze"
>
<Compass className="w-4 h-4" />
<span>Waze</span>
</button>
<button
onClick={() => handleOpenGoogleMaps(delivery.address, delivery.cityName)}
className="bg-neutral-800 hover:bg-neutral-700 text-white font-bold text-xs p-3 rounded-2xl shadow cursor-pointer transition-all"
title="Abrir en Google Maps"
>
<Navigation className="w-4 h-4 text-emerald-400" />
</button>
<button
onClick={() => handleNotifyCustomerOnTheWay(delivery)}
className="bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs px-3.5 py-3 rounded-2xl shadow flex items-center gap-1.5 cursor-pointer active:scale-95 transition-all"
title="Enviar mensaje de WhatsApp notificando que el mensajero va en camino"
>
<MessageCircle className="w-4 h-4" />
<span className="hidden sm:inline">En Camino</span>
</button>
</div>
</div>
))
)}
</div>
</div>
)}
</div>

View File

@ -1,14 +1,36 @@
'use client';
import React, { useState, useEffect } from 'react';
import { ShoppingBag, Truck, CheckCircle2, Clock, Phone, MapPin, Search, MessageCircle, Send, ShieldCheck, UserCheck } from 'lucide-react';
import {
ShoppingBag,
Truck,
CheckCircle2,
Clock,
Phone,
MapPin,
Search,
MessageCircle,
Send,
ShieldCheck,
Building2,
Navigation,
ExternalLink,
DollarSign,
Layers,
Sparkles,
Compass,
Check,
AlertCircle
} from 'lucide-react';
import { Order, OrderItem } from '@/types';
import { formatCOP } from '@/lib/pricing-engine';
export default function AdminOrdersPage() {
const [orders, setOrders] = useState<Order[]>([]);
const [activeTab, setActiveTab] = useState<'individual' | 'suppliers' | 'routes'>('individual');
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState<string>('all');
const [toastMessage, setToastMessage] = useState<string | null>(null);
useEffect(() => {
fetch('/api/orders')
@ -22,14 +44,19 @@ export default function AdminOrdersPage() {
prev.map((o) => (o.id === orderId ? { ...o, status: newStatus } : o))
);
await fetch('/api/orders', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: orderId, status: newStatus }),
}).catch(() => {});
try {
await fetch('/api/orders', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: orderId, status: newStatus }),
});
setToastMessage(`Estado de la orden #${orderId} actualizado a "${newStatus}".`);
} catch (e) {
console.error(e);
}
};
const handleNotifySupplier = (order: Order, item: OrderItem) => {
const handleNotifySingleSupplier = (order: Order, item: OrderItem) => {
const supplierPhone = item.supplierPhone || '573134848203';
const supplierName = item.supplierName || 'Proveedor Mayorista';
const supplierCost = item.supplierPrice ? formatCOP(item.supplierPrice) : 'Precio Mayorista';
@ -44,6 +71,50 @@ export default function AdminOrdersPage() {
}
};
const handleSendSupplierBatchManifest = (supplierName: string, supplierPhone: string, itemsList: { order: Order; item: OrderItem }[]) => {
const totalPairs = itemsList.reduce((acc, curr) => acc + curr.item.quantity, 0);
const totalCost = itemsList.reduce((acc, curr) => acc + (curr.item.supplierPrice || 0) * curr.item.quantity, 0);
let text = `📦 *MANIFIESTO CONSOLIDADO DE SEPARACIÓN*\n`;
text += `🏢 *Proveedor:* ${supplierName}\n`;
text += `📅 *Fecha:* ${new Date().toLocaleDateString('es-CO')} | *Total Pares:* ${totalPairs}\n`;
text += `💰 *Valor Total a Pagar Proveedor:* ${formatCOP(totalCost)}\n\n`;
text += `👟 *DETALLE DE PARES A SEPARAR:*\n`;
itemsList.forEach((entry, idx) => {
text += `\n${idx + 1}. *${entry.item.productName}*\n`;
text += ` • Talla: *${entry.item.size}* (x${entry.item.quantity})\n`;
text += ` • Costo Base: ${formatCOP(entry.item.supplierPrice || 0)}\n`;
text += ` • Orden Cliente: #${entry.order.id} (${entry.order.cityName})\n`;
});
text += `\n🤝 *Por favor confirmar disponibilidad y empaque para recogida del mensajero.* ¡Muchas gracias!`;
const cleanPhone = supplierPhone.startsWith('57') ? supplierPhone : `57${supplierPhone}`;
window.open(`https://wa.me/${cleanPhone}?text=${encodeURIComponent(text)}`, '_blank');
setToastMessage(`📲 Manifiesto de ${totalPairs} pares enviado a ${supplierName}.`);
};
const handleOpenWaze = (address: string, city: string) => {
const query = encodeURIComponent(`${address}, ${city}, Colombia`);
const wazeUrl = `https://waze.com/ul?q=${query}&navigate=yes`;
window.open(wazeUrl, '_blank');
};
const handleOpenGoogleMaps = (address: string, city: string) => {
const query = encodeURIComponent(`${address}, ${city}, Colombia`);
const mapsUrl = `https://www.google.com/maps/search/?api=1&query=${query}`;
window.open(mapsUrl, '_blank');
};
const handleNotifyCustomerOnTheWay = (order: Order) => {
const cleanPhone = order.customerPhone.startsWith('57') ? order.customerPhone : `57${order.customerPhone}`;
const text = `👋 ¡Hola *${order.customerName}*!\n\n🛵 Tu pedido ya va *EN CAMINO* con nuestro mensajero.\n\n📍 *Dirección de Entrega:* ${order.address}\n🏙️ *Ciudad:* ${order.cityName}\n💵 *Saldo a Pagar Contraentrega:* *${formatCOP(order.balanceDue)}*\n💳 *Medio de Pago:* Efectivo o Transferencia Nequi\n\nPor favor estar atento a la portería o celular. ¡Gracias por tu compra! 🔥`;
window.open(`https://wa.me/${cleanPhone}?text=${encodeURIComponent(text)}`, '_blank');
handleStatusChange(order.id, 'dispatched');
};
const getStatusBadge = (status: Order['status']) => {
switch (status) {
case 'pending':
@ -51,9 +122,9 @@ export default function AdminOrdersPage() {
case 'deposit_received':
return <span className="bg-blue-500/20 text-blue-400 border border-blue-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">Separación $10k Pagada</span>;
case 'supplier_notified':
return <span className="bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><CheckCircle2 className="w-3 h-3 text-emerald-400" /> Proveedor Notificado</span>;
return <span className="bg-purple-500/20 text-purple-300 border border-purple-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><CheckCircle2 className="w-3 h-3 text-purple-400" /> Proveedor Notificado</span>;
case 'dispatched':
return <span className="bg-purple-500/20 text-purple-400 border border-purple-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">En Ruta con Mensajero</span>;
return <span className="bg-fuchsia-500/20 text-fuchsia-400 border border-fuchsia-500/30 font-bold px-2.5 py-1 rounded-full text-[10px] flex items-center gap-1"><Truck className="w-3 h-3" /> En Ruta con Mensajero</span>;
case 'delivered':
return <span className="bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 font-bold px-2.5 py-1 rounded-full text-[10px]">Entregado & Cobrado</span>;
case 'cancelled':
@ -65,6 +136,7 @@ export default function AdminOrdersPage() {
const matchesSearch =
o.customerName.toLowerCase().includes(search.toLowerCase()) ||
o.customerPhone.includes(search) ||
o.address.toLowerCase().includes(search.toLowerCase()) ||
o.cityName.toLowerCase().includes(search.toLowerCase()) ||
o.id.toLowerCase().includes(search.toLowerCase());
@ -73,201 +145,440 @@ export default function AdminOrdersPage() {
return true;
});
const supplierGroups = orders.reduce((acc, order) => {
order.items.forEach((item) => {
const suppName = item.supplierName || 'DIFUSION JOVIC STORE';
const suppPhone = item.supplierPhone || '573134848203';
if (!acc[suppName]) {
acc[suppName] = {
name: suppName,
phone: suppPhone,
entries: [],
totalPairs: 0,
totalCost: 0,
};
}
acc[suppName].entries.push({ order, item });
acc[suppName].totalPairs += item.quantity;
acc[suppName].totalCost += (item.supplierPrice || 0) * item.quantity;
});
return acc;
}, {} as Record<string, { name: string; phone: string; entries: { order: Order; item: OrderItem }[]; totalPairs: number; totalCost: number }>);
const pendingDeliveries = orders.filter((o) => o.status !== 'delivered' && o.status !== 'cancelled');
const totalBalanceToCollect = pendingDeliveries.reduce((acc, curr) => acc + curr.balanceDue, 0);
return (
<div className="space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-black text-white flex items-center gap-2">
<ShoppingBag className="w-6 h-6 text-emerald-400" />
<span>Gestión de Pedidos & Separación con Proveedores ({orders.length})</span>
</h1>
<p className="text-xs text-slate-400 mt-1">
Control logístico de pedidos contraentrega, cobro de anticipos de $10.000 COP y <strong>notificación inmediata de separación al proveedor</strong> vía WhatsApp.
</p>
</div>
{/* Toolbar */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="flex items-center space-x-1.5 bg-slate-900 border border-slate-800 p-1 rounded-2xl overflow-x-auto">
<button
onClick={() => setStatusFilter('all')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'all' ? 'bg-white text-slate-950 shadow' : 'text-slate-400 hover:text-white'
}`}
>
Todos ({orders.length})
</button>
<button
onClick={() => setStatusFilter('pending')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'pending' ? 'bg-amber-500/30 text-amber-300' : 'text-slate-400'
}`}
>
Pendientes
</button>
<button
onClick={() => setStatusFilter('supplier_notified')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'supplier_notified' ? 'bg-emerald-500/30 text-emerald-300' : 'text-slate-400'
}`}
>
Proveedor Notificado
</button>
<button
onClick={() => setStatusFilter('dispatched')}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === 'dispatched' ? 'bg-purple-500/30 text-purple-300' : 'text-slate-400'
}`}
>
En Ruta
</button>
</div>
<div className="max-w-xs w-full">
<input
type="text"
placeholder="Buscar por cliente, ciudad o # orden..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-slate-900 border border-slate-800 rounded-xl px-3.5 py-2 text-xs text-white outline-hidden focus:border-emerald-400"
/>
</div>
</div>
{filteredOrders.length === 0 ? (
<div className="bg-slate-900 border border-slate-800 rounded-3xl p-12 text-center space-y-3">
<div className="w-16 h-16 rounded-full bg-slate-950 text-slate-500 flex items-center justify-center mx-auto border border-slate-800">
<ShoppingBag className="w-8 h-8 text-emerald-400" />
</div>
<h3 className="font-bold text-white text-base">No hay pedidos en esta sección</h3>
<p className="text-xs text-slate-400 max-w-sm mx-auto">
Cuando los clientes hagan un pedido en la tienda web o por WhatsApp, aparecerán aquí con el botón para separar inmediatamente con el proveedor correspondiente.
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-black text-white flex items-center gap-2">
<ShoppingBag className="w-6 h-6 text-purple-400" />
<span>Centro Logístico: Pedidos, Proveedores & Rutas Waze</span>
</h1>
<p className="text-xs text-neutral-400 mt-1">
Consolidación automática para <strong>separación con proveedores</strong> y <strong>hoja de ruta para mensajeros con navegación Waze en 1 clic</strong>.
</p>
</div>
) : (
</div>
{toastMessage && (
<div className="bg-purple-950/80 border border-purple-500 text-purple-300 px-4 py-3 rounded-2xl text-xs flex items-center justify-between animate-in fade-in shadow-md">
<span>{toastMessage}</span>
<button onClick={() => setToastMessage(null)} className="font-bold text-white cursor-pointer ml-3">
✕
</button>
</div>
)}
{/* Main Tabs Navigation */}
<div className="grid grid-cols-1 sm:grid-cols-3 p-1 bg-neutral-900 border border-neutral-800 rounded-2xl gap-1">
<button
onClick={() => setActiveTab('individual')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'individual'
? 'bg-purple-600 text-white shadow-lg shadow-purple-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Layers className="w-4 h-4" />
<span>1. Todos los Pedidos ({orders.length})</span>
</button>
<button
onClick={() => setActiveTab('suppliers')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'suppliers'
? 'bg-purple-600 text-white shadow-lg shadow-purple-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Building2 className="w-4 h-4 text-fuchsia-300" />
<span>2. Consolidado por Proveedor ({Object.keys(supplierGroups).length})</span>
</button>
<button
onClick={() => setActiveTab('routes')}
className={`py-3 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 cursor-pointer ${
activeTab === 'routes'
? 'bg-gradient-to-r from-purple-600 to-pink-600 text-white shadow-lg shadow-pink-600/30'
: 'text-neutral-400 hover:text-white'
}`}
>
<Compass className="w-4 h-4 text-cyan-300" />
<span>3. Rutas de Entrega & Waze ({pendingDeliveries.length})</span>
</button>
</div>
{/* TAB 1: INDIVIDUAL ORDERS LIST */}
{activeTab === 'individual' && (
<div className="space-y-4">
{filteredOrders.map((order) => (
<div
key={order.id}
className="bg-slate-900 border border-slate-800 rounded-3xl p-5 space-y-4 shadow-xl"
>
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-slate-800 pb-3">
<div className="flex items-center space-x-2">
<span className="font-black text-white text-sm">Orden #{order.id}</span>
<span className="text-xs text-slate-500">• {new Date(order.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
</div>
{getStatusBadge(order.status)}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="flex items-center space-x-1.5 bg-neutral-900 border border-neutral-800 p-1 rounded-2xl overflow-x-auto">
{['all', 'pending', 'deposit_received', 'supplier_notified', 'dispatched', 'delivered'].map((st) => (
<button
key={st}
onClick={() => setStatusFilter(st)}
className={`text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer transition-all ${
statusFilter === st ? 'bg-white text-neutral-950 shadow' : 'text-neutral-400 hover:text-white'
}`}
>
{st === 'all' ? 'Todos' : st === 'pending' ? 'Pendientes' : st === 'deposit_received' ? 'Anticipo Pagado' : st === 'supplier_notified' ? 'Prov. Notificado' : st === 'dispatched' ? 'En Ruta' : 'Entregados'}
</button>
))}
</div>
<div className="max-w-xs w-full">
<input
type="text"
placeholder="Buscar por cliente, celular, ciudad o #..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-neutral-900 border border-neutral-800 rounded-xl px-3.5 py-2 text-xs text-white outline-hidden focus:border-purple-400"
/>
</div>
</div>
<div className="space-y-4">
{filteredOrders.length === 0 ? (
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl p-12 text-center text-neutral-500">
No hay pedidos que coincidan con los filtros.
</div>
) : (
filteredOrders.map((order) => (
<div
key={order.id}
className="bg-neutral-900 border border-neutral-800 hover:border-purple-500/40 rounded-3xl p-5 shadow-xl transition-all space-y-4"
>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 border-b border-neutral-800 pb-3">
<div className="flex items-center space-x-3">
<span className="font-black text-white text-base">Orden #{order.id}</span>
{getStatusBadge(order.status)}
<span className="text-[11px] text-neutral-500 font-mono">{order.createdAt}</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
<div className="bg-slate-950/60 border border-slate-800/80 rounded-2xl p-3.5 space-y-1.5">
<span className="text-[10px] text-emerald-400 font-bold uppercase block tracking-wider">
Cliente & Entrega (24 Horas)
</span>
<p className="font-bold text-white text-sm">{order.customerName}</p>
<p className="text-slate-300 flex items-center gap-1.5">
<Phone className="w-3.5 h-3.5 text-emerald-400" /> {order.customerPhone}
</p>
<p className="text-slate-300 flex items-center gap-1.5">
<MapPin className="w-3.5 h-3.5 text-emerald-400" /> {order.address} ({order.cityName})
</p>
</div>
<div className="flex items-center space-x-2">
<span className="text-xs text-neutral-400 font-bold">Estado:</span>
<select
value={order.status}
onChange={(e) => handleStatusChange(order.id, e.target.value as any)}
className="bg-neutral-950 border border-neutral-800 text-xs text-white rounded-xl px-3 py-1.5 outline-hidden font-bold cursor-pointer"
>
<option value="pending">⏳ Pendiente Confirmar</option>
<option value="deposit_received">💳 Separación $10k Pagada</option>
<option value="supplier_notified">🏢 Proveedor Notificado</option>
<option value="dispatched">🛵 En Ruta con Mensajero</option>
<option value="delivered">✅ Entregado & Cobrado</option>
<option value="cancelled">❌ Cancelado</option>
</select>
</div>
</div>
<div className="bg-slate-950/60 border border-slate-800/80 rounded-2xl p-3.5 space-y-2">
<span className="text-[10px] text-slate-400 font-bold uppercase block tracking-wider">
Zapatillas & Proveedor Asignado:
</span>
<div className="space-y-2.5">
{order.items.map((it, idx) => {
const suppName = it.supplierName || 'DIFUSION JOVIC STORE';
return (
<div key={idx} className="bg-slate-900 border border-slate-800 rounded-xl p-2.5 space-y-2">
<div className="flex items-center justify-between gap-2">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
<div className="space-y-1 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Cliente & Destino</p>
<p className="text-white font-bold text-sm">{order.customerName}</p>
<p className="text-emerald-400 font-mono font-semibold flex items-center gap-1">
<Phone className="w-3 h-3" /> {order.customerPhone}
</p>
<p className="text-neutral-300 flex items-start gap-1">
<MapPin className="w-3 h-3 shrink-0 text-purple-400 mt-0.5" />
<span>{order.address} ({order.cityName})</span>
</p>
</div>
<div className="space-y-1 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Resumen de Cobro</p>
<div className="flex justify-between text-neutral-300">
<span>Total Venta:</span>
<strong className="text-white">{formatCOP(order.totalAmount)}</strong>
</div>
<div className="flex justify-between text-neutral-300">
<span>Anticipo / Separación:</span>
<strong className="text-blue-400">-{formatCOP(order.depositAmount)}</strong>
</div>
<div className="flex justify-between text-neutral-300 pt-1 border-t border-neutral-800 font-bold">
<span className="text-purple-300">Saldo Contraentrega:</span>
<span className="text-emerald-400 text-sm">{formatCOP(order.balanceDue)}</span>
</div>
</div>
<div className="space-y-2 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80 flex flex-col justify-center">
<button
onClick={() => handleOpenWaze(order.address, order.cityName)}
className="w-full bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-1.5 px-3 rounded-xl flex items-center justify-center gap-1.5 cursor-pointer shadow transition-all text-xs"
>
<Compass className="w-3.5 h-3.5" />
<span>Navegar con Waze</span>
</button>
<button
onClick={() => handleNotifyCustomerOnTheWay(order)}
className="w-full bg-emerald-600 hover:bg-emerald-500 text-white font-bold py-1.5 px-3 rounded-xl flex items-center justify-center gap-1.5 cursor-pointer shadow transition-all text-xs"
>
<MessageCircle className="w-3.5 h-3.5" />
<span>Avisar "Voy en camino"</span>
</button>
</div>
</div>
<div className="space-y-2 pt-2">
<p className="font-bold text-neutral-400 uppercase text-[10px]">Pares en esta orden:</p>
<div className="space-y-2">
{order.items.map((item, idx) => (
<div
key={idx}
className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 bg-neutral-950 p-3 rounded-2xl border border-neutral-800"
>
<div className="flex items-center space-x-3">
<img src={item.image} alt="" className="w-12 h-12 rounded-xl object-cover border border-neutral-800 shrink-0" />
<div>
<p className="font-bold text-white text-xs">{it.productName}</p>
<p className="text-[11px] text-slate-400">
Talla <strong className="text-emerald-400 font-bold">{it.size}</strong> • Cant: {it.quantity}
<p className="font-bold text-white text-xs">{item.productName}</p>
<p className="text-[11px] text-neutral-400">
Talla: <strong className="text-purple-400">{item.size}</strong> | Cantidad: <strong>{item.quantity}</strong> | Proveedor: <strong className="text-purple-300">{item.supplierName || 'Bodega Principal'}</strong>
</p>
</div>
<span className="text-[10px] bg-slate-950 text-slate-300 border border-slate-800 px-2 py-0.5 rounded font-mono">
Prov: {suppName}
</span>
</div>
<button
onClick={() => handleNotifySupplier(order, it)}
className="w-full bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-300 border border-emerald-500/30 font-bold text-xs py-1.5 px-2.5 rounded-lg flex items-center justify-center space-x-1.5 transition-all cursor-pointer shadow-xs"
onClick={() => handleNotifySingleSupplier(order, item)}
className="bg-purple-600 hover:bg-purple-500 text-white font-bold text-xs px-3.5 py-2 rounded-xl flex items-center space-x-1.5 shadow cursor-pointer shrink-0 self-end sm:self-auto"
>
<MessageCircle className="w-3.5 h-3.5 text-emerald-400" />
<span>📲 Separar con {suppName} (WhatsApp)</span>
<Send className="w-3.5 h-3.5" />
<span>Separar con Proveedor</span>
</button>
</div>
);
})}
))}
</div>
</div>
</div>
))
)}
</div>
</div>
)}
{/* TAB 2: CONSOLIDADO AGRUPADO POR PROVEEDOR */}
{activeTab === 'suppliers' && (
<div className="space-y-6 animate-in fade-in">
<div className="bg-purple-950/40 border border-purple-500/30 rounded-3xl p-5 text-xs text-neutral-300 space-y-2 shadow-lg">
<div className="flex items-center space-x-2 text-purple-300 font-bold text-sm">
<Building2 className="w-5 h-5 text-purple-400" />
<span>Consolidado Automático de Compras & Separaciones</span>
</div>
<p>
Aquí se agrupan todos los pares pendientes clasificados por cada <strong>Proveedor Mayorista</strong>. Puedes enviar el <strong>manifiesto consolidado en 1 solo mensaje de WhatsApp</strong> para que el proveedor prepare y empaque todos los pedidos de la jornada juntos.
</p>
</div>
<div className="grid grid-cols-1 gap-6">
{Object.values(supplierGroups).map((group) => (
<div
key={group.name}
className="bg-neutral-900 border border-neutral-800 rounded-3xl p-6 shadow-xl space-y-4"
>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 border-b border-neutral-800 pb-4">
<div>
<span className="bg-purple-500/20 text-purple-300 border border-purple-500/40 text-[10px] font-bold px-2.5 py-0.5 rounded-full uppercase tracking-wider">
Proveedor Mayorista
</span>
<h3 className="text-xl font-black text-white mt-1">{group.name}</h3>
<p className="text-xs text-neutral-400 flex items-center gap-1 mt-0.5">
<Phone className="w-3.5 h-3.5 text-emerald-400" />
<span>WhatsApp Proveedor: <strong>{group.phone}</strong></span>
</p>
</div>
<div className="flex items-center gap-3">
<div className="bg-neutral-950 border border-neutral-800 px-4 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Total Pares</p>
<p className="text-lg font-black text-purple-400">{group.totalPairs} par(es)</p>
</div>
<div className="bg-neutral-950 border border-neutral-800 px-4 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Total a Pagar</p>
<p className="text-lg font-black text-emerald-400">{formatCOP(group.totalCost)}</p>
</div>
</div>
</div>
<div className="bg-slate-950/60 border border-slate-800/80 rounded-2xl p-3.5 space-y-2">
<span className="text-[10px] text-slate-400 font-bold uppercase block tracking-wider">
Cobro & Pagos Contraentrega
</span>
<div className="space-y-1 text-xs">
<p className="text-slate-400 flex justify-between">
<span>Total de la Orden:</span>
<strong className="text-white font-mono">{formatCOP(order.totalAmount)}</strong>
</p>
{order.depositAmount > 0 ? (
<>
<p className="text-amber-300 flex justify-between">
<span>Anticipo / Separación:</span>
<strong className="font-mono">{formatCOP(order.depositAmount)}</strong>
</p>
<p className="text-emerald-400 font-bold flex justify-between pt-1 border-t border-slate-800">
<span>Saldo al Mensajero:</span>
<strong className="font-mono">{formatCOP(order.balanceDue)}</strong>
</p>
</>
) : (
<p className="text-emerald-400 font-bold pt-1 border-t border-slate-800">
100% Contraentrega al recibir
</p>
)}
</div>
<div className="overflow-x-auto">
<table className="w-full text-left text-xs text-neutral-300">
<thead className="bg-neutral-950 text-neutral-400 font-bold uppercase text-[10px]">
<tr>
<th className="py-2.5 px-3">Modelo / Zapatilla</th>
<th className="py-2.5 px-3">Talla</th>
<th className="py-2.5 px-3">Cant.</th>
<th className="py-2.5 px-3">Costo Base</th>
<th className="py-2.5 px-3">Orden Cliente</th>
<th className="py-2.5 px-3">Destino</th>
<th className="py-2.5 px-3">Estado Orden</th>
</tr>
</thead>
<tbody className="divide-y divide-neutral-800">
{group.entries.map((entry, idx) => (
<tr key={idx} className="hover:bg-neutral-800/40">
<td className="py-2.5 px-3 font-bold text-white flex items-center gap-2">
<img src={entry.item.image} alt="" className="w-8 h-8 rounded-lg object-cover border border-neutral-800" />
<span>{entry.item.productName}</span>
</td>
<td className="py-2.5 px-3 font-bold text-purple-400 font-mono">{entry.item.size}</td>
<td className="py-2.5 px-3 font-bold">{entry.item.quantity}</td>
<td className="py-2.5 px-3 font-mono font-semibold">{formatCOP(entry.item.supplierPrice || 0)}</td>
<td className="py-2.5 px-3 font-mono text-neutral-400">#{entry.order.id}</td>
<td className="py-2.5 px-3">{entry.order.cityName}</td>
<td className="py-2.5 px-3">{getStatusBadge(entry.order.status)}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="pt-2 flex justify-end">
<button
onClick={() => handleSendSupplierBatchManifest(group.name, group.phone, group.entries)}
className="bg-gradient-to-r from-purple-600 via-fuchsia-600 to-pink-600 hover:opacity-95 text-white font-black text-xs px-5 py-3 rounded-2xl shadow-lg flex items-center gap-2 cursor-pointer transition-all active:scale-95"
>
<MessageCircle className="w-4 h-4" />
<span>Enviar Manifiesto de {group.totalPairs} Pares por WhatsApp a {group.name}</span>
</button>
</div>
</div>
))}
</div>
</div>
)}
<div className="flex flex-wrap items-center justify-between gap-2 pt-2 border-t border-slate-800">
<span className="text-[11px] text-slate-500">Método: {order.paymentMethodName}</span>
{/* TAB 3: RUTAS DE ENTREGA ORGANIZADAS POR DIRECCIONES & WAZE */}
{activeTab === 'routes' && (
<div className="space-y-6 animate-in fade-in">
<div className="bg-gradient-to-r from-neutral-900 via-neutral-900 to-purple-950/60 border border-purple-500/30 rounded-3xl p-6 shadow-xl flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<div className="flex items-center space-x-2 text-cyan-300 font-bold text-sm">
<Compass className="w-5 h-5 text-cyan-400" />
<span>Hoja de Ruta & Navegación GPS (Waze / Google Maps)</span>
</div>
<p className="text-xs text-neutral-300 mt-1 max-w-xl">
Entregas contraentrega secuenciadas por zona geográfica. El mensajero puede presionar <strong>"Abrir en Waze"</strong> para iniciar la navegación instantánea a cada portería/casa.
</p>
</div>
<div className="flex flex-wrap gap-2">
<button
onClick={() => handleStatusChange(order.id, 'deposit_received')}
className="bg-blue-600/20 hover:bg-blue-600/30 text-blue-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-blue-500/20"
>
Confirmar $10k
</button>
<button
onClick={() => handleStatusChange(order.id, 'supplier_notified')}
className="bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-emerald-500/20"
>
Marcar Proveedor Notificado
</button>
<button
onClick={() => handleStatusChange(order.id, 'dispatched')}
className="bg-purple-600/20 hover:bg-purple-600/30 text-purple-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-purple-500/20"
>
Despachar en Ruta
</button>
<button
onClick={() => handleStatusChange(order.id, 'delivered')}
className="bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-300 text-xs font-bold px-3 py-1.5 rounded-xl cursor-pointer border border-emerald-500/20"
>
Marcar Entregado
</button>
</div>
<div className="flex items-center gap-3">
<div className="bg-neutral-950 border border-neutral-800 p-3 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 uppercase font-bold">Paradas / Entregas</p>
<p className="text-xl font-black text-white">{pendingDeliveries.length} Paradas</p>
</div>
<div className="bg-neutral-950 border border-emerald-500/30 p-3 rounded-2xl text-right">
<p className="text-[10px] text-emerald-400 uppercase font-bold">Total a Recaudar</p>
<p className="text-xl font-black text-emerald-400">{formatCOP(totalBalanceToCollect)}</p>
</div>
</div>
))}
</div>
<div className="space-y-3">
{pendingDeliveries.length === 0 ? (
<div className="bg-neutral-900 border border-neutral-800 rounded-3xl p-12 text-center text-neutral-500">
🎉 No hay entregas pendientes por despachar en este momento.
</div>
) : (
pendingDeliveries.map((delivery, index) => (
<div
key={delivery.id}
className="bg-neutral-900 border border-neutral-800 hover:border-cyan-500/40 rounded-3xl p-5 shadow-lg transition-all flex flex-col lg:flex-row lg:items-center justify-between gap-4"
>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 rounded-2xl bg-cyan-500/20 border border-cyan-500/40 flex flex-col items-center justify-center text-cyan-300 font-black shrink-0 shadow-sm">
<span className="text-[10px] uppercase font-bold text-neutral-400 leading-none">Parada</span>
<span className="text-lg leading-tight">#{index + 1}</span>
</div>
<div className="space-y-1">
<div className="flex items-center space-x-2">
<span className="font-black text-white text-base">{delivery.customerName}</span>
<span className="text-xs text-neutral-400">• Orden #{delivery.id}</span>
{getStatusBadge(delivery.status)}
</div>
<p className="text-xs font-bold text-white flex items-center gap-1.5 bg-neutral-950 px-3 py-1 rounded-xl border border-neutral-800 w-fit">
<MapPin className="w-4 h-4 text-cyan-400 shrink-0" />
<span>{delivery.address} ({delivery.cityName})</span>
</p>
<div className="flex items-center space-x-3 text-xs text-neutral-400 pt-0.5">
<span className="flex items-center gap-1 text-emerald-400 font-semibold">
<Phone className="w-3 h-3" /> {delivery.customerPhone}
</span>
<span>•</span>
<span>
{delivery.items.map((i) => `${i.productName} (Talla ${i.size})`).join(', ')}
</span>
</div>
{delivery.notes && (
<p className="text-[11px] text-amber-300/90 italic bg-amber-500/10 px-2.5 py-0.5 rounded-lg border border-amber-500/20 w-fit">
Nota: {delivery.notes}
</p>
)}
</div>
</div>
<div className="flex items-center gap-3 self-end lg:self-auto shrink-0">
<div className="bg-neutral-950 border border-neutral-800 px-3.5 py-2 rounded-2xl text-right">
<p className="text-[10px] text-neutral-400 font-bold uppercase">Cobrar Contraentrega</p>
<p className="text-base font-black text-emerald-400">{formatCOP(delivery.balanceDue)}</p>
</div>
<button
onClick={() => handleOpenWaze(delivery.address, delivery.cityName)}
className="bg-cyan-600 hover:bg-cyan-500 text-white font-black text-xs px-4 py-3 rounded-2xl shadow-lg flex items-center gap-1.5 cursor-pointer active:scale-95 transition-all"
title="Abrir ubicación directamente en Waze"
>
<Compass className="w-4 h-4" />
<span>Waze</span>
</button>
<button
onClick={() => handleOpenGoogleMaps(delivery.address, delivery.cityName)}
className="bg-neutral-800 hover:bg-neutral-700 text-white font-bold text-xs p-3 rounded-2xl shadow cursor-pointer transition-all"
title="Abrir en Google Maps"
>
<Navigation className="w-4 h-4 text-emerald-400" />
</button>
<button
onClick={() => handleNotifyCustomerOnTheWay(delivery)}
className="bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs px-3.5 py-3 rounded-2xl shadow flex items-center gap-1.5 cursor-pointer active:scale-95 transition-all"
title="Enviar mensaje de WhatsApp notificando que el mensajero va en camino"
>
<MessageCircle className="w-4 h-4" />
<span className="hidden sm:inline">En Camino</span>
</button>
</div>
</div>
))
)}
</div>
</div>
)}
</div>