El carrito era un conjunto: añadir dos veces el mismo ítem no hacía nada y el
botón se quedaba en "Añadido". Ahora cada línea lleva sus copias, editables con
un <input number> en la columna Cant, y volver a pulsar Añadir suma una (mismo
patrón que el carrito de send-gift, que ya lo hacía así).
Ojo con los dos "cantidad" que conviven, que no son lo mismo:
`home_store_item.quantity` es el LOTE que entrega cada copia (Paño de lino = 20)
y `copies` es cuántas veces se compra la línea. Por eso la fila enseña "3 × 20"
y el total de la columna Cant sigue siendo unidades (60), como en el original.
Cada copia se manda al SOAP como una entrada propia (`2589:20 2589:20`), que es
justo lo que ya sabía trocear sendStoreItems (12 por correo) y parsear
fulfillStoreOrder: el formato del pedido de tarjeta no cambia.
El servidor no se fía del cliente: priceStoreCart recibe {id, copies}, valida las
copias (1..MAX_COPIES, como el MAX_QTY de send-gift), rechaza el carrito si algún
id no existe y recalcula los totales como precio*copias contra la BD.
Verificado contra la API con saldo de prueba, no solo en la interfaz: con 500 PD
y un ítem de 200, 2 copias (400) pasan el cobro y 3 (600) dan insufficientPd. Si
el servidor ignorase las copias ambas darían lo mismo, así que el corte exacto
entre 2 y 3 prueba que multiplica. Comprobado también que el reembolso del
deliveryFailed devuelve los 400 PD, y que copias=0 o 101 se rechazan.
El CSS del input va con `#cart-list` por especificidad, no por gusto: el tema
define `input[type=number] { width: 290px }` y se carga el último para ganar la
cascada, así que un `.store-copies` a secas perdía y el input salía de 290px
reventando la tabla (send-gift se libra porque usa estilo en línea).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El carrito enseñaba PD/PV aunque eligieras tarjeta, y por defecto ya presuponía
saldo. Ahora se muestran SIEMPRE las dos monedas y se apaga la que no se va a
cobrar con el método elegido, así que no presupone nada ni se contradice.
Los euros por línea van con hasta 3 decimales a propósito: un ítem en PV con
precio impar cuesta medio céntimo (75 PV = 0,375 €), y redondear cada línea a 2
haría que las líneas no sumaran el total (0,38 + 0,38 = 0,76 contra un total de
0,75). El total sí va a céntimos: es el importe que se cobra de verdad. Y con
mínimo 2 decimales, que es dinero: "1,50 €", no "1,5 €". El formateo va por
Intl con el locale de la web, así que en español sale con coma; antes el
selector interpolaba el número crudo y decía "0.75 €" con punto.
Mínimos de las pasarelas: SumUp ya estaba en 1 € y Stripe estaba en 0,50 €,
ahora también 1 €. Estaban sueltos en la ruta; se centralizan en
`lib/store-pricing`, un módulo PURO que importan tanto la ruta como el
componente, para que el carrito enseñe exactamente lo que se valida y se cobra.
`storeEuroTotal` pasa a vivir ahí (una sola implementación) y lib/store lo
envuelve con un guard: si alguien cambia PD_PER_UNIT o VP_PRICE_FACTOR sin tocar
store-pricing, revienta al primer uso en vez de cobrar mal en silencio (esos
módulos tocan BD y no pueden llegar al bundle del cliente, de ahí la
duplicación, igual que en PaymentMethodSelect).
Por debajo del mínimo, la opción de tarjeta se desactiva y dice "Mínimo 1,00 €"
en vez de dejarte pulsar Enviar para que la API la rechace. El método efectivo
se deriva en el render (si el carrito baja del mínimo con tarjeta ya elegida,
cae al saldo) en vez de sincronizarlo con un setState en un efecto, que
provocaría renders en cascada.
Verificado en el navegador: con 0,375 € las dos tarjetas salen desactivadas y
elegido el saldo; con 1,50 € se habilitan. Las dos líneas de 0,375 € suman el
total de 0,75 €.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>