// Checkout.jsx — full 3-step checkout flow.
// Steps: dirección → pago → confirmar → (CheckoutDone in Cart.jsx)
const { useState: useChkState } = React;
// =====================================================================
// Saved addresses + cards (reused from Account.jsx fakes; could be hoisted).
const CHK_ADDRESSES = [
{ id: "a1", label: "Casa", name: "Bau Andalón", street: "Av. Francisco Villa 1180, Int. 4", area: "Versalles, Puerto Vallarta", state: "Jalisco", zip: "48330", phone: "+52 322 145 2287", default: true },
{ id: "a2", label: "Oficina", name: "Bau Andalón", street: "Calle Honduras 156", area: "5 de Diciembre, Puerto Vallarta", state: "Jalisco", zip: "48350", phone: "+52 322 145 2287" },
];
const CHK_CARDS = [
{ id: "c1", brand: "VISA", last4: "4242", exp: "08/28", default: true, gradient: "linear-gradient(135deg, #1F3A93 0%, #0A1740 100%)" },
{ id: "c2", brand: "MASTERCARD", last4: "5588", exp: "11/27", default: false, gradient: "linear-gradient(135deg, #18181B 0%, #3A3A40 100%)" },
];
const CheckoutPage = ({ cart, user, onNav, onComplete }) => {
const [step, setStep] = useChkState(0);
const { isMobile } = useViewport();
const [addressId, setAddressId] = useChkState(CHK_ADDRESSES.find(a => a.default).id);
const [showNewAddr, setShowNewAddr] = useChkState(false);
const [newAddr, setNewAddr] = useChkState({ label: "Casa", name: user?.name || "", street: "", area: "", zip: "", phone: "" });
const [savedAddresses, setSavedAddresses] = useChkState(CHK_ADDRESSES);
const [paymentType, setPaymentType] = useChkState("card"); // 'card' | 'newCard' | 'oxxo' | 'paypal'
const [cardId, setCardId] = useChkState(CHK_CARDS.find(c => c.default).id);
const [msi, setMsi] = useChkState(1);
const [newCard, setNewCard] = useChkState({ number: "", titular: "", exp: "", cvv: "", saveCard: true });
const [processing, setProcessing] = useChkState(false);
const toast = useToast();
const subtotal = cart.reduce((s, it) => s + it.p.price * it.qty, 0);
const savings = cart.reduce((s, it) => s + ((it.p.was || it.p.price) - it.p.price) * it.qty, 0);
const shipping = subtotal >= 599 ? 0 : 89;
const total = subtotal + shipping;
const selectedAddress = savedAddresses.find(a => a.id === addressId);
const selectedCard = CHK_CARDS.find(c => c.id === cardId);
// Demo mode: all steps pass validation
const canNext = true;
const next = () => {
if (!canNext) return;
if (step < 2) setStep(s => s + 1);
else pay();
};
const pay = () => {
setProcessing(true);
setTimeout(() => {
setProcessing(false);
const orderNum = "AL-" + Math.floor(Math.random() * 900000 + 100000);
onComplete({ orderNum, address: selectedAddress, payment: { type: paymentType, card: selectedCard, msi }, total, items: cart });
}, 1600);
};
// Empty cart guard removed in demo mode — user can walk checkout with empty cart too
if (false && cart.length === 0 && !processing) {
return (
Tu carrito está vacío
Agrega productos antes de continuar al pago.
onNav("home")} style={{ marginTop: 20, alignSelf: "center" }}>Seguir comprando
);
}
const addNewAddr = () => {
// Demo mode: accept any address
const id = "a" + (savedAddresses.length + 1);
const added = { id, ...newAddr, street: newAddr.street || "Nueva calle 123", zip: newAddr.zip || "48330", city: "Puerto Vallarta", state: "Jalisco" };
setSavedAddresses([...savedAddresses, added]);
setAddressId(id);
setShowNewAddr(false);
toast("Dirección agregada");
};
return (
{/* Stepper */}
{["Dirección", "Pago", "Confirmar"].map((label, i) => (
{i < step ? "✓" : i + 1}
{label}
))}
{/* LEFT — step content */}
{step === 0 && (
<>
Dirección de entrega
Elige a dónde te llega el pedido.
{savedAddresses.map(a => (
setAddressId(a.id)} />
{a.label}
{a.default && Predeterminada }
{a.name}
{a.street}
{a.area} · CP {a.zip}
{a.phone}
{ e.preventDefault(); toast("Editar dirección"); }}
style={{ background: "transparent", border: 0, color: "var(--primary)", font: "700 12px var(--font-sans)", cursor: "pointer", alignSelf: "center" }}>
Editar
))}
{!showNewAddr ? (
setShowNewAddr(true)}
style={{
padding: 18, background: "transparent", border: "2px dashed var(--border-strong)",
borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", justifyContent: "center", gap: 8,
color: "var(--fg-muted)", font: "600 14px var(--font-sans)", cursor: "pointer",
}}>
Agregar nueva dirección
) : (
Nueva dirección
setShowNewAddr(false)} style={{ background: "transparent", border: 0, cursor: "pointer", color: "var(--fg-muted)", padding: 4, display: "grid", placeItems: "center" }}>
setNewAddr({ ...newAddr, label: e.target.value })}
style={{ ...inputStyle, width: "100%", appearance: "none", paddingRight: 36, backgroundImage: "url(\"data:image/svg+xml;utf8, \")", backgroundRepeat: "no-repeat", backgroundPosition: "right 14px center" }}>
Casa Oficina Otra
setNewAddr({ ...newAddr, name: e.target.value })} />
setNewAddr({ ...newAddr, street: e.target.value })} />
setNewAddr({ ...newAddr, area: e.target.value })} />
setNewAddr({ ...newAddr, zip: e.target.value })} maxLength={5} />
setNewAddr({ ...newAddr, phone: e.target.value })} />
Guardar dirección
setShowNewAddr(false)} style={{ background: "transparent", border: 0, color: "var(--fg-muted)", font: "600 13px var(--font-sans)", cursor: "pointer", padding: "0 16px" }}>Cancelar
)}
Llega el viernes 5 de marzo entre 10:00 y 18:00 a {selectedAddress?.area}.
>
)}
{step === 1 && (
<>
Método de pago
Tus datos están cifrados. Nunca guardamos el CVV.
{/* Method tabs */}
{[
{ id: "card", label: "Tarjeta guardada", icon: "credit_card" },
{ id: "newCard",label: "Tarjeta nueva", icon: "add" },
{ id: "oxxo", label: "OXXO efectivo", icon: "shopping_basket" },
].map(t => (
setPaymentType(t.id)}
style={{
padding: "10px 12px", borderRadius: "9999px",
background: paymentType === t.id ? "var(--bg-elev-1)" : "transparent",
color: paymentType === t.id ? "var(--fg-strong)" : "var(--fg-muted)",
border: 0, font: paymentType === t.id ? "700 12px var(--font-sans)" : "500 12px var(--font-sans)",
cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: 6,
boxShadow: paymentType === t.id ? "var(--elev-1)" : "none",
transition: "all var(--dur-fast) var(--ease-out-quart)",
}}>
{t.label}
))}
{paymentType === "card" && (
{CHK_CARDS.map(c => (
setCardId(c.id)} />
{c.brand.slice(0,4)}
•••• {c.last4}
Vence {c.exp}
{c.brand}
))}
{/* MSI selector */}
Meses sin intereses
Solo en bancos participantes
{[1, 3, 6, 9, 12].map(n => (
setMsi(n)}
style={{
padding: "10px 6px", borderRadius: "var(--radius-sm)",
background: msi === n ? "var(--primary)" : "var(--bg-elev-1)",
color: msi === n ? "#fff" : "var(--fg-strong)",
border: msi === n ? 0 : "1px solid var(--border)",
cursor: "pointer", textAlign: "center",
}}>
{n === 1 ? "Único" : `${n} MSI`}
{n > 1 && ${Math.round(total / n).toLocaleString("es-MX")}/mes
}
))}
)}
{paymentType === "newCard" && (
setNewCard({ ...newCard, number: e.target.value.replace(/\D/g, "").slice(0, 16).replace(/(\d{4})(?=\d)/g, "$1 ").trim() })}
placeholder="4242 4242 4242 4242"
maxLength={19}
style={{ width: "100%", paddingRight: 56, fontFamily: "var(--font-mono)", letterSpacing: ".05em" }}
autoComplete="cc-number" inputMode="numeric"
/>
{detectCardBrand(newCard.number) || "TARJETA"}
setNewCard({ ...newCard, titular: e.target.value })} placeholder="BAU ANDALON" autoComplete="cc-name" />
{ let v = e.target.value.replace(/\D/g, "").slice(0, 4); if (v.length > 2) v = v.slice(0, 2) + "/" + v.slice(2); setNewCard({ ...newCard, exp: v }); }}
placeholder="MM/AA" maxLength={5} autoComplete="cc-exp" inputMode="numeric"
style={{ fontFamily: "var(--font-mono)", letterSpacing: ".05em" }}
/>
setNewCard({ ...newCard, cvv: e.target.value.replace(/\D/g, "").slice(0, 4) })}
placeholder="•••" maxLength={4} autoComplete="cc-csc" inputMode="numeric"
style={{ fontFamily: "var(--font-mono)", letterSpacing: ".05em" }}
/>
setNewCard({ ...newCard, saveCard: e.target.checked })} style={{ accentColor: "var(--primary)", width: 16, height: 16 }} />
Guardar tarjeta para próximas compras
)}
{paymentType === "oxxo" && (
OXXO
Al confirmar generamos una ficha de pago con código de barras. Tienes 48 horas para pagar en cualquier OXXO. Tu pedido empieza a procesarse cuando confirmamos el pago (en 1-2 horas).
Comisión de OXXO incluida en el total: $12 MXN
)}
>
)}
{step === 2 && (
<>
Revisa tu pedido
Está todo listo. Confirma para pagar.
setStep(0)}>
{selectedAddress.name}
{selectedAddress.street}
{selectedAddress.area} · CP {selectedAddress.zip}
setStep(1)}>
{paymentType === "card" && (
<>
{selectedCard.brand}
•••• {selectedCard.last4}
{msi > 1 && {msi} meses sin intereses · ${Math.round(total / msi).toLocaleString("es-MX")}/mes
}
>
)}
{paymentType === "newCard" && (
•••• {newCard.number.slice(-4)}
{newCard.saveCard ? "(se guardará)" : ""}
)}
{paymentType === "oxxo" && (
OXXO efectivo · ficha al confirmar
)}
{cart.map(it => (
{it.p.name}
Cantidad: {it.qty}
${(it.p.price * it.qty).toLocaleString("es-MX")}
))}
>
)}
{/* Footer buttons */}
step === 0 ? onNav("cart") : setStep(s => s - 1)}
style={{ background: "transparent", color: "var(--fg-strong)", border: 0, font: "600 13px var(--font-sans)", cursor: "pointer", padding: "10px 0" }}>
← {step === 0 ? "Volver al carrito" : "Atrás"}
{processing ? (
Procesando…
) : step === 2 ? `Pagar $${total.toLocaleString("es-MX")}` : "Continuar"}
{/* RIGHT — sticky summary */}
);
};
// =====================================================================
// Helpers
const Radio = ({ checked, onChange }) => (
);
const ReviewBlock = ({ title, icon, onEdit, children }) => (
);
const SumLine = ({ k, v, color }) => (
{k}
{v}
);
const Spinner = () => (
);
const detectCardBrand = (num) => {
const n = num.replace(/\s/g, "");
if (/^4/.test(n)) return "VISA";
if (/^5[1-5]/.test(n) || /^2[2-7]/.test(n)) return "MASTERCARD";
if (/^3[47]/.test(n)) return "AMEX";
if (/^6/.test(n)) return "DISCOVER";
return null;
};
window.CheckoutPage = CheckoutPage;