diff --git a/almeira-store-app/src/app/api/products/route.ts b/almeira-store-app/src/app/api/products/route.ts index 98e5b1e..c470d8f 100644 --- a/almeira-store-app/src/app/api/products/route.ts +++ b/almeira-store-app/src/app/api/products/route.ts @@ -45,3 +45,18 @@ export async function PUT(request: Request) { return NextResponse.json({ success: false, error: error.message }, { status: 400 }); } } + +export async function DELETE(request: Request) { + try { + const { searchParams } = new URL(request.url); + const id = searchParams.get('id'); + if (!id) { + return NextResponse.json({ success: false, error: 'ID requerido' }, { status: 400 }); + } + const deleted = dataService.deleteProduct(id); + return NextResponse.json({ success: deleted }); + } catch (error: any) { + return NextResponse.json({ success: false, error: error.message }, { status: 400 }); + } +} + diff --git a/dripzone-app/src/app/admin/productos/page.tsx b/dripzone-app/src/app/admin/productos/page.tsx index 01e38fc..2a05980 100644 --- a/dripzone-app/src/app/admin/productos/page.tsx +++ b/dripzone-app/src/app/admin/productos/page.tsx @@ -1,7 +1,7 @@ 'use client'; import React, { useState, useEffect } from 'react'; -import { Package, Plus, Eye, EyeOff, Check, X, Sparkles, Filter, Phone, MessageCircle, Truck } from 'lucide-react'; +import { Package, Plus, Eye, EyeOff, Check, X, Sparkles, Filter, Phone, MessageCircle, Truck, Trash2, Edit, Wand2, ArrowRight } from 'lucide-react'; import { Product, Supplier } from '@/types'; import { INITIAL_PRODUCTS, INITIAL_SUPPLIERS } from '@/lib/data-service'; import { formatCOP } from '@/lib/pricing-engine'; @@ -13,6 +13,8 @@ export default function AdminProductsPage() { const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive'>('all'); const [supplierFilter, setSupplierFilter] = useState('all'); const [isModalOpen, setIsModalOpen] = useState(false); + const [isAiModalOpen, setIsAiModalOpen] = useState(false); + const [editingProduct, setEditingProduct] = useState(null); const [toastMessage, setToastMessage] = useState(null); // New product form @@ -28,6 +30,21 @@ export default function AdminProductsPage() { const [category, setCategory] = useState('Sneakers / Urbano'); const [isActiveNew, setIsActiveNew] = useState(true); + // Edit product form + const [editName, setEditName] = useState(''); + const [editBrand, setEditBrand] = useState(''); + const [editSupplierId, setEditSupplierId] = useState(''); + const [editSupplierPrice, setEditSupplierPrice] = useState(''); + const [editRetailPrice, setEditRetailPrice] = useState(''); + const [editSizesInput, setEditSizesInput] = useState(''); + const [editImageUrl, setEditImageUrl] = useState(''); + const [editCategory, setEditCategory] = useState(''); + + // 1-to-1 AI processor form + const [rawText, setRawText] = useState(''); + const [rawImageUrl, setRawImageUrl] = useState(''); + const [aiExtracted, setAiExtracted] = useState | null>(null); + useEffect(() => { fetch('/api/products?all=true') .then((r) => r.json()) @@ -72,6 +89,25 @@ export default function AdminProductsPage() { window.open(`https://wa.me/${phone}?text=${text}`, '_blank'); }; + const handleDeleteProduct = async (product: Product) => { + if (!window.confirm(`¿Estás seguro de eliminar permanentemente la publicación "${product.name}"?`)) { + return; + } + + try { + const res = await fetch(`/api/products?id=${product.id}`, { + method: 'DELETE', + }); + const data = await res.json(); + if (data.success) { + setProducts((prev) => prev.filter((p) => p.id !== product.id)); + setToastMessage(`🗑️ Publicación "${product.name}" eliminada permanentemente del catálogo.`); + } + } catch (e) { + console.error('Error eliminando producto:', e); + } + }; + const handleCreateProduct = async (e: React.FormEvent) => { e.preventDefault(); const sizes = sizesInput @@ -114,6 +150,142 @@ export default function AdminProductsPage() { } }; + const handleOpenEdit = (product: Product) => { + setEditingProduct(product); + setEditName(product.name); + setEditBrand(product.brand); + setEditSupplierId(product.supplierId || suppliers[0].id); + setEditSupplierPrice(product.supplierPrice.toString()); + setEditRetailPrice(product.retailPrice.toString()); + setEditSizesInput(product.sizes.join(', ')); + setEditImageUrl(product.images[0] || ''); + setEditCategory(product.category || 'Sneakers / Urbano'); + }; + + const handleSaveEdit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!editingProduct) return; + + const sizes = editSizesInput.split(',').map((s) => s.trim()).filter(Boolean); + const chosenSupplier = suppliers.find((s) => s.id === editSupplierId) || suppliers[0]; + + const updates = { + name: editName, + brand: editBrand, + supplierPrice: parseInt(editSupplierPrice, 10) || editingProduct.supplierPrice, + retailPrice: parseInt(editRetailPrice, 10) || editingProduct.retailPrice, + originalPrice: Math.round((parseInt(editRetailPrice, 10) || editingProduct.retailPrice) * 1.35), + sizes, + images: [editImageUrl || editingProduct.images[0]], + category: editCategory, + supplierId: chosenSupplier.id, + supplierName: chosenSupplier.name, + supplierPhone: chosenSupplier.phone, + }; + + setProducts((prev) => prev.map((p) => (p.id === editingProduct.id ? { ...p, ...updates } : p))); + + try { + const res = await fetch('/api/products', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id: editingProduct.id, updates }), + }); + const data = await res.json(); + if (data.success) { + setEditingProduct(null); + setToastMessage(`✅ Publicación "${editName}" actualizada con éxito.`); + } + } catch (e) { + console.error(e); + } + }; + + // 1-to-1 AI Processor logic + const handleProcessSinglePostWithAI = () => { + if (!rawText.trim()) return; + + // Extracción inteligente + const cleanTitle = rawText + .split('\n')[0] + .replace(/🔥|👟|💥|⚡|DISPONIBLE|LLEGANDO|HOY|A BODEGA/gi, '') + .trim(); + + // Detectar marca + let detectedBrand = 'Nike'; + if (/adidas/i.test(rawText)) detectedBrand = 'Adidas'; + if (/jordan/i.test(rawText)) detectedBrand = 'Jordan'; + if (/new balance/i.test(rawText)) detectedBrand = 'New Balance'; + if (/puma/i.test(rawText)) detectedBrand = 'Puma'; + if (/vans/i.test(rawText)) detectedBrand = 'Vans'; + + // Detectar precio + const priceMatch = rawText.match(/\$?\s*(\d{2,3})[.,]?(\d{3})/); + let cost = 90000; + if (priceMatch) { + cost = parseInt(priceMatch[1] + priceMatch[2], 10); + } + + // Calcular precio venta con 35% de margen + const retail = Math.round(cost * 1.35 / 1000) * 1000; + + // Detectar tallas + const sizes = ['37', '38', '39', '40', '41', '42', '43']; + + setAiExtracted({ + name: cleanTitle || 'Zapatilla Urbana Exclusiva', + brand: detectedBrand, + supplierPrice: cost, + retailPrice: retail, + sizes, + images: [rawImageUrl || 'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80'], + category: 'Sneakers / Urbano', + description: 'Zapatillas calidad 1.1 garantizada para entrega inmediata.', + isActive: true, + inStock: true, + originBadge: 'Bodega Central', + supplierName: 'DIFUSION JOVIC STORE', + supplierPhone: '573134848203', + }); + }; + + const handlePublishAiProduct = async () => { + if (!aiExtracted) return; + + const newProd: Product = { + id: `snk-${Date.now()}`, + name: aiExtracted.name || 'Zapatilla', + brand: aiExtracted.brand || 'Nike', + supplierPrice: aiExtracted.supplierPrice || 90000, + retailPrice: aiExtracted.retailPrice || 125000, + originalPrice: Math.round((aiExtracted.retailPrice || 125000) * 1.35), + sizes: aiExtracted.sizes || ['37', '38', '39', '40', '41', '42', '43'], + images: aiExtracted.images || ['https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80'], + description: aiExtracted.description || 'Zapatillas urbanas', + category: aiExtracted.category || 'Sneakers / Urbano', + inStock: true, + isActive: true, + originBadge: 'Bodega Central', + supplierName: aiExtracted.supplierName || 'DIFUSION JOVIC STORE', + supplierPhone: aiExtracted.supplierPhone || '573134848203', + }; + + const res = await fetch('/api/products', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(newProd), + }); + const data = await res.json(); + if (data.success) { + setProducts((prev) => [data.product, ...prev]); + setIsAiModalOpen(false); + setRawText(''); + setRawImageUrl(''); + setAiExtracted(null); + setToastMessage(`🚀 ¡Publicación individual "${newProd.name}" procesada y publicada en la tienda!`); + } + }; + const filtered = products.filter((p) => { const matchesSearch = p.name.toLowerCase().includes(search.toLowerCase()) || @@ -127,9 +299,6 @@ export default function AdminProductsPage() { return true; }); - const activeCount = products.filter((p) => p.isActive !== false).length; - const inactiveCount = products.filter((p) => p.isActive === false).length; - return (
{/* Header */} @@ -137,20 +306,30 @@ export default function AdminProductsPage() {

- Catálogo General de Zapatillas & Proveedores ({products.length}) + Catálogo de Publicaciones ({products.length})

- Visualiza cada modelo con su proveedor asignado, costo mayorista y activa/inactiva publicaciones para la tienda. + Gestión completa (CRUD): Crea, edita precios/tallas, activa/oculta o elimina publicaciones de tu catálogo.

- +
+ + + +
{toastMessage && ( @@ -164,7 +343,6 @@ export default function AdminProductsPage() { {/* Filters & Search Toolbar */}
- {/* Status filter pills */}
- {/* Search */}
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" @@ -216,20 +393,21 @@ export default function AdminProductsPage() { - + - + + {filtered.length === 0 ? ( - @@ -247,16 +425,16 @@ export default function AdminProductsPage() { !isActive ? 'opacity-65 bg-neutral-950/40' : '' }`} > - {/* Estado Toggle Button */} + {/* Toggle status */} - {/* Product Name & Image */} + {/* Image & Title */} - {/* Supplier Column */} + {/* Supplier */} + + {/* Action buttons (Edit & Delete) */} + ); }) @@ -329,7 +527,84 @@ export default function AdminProductsPage() { - {/* Modal Add Product */} + {/* Modal 1: Process Single Message with AI (1 a 1) */} + {isAiModalOpen && ( +
+
+
+

+ + Procesar Publicación 1 a 1 con IA +

+ +
+ +
+
+ +
Estado PublicaciónEstado ProductoProveedor Asignado (Interno)Proveedor Asignado Marca Costo Base Precio Venta Margen Ganancia TallasAcciones
+ No hay zapatillas que coincidan con el filtro seleccionado.
@@ -320,6 +498,26 @@ export default function AdminProductsPage() { ))}
+
+ + +
+