# v0.1.6

**Fecha de lanzamiento:** 3 de mayo de 2026

## Nuevas Funcionalidades

### Reducciones de Precio Conformes con la UE
- Los productos tienen un nuevo interruptor "Mostrar precio anterior" en el formulario del producto para optar por mostrar una reducción de precio
- El precio tachado en la tienda ahora utiliza el precio más bajo de los últimos 30 días, conforme a la Directiva Omnibus de la UE — y no el precio anterior introducido manualmente
- El formulario del producto avisa cuando ninguna insignia de oferta se mostrará (porque el precio actual no es menor que el mínimo de 30 días), para que los admins sepan que su reducción no aparecerá
- Los cambios de precio se registran automáticamente y un cron semanal elimina el historial con más de 90 días

### Precio por Unidad
- Los productos ahora pueden declarar una cantidad y una unidad (kg, g, L, ml, m, cm, m²), y la tienda muestra una línea "por kg" / "por L" / etc. debajo del precio en las tarjetas, la página del producto, la colección y la búsqueda
- Los productos vendidos por unidad ("por artículo") están exentos del precio por unidad, conforme a lo exigido por la directiva
- El precio por unidad se exporta al feed de Google Merchant como `unit_pricing_measure` y `unit_pricing_base_measure`

### Precios con Impuestos Incluidos en la Tienda
- Todos los precios mostrados en la tienda (tarjetas, página del producto, carrito, checkout, correos de pedido) ahora siempre incluyen el impuesto, conforme a lo exigido para ventas B2C en la UE
- La página del producto muestra un aviso cuando una regla de impuestos tiene anulaciones por país, indicando que el monto final puede recalcularse en el checkout una vez que se conozca el país de facturación del cliente
- El formulario de producto del admin tiene una nueva vista previa de impuestos que muestra cuánto se cobrará a cada país cuando una regla de impuestos tiene anulaciones por país

### Dirección de Facturación en el Checkout
- El flujo de checkout tiene un nuevo paso de dirección de facturación: mantener la dirección de envío (predeterminado), elegir una dirección guardada o introducir una nueva
- Los clientes pueden opcionalmente guardar una dirección de facturación personalizada en su libreta para pedidos futuros
- Cambiar el país de facturación borra el método de pago seleccionado y cualquier Stripe PaymentIntent en curso, para que los impuestos se recalculen para el nuevo país
- Los correos de confirmación de pedido y la página de historial de pedidos del cliente muestran las direcciones de envío y facturación por separado cuando son diferentes
- La vista de pedido del admin muestra envío y facturación lado a lado

## Mejoras

### Dashboard de Analytics
- Las tablas en las páginas de Tráfico, Productos y Carritos Abandonados ahora usan paginación en el servidor, manteniendo las tiendas grandes responsivas
- La interfaz del gráfico de embudo se refinó para una visualización más clara de los puntos de abandono
- Las sesiones de admins autenticados se excluyen del analytics para que los números reflejen la actividad real de los clientes
- Las rutas de página se normalizan para que la misma página lógica no se fragmente en varias filas

### Pedidos
- Nuevo filtro de origen del pedido en la lista de pedidos separa los pedidos de checkout de los pedidos creados manualmente
- La creación de pedidos manuales bloquea correos de placeholder cuando un correo real del cliente está disponible, para que las notificaciones lleguen al buzón correcto

### Onboarding
- El paso de Idiomas ahora preselecciona Inglés, Español, Holandés y Portugués (antes solo Inglés); los admins aún pueden ajustar la lista

### Estabilidad del Checkout
- Locks consultivos y actualizaciones de snapshot serializables protegen las acciones concurrentes de checkout contra condiciones de carrera
- Las fechas en el selector de fechas del checkout y en los correos de pedido ahora respetan el locale del cliente

### Envío y Locale
- Los umbrales mínimos de pedido para envío ahora se comparan con el subtotal + impuesto menos cualquier descuento de mercancía, alineado con lo que el cliente ve en el resumen del pedido (los cupones exclusivos de envío se excluyen del descuento)
- Las páginas de tienda renderizadas en el servidor y los correos de pedido resuelven el idioma mediante la preferencia guardada del cliente, luego la cookie `locale`, luego el primer idioma habilitado por la tienda, con fallback a Inglés — así una cookie obsoleta ya no renderiza un idioma que la tienda haya desactivado

### Operaciones
- Una nueva variable de entorno opcional `ENABLE_AUTH_RATE_LIMIT` permite a los operadores desactivar el rate limit de Better Auth en entornos de CI y pruebas; el comportamiento en producción no cambia a menos que la variable se establezca explícitamente

## Correcciones de Errores

- La fusión de carritos ahora valida el stock para ambos lados y notifica a los clientes cuando se ajustan las cantidades
- Un toast de aviso se muestra dos minutos antes de que expire la reserva del carrito
- Los handlers de webhook de Stripe (éxito y fallo) ahora son idempotentes y usan guards atómicos para evitar el procesamiento duplicado
- Aplicar y quitar cupones se serializa mediante el lock del checkout; los cupones de uso único por cliente ya no cuentan pedidos cancelados o con fallo de pago
- La revalidación de cupones de envío se omite cuando el cliente cambia de país antes de elegir un método de envío
- El cohort del visitante se persiste en el snapshot del pedido para que los eventos de compra del servidor permanezcan en el cohort correcto
- El botón Cancelar del formulario de edición de usuario ahora restablece los campos sin dejarlos marcados como modificados

## Correcciones de Seguridad

- El canal del iframe del page builder ahora está restringido a postMessage del mismo origen
- Las operaciones PATCH y DELETE de dirección ahora están vinculadas a la propiedad a nivel SQL
- Se aplica rate limit por IP en el endpoint del webhook de Stripe
- Las consultas agregadas de analytics ahora usan llamadas parametrizadas a `make_interval()` en lugar de interpolación de cadenas
- La tolerancia de timestamp de `constructEvent` de Stripe está fijada en 300 segundos
- `Order.stripePaymentIntentId` ahora tiene un índice único y el webhook usa `findUnique` por seguridad
