# website_membership_signup_required Módulo in-house (Criptomart) para el cliente **EMES** que replica el flujo de alta de asociados de YourMembership (YM) dentro de Odoo 18. ## Qué hace Intercepta el **"Add to Cart"** del e-commerce **solo para productos de membresía** (`product.template.membership = True`). Si el visitante es anónimo (usuario público) y el ajuste `membership_signup_required` del website está activo (por defecto ON), el request no se completa y se redirige al formulario estándar de `/web/signup` (de `auth_signup`), con un `redirect` de vuelta a la ficha del producto y un marcador efímero `membership_signup=1` que re-habilita el signup de modo condicional aunque el website esté en modo **b2b** (*guest checkout*). Tras crear la cuenta, el usuario queda autenticado y vuelve a la ficha del producto, donde ya puede añadirlo al carrito y continuar al checkout. Para el resto del catálogo (productos no miembros) el flujo **b2b nativo** (*guest checkout*) se mantiene intacto, sin interferencia. ## Por qué es necesario un módulo propio `website_sale` fuerza por defecto `website.auth_signup_uninvited = 'b2c'` (signup/registro dentro del checkout). Pasar el website a **b2b** — lo que permite *guest checkout* para el catálogo general — **deshabilita el route `/web/signup`** para el público general. Para re-abrirlo **solo** durante el flujo de membresía, este módulo extiende con un override conservador de `res.users._get_signup_invitation_scope()` que devuelve `'b2c'` únicamente cuando el request porta el marcador efímero (`membership_signup=1` en la query o `membership_signup_active` en sesión). El marcador se setea al interceptar el `cart_update` y se limpia tras `signup`/`login` exitosos. ## Características / requisitos funcionales - **RF-01** Interceptación de `/shop/cart/update` y `/shop/cart/update_json` solo para `product.template.membership = True` y usuario público. - **RF-02** Redirección a `/web/signup?membership_signup=1&redirect=/shop/`. - **RF-03** Re-habilitación condicional del signup en b2b vía override de `_get_signup_invitation_scope`. - **RF-04** Flujo completo: signup → vuelta a la ficha del producto → add to cart → carrito. - **RF-05** Productos no miembros: sin interceptación, flujo b2b normal. - **RF-06** Usuarios ya autenticados no se interceptan. - **RF-07** Configurable por website desde Ajustes → E-commerce → "Membership signup required" (default ON). - **RF-08** No afecta al backend ni a métodos de pago: la interceptación vive solo en los controllers `/shop/cart/update*`. - **RF-09** Separación `firstname` / `lastname` en el formulario de signup (gracias a la dependencia OCA `partner_firstname`). - **RF-10** El enlace "Already have an account?" del formulario de signup preserva el `redirect`, por lo que un usuario con cuenta existente puede loguearse y volver a la ficha del producto. - **RF-11** i18n: `module.pot` + `es.po` (español dominante) + `gl.po` y `ca.po` como esqueleto. ## Dependencias - `website_sale` - `auth_signup` - `membership` - `partner_firstname` (OCA, `partner-contact/`). ## Configuración 1. Settings → Website → Ajustes e-commerce → "Customer Accounts" debe estar en **Optional (guest checkout)** o lo equivalente a `b2b` en el website activo (esto es `website.account_on_checkout = 'disabled'`). 2. Settings → "Membership signup required" checkbox: activado por defecto. 3. Para productos con `membership = True`, el flujo aplica; para el resto, no. ## Cómo correr los tests ```bash # En el entorno EMES (desde OCB): ./odoo-bin -c emes.conf -i website_membership_signup_required \ --test-tags website_membership_signup_required --stop-after-init ``` Los tests unitarios (TransactionCase) cubren RF-01..RF-08 con lógica; el tour HttpCase end-to-end cubre RF-04 (flujo completo). ## Notas técnicas - El override de `_get_signup_invitation_scope` es el punto más sensible de seguridad: solo devuelve `'b2c'` cuando el request porta el marcador efímero. No re-abre el signup libre en b2b para el catálogo general. - La decisión de interceptar está centralizada en `website._membership_signup_required_for(product_id, is_public)` (capa modelo) para poder testearla sin HTTP y reutilizarla en futuras extensiones. - El controller `_membership_signup_is_required(product_id)` delega en ese helper. - Tras signup / login exitoso, se limpian los flags `membership_signup_active` y `membership_signup_product_id` de la sesión. ## Licencia AGPL-3. ## Autor Criptomart (). ## Limitaciones / notas de seguridad El marcador `membership_signup=1` que habilita el route `/web/signup` en modo b2b es un **query param público**: cualquier visitante que conozca ese param puede acceder al formulario de signup directamente, sin pasar por el carrito de membresía. Esta **decisión está aceptada por diseño**: el signup crea cuentas de tipo *portal* (no internal), y la postura b2b del catálogo general se mantiene — el route solo se reabre de forma condicional vía el override de `_get_signup_invitation_scope` cuando el request porta el marcador efímero (ver `models/res_users.py` y `controllers/website_sale.py`); nunca se reabre el signup libre para el resto del tráfico. Para reforzar la seguridad se podría sustituir el query param estático por un token HMAC firmado (vinculado a la sesión y al producto objetivo), de modo que solo el flujo de `cart_update` pueda generar un redirect válido y el acceso directo a `/web/signup` fuera de ese flujo quede restringido. Ver `requirements.md` §7 R-01.