Para qué sirve y para quién
Carrito y pago es una libreta interna de carritos y pedidos. Guarda carritos — de una persona (su email y su nombre) o de una visita anónima, que se distingue por un código de sesión — con sus líneas (producto, cantidad y precio), y los convierte en pedidos con número, direcciones, método de envío y de pago y un estado: Iniciado, Pagado o Fallido, y Completado.
Lo más importante: no es el TPV y nada de lo que hace es fiscal. Un pedido pagado y completado aquí no genera venta, ni factura, ni registro VeriFactu, ni movimiento de caja, ni movimiento de stock, y nada de lo cobrado aquí llega a la AEAT. «Pagado» es un estado que una persona marca después de cobrar por otra vía: la app no cobra, no habla con ninguna pasarela y no pide importe ni justificante. Para cobrar en el mostrador está Ventas.
No tiene tienda ni página pública: solo la usa personal con sesión en el Hub.
Quién la usa:
- El empleado: solo mira los carritos y los pedidos.
- El responsable, el administrador y el propietario: además crean, abandonan y borran carritos, y marcan los pedidos como pagados, completados o fallidos.
- El asistente: hace lo mismo que la persona que le habla y, además, lo que la pantalla no ofrece: añadir y cambiar líneas, vaciar un carrito, iniciar un pedido y caducar los carritos vencidos.
Referencia adoptada
El modelo de carrito y pedido de una tienda en línea (Shopify Checkout, WooCommerce, Odoo eCommerce): carrito con código de sesión, pedido con número correlativo y estados iniciado → pagado o fallido → completado. Se adopta solo el modelo de estados: la tienda pública, la pasarela de pago, los impuestos, el envío y el stock de esas referencias no existen aquí. Sin investigación nueva: es el mismo alcance que el producto actual.
Antes de empezar
- Carrito y pago no viene encendida con ningún tipo de negocio: se enciende desde la tienda de apps. No enciende ninguna otra app ni la necesita: se puede encender y apagar sola.
- No tiene ajustes. La moneda de un carrito nuevo es la del negocio (euros mientras el Hub no guarde otra), salvo que quien lo crea por el asistente o la API diga otra.
- Un carrito puede llevar fecha de caducidad; sin ella no caduca nunca.
Pantallas
Carrito y pago se abre desde el menú y tiene dos pestañas: Carritos y Pedidos. Lo que una persona no puede hacer no aparece, y la app lo rechaza aunque lo intente por la API o por el asistente.
Carritos
Una tabla con las columnas Sesión, Email, Estado (Activo, Abandonado, Convertido, Expirado), Ítems y Total. Buscador «Buscar sesión o email…» (busca en el código de sesión, el email y el nombre), filtros por columna — sesión y email por un trozo del texto, el estado por lista, los ítems y el total «desde / hasta» —, orden por columna, vista de lista o de tarjetas y 50 filas por página. Va ordenada por fecha de alta, el más reciente arriba. El total sale en la moneda del carrito; el filtro de Total se escribe en la moneda del negocio (en euros: «12» son 12,00 €) y compara el importe tal cual, sin convertir monedas.
El botón Nuevo carrito abre el formulario «Nuevo carrito»: Sesión (obligatoria; «Guardar» no se activa sin ella), Email y Nombre. Cada fila ofrece Abandonar (solo un carrito activo) y Borrar (pide confirmación). No hay pantalla de las líneas de un carrito ni para añadirlas: eso lo hace el asistente o la API.
Pedidos
Una tabla con Pedido (su número), Email, Estado (Iniciado, Pagado, Fallido, Completado), Pago (el método de pago, texto) y Total, en la moneda del pedido. Buscador «Buscar pedido o email…», filtros por columna, orden por columna, vista de lista o de tarjetas y 50 filas por página, el más reciente arriba. Un pedido fallido enseña su motivo debajo del estado.
Cada fila ofrece la acción que le toca: Marcar pagado (Iniciado), Completar (Pagado) y Fallar (Iniciado o Pagado), que abre un formulario que pide el motivo. Mientras una acción de fila trabaja, su botón gira y un segundo toque no hace nada. No hay botón para crear un pedido: nacen del flujo CART_CHECKOUT-F08.
Estados de cada pantalla
| Estado | Qué ve la persona |
|---|---|
| Cargando | «Cargando…» mientras llega el primer resultado. |
| Vacío | «Sin carritos.» o «Sin pedidos.»; con búsqueda o filtro, «Sin resultados». |
| Error | El aviso de la tabla con «Reintentar». Un rechazo de un formulario sale dentro del formulario, cada motivo debajo de su campo, sin perder lo tecleado; el de una acción de fila, encima de la tabla. |
| Reconectando | Lo pintado se queda; una franja avisa «Reconectando…». |
| App inactiva | «Esta app no está activa en este negocio». |
| Sin permiso | No aparece lo que no puede hacer; si llega por otra puerta, «No tienes permiso». |
Importes con el formato del idioma de quien mira y en la moneda de cada fila; cantidades sin ceros de adorno («2», «0,5»).
Flujos
| ID | Flujo | Estado |
|---|---|---|
| CART_CHECKOUT-F01 | Crear un carrito | hecho (pytest y Vitest); e2e pendiente |
| CART_CHECKOUT-F02 | Añadir una línea a un carrito | hecho (pytest y Vitest); e2e pendiente (asistente o API; sin pantalla, como hoy) |
| CART_CHECKOUT-F03 | Cambiar la cantidad de una línea o quitarla | hecho (pytest y Vitest); e2e pendiente (asistente o API) |
| CART_CHECKOUT-F04 | Vaciar un carrito | hecho (pytest y Vitest); e2e pendiente (asistente o API) |
| CART_CHECKOUT-F05 | Abandonar un carrito | hecho (pytest y Vitest); e2e pendiente |
| CART_CHECKOUT-F06 | Borrar un carrito | hecho (pytest y Vitest); e2e pendiente |
| CART_CHECKOUT-F07 | Caducar los carritos vencidos | hecho (pytest y Vitest); e2e pendiente (orden y lectura); la tarea sola, con el worker de la fase 2 |
| CART_CHECKOUT-F08 | Iniciar el pedido desde un carrito | hecho (pytest y Vitest); e2e pendiente (asistente o API) |
| CART_CHECKOUT-F09 | Marcar un pedido como pagado | hecho (pytest y Vitest); e2e pendiente |
| CART_CHECKOUT-F10 | Registrar que el pago falló | hecho (pytest y Vitest); e2e pendiente |
| CART_CHECKOUT-F11 | Completar un pedido | hecho (pytest y Vitest); e2e pendiente |
| CART_CHECKOUT-F12 | Consultar carritos, líneas y pedidos | hecho (pytest y Vitest); e2e pendiente |
| CART_CHECKOUT-F13 | Avisar a otras apps de lo que ocurre | hecho (pytest y Vitest); e2e pendiente; el aviso sale al outbox y nadie lo escucha |
| CART_CHECKOUT-F14 | Borrar los datos personales de un cliente | hecho (pytest y Vitest); e2e pendiente (asistente, API o admin) |
CART_CHECKOUT-F01 Crear un carrito
Estado: hecho (pytest y Vitest); e2e pendiente
Actor: responsable, administrador, propietario, asistente
Pantalla: Carritos
Pasos:
1. En Carritos pulsa Nuevo carrito.
2. Escribe la Sesión (el código que distingue la visita, obligatorio) y, si quieres, Email y
Nombre.
3. Pulsa Guardar: el formulario se cierra y el carrito sale en la lista como Activo, sin líneas
y con total cero.
Entra: código de sesión (hasta 128 caracteres), email y nombre, de la persona; el asistente o la API
pueden dar además la moneda (código ISO 4217) y la fecha de caducidad.
Sale: un carrito activo en la moneda dicha o, si no se dice, la del negocio
(cart_checkout.cart.created).
Si falla: dentro del formulario, debajo de su campo: una sesión vacía; una sesión que ya usa otro
carrito no borrado («Ya hay un carrito con esa sesión.»); un email que no es un email; una moneda que no
existe; una caducidad que ya pasó. Lo tecleado se conserva. Sin permiso, se rechaza.
CART_CHECKOUT-F02 Añadir una línea a un carrito
Estado: hecho (pytest y Vitest); e2e pendiente (asistente o API; sin pantalla, como hoy)
Actor: responsable, administrador, propietario, asistente
Pantalla: asistente
Pasos:
1. Pide al asistente añadir un producto a un carrito, diciendo el carrito (su sesión, o el email o el
nombre del cliente), la referencia y el nombre del producto, la cantidad y el precio por unidad.
2. Confirma la tarjeta del asistente.
3. La línea queda guardada y los totales del carrito (ítems e importe) se recalculan.
Entra: el carrito; referencia y nombre del producto (texto libre: no se comprueban contra el
catálogo), SKU y atributos de variante (talla, color…) opcionales, cantidad mayor que cero y como mucho
1 000 000 (hasta tres decimales: «0,5») y precio por unidad en céntimos, que dice quien llama.
Sale: la línea con su total (cantidad × precio, redondeado al céntimo, la mitad hacia arriba), los
totales del carrito y su última actividad (cart_checkout.item.added).
Si falla: carrito que no existe («Ese carrito no existe en este negocio.»); varios carritos para lo
dicho (los nombra y pide cuál); carrito que no está activo, también uno vencido («Este carrito ya no
admite cambios: está Abandonado.»); referencia o nombre vacíos, cantidad cero, negativa o de más de
1 000 000, precio negativo: cada uno en su campo; un total de la línea, o del carrito con ella, por encima
de lo que cabe en un carrito (99 999 999 999,99 €), debajo de la cantidad. No se guarda nada.
CART_CHECKOUT-F03 Cambiar la cantidad de una línea o quitarla
Estado: hecho (pytest y Vitest); e2e pendiente (asistente o API)
Actor: responsable, administrador, propietario, asistente
Pantalla: asistente
Pasos:
1. Pide al asistente cambiar la cantidad de una línea, o quitarla.
2. Confirma la tarjeta.
3. Con cantidad mayor que cero la línea cambia y su total se recalcula con el precio guardado en la
línea; con cantidad cero la línea se quita. También existe la orden «quitar línea», que solo quita.
4. Los totales del carrito se recalculan en la misma operación.
Entra: la línea (su id) y la cantidad; el precio lo lee el Hub de la línea, nunca de quien llama.
Sale: la línea cambiada o quitada, los totales del carrito y su última actividad
(cart_checkout.item.updated, con removed: true cuando la cantidad es cero;
cart_checkout.item.removed sale de «quitar línea»).
Si falla: línea que no existe; carrito que ya no está activo; cantidad negativa o de más de 1 000 000; un
total por encima de lo que cabe en un carrito, debajo de la cantidad. No cambia nada.
CART_CHECKOUT-F04 Vaciar un carrito
Estado: hecho (pytest y Vitest); e2e pendiente (asistente o API)
Actor: responsable, administrador, propietario, asistente
Pantalla: asistente
Pasos:
1. Pide al asistente vaciar un carrito.
2. Confirma la tarjeta.
3. Se quitan todas sus líneas y los totales vuelven a cero; el carrito sigue Activo.
Entra: el carrito.
Sale: el carrito sin líneas, totales a cero (cart_checkout.cart.cleared). Vaciar uno que ya estaba
vacío no cambia nada ni avisa.
Si falla: carrito que no existe o que no está activo. No cambia nada.
CART_CHECKOUT-F05 Abandonar un carrito
Estado: hecho (pytest y Vitest); e2e pendiente
Actor: responsable, administrador, propietario, asistente
Pantalla: Carritos
Pasos:
1. En Carritos, en la fila de un carrito activo, pulsa Abandonar.
2. El carrito pasa a Abandonado y conserva sus líneas. El asistente puede dar un motivo, que queda
en el carrito.
Entra: el carrito y un motivo opcional (hasta 500 caracteres).
Sale: estado Abandonado (cart_checkout.cart.abandoned).
Si falla: solo se abandona un carrito activo; en otro estado se rechaza diciendo en cuál está, encima de
la tabla, y no cambia nada.
CART_CHECKOUT-F06 Borrar un carrito
Estado: hecho (pytest y Vitest); e2e pendiente
Actor: responsable, administrador, propietario, asistente
Pantalla: Carritos
Pasos:
1. En Carritos, en la fila, pulsa Borrar.
2. Confirma «¿Borrar el carrito …?» con Borrar; Cancelar lo deja como estaba.
3. El carrito desaparece de la lista. El asistente pide confirmación reforzada (acción peligrosa).
Entra: el carrito.
Sale: el carrito borrado en cualquier estado, también Convertido (cart_checkout.cart.deleted). Sus
pedidos siguen existiendo y su sesión queda libre para otro carrito.
Si falla: un carrito que no existe (o ya borrado) se rechaza; no cambia nada.
CART_CHECKOUT-F07 Caducar los carritos vencidos
Estado: hecho (pytest y Vitest); e2e pendiente (orden y lectura); la tarea sola, con el worker de la fase 2
Actor: sistema (tarea programada), administrador, asistente
Pantalla: Carritos (el estado)
Pasos:
1. Un carrito activo cuya fecha de caducidad ya pasó se lee como Expirado en la lista, en su
detalle y en el filtro por estado, y ya no admite cambios ni pedidos.
2. Cada quince minutos, la tarea de la app lo deja guardado como Expirado. También se puede pedir al
asistente o a la API «caducar los carritos vencidos».
Entra: nada; la hora la pone el Hub.
Sale: los carritos vencidos pasan a Expirado y la orden dice cuántos
(cart_checkout.cart.expired, con el número; sin ninguno no avisa).
Si falla: sin carritos vencidos contesta «0» y no avisa. Un carrito sin caducidad no caduca nunca. Con la
lista ya abierta, un carrito que vence en ese momento sigue pintado «Activo» hasta que la lista se vuelve a
leer: con el siguiente cambio, al volver a abrirla o con la relectura de seguridad del Hub (cada 5
minutos); hasta la tarea de la fase 2 no hay aviso que la refresque en el instante.
CART_CHECKOUT-F08 Iniciar el pedido desde un carrito
Estado: hecho (pytest y Vitest); e2e pendiente (asistente o API)
Actor: responsable, administrador, propietario, asistente
Pantalla: asistente
Pasos:
1. Pide al asistente iniciar el pedido de un carrito y, si quieres, el email del cliente, las
direcciones de envío y de facturación, el método de envío, el método de pago y unas notas.
2. Confirma la tarjeta.
3. Aparece en Pedidos un pedido Iniciado con número OS-AAAAMMDD-NNNN (correlativo por día
del negocio, sin huecos) y el total del carrito copiado en ese momento, en su moneda.
Entra: el carrito; el email del cliente (sin él, el del carrito); direcciones (si falta la de
facturación se copia la de envío); métodos de envío y de pago (texto) y notas.
Sale: un pedido Iniciado con el total del carrito en ese instante (cart_checkout.checkout.initiated).
El carrito sigue activo: se pueden cambiar sus líneas e iniciar otro pedido; el total del pedido ya no
cambia.
Si falla: carrito que no existe, que no está activo (también vencido) o sin líneas; email que falta o
que no es un email. No se crea pedido, no se gasta número y no avisa.
CART_CHECKOUT-F09 Marcar un pedido como pagado
Estado: hecho (pytest y Vitest); e2e pendiente
Actor: responsable, administrador, propietario, asistente
Pantalla: Pedidos
Pasos:
1. Cobra al cliente fuera de esta app.
2. En Pedidos, en la fila de un pedido Iniciado, pulsa Marcar pagado.
3. El pedido pasa a Pagado con la hora del pago.
Entra: el pedido (por su número o su id).
Sale: estado Pagado y hora del pago (cart_checkout.order.paid). No se cobra ni se pide importe, medio
ni justificante, y no se crea venta, factura, registro fiscal, movimiento de caja ni de stock. El
asistente pide confirmación reforzada.
Si falla: solo se marca un pedido Iniciado; en otro estado se rechaza diciendo en cuál está. El
empleado no puede.
CART_CHECKOUT-F10 Registrar que el pago falló
Estado: hecho (pytest y Vitest); e2e pendiente
Actor: responsable, administrador, propietario, asistente
Pantalla: Pedidos
Pasos:
1. En Pedidos, en la fila de un pedido Iniciado o Pagado, pulsa Fallar.
2. Escribe el motivo (obligatorio; «Fallar pedido» no se activa sin él) y pulsa Fallar pedido;
Mantener lo deja como estaba.
3. El pedido pasa a Fallido y enseña su motivo.
Entra: el pedido y el motivo (hasta 500 caracteres).
Sale: estado Fallido, el motivo y la hora (cart_checkout.order.failed). Un pedido ya Pagado también
puede fallar, como hoy: no deja rastro de devolución (no hay devoluciones en esta app). El carrito
sigue como estaba. El asistente pide confirmación reforzada.
Si falla: un pedido Completado o ya Fallido se rechaza; un motivo vacío, debajo de su campo, sin perder
lo escrito.
CART_CHECKOUT-F11 Completar un pedido
Estado: hecho (pytest y Vitest); e2e pendiente
Actor: responsable, administrador, propietario, asistente
Pantalla: Pedidos
Pasos:
1. En Pedidos, en la fila de un pedido Pagado, pulsa Completar.
2. El pedido pasa a Completado y su carrito, a Convertido, en la misma operación.
Entra: el pedido.
Sale: pedido Completado con su hora y carrito Convertido (cart_checkout.order.completed). Nada
fiscal ni de stock. Si el carrito ya estaba borrado, el pedido se completa igual.
Si falla: solo se completa un pedido Pagado; en otro estado se rechaza diciendo en cuál está.
CART_CHECKOUT-F12 Consultar carritos, líneas y pedidos
Estado: hecho (pytest y Vitest); e2e pendiente Actor: empleado, responsable, administrador, propietario, asistente Pantalla: Carritos, Pedidos Pasos: 1. Abre Carritos o Pedidos. 2. Busca por sesión, email o nombre (carritos) o por número de pedido o email (pedidos), filtra por columna y ordena. 3. Las líneas de un carrito y el detalle de un pedido (direcciones, métodos, fechas, notas) se leen con el asistente o la API: un carrito por su sesión o por el email o el nombre de su cliente, un pedido por su número. Entra: permiso «ver carritos» (el empleado lo tiene). Sale: listas de 50 filas (hasta 100 por petición). Cantidades con sus decimales, importes en céntimos con la moneda de la fila. Si falla: error de carga con «Reintentar»; sin permiso, «No tienes permiso».
CART_CHECKOUT-F13 Avisar a otras apps de lo que ocurre
Estado: hecho (pytest y Vitest); e2e pendiente; el aviso sale al outbox y nadie lo escucha
Actor: sistema
Pantalla: ninguna
Pasos:
1. Cada cambio de los flujos anteriores deja un aviso en el outbox del Hub en su misma transacción.
2. Las dos pantallas se actualizan solas con lo que cambió.
Entra: el cambio que lo provoca.
Sale: trece avisos: los doce del producto actual (cart.created, cart.abandoned, cart.deleted,
cart.cleared, cart.expired, item.added, item.updated, item.removed, checkout.initiated,
order.paid, order.failed, order.completed) y customer.erased, con el prefijo cart_checkout..
Ninguna app del Hub los escucha.
Si falla: un cambio rechazado no deja ni el cambio ni el aviso.
CART_CHECKOUT-F14 Borrar los datos personales de un cliente
Estado: hecho (pytest y Vitest); e2e pendiente (asistente, API o admin)
Actor: responsable, administrador, propietario, asistente; el personal de la plataforma desde el admin
Pantalla: asistente; admin del Hub (acción «Borrar los datos personales de sus clientes»)
Pasos:
1. Un cliente pide que se borren sus datos (derecho de supresión, RGPD).
2. Pide al asistente borrar los datos del cliente con su email exacto, o el personal de la plataforma
marca sus carritos o pedidos en el admin y lanza la acción.
3. Confirma la tarjeta (acción peligrosa: no se deshace).
Entra: el email exacto del cliente (sin distinguir mayúsculas).
Sale: todos los carritos con ese email, también los borrados, se quedan sin email, nombre, notas ni
motivo de abandono; todos los pedidos con ese email o de esos carritos, sin email, direcciones, notas ni
motivo de fallo. Número, importes, estados y fechas se quedan. Dice cuántos carritos y pedidos cambió
(cart_checkout.customer.erased, con los dos números y nunca el email; si no había nada, no avisa).
Si falla: un email vacío o que no es un email, en su campo. Sin permiso «gestionar carritos», se rechaza.
En el admin, sin hub elegido no hace nada y lo dice.
Carrito y pago: lo que entra y lo que sale
Lo que ofrece: consultas (leen, no cambian nada)
| Operación | Qué da |
|---|---|
cart_checkout.cart.list |
Carritos, con búsqueda, filtros, orden y páginas de hasta 100. |
cart_checkout.cart.get |
Un carrito con sus líneas, por id, sesión o cliente (email o nombre). |
cart_checkout.item.list |
Las líneas de un carrito, con búsqueda, filtros y páginas. |
cart_checkout.item.get |
Una línea por su id. |
cart_checkout.order.list |
Pedidos, con búsqueda, filtros, orden y páginas de hasta 100. |
cart_checkout.order.get |
Un pedido con todo (direcciones, métodos, fechas, notas), por id o número. |
Lo que ofrece: acciones (cambian datos)
| Operación | Qué hace | Permiso |
|---|---|---|
cart_checkout.cart.create |
Crea un carrito | gestionar carritos |
cart_checkout.cart.mark_abandoned |
Abandona un carrito activo | gestionar carritos |
cart_checkout.cart.delete |
Borra un carrito (peligrosa) | gestionar carritos |
cart_checkout.cart.clear |
Vacía un carrito activo | gestionar carritos |
cart_checkout.cart.cleanup_expired |
Caduca los carritos vencidos (también la tarea) | gestionar carritos |
cart_checkout.item.add |
Añade una línea | gestionar carritos |
cart_checkout.item.update |
Cambia la cantidad (0 la quita) | gestionar carritos |
cart_checkout.item.remove |
Quita una línea | gestionar carritos |
cart_checkout.checkout.initiate |
Inicia un pedido desde un carrito | cobrar |
cart_checkout.order.mark_paid |
Marca un pedido pagado (peligrosa) | cobrar |
cart_checkout.order.fail |
Marca un pedido fallido con su motivo (peligrosa) | cobrar |
cart_checkout.order.complete |
Completa un pedido pagado y convierte su carrito | cobrar |
cart_checkout.customer.erase |
Borra los datos personales de un cliente por su email (peligrosa) | gestionar carritos |
Lo que avisa
Los trece avisos de CART_CHECKOUT-F13. Solo salen cuando el cambio ocurrió.
A qué reacciona y lo que necesita de otras apps
Nada. No lee ni escribe en otras apps: producto, referencia, SKU y precio son texto y números libres, sin vínculo con Inventario, y el cliente es un email y un nombre sin vínculo con Clientes.
Quién puede qué
| Permiso | Qué deja hacer | Quién lo tiene |
|---|---|---|
cart_checkout.view_cart |
Ver carritos, líneas y pedidos | todos, también el empleado |
cart_checkout.manage_cart |
Crear, abandonar, borrar, vaciar y caducar carritos; añadir, cambiar y quitar líneas; borrar los datos personales de un cliente | responsable, administrador, propietario |
cart_checkout.checkout |
Iniciar pedidos y marcarlos pagados, fallidos o completados | responsable, administrador, propietario |