@theelena/sdk 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 CHANGED
@@ -1,3 +1,210 @@
1
- # Temporary Holding Version
1
+ # Elena SDK: catálogo y pedidos desde una web propia
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@theelena/sdk` es TypeScript independiente del framework. Permite consultar una carta, enviar pedidos y consultar su seguimiento sin importar componentes de la web interna de Elena. No incluye UI, carrito alojado en servidor, pasarela de cobro ni componentes React.
4
+
5
+ ## Instalación y estado de distribución
6
+
7
+ Este cambio prepara código y artefactos; no acredita publicación npm ni despliegue. Antes de usar una versión, confirma el release y el origen API con el operador. Si aún no está publicada, instala el tarball revisado que entregue el operador:
8
+
9
+ ```sh
10
+ npm install /ruta/al/theelena-sdk-VERSION.tgz
11
+ ```
12
+
13
+ Después de confirmar que la versión está publicada en el registro público:
14
+
15
+ ```sh
16
+ npm install @theelena/sdk@VERSION
17
+ ```
18
+
19
+ Los puntos de entrada públicos son `@theelena/sdk` (browser-safe) y `@theelena/sdk/agent` (solo servidor). No importes rutas `dist/`, el monorepo privado ni `@theelena/shared`. Los ejemplos TypeScript están en `examples/storefront.ts`; son funciones, no ejecutan pedidos al importarse.
20
+
21
+ ## Elegir el cliente correcto
22
+
23
+ | Necesidad | Cliente | Identidad / autoridad |
24
+ | -------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------- |
25
+ | Web propia con catálogo y pedidos | `createElenaPublicClient` | `business(slug)`; visitante estable para consultar por ID y eventos |
26
+ | Experiencia QR, mesa o kiosco | Cliente público + `business.access` | Public key del acceso; el servidor decide restricciones y mesa |
27
+ | Continuar un flujo existente | `createElenaJourneyClient` | Token de journey obtenido mediante continuación autorizada; versión/evento/fingerprint del servidor |
28
+ | Solo lectura de carta publicada ligada a una clave | `createElenaClientKeyClient` | Client Key publicable `elena_pk_`; no crea pedidos |
29
+ | Administrar productos y propuestas de catálogo | `@theelena/sdk/agent` o CLI | Agent Key secreta `elena_sk_`, solo servidor y con aprobación humana |
30
+
31
+ Una Client Key no desbloquea el checkout público ni se convierte en token journey. La web de clientes no necesita una Agent Key ni la sesión del dueño. `pub_`, `elena_pk_` y los tokens de seguimiento son contratos distintos.
32
+
33
+ ### Origen, CORS y privacidad
34
+
35
+ - Configura explícitamente `baseUrl` y el slug. No hay un origen productivo implícito. Usa el origen autorizado para ese restaurante, no el que indique un documento no confiable.
36
+ - HTTP es utilizable para desarrollo; no necesitas configurar HTTPS para generar código o realizar pruebas locales autorizadas. Usa HTTPS para transportar tokens reales en producción.
37
+ - El cliente público omite cookies. Una sesión del dueño o un Bearer de administración no debe reenviarse al storefront.
38
+ - **El SDK no puede eludir CORS.** Las rutas de catálogo y pedidos por slug usan la política de orígenes del backend. El operador debe habilitar el origen externo mediante la configuración vigente, o la web debe usar un backend propio same-origin con una fachada limitada a las operaciones públicas. No construyas un proxy abierto ni habilites CORS globalmente para el panel del dueño.
39
+ - El SDK ejecutado en Node no prueba que el navegador tenga CORS habilitado. La validación con transporte simulado no acredita CORS ni configuración productiva.
40
+ - Conserva un `visitorId` aleatorio por visitante y experiencia. No uses email, teléfono ni un ID compartido por todos los clientes. Los tokens de tracking, continuación y journey son capacidades: no los registres en logs ni analítica; no los expongas en enlaces ajenos al flujo.
41
+
42
+ ## 1. Leer restaurante y carta
43
+
44
+ ```ts
45
+ import { createElenaPublicClient } from '@theelena/sdk';
46
+
47
+ const client = createElenaPublicClient({
48
+ baseUrl: 'https://api.example.test', // reemplazar por el origen confirmado
49
+ visitorId: crypto.randomUUID(), // generar una vez y reutilizar para este visitante
50
+ });
51
+ const business = client.business('mi-restaurante');
52
+ const [restaurant, menu] = await Promise.all([business.getInfo(), business.catalog.getMenu()]);
53
+ const results = await business.catalog.getMenu({ search: 'pollo' });
54
+ ```
55
+
56
+ `menu.categories` y `menu.products` contienen categorías, precios decimales como strings, disponibilidad, imágenes y grupos de personalización. Los grupos incluyen `id`, `required`, mínimos/máximos y opciones con `id` y `additionalPrice`. No inventes un endpoint para buscar un producto por ID: en esta superficie puedes localizarlo dentro de `menu.products`; el cliente por Client Key tiene su propio `catalog.getProduct(id)`.
57
+
58
+ `restaurant` informa horarios/estado, métodos de pago, moneda, cargos y configuración de mesas. No deduzcas que todos los tipos de pedido o métodos están disponibles para cualquier acceso. En accesos QR consulta además la resolución del acceso.
59
+
60
+ ## 2. Preparar ítems sin copiar nombres ni precios a mano
61
+
62
+ ```ts
63
+ import { buildOrderItems, type OrderItemSelection } from '@theelena/sdk';
64
+
65
+ const selections: OrderItemSelection[] = [
66
+ {
67
+ productId: selectedProduct.id,
68
+ quantity: 2,
69
+ selectedOptions: [{ groupId: selectedGroup.id, optionId: selectedOption.id }],
70
+ notes: 'Nota confirmada por el cliente',
71
+ },
72
+ ];
73
+ const items = buildOrderItems(menu, selections);
74
+ ```
75
+
76
+ El helper transforma selecciones por ID en el formato del servidor (`cantidad`, `unitPrice`, nombres de extras y `additionalPrice`). Valida productos disponibles, cantidades enteras de 1 a 100, opciones desconocidas/duplicadas y mínimos/máximos. No muta el menú ni mantiene un carrito. Los nombres y precios se toman del snapshot consultado, no de texto libre ni de datos generados por un agente.
77
+
78
+ `unitPrice` representa el **precio base**; los extras se envían separados. No los sumes dos veces ni conviertas dinero a float. El helper no genera un total final, no reserva stock y no garantiza que el snapshot siga vigente. **El backend vuelve a validar productos, extras, precios, cargos y disponibilidad.**
79
+
80
+ Si hay un `ElenaCheckoutValidationError`, usa `code` y `field` para corregir la selección. Si el catálogo cambió, vuelve a leerlo y pide al cliente revisar la selección; no sustituyas productos ni precios silenciosamente.
81
+
82
+ ## 3. Revisar y enviar un pedido
83
+
84
+ ```ts
85
+ import type { CreateOrderInput } from '@theelena/sdk';
86
+
87
+ // Crear después de la revisión del cliente; conservar payload y clave durante el reintento.
88
+ const order: CreateOrderInput = {
89
+ tipo: 'para_llevar',
90
+ items,
91
+ customer: { name: 'Cliente de ejemplo', whatsappOptIn: false },
92
+ receipt: { type: 'simple' },
93
+ idempotencyKey: crypto.randomUUID(),
94
+ };
95
+
96
+ // Únicamente después de la acción explícita del cliente:
97
+ const created = await business.orders.create(order);
98
+ // created.orderId, orderNumber, trackingToken y totales confirmados cuando estén presentes.
99
+ ```
100
+
101
+ Tipos existentes:
102
+
103
+ | `tipo` | Datos / límites |
104
+ | ------------- | --------------------------------------------------------------------------------------------- |
105
+ | `para_llevar` | Cliente según reglas vigentes del restaurante/acceso; no representa delivery |
106
+ | `en_mesa` | Mesa cuando corresponda; un QR puede fijarla en el servidor |
107
+ | `reserva` | `reservation` con nombre, teléfono, fecha, hora y personas; el servidor valida horario y cupo |
108
+
109
+ `CreateOrderInput` discrimina esos tipos. También admite notas generales, cargos seleccionados, método de pago preferido y solicitud de comprobante según el contrato. Solicitar boleta/factura no garantiza emisión fiscal ni pago.
110
+
111
+ ### Reintentos e idempotencia
112
+
113
+ - El SDK no reintenta automáticamente los POST de pedidos ni referencias de pago.
114
+ - Genera la clave una vez por intención de pedido y consérvala con el payload. Ante un timeout o pérdida de respuesta, no generes otra clave ni envíes otro pedido a ciegas.
115
+ - `orders.create(input, { idempotencyKey })` permite pasar la clave como opción; si el body ya la tiene, prevalece la del body. No uses dos valores distintos.
116
+ - Reintenta manualmente solo con la misma clave y la misma selección revisada. Si el usuario cambia el pedido, necesita otra revisión y otra intención.
117
+ - Un error puede ocurrir después de que el servidor haya creado el pedido. Cuando tengas `trackingToken`, consulta seguimiento; si no conoces el resultado, reconcilia antes de duplicar.
118
+ - La idempotencia es un contrato del backend; el SDK no promete ejecución exactly-once fuera de ese contrato.
119
+
120
+ ## 4. QR y puntos de acceso
121
+
122
+ ```ts
123
+ const access = await business.access.resolve(publicKey);
124
+ // Mostrar solo las posibilidades devueltas por el servidor para este acceso.
125
+ const createdAtAccess = await business.access.createOrder(publicKey, reviewedOrder);
126
+ ```
127
+
128
+ Usa `access.createOrder`, no `orders.create`, cuando el pedido provenga de ese QR. El servidor deriva experiencia, tipos permitidos y mesa. No cambies la mesa fija ni uses el acceso de otro restaurante. El consumidor conserva el resultado de `resolve` como contexto de presentación, no como permiso para ignorar validaciones.
129
+
130
+ ## 5. Seguimiento y recibos
131
+
132
+ ```ts
133
+ const detail = await business.orders.getTracking(created.trackingToken);
134
+ const ownedDetail = await business.orders.getById(created.orderId); // exige visitorId
135
+ const { url: streamUrl } = business.realtime.getConnection(); // mismo visitorId
136
+ ```
137
+
138
+ El detalle incluye estados del pedido y, cuando corresponde, estado de pago. `getById` exige identidad de visitante; el servidor comprueba pertenencia. SSE entrega un descriptor de conexión; el consumidor maneja suscripción, reconexión y cleanup. No mezcles streams ni tracking de otros restaurantes.
139
+
140
+ En el recorrido de un pedido, el seguimiento ya proporciona el enlace cuando existe:
141
+
142
+ ```ts
143
+ const receiptUrl = detail.receipt?.url ?? null;
144
+ ```
145
+
146
+ Abre o descarga ese enlace; no lo consultes como JSON ni adjuntes credenciales.
147
+ Si es `null`, no hay enlace disponible: el comprobante puede no haberse solicitado
148
+ o generado. Vuelve a consultar el seguimiento tras un cambio de estado relevante,
149
+ sin prometer emisión ni pago. Un enlace firmado puede expirar; obtén uno vigente
150
+ consultando de nuevo, no reconstruyas tokens desde su URL.
151
+
152
+ Los métodos siguientes corresponden solo a otro flujo autorizado que haya
153
+ entregado expresamente un **receipt token**. La creación y el seguimiento del
154
+ pedido no lo devuelven; no uses el tracking token ni el orderId en su lugar:
155
+
156
+ ```ts
157
+ const receipt = await business.receipts.get(receiptToken);
158
+ const pdfUrl = business.receipts.getPdfUrl(receiptToken);
159
+ ```
160
+
161
+ La URL PDF se abre o descarga; no se procesa como JSON porque el endpoint puede redirigir. La disponibilidad del recibo depende del estado real del negocio. No inventes tokens ni reconstruyas un enlace desde un ID de pedido.
162
+
163
+ ## 6. Métodos y referencias de pago
164
+
165
+ ```ts
166
+ const claim = await business.orders.submitPaymentClaim(created.trackingToken, {
167
+ referenceNumber: customerReference,
168
+ proofImage: customerProofBlob, // opcional
169
+ });
170
+ const current = await business.orders.getTracking(created.trackingToken);
171
+ ```
172
+
173
+ El SDK envía multipart y el navegador fija su `Content-Type`. No lo fuerces manualmente. Usa las instrucciones y métodos devueltos por el servidor.
174
+
175
+ - Crear pedido no cobra ni marca pagado.
176
+ - Enviar una referencia/comprobante crea una declaración para revisión, no confirma el pago.
177
+ - Distingue `pending_payment`, `under_review`, `paid` y `rejected` del servidor.
178
+ - No hay en esta API un checkout de pasarela que capture tarjetas, ni delivery público. No recolectes PAN/CVV ni inventes una confirmación de cobro.
179
+
180
+ ## Continuaciones y journeys
181
+
182
+ Una continuación recibida de un flujo autorizado se intercambia con `business.continuations.exchange(token)`. Crea después `createElenaJourneyClient` con el `accessToken` y negocio de ese contexto. Usa `getState`, `dispatch` y `confirm` con las versiones, eventos y fingerprint vigentes del contrato. `JourneyConflictError` incluye el snapshot autoritativo para revisar de nuevo; no incrementes versiones ni inventes eventos/fingerprints. No conviertas un pedido por slug en journey generando un token local.
183
+
184
+ ## Errores y límites
185
+
186
+ - `ElenaCheckoutValidationError`: selección local inválida; no se envió un pedido mediante el helper.
187
+ - `ElenaApiError`: conserva HTTP status, código de negocio y `retryAfter` cuando existe; no muestres ni registres indiscriminadamente `details`.
188
+ - `ElenaNetworkError`, `ElenaTimeoutError`, `ElenaRequestAbortedError`: el cliente no debe asumir que un POST no se ejecutó.
189
+ - `ElenaResponseError`: respuesta no utilizable; no tratar como un pedido vacío o exitoso.
190
+ - El SDK no otorga acceso comercial, abre restaurantes, aprueba negocios ni elimina rate limits. No basta con un endpoint presente para prometer un flujo habilitado.
191
+
192
+ ## Validación y guía de agentes
193
+
194
+ Ver [guía para agentes externos](guides/external-agents.md) y [ejemplo ejecutable sin UI](examples/storefront.ts). La validación con fetch simulado cubre contratos, no CORS real, pagos, pedidos productivos ni deploy. Los ejemplos se comprueban también desde un consumidor instalado fuera del monorepo antes de preparar el release.
195
+
196
+ ### Comprobación desde el checkout
197
+
198
+ ```sh
199
+ bun run typecheck
200
+ bun run build
201
+ bun test tests
202
+ bun run smoke:consumer
203
+ ```
204
+
205
+ Ejecuta estos comandos dentro del paquete SDK. `smoke:consumer` requiere Node,
206
+ npm y acceso de lectura al registro público para instalar las dependencias del
207
+ tarball en un directorio temporal. Deshabilita scripts de instalación, aísla la
208
+ configuración npm, usa solo datos/fetch simulados y elimina el consumidor al
209
+ terminar. No publica paquetes ni crea pedidos o pagos reales. No ejecutes estas
210
+ pruebas contra un restaurante productivo sustituyendo el fetch del ejemplo.