"use client"; import { Icon } from "@iconify/react"; import { useMemo, useState } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { iconClockCircle, iconPrinter, iconUndo } from "../../_core/icons"; import { apiClient } from "../../_core/services/api-client"; import { cartProductsResource, cashMovementsResource, cashSessionsResource, customersResource, inventoryMovementsResource, paymentsResource, productHistoryResource, productsResource, shopCartsResource, usersResource, } from "../../_core/services/entities"; import { ConfirmDialog } from "../../_core/components/confirm-dialog"; import { Dialog } from "../../_core/components/dialog"; import { TableShell, tdClass, thClass } from "../../_core/components/table-shell"; import type { ShopCart } from "../../_core/types/entities"; const PAYMENT_LABELS: Record = { cash: "Efectivo", card: "Tarjeta", transfer: "Transferencia" }; function money(value: string | number) { return Number(value).toLocaleString("es-AR", { style: "currency", currency: "USD" }); } export default function PosHistoryPage() { const queryClient = useQueryClient(); const { data: cartsRaw, isLoading } = shopCartsResource.useList(); const carts = useMemo( () => (cartsRaw ? [...cartsRaw].sort((a, b) => b.createdAt.localeCompare(a.createdAt)) : isLoading ? null : []), [cartsRaw, isLoading], ); const { data: cartProducts = [] } = cartProductsResource.useList(); const { data: history = [] } = productHistoryResource.useList(); const { data: products = [] } = productsResource.useList(); const { data: payments = [] } = paymentsResource.useList(); const { data: users = [] } = usersResource.useList(); const { data: customers = [] } = customersResource.useList(); const { data: sessions = [] } = cashSessionsResource.useList(); const [selected, setSelected] = useState(null); const [returning, setReturning] = useState(null); function refreshAfterReturn() { queryClient.invalidateQueries({ queryKey: shopCartsResource.key }); queryClient.invalidateQueries({ queryKey: inventoryMovementsResource.key }); queryClient.invalidateQueries({ queryKey: paymentsResource.key }); queryClient.invalidateQueries({ queryKey: cashMovementsResource.key }); queryClient.invalidateQueries({ queryKey: cartProductsResource.key }); } function linesFor(cartId: number) { return cartProducts .filter((cp) => cp.cartId === cartId) .map((cp) => { const h = history.find((row) => row.id === cp.historyId); const product = h ? products.find((p) => p.id === h.productId) : undefined; return { productId: h?.productId ?? 0, name: product?.name ?? "Producto", unitPrice: h ? Number(h.unitPrice) : 0, quantity: cp.quantity, }; }); } function cashierName(userId: number | null) { return users.find((u) => u.id === userId)?.name ?? "—"; } function customerName(customerId: number | null) { if (!customerId) return "Cliente mostrador"; return customers.find((c) => c.id === customerId)?.name ?? "Cliente mostrador"; } function paymentFor(cartId: number) { return payments.find((p) => p.cartId === cartId) ?? null; } const visibleCarts = useMemo( () => carts?.filter((c) => c.status === "paid" || c.status === "refunded") ?? [], [carts], ); async function handleReturn() { if (!returning) return; const lines = linesFor(returning.id); for (const line of lines) { const warehouses = await apiClient.get<{ inventoryId: number; productId: number }[]>("/warehouses"); const inventoryId = warehouses.find((w) => w.productId === line.productId)?.inventoryId; if (!inventoryId) continue; await apiClient.post("/inventory-movements", { productId: line.productId, inventoryId, type: "in", quantity: line.quantity, reason: `Devolución venta #${returning.id}`, }); } const original = paymentFor(returning.id); await apiClient.post("/payments", { cartId: returning.id, userId: original?.id ?? 0, amount: `-${returning.totalPrice}`, status: "refunded", paid: true, paymentMethod: original?.paymentMethod ?? "cash", paidAt: new Date().toISOString(), }); if ((original?.paymentMethod ?? "cash") === "cash") { const openSession = sessions.find((s) => s.status === "open"); if (openSession) { await apiClient.post("/cash-movements", { sessionId: openSession.id, type: "out", amount: returning.totalPrice, reason: `Devolución venta #${returning.id}`, }); } } await apiClient.patch(`/shop-carts/${returning.id}`, { status: "refunded" }); setReturning(null); setSelected(null); refreshAfterReturn(); } return (

Historial de ventas

Ventas cobradas, reimpresión de tickets y devoluciones.

{carts === null ? (
Cargando…
) : visibleCarts.length === 0 ? (
) : ( Venta Cliente Cajero Total Estado Fecha {visibleCarts.map((cart) => ( setSelected(cart)} className="cursor-pointer transition hover:bg-foreground/5" > #{cart.id} {customerName(cart.customerId)} {cashierName(cart.userId)} {money(cart.totalPrice)} {cart.status === "refunded" ? "Devuelta" : "Pagada"} {new Date(cart.createdAt).toLocaleString("es-AR", { dateStyle: "short", timeStyle: "short" })} ))} )} setSelected(null)} title={`Venta #${selected?.id ?? ""}`} widthClassName="max-w-sm"> {selected && (

{new Date(selected.createdAt).toLocaleString("es-AR")}

{customerName(selected.customerId)}

{linesFor(selected.id).map((l, i) => (
{l.quantity} × {l.name} {money(l.unitPrice * l.quantity)}
))}
Descuento -{money(selected.discount)}
Total {money(selected.totalPrice)}

{paymentFor(selected.id) ? PAYMENT_LABELS[paymentFor(selected.id)!.paymentMethod] : ""}

{selected.status === "paid" && ( )}
)}
setReturning(null)} title="Devolver venta" description={`Se repondrá el stock de la venta #${returning?.id} y se registrará el reembolso. Esta acción no se puede deshacer.`} confirmLabel="Confirmar devolución" onConfirm={handleReturn} />
); }