"use client"; import Link from "next/link"; import { useCallback, useEffect, useState } from "react"; import { AppShell } from "@/components/AppShell"; import { CustomerPicker } from "@/components/CustomerPicker"; import { createCashReceipt, getNextCashFolio, listCashReceipts, setCashReceiptDeposit, } from "@/lib/api"; import { useCan } from "@/lib/abilities"; import { formatDate, formatMoney } from "@/lib/labels"; import type { CashReceipt, Currency } from "@/lib/types"; /** * The EFE cash receipt book — the legacy EFE form. Each receipt takes the next * folio, prints for the customer, and credits their account as C in the * same step. Elaboró is whoever is logged in. */ export default function EfectivoPage() { return ( ); } // The office's calendar day, not UTC's — in the evening UTC is already tomorrow. const today = () => new Date().toLocaleDateString("en-CA"); function CashBook() { const canCapture = useCan("ledger:create"); const [query, setQuery] = useState(""); const [items, setItems] = useState([]); const [error, setError] = useState(null); const load = useCallback(async () => { try { const r = await listCashReceipts({ query: query.trim() || undefined }); setItems(r.items); setError(null); } catch (e) { setError((e as Error)?.message ?? "No se pudieron cargar los recibos."); } }, [query]); useEffect(() => { const t = setTimeout(() => void load(), 250); return () => clearTimeout(t); }, [load]); return (

Recibos de efectivo

Cada recibo toma el siguiente folio y abona la cuenta del cliente como C‹folio›.

{canCapture && }
setQuery(e.target.value)} style={{ marginBottom: 12, maxWidth: 360 }} /> {error &&
{error}
} {items.map((r) => ( ))} {!items.length && ( )}
Folio Fecha Carpeta Cliente Monto Concepto Elaboró Depósito
Sin recibos.
); } function ReceiptForm({ onIssued }: { onIssued: () => void }) { const [customerId, setCustomerId] = useState(""); const [customerName, setCustomerName] = useState(""); const [folio, setFolio] = useState(""); const [receivedAt, setReceivedAt] = useState(today); const [amount, setAmount] = useState(""); const [currency, setCurrency] = useState("MXN"); const [exchangeRate, setExchangeRate] = useState(""); const [concept, setConcept] = useState(""); const [depositedAt, setDepositedAt] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [issued, setIssued] = useState(null); const refreshFolio = useCallback(() => { getNextCashFolio() .then((r) => setFolio(String(r.folio))) .catch(() => {}); }, []); useEffect(refreshFolio, [refreshFolio]); // The wording the office already writes on every receipt. const suggested = amount ? currency === "USD" ? `RECEIVED $${amount} DLLS FOR ACCOUNT.` : `RECIBI $${amount} PESOS PARA CUENTA.` : ""; async function submit(e: React.FormEvent) { e.preventDefault(); setBusy(true); setError(null); try { const r = await createCashReceipt({ customerId, folio: folio ? Number(folio) : undefined, receivedAt, amount: Number(amount), currency, exchangeRate: currency === "USD" && exchangeRate ? Number(exchangeRate) : undefined, concept: concept.trim() || suggested, depositedAt: depositedAt || undefined, }); setIssued(r); setCustomerId(""); setCustomerName(""); setAmount(""); setExchangeRate(""); setConcept(""); setDepositedAt(""); refreshFolio(); onIssued(); } catch (e2) { setError((e2 as Error)?.message ?? "No se pudo emitir el recibo."); } finally { setBusy(false); } } return (

Nuevo recibo

{error &&
{error}
} {issued && (
Folio {issued.folio} emitido a {issued.customer.name} por{" "} {formatMoney(issued.amount, issued.currency)}.{" "} Imprimir
)}
Cliente * { setCustomerId(id); setCustomerName(name); }} />
{currency === "USD" && ( )}
); } function ReceiptRow({ r, canCapture, onChange, }: { r: CashReceipt; canCapture: boolean; onChange: () => void; }) { const [deposit, setDeposit] = useState(""); const voided = r.transaction.voidedAt != null; async function saveDeposit() { await setCashReceiptDeposit(r.id, deposit); onChange(); } return ( {r.folio} {formatDate(r.receivedAt)} {r.customer.folderNumber ?? "—"} {r.customer.name} {formatMoney(r.amount, r.currency)} {r.concept} {r.preparedBy.name} {r.depositedAt ? ( formatDate(r.depositedAt) ) : canCapture && !voided ? ( setDeposit(e.target.value)} /> ) : ( "—" )} {voided ? "Anulado" : Imprimir} ); }