@lit-roa/resumen-pedido 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/resumen-pedido.d.ts +54 -0
- package/dist/resumen-pedido.d.ts.map +1 -0
- package/dist/resumen-pedido.js +309 -0
- package/dist/resumen-pedido.js.map +1 -0
- package/package.json +31 -4
- package/src/index.ts +1 -0
- package/src/resumen-pedido.ts +290 -0
package/README.md
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lit-roa/resumen-pedido
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Resumen de pedido: subtotal, descuento, envío (o "Gratis", o "Se calcula en
|
|
4
|
+
el siguiente paso"), cargos extra y total, con campo de cupón opcional.
|
|
5
|
+
No calcula precios: los montos vienen del sitio o de la API. El total se
|
|
6
|
+
calcula solo si no lo indicas.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @lit-roa/resumen-pedido
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<lr-resumen-pedido subtotal="527" piezas="3" envio="0" con-cupon>
|
|
14
|
+
<lr-boton slot="acciones" bloque>Ir a pagar</lr-boton>
|
|
15
|
+
<span slot="nota">Precios con IVA incluido.</span>
|
|
16
|
+
</lr-resumen-pedido>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Cupones
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
resumen.addEventListener('aplicar-cupon', async (e) => {
|
|
23
|
+
resumen.cuponCargando = true;
|
|
24
|
+
const r = await api.validarCupon(e.detail.codigo);
|
|
25
|
+
resumen.cuponCargando = false;
|
|
26
|
+
if (r.ok) {
|
|
27
|
+
resumen.cuponAplicado = e.detail.codigo;
|
|
28
|
+
resumen.descuento = r.descuento;
|
|
29
|
+
} else {
|
|
30
|
+
resumen.errorCupon = 'Ese código no existe o ya venció.';
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
resumen.addEventListener('quitar-cupon', () => { resumen.cuponAplicado = ''; resumen.descuento = 0; });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Propiedades
|
|
37
|
+
|
|
38
|
+
| Atributo | Tipo | Por omisión |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `titulo` | `string` (vacío para ocultarlo) | `'Resumen del pedido'` |
|
|
41
|
+
| `subtotal` | `number` | `0` |
|
|
42
|
+
| `piezas` | `number` (muestra "Subtotal (3 productos)") | — |
|
|
43
|
+
| `envio` | `number` (`0` = Gratis; sin valor = pendiente) | — |
|
|
44
|
+
| `descuento` | `number` positivo | `0` |
|
|
45
|
+
| `cargos` | `{ concepto, monto }[]` (JSON en atributo) | `[]` |
|
|
46
|
+
| `total` | `number` | calculado |
|
|
47
|
+
| `moneda` / `mostrar-moneda` | `string` / `boolean` | `'MXN'` / `false` |
|
|
48
|
+
| `con-cupon` | `boolean` | `false` |
|
|
49
|
+
| `cupon-aplicado` / `error-cupon` | `string` | `''` |
|
|
50
|
+
| `cupon-cargando` | `boolean` | `false` |
|
|
51
|
+
| `sin-borde` | `boolean` (sin caja, para meterlo en otro panel) | `false` |
|
|
52
|
+
|
|
53
|
+
## Eventos
|
|
54
|
+
|
|
55
|
+
| Evento | `detail` |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `aplicar-cupon` | `{ codigo }` (en mayúsculas) |
|
|
58
|
+
| `quitar-cupon` | `{ codigo }` |
|
|
59
|
+
|
|
60
|
+
Slots: por omisión (arriba de los montos), `acciones` y `nota`.
|
|
61
|
+
Estilos: `--lr-resumen-fondo`, `--lr-resumen-relleno`, `--lr-color-exito`, `::part(resumen)`.
|
|
62
|
+
|
|
63
|
+
## Desarrollo
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm run dev -w @lit-roa/resumen-pedido
|
|
67
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAsC,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
export interface DetalleCupon {
|
|
3
|
+
codigo: string;
|
|
4
|
+
}
|
|
5
|
+
export interface CargoExtra {
|
|
6
|
+
concepto: string;
|
|
7
|
+
monto: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resumen de pedido: subtotal, envío, descuento, cargos extra y total, con
|
|
11
|
+
* un campo opcional para cupones. No calcula precios por su cuenta salvo el
|
|
12
|
+
* total si no se lo das: el sitio (o la API) manda los montos.
|
|
13
|
+
*
|
|
14
|
+
* @fires aplicar-cupon - `detail`: `{ codigo }`. Mientras validas, pon `cupon-cargando`.
|
|
15
|
+
* @fires quitar-cupon - `detail`: `{ codigo }`.
|
|
16
|
+
* @slot - Arriba de los montos (ej. líneas del carrito en modo solo lectura).
|
|
17
|
+
* @slot acciones - Debajo del total (ej. botón "Pagar").
|
|
18
|
+
* @slot nota - Texto pequeño al final (ej. "Impuestos incluidos").
|
|
19
|
+
*/
|
|
20
|
+
export declare class LrResumenPedido extends LitElement {
|
|
21
|
+
static styles: import("lit").CSSResult[];
|
|
22
|
+
titulo: string;
|
|
23
|
+
subtotal: number;
|
|
24
|
+
/** Costo de envío. `0` = "Gratis"; sin valor = "Se calcula en el siguiente paso". */
|
|
25
|
+
envio?: number;
|
|
26
|
+
/** Descuento como número positivo; se muestra restando. */
|
|
27
|
+
descuento: number;
|
|
28
|
+
/** Cargos extra (ej. impuestos, propina). Como atributo, en JSON. */
|
|
29
|
+
cargos: CargoExtra[];
|
|
30
|
+
/** Total. Si no se indica: subtotal + envío + cargos − descuento. */
|
|
31
|
+
total?: number;
|
|
32
|
+
moneda: string;
|
|
33
|
+
/** Muestra el código de moneda junto al total. */
|
|
34
|
+
mostrarMoneda: boolean;
|
|
35
|
+
/** Muestra el campo de cupón. */
|
|
36
|
+
conCupon: boolean;
|
|
37
|
+
/** Código ya aplicado: se muestra como etiqueta con botón para quitarlo. */
|
|
38
|
+
cuponAplicado: string;
|
|
39
|
+
errorCupon: string;
|
|
40
|
+
cuponCargando: boolean;
|
|
41
|
+
piezas?: number;
|
|
42
|
+
private codigo;
|
|
43
|
+
private conAcciones;
|
|
44
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
45
|
+
private renderCupon;
|
|
46
|
+
private aplicarCupon;
|
|
47
|
+
private quitarCupon;
|
|
48
|
+
}
|
|
49
|
+
declare global {
|
|
50
|
+
interface HTMLElementTagNameMap {
|
|
51
|
+
'lr-resumen-pedido': LrResumenPedido;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=resumen-pedido.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resumen-pedido.d.ts","sourceRoot":"","sources":["../src/resumen-pedido.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAIrD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;GAUG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAC7C,OAAgB,MAAM,4BAmJpB;IAEU,MAAM,SAAwB;IAEd,QAAQ,SAAK;IAEzC,qFAAqF;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IAE3C,2DAA2D;IAC/B,SAAS,SAAK;IAE1C,qEAAqE;IAC1C,MAAM,EAAE,UAAU,EAAE,CAAM;IAErD,qEAAqE;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IAE/B,MAAM,SAAS;IAE3B,kDAAkD;IACQ,aAAa,UAAS;IAEhF,iCAAiC;IACoB,QAAQ,UAAS;IAEtE,4EAA4E;IACjC,aAAa,SAAM;IAEtB,UAAU,SAAM;IAEE,aAAa,UAAS;IAE/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAExD,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,WAAW,CAAS;IAE5B,MAAM;IAuCf,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;CAKpB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,mBAAmB,EAAE,eAAe,CAAC;KACtC;CACF"}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, css, html, nothing } from 'lit';
|
|
8
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
|
+
import { emitir, estilosBase, formatearPrecio, iconoTag, iconoX, tokens } from '@lit-roa/base';
|
|
10
|
+
/**
|
|
11
|
+
* Resumen de pedido: subtotal, envío, descuento, cargos extra y total, con
|
|
12
|
+
* un campo opcional para cupones. No calcula precios por su cuenta salvo el
|
|
13
|
+
* total si no se lo das: el sitio (o la API) manda los montos.
|
|
14
|
+
*
|
|
15
|
+
* @fires aplicar-cupon - `detail`: `{ codigo }`. Mientras validas, pon `cupon-cargando`.
|
|
16
|
+
* @fires quitar-cupon - `detail`: `{ codigo }`.
|
|
17
|
+
* @slot - Arriba de los montos (ej. líneas del carrito en modo solo lectura).
|
|
18
|
+
* @slot acciones - Debajo del total (ej. botón "Pagar").
|
|
19
|
+
* @slot nota - Texto pequeño al final (ej. "Impuestos incluidos").
|
|
20
|
+
*/
|
|
21
|
+
let LrResumenPedido = class LrResumenPedido extends LitElement {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(...arguments);
|
|
24
|
+
this.titulo = 'Resumen del pedido';
|
|
25
|
+
this.subtotal = 0;
|
|
26
|
+
/** Descuento como número positivo; se muestra restando. */
|
|
27
|
+
this.descuento = 0;
|
|
28
|
+
/** Cargos extra (ej. impuestos, propina). Como atributo, en JSON. */
|
|
29
|
+
this.cargos = [];
|
|
30
|
+
this.moneda = 'MXN';
|
|
31
|
+
/** Muestra el código de moneda junto al total. */
|
|
32
|
+
this.mostrarMoneda = false;
|
|
33
|
+
/** Muestra el campo de cupón. */
|
|
34
|
+
this.conCupon = false;
|
|
35
|
+
/** Código ya aplicado: se muestra como etiqueta con botón para quitarlo. */
|
|
36
|
+
this.cuponAplicado = '';
|
|
37
|
+
this.errorCupon = '';
|
|
38
|
+
this.cuponCargando = false;
|
|
39
|
+
this.codigo = '';
|
|
40
|
+
this.conAcciones = false;
|
|
41
|
+
}
|
|
42
|
+
static { this.styles = [
|
|
43
|
+
tokens,
|
|
44
|
+
estilosBase,
|
|
45
|
+
css `
|
|
46
|
+
:host {
|
|
47
|
+
display: block;
|
|
48
|
+
}
|
|
49
|
+
.resumen {
|
|
50
|
+
padding: var(--lr-resumen-relleno, 1.25rem);
|
|
51
|
+
border: 1px solid var(--_color-borde);
|
|
52
|
+
border-radius: var(--_radio);
|
|
53
|
+
background: var(--lr-resumen-fondo, var(--_color-fondo));
|
|
54
|
+
}
|
|
55
|
+
:host([sin-borde]) .resumen {
|
|
56
|
+
padding: 0;
|
|
57
|
+
border: 0;
|
|
58
|
+
background: transparent;
|
|
59
|
+
}
|
|
60
|
+
h2 {
|
|
61
|
+
margin: 0 0 1rem;
|
|
62
|
+
font-size: 1.125rem;
|
|
63
|
+
}
|
|
64
|
+
dl {
|
|
65
|
+
display: grid;
|
|
66
|
+
grid-template-columns: 1fr auto;
|
|
67
|
+
row-gap: 0.625rem;
|
|
68
|
+
margin: 0;
|
|
69
|
+
font-size: 0.9375rem;
|
|
70
|
+
}
|
|
71
|
+
dt {
|
|
72
|
+
color: var(--_color-texto-suave);
|
|
73
|
+
}
|
|
74
|
+
dd {
|
|
75
|
+
margin: 0;
|
|
76
|
+
padding-left: 1rem;
|
|
77
|
+
text-align: right;
|
|
78
|
+
font-variant-numeric: tabular-nums;
|
|
79
|
+
}
|
|
80
|
+
.descuento {
|
|
81
|
+
color: var(--lr-color-exito, #15803d);
|
|
82
|
+
}
|
|
83
|
+
.gratis {
|
|
84
|
+
color: var(--lr-color-exito, #15803d);
|
|
85
|
+
font-weight: 600;
|
|
86
|
+
}
|
|
87
|
+
.pendiente {
|
|
88
|
+
color: var(--_color-texto-suave);
|
|
89
|
+
font-size: 0.8125rem;
|
|
90
|
+
}
|
|
91
|
+
.total {
|
|
92
|
+
padding-top: 0.875rem;
|
|
93
|
+
border-top: 1px solid var(--_color-borde);
|
|
94
|
+
font-size: 1.125rem;
|
|
95
|
+
font-weight: 700;
|
|
96
|
+
color: var(--_color-texto);
|
|
97
|
+
}
|
|
98
|
+
.moneda {
|
|
99
|
+
color: var(--_color-texto-suave);
|
|
100
|
+
font-size: 0.75rem;
|
|
101
|
+
font-weight: 500;
|
|
102
|
+
margin-right: 0.25rem;
|
|
103
|
+
}
|
|
104
|
+
form {
|
|
105
|
+
display: flex;
|
|
106
|
+
gap: 0.5rem;
|
|
107
|
+
margin: 1rem 0;
|
|
108
|
+
}
|
|
109
|
+
input {
|
|
110
|
+
flex: 1;
|
|
111
|
+
min-width: 0;
|
|
112
|
+
height: 2.5rem;
|
|
113
|
+
padding: 0 0.75rem;
|
|
114
|
+
border: 1.5px solid #d1d5db;
|
|
115
|
+
border-radius: calc(var(--_radio) / 1.5);
|
|
116
|
+
font: inherit;
|
|
117
|
+
text-transform: uppercase;
|
|
118
|
+
}
|
|
119
|
+
input:focus {
|
|
120
|
+
outline: 0;
|
|
121
|
+
border-color: var(--_color-primario);
|
|
122
|
+
}
|
|
123
|
+
input[aria-invalid='true'] {
|
|
124
|
+
border-color: var(--lr-color-peligro, #dc2626);
|
|
125
|
+
}
|
|
126
|
+
form button {
|
|
127
|
+
all: unset;
|
|
128
|
+
padding: 0 1rem;
|
|
129
|
+
border-radius: calc(var(--_radio) / 1.5);
|
|
130
|
+
border: 1.5px solid var(--_color-primario);
|
|
131
|
+
color: var(--_color-primario);
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
}
|
|
135
|
+
form button:disabled {
|
|
136
|
+
opacity: 0.5;
|
|
137
|
+
cursor: wait;
|
|
138
|
+
}
|
|
139
|
+
form button:focus-visible,
|
|
140
|
+
.chip button:focus-visible {
|
|
141
|
+
outline: 2px solid var(--_color-primario);
|
|
142
|
+
outline-offset: 2px;
|
|
143
|
+
}
|
|
144
|
+
.error-cupon {
|
|
145
|
+
margin: -0.5rem 0 1rem;
|
|
146
|
+
color: var(--lr-color-peligro, #dc2626);
|
|
147
|
+
font-size: 0.8125rem;
|
|
148
|
+
}
|
|
149
|
+
.chip {
|
|
150
|
+
display: inline-flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: 0.375rem;
|
|
153
|
+
margin: 1rem 0;
|
|
154
|
+
padding: 0.25rem 0.375rem 0.25rem 0.625rem;
|
|
155
|
+
border-radius: 999px;
|
|
156
|
+
background: #dcfce7;
|
|
157
|
+
color: #166534;
|
|
158
|
+
font-size: 0.8125rem;
|
|
159
|
+
font-weight: 700;
|
|
160
|
+
}
|
|
161
|
+
.chip button {
|
|
162
|
+
all: unset;
|
|
163
|
+
display: grid;
|
|
164
|
+
place-items: center;
|
|
165
|
+
width: 1.25rem;
|
|
166
|
+
height: 1.25rem;
|
|
167
|
+
border-radius: 999px;
|
|
168
|
+
cursor: pointer;
|
|
169
|
+
}
|
|
170
|
+
.chip button:hover {
|
|
171
|
+
background: rgb(0 0 0 / 0.08);
|
|
172
|
+
}
|
|
173
|
+
.acciones {
|
|
174
|
+
display: grid;
|
|
175
|
+
gap: 0.5rem;
|
|
176
|
+
margin-top: 1.25rem;
|
|
177
|
+
}
|
|
178
|
+
.acciones[hidden] {
|
|
179
|
+
display: none;
|
|
180
|
+
}
|
|
181
|
+
::slotted([slot='nota']) {
|
|
182
|
+
display: block;
|
|
183
|
+
margin-top: 0.75rem;
|
|
184
|
+
color: var(--_color-texto-suave);
|
|
185
|
+
font-size: 0.75rem;
|
|
186
|
+
text-align: center;
|
|
187
|
+
}
|
|
188
|
+
`,
|
|
189
|
+
]; }
|
|
190
|
+
render() {
|
|
191
|
+
const extra = this.cargos.reduce((s, c) => s + c.monto, 0);
|
|
192
|
+
const total = this.total ?? this.subtotal + (this.envio ?? 0) + extra - this.descuento;
|
|
193
|
+
const p = (n) => formatearPrecio(n, this.moneda);
|
|
194
|
+
const etiquetaSubtotal = this.piezas !== undefined ? `Subtotal (${this.piezas} ${this.piezas === 1 ? 'producto' : 'productos'})` : 'Subtotal';
|
|
195
|
+
return html `<section class="resumen" part="resumen" aria-label=${this.titulo}>
|
|
196
|
+
${this.titulo ? html `<h2>${this.titulo}</h2>` : nothing}
|
|
197
|
+
<slot></slot>
|
|
198
|
+
${this.conCupon ? this.renderCupon() : nothing}
|
|
199
|
+
<dl>
|
|
200
|
+
<dt>${etiquetaSubtotal}</dt>
|
|
201
|
+
<dd>${p(this.subtotal)}</dd>
|
|
202
|
+
${this.descuento
|
|
203
|
+
? html `<dt>Descuento${this.cuponAplicado ? ` (${this.cuponAplicado})` : ''}</dt>
|
|
204
|
+
<dd class="descuento">−${p(this.descuento)}</dd>`
|
|
205
|
+
: nothing}
|
|
206
|
+
<dt>Envío</dt>
|
|
207
|
+
<dd>
|
|
208
|
+
${this.envio === undefined
|
|
209
|
+
? html `<span class="pendiente">Se calcula en el siguiente paso</span>`
|
|
210
|
+
: this.envio === 0
|
|
211
|
+
? html `<span class="gratis">Gratis</span>`
|
|
212
|
+
: p(this.envio)}
|
|
213
|
+
</dd>
|
|
214
|
+
${this.cargos.map((c) => html `<dt>${c.concepto}</dt><dd>${p(c.monto)}</dd>`)}
|
|
215
|
+
<dt class="total">Total</dt>
|
|
216
|
+
<dd class="total">${this.mostrarMoneda ? html `<span class="moneda">${this.moneda}</span>` : nothing}${p(total)}</dd>
|
|
217
|
+
</dl>
|
|
218
|
+
<div class="acciones" ?hidden=${!this.conAcciones}>
|
|
219
|
+
<slot
|
|
220
|
+
name="acciones"
|
|
221
|
+
@slotchange=${(e) => (this.conAcciones = e.target.assignedNodes().length > 0)}
|
|
222
|
+
></slot>
|
|
223
|
+
</div>
|
|
224
|
+
<slot name="nota"></slot>
|
|
225
|
+
</section>`;
|
|
226
|
+
}
|
|
227
|
+
renderCupon() {
|
|
228
|
+
if (this.cuponAplicado) {
|
|
229
|
+
return html `<span class="chip">${iconoTag} ${this.cuponAplicado}
|
|
230
|
+
<button aria-label="Quitar cupón ${this.cuponAplicado}" @click=${this.quitarCupon}>${iconoX}</button></span
|
|
231
|
+
>`;
|
|
232
|
+
}
|
|
233
|
+
return html `<form @submit=${this.aplicarCupon}>
|
|
234
|
+
<input
|
|
235
|
+
aria-label="Código de descuento"
|
|
236
|
+
placeholder="Código de descuento"
|
|
237
|
+
autocomplete="off"
|
|
238
|
+
.value=${this.codigo}
|
|
239
|
+
aria-invalid=${this.errorCupon ? 'true' : 'false'}
|
|
240
|
+
aria-describedby=${this.errorCupon ? 'error-cupon' : nothing}
|
|
241
|
+
@input=${(e) => (this.codigo = e.target.value)}
|
|
242
|
+
/>
|
|
243
|
+
<button ?disabled=${this.cuponCargando || !this.codigo.trim()}>${this.cuponCargando ? 'Aplicando…' : 'Aplicar'}</button>
|
|
244
|
+
</form>
|
|
245
|
+
${this.errorCupon ? html `<p class="error-cupon" id="error-cupon" role="alert">${this.errorCupon}</p>` : nothing}`;
|
|
246
|
+
}
|
|
247
|
+
aplicarCupon(evento) {
|
|
248
|
+
evento.preventDefault();
|
|
249
|
+
const codigo = this.codigo.trim().toUpperCase();
|
|
250
|
+
if (!codigo || this.cuponCargando)
|
|
251
|
+
return;
|
|
252
|
+
emitir(this, 'aplicar-cupon', { codigo });
|
|
253
|
+
}
|
|
254
|
+
quitarCupon() {
|
|
255
|
+
const codigo = this.cuponAplicado;
|
|
256
|
+
this.codigo = '';
|
|
257
|
+
emitir(this, 'quitar-cupon', { codigo });
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
__decorate([
|
|
261
|
+
property()
|
|
262
|
+
], LrResumenPedido.prototype, "titulo", void 0);
|
|
263
|
+
__decorate([
|
|
264
|
+
property({ type: Number })
|
|
265
|
+
], LrResumenPedido.prototype, "subtotal", void 0);
|
|
266
|
+
__decorate([
|
|
267
|
+
property({ type: Number })
|
|
268
|
+
], LrResumenPedido.prototype, "envio", void 0);
|
|
269
|
+
__decorate([
|
|
270
|
+
property({ type: Number })
|
|
271
|
+
], LrResumenPedido.prototype, "descuento", void 0);
|
|
272
|
+
__decorate([
|
|
273
|
+
property({ type: Array })
|
|
274
|
+
], LrResumenPedido.prototype, "cargos", void 0);
|
|
275
|
+
__decorate([
|
|
276
|
+
property({ type: Number })
|
|
277
|
+
], LrResumenPedido.prototype, "total", void 0);
|
|
278
|
+
__decorate([
|
|
279
|
+
property()
|
|
280
|
+
], LrResumenPedido.prototype, "moneda", void 0);
|
|
281
|
+
__decorate([
|
|
282
|
+
property({ type: Boolean, attribute: 'mostrar-moneda' })
|
|
283
|
+
], LrResumenPedido.prototype, "mostrarMoneda", void 0);
|
|
284
|
+
__decorate([
|
|
285
|
+
property({ type: Boolean, attribute: 'con-cupon' })
|
|
286
|
+
], LrResumenPedido.prototype, "conCupon", void 0);
|
|
287
|
+
__decorate([
|
|
288
|
+
property({ attribute: 'cupon-aplicado' })
|
|
289
|
+
], LrResumenPedido.prototype, "cuponAplicado", void 0);
|
|
290
|
+
__decorate([
|
|
291
|
+
property({ attribute: 'error-cupon' })
|
|
292
|
+
], LrResumenPedido.prototype, "errorCupon", void 0);
|
|
293
|
+
__decorate([
|
|
294
|
+
property({ type: Boolean, attribute: 'cupon-cargando' })
|
|
295
|
+
], LrResumenPedido.prototype, "cuponCargando", void 0);
|
|
296
|
+
__decorate([
|
|
297
|
+
property({ type: Number, attribute: 'piezas' })
|
|
298
|
+
], LrResumenPedido.prototype, "piezas", void 0);
|
|
299
|
+
__decorate([
|
|
300
|
+
state()
|
|
301
|
+
], LrResumenPedido.prototype, "codigo", void 0);
|
|
302
|
+
__decorate([
|
|
303
|
+
state()
|
|
304
|
+
], LrResumenPedido.prototype, "conAcciones", void 0);
|
|
305
|
+
LrResumenPedido = __decorate([
|
|
306
|
+
customElement('lr-resumen-pedido')
|
|
307
|
+
], LrResumenPedido);
|
|
308
|
+
export { LrResumenPedido };
|
|
309
|
+
//# sourceMappingURL=resumen-pedido.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resumen-pedido.js","sourceRoot":"","sources":["../src/resumen-pedido.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lit-roa/resumen-pedido",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Resumen de pedido con subtotal, envío, cupón y total.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": [
|
|
19
|
+
"./dist/index.js"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "vite",
|
|
23
|
+
"build": "tsc -b"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@lit-roa/base": "^0.4.0",
|
|
27
|
+
"lit": "^3.3.3"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@lit-roa/boton": "^0.1.2"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LrResumenPedido, type CargoExtra, type DetalleCupon } from './resumen-pedido.js';
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { LitElement, css, html, nothing } from 'lit';
|
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
3
|
+
import { emitir, estilosBase, formatearPrecio, iconoTag, iconoX, tokens } from '@lit-roa/base';
|
|
4
|
+
|
|
5
|
+
export interface DetalleCupon {
|
|
6
|
+
codigo: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CargoExtra {
|
|
10
|
+
concepto: string;
|
|
11
|
+
monto: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resumen de pedido: subtotal, envío, descuento, cargos extra y total, con
|
|
16
|
+
* un campo opcional para cupones. No calcula precios por su cuenta salvo el
|
|
17
|
+
* total si no se lo das: el sitio (o la API) manda los montos.
|
|
18
|
+
*
|
|
19
|
+
* @fires aplicar-cupon - `detail`: `{ codigo }`. Mientras validas, pon `cupon-cargando`.
|
|
20
|
+
* @fires quitar-cupon - `detail`: `{ codigo }`.
|
|
21
|
+
* @slot - Arriba de los montos (ej. líneas del carrito en modo solo lectura).
|
|
22
|
+
* @slot acciones - Debajo del total (ej. botón "Pagar").
|
|
23
|
+
* @slot nota - Texto pequeño al final (ej. "Impuestos incluidos").
|
|
24
|
+
*/
|
|
25
|
+
@customElement('lr-resumen-pedido')
|
|
26
|
+
export class LrResumenPedido extends LitElement {
|
|
27
|
+
static override styles = [
|
|
28
|
+
tokens,
|
|
29
|
+
estilosBase,
|
|
30
|
+
css`
|
|
31
|
+
:host {
|
|
32
|
+
display: block;
|
|
33
|
+
}
|
|
34
|
+
.resumen {
|
|
35
|
+
padding: var(--lr-resumen-relleno, 1.25rem);
|
|
36
|
+
border: 1px solid var(--_color-borde);
|
|
37
|
+
border-radius: var(--_radio);
|
|
38
|
+
background: var(--lr-resumen-fondo, var(--_color-fondo));
|
|
39
|
+
}
|
|
40
|
+
:host([sin-borde]) .resumen {
|
|
41
|
+
padding: 0;
|
|
42
|
+
border: 0;
|
|
43
|
+
background: transparent;
|
|
44
|
+
}
|
|
45
|
+
h2 {
|
|
46
|
+
margin: 0 0 1rem;
|
|
47
|
+
font-size: 1.125rem;
|
|
48
|
+
}
|
|
49
|
+
dl {
|
|
50
|
+
display: grid;
|
|
51
|
+
grid-template-columns: 1fr auto;
|
|
52
|
+
row-gap: 0.625rem;
|
|
53
|
+
margin: 0;
|
|
54
|
+
font-size: 0.9375rem;
|
|
55
|
+
}
|
|
56
|
+
dt {
|
|
57
|
+
color: var(--_color-texto-suave);
|
|
58
|
+
}
|
|
59
|
+
dd {
|
|
60
|
+
margin: 0;
|
|
61
|
+
padding-left: 1rem;
|
|
62
|
+
text-align: right;
|
|
63
|
+
font-variant-numeric: tabular-nums;
|
|
64
|
+
}
|
|
65
|
+
.descuento {
|
|
66
|
+
color: var(--lr-color-exito, #15803d);
|
|
67
|
+
}
|
|
68
|
+
.gratis {
|
|
69
|
+
color: var(--lr-color-exito, #15803d);
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
}
|
|
72
|
+
.pendiente {
|
|
73
|
+
color: var(--_color-texto-suave);
|
|
74
|
+
font-size: 0.8125rem;
|
|
75
|
+
}
|
|
76
|
+
.total {
|
|
77
|
+
padding-top: 0.875rem;
|
|
78
|
+
border-top: 1px solid var(--_color-borde);
|
|
79
|
+
font-size: 1.125rem;
|
|
80
|
+
font-weight: 700;
|
|
81
|
+
color: var(--_color-texto);
|
|
82
|
+
}
|
|
83
|
+
.moneda {
|
|
84
|
+
color: var(--_color-texto-suave);
|
|
85
|
+
font-size: 0.75rem;
|
|
86
|
+
font-weight: 500;
|
|
87
|
+
margin-right: 0.25rem;
|
|
88
|
+
}
|
|
89
|
+
form {
|
|
90
|
+
display: flex;
|
|
91
|
+
gap: 0.5rem;
|
|
92
|
+
margin: 1rem 0;
|
|
93
|
+
}
|
|
94
|
+
input {
|
|
95
|
+
flex: 1;
|
|
96
|
+
min-width: 0;
|
|
97
|
+
height: 2.5rem;
|
|
98
|
+
padding: 0 0.75rem;
|
|
99
|
+
border: 1.5px solid #d1d5db;
|
|
100
|
+
border-radius: calc(var(--_radio) / 1.5);
|
|
101
|
+
font: inherit;
|
|
102
|
+
text-transform: uppercase;
|
|
103
|
+
}
|
|
104
|
+
input:focus {
|
|
105
|
+
outline: 0;
|
|
106
|
+
border-color: var(--_color-primario);
|
|
107
|
+
}
|
|
108
|
+
input[aria-invalid='true'] {
|
|
109
|
+
border-color: var(--lr-color-peligro, #dc2626);
|
|
110
|
+
}
|
|
111
|
+
form button {
|
|
112
|
+
all: unset;
|
|
113
|
+
padding: 0 1rem;
|
|
114
|
+
border-radius: calc(var(--_radio) / 1.5);
|
|
115
|
+
border: 1.5px solid var(--_color-primario);
|
|
116
|
+
color: var(--_color-primario);
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
}
|
|
120
|
+
form button:disabled {
|
|
121
|
+
opacity: 0.5;
|
|
122
|
+
cursor: wait;
|
|
123
|
+
}
|
|
124
|
+
form button:focus-visible,
|
|
125
|
+
.chip button:focus-visible {
|
|
126
|
+
outline: 2px solid var(--_color-primario);
|
|
127
|
+
outline-offset: 2px;
|
|
128
|
+
}
|
|
129
|
+
.error-cupon {
|
|
130
|
+
margin: -0.5rem 0 1rem;
|
|
131
|
+
color: var(--lr-color-peligro, #dc2626);
|
|
132
|
+
font-size: 0.8125rem;
|
|
133
|
+
}
|
|
134
|
+
.chip {
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
gap: 0.375rem;
|
|
138
|
+
margin: 1rem 0;
|
|
139
|
+
padding: 0.25rem 0.375rem 0.25rem 0.625rem;
|
|
140
|
+
border-radius: 999px;
|
|
141
|
+
background: #dcfce7;
|
|
142
|
+
color: #166534;
|
|
143
|
+
font-size: 0.8125rem;
|
|
144
|
+
font-weight: 700;
|
|
145
|
+
}
|
|
146
|
+
.chip button {
|
|
147
|
+
all: unset;
|
|
148
|
+
display: grid;
|
|
149
|
+
place-items: center;
|
|
150
|
+
width: 1.25rem;
|
|
151
|
+
height: 1.25rem;
|
|
152
|
+
border-radius: 999px;
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
}
|
|
155
|
+
.chip button:hover {
|
|
156
|
+
background: rgb(0 0 0 / 0.08);
|
|
157
|
+
}
|
|
158
|
+
.acciones {
|
|
159
|
+
display: grid;
|
|
160
|
+
gap: 0.5rem;
|
|
161
|
+
margin-top: 1.25rem;
|
|
162
|
+
}
|
|
163
|
+
.acciones[hidden] {
|
|
164
|
+
display: none;
|
|
165
|
+
}
|
|
166
|
+
::slotted([slot='nota']) {
|
|
167
|
+
display: block;
|
|
168
|
+
margin-top: 0.75rem;
|
|
169
|
+
color: var(--_color-texto-suave);
|
|
170
|
+
font-size: 0.75rem;
|
|
171
|
+
text-align: center;
|
|
172
|
+
}
|
|
173
|
+
`,
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
@property() titulo = 'Resumen del pedido';
|
|
177
|
+
|
|
178
|
+
@property({ type: Number }) subtotal = 0;
|
|
179
|
+
|
|
180
|
+
/** Costo de envío. `0` = "Gratis"; sin valor = "Se calcula en el siguiente paso". */
|
|
181
|
+
@property({ type: Number }) envio?: number;
|
|
182
|
+
|
|
183
|
+
/** Descuento como número positivo; se muestra restando. */
|
|
184
|
+
@property({ type: Number }) descuento = 0;
|
|
185
|
+
|
|
186
|
+
/** Cargos extra (ej. impuestos, propina). Como atributo, en JSON. */
|
|
187
|
+
@property({ type: Array }) cargos: CargoExtra[] = [];
|
|
188
|
+
|
|
189
|
+
/** Total. Si no se indica: subtotal + envío + cargos − descuento. */
|
|
190
|
+
@property({ type: Number }) total?: number;
|
|
191
|
+
|
|
192
|
+
@property() moneda = 'MXN';
|
|
193
|
+
|
|
194
|
+
/** Muestra el código de moneda junto al total. */
|
|
195
|
+
@property({ type: Boolean, attribute: 'mostrar-moneda' }) mostrarMoneda = false;
|
|
196
|
+
|
|
197
|
+
/** Muestra el campo de cupón. */
|
|
198
|
+
@property({ type: Boolean, attribute: 'con-cupon' }) conCupon = false;
|
|
199
|
+
|
|
200
|
+
/** Código ya aplicado: se muestra como etiqueta con botón para quitarlo. */
|
|
201
|
+
@property({ attribute: 'cupon-aplicado' }) cuponAplicado = '';
|
|
202
|
+
|
|
203
|
+
@property({ attribute: 'error-cupon' }) errorCupon = '';
|
|
204
|
+
|
|
205
|
+
@property({ type: Boolean, attribute: 'cupon-cargando' }) cuponCargando = false;
|
|
206
|
+
|
|
207
|
+
@property({ type: Number, attribute: 'piezas' }) piezas?: number;
|
|
208
|
+
|
|
209
|
+
@state() private codigo = '';
|
|
210
|
+
@state() private conAcciones = false;
|
|
211
|
+
|
|
212
|
+
override render() {
|
|
213
|
+
const extra = this.cargos.reduce((s, c) => s + c.monto, 0);
|
|
214
|
+
const total = this.total ?? this.subtotal + (this.envio ?? 0) + extra - this.descuento;
|
|
215
|
+
const p = (n: number) => formatearPrecio(n, this.moneda);
|
|
216
|
+
const etiquetaSubtotal = this.piezas !== undefined ? `Subtotal (${this.piezas} ${this.piezas === 1 ? 'producto' : 'productos'})` : 'Subtotal';
|
|
217
|
+
|
|
218
|
+
return html`<section class="resumen" part="resumen" aria-label=${this.titulo}>
|
|
219
|
+
${this.titulo ? html`<h2>${this.titulo}</h2>` : nothing}
|
|
220
|
+
<slot></slot>
|
|
221
|
+
${this.conCupon ? this.renderCupon() : nothing}
|
|
222
|
+
<dl>
|
|
223
|
+
<dt>${etiquetaSubtotal}</dt>
|
|
224
|
+
<dd>${p(this.subtotal)}</dd>
|
|
225
|
+
${this.descuento
|
|
226
|
+
? html`<dt>Descuento${this.cuponAplicado ? ` (${this.cuponAplicado})` : ''}</dt>
|
|
227
|
+
<dd class="descuento">−${p(this.descuento)}</dd>`
|
|
228
|
+
: nothing}
|
|
229
|
+
<dt>Envío</dt>
|
|
230
|
+
<dd>
|
|
231
|
+
${this.envio === undefined
|
|
232
|
+
? html`<span class="pendiente">Se calcula en el siguiente paso</span>`
|
|
233
|
+
: this.envio === 0
|
|
234
|
+
? html`<span class="gratis">Gratis</span>`
|
|
235
|
+
: p(this.envio)}
|
|
236
|
+
</dd>
|
|
237
|
+
${this.cargos.map((c) => html`<dt>${c.concepto}</dt><dd>${p(c.monto)}</dd>`)}
|
|
238
|
+
<dt class="total">Total</dt>
|
|
239
|
+
<dd class="total">${this.mostrarMoneda ? html`<span class="moneda">${this.moneda}</span>` : nothing}${p(total)}</dd>
|
|
240
|
+
</dl>
|
|
241
|
+
<div class="acciones" ?hidden=${!this.conAcciones}>
|
|
242
|
+
<slot
|
|
243
|
+
name="acciones"
|
|
244
|
+
@slotchange=${(e: Event) => (this.conAcciones = (e.target as HTMLSlotElement).assignedNodes().length > 0)}
|
|
245
|
+
></slot>
|
|
246
|
+
</div>
|
|
247
|
+
<slot name="nota"></slot>
|
|
248
|
+
</section>`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private renderCupon() {
|
|
252
|
+
if (this.cuponAplicado) {
|
|
253
|
+
return html`<span class="chip">${iconoTag} ${this.cuponAplicado}
|
|
254
|
+
<button aria-label="Quitar cupón ${this.cuponAplicado}" @click=${this.quitarCupon}>${iconoX}</button></span
|
|
255
|
+
>`;
|
|
256
|
+
}
|
|
257
|
+
return html`<form @submit=${this.aplicarCupon}>
|
|
258
|
+
<input
|
|
259
|
+
aria-label="Código de descuento"
|
|
260
|
+
placeholder="Código de descuento"
|
|
261
|
+
autocomplete="off"
|
|
262
|
+
.value=${this.codigo}
|
|
263
|
+
aria-invalid=${this.errorCupon ? 'true' : 'false'}
|
|
264
|
+
aria-describedby=${this.errorCupon ? 'error-cupon' : nothing}
|
|
265
|
+
@input=${(e: Event) => (this.codigo = (e.target as HTMLInputElement).value)}
|
|
266
|
+
/>
|
|
267
|
+
<button ?disabled=${this.cuponCargando || !this.codigo.trim()}>${this.cuponCargando ? 'Aplicando…' : 'Aplicar'}</button>
|
|
268
|
+
</form>
|
|
269
|
+
${this.errorCupon ? html`<p class="error-cupon" id="error-cupon" role="alert">${this.errorCupon}</p>` : nothing}`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private aplicarCupon(evento: Event) {
|
|
273
|
+
evento.preventDefault();
|
|
274
|
+
const codigo = this.codigo.trim().toUpperCase();
|
|
275
|
+
if (!codigo || this.cuponCargando) return;
|
|
276
|
+
emitir<DetalleCupon>(this, 'aplicar-cupon', { codigo });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private quitarCupon() {
|
|
280
|
+
const codigo = this.cuponAplicado;
|
|
281
|
+
this.codigo = '';
|
|
282
|
+
emitir<DetalleCupon>(this, 'quitar-cupon', { codigo });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
declare global {
|
|
287
|
+
interface HTMLElementTagNameMap {
|
|
288
|
+
'lr-resumen-pedido': LrResumenPedido;
|
|
289
|
+
}
|
|
290
|
+
}
|