Storefront API

Storefront API — Referencia Completa

Documentación para desarrolladores que construyen frontends custom (React, Next.js, etc.) sobre Demosle CMS.

Configuración base#

Todos los endpoints son públicos salvo los marcados con Requiere auth Requiere autenticación (Customer JWT).

BASE_URL: https://api.tudominio.com
Content-Type: application/json
X-Tenant-Slug: {tu-tenant-slug}   ← requerido en todos los requests

Para endpoints autenticados, incluir el token en el header:

Authorization: Bearer {customer_jwt}

Notas generales#

  • Caché: Endpoints públicos tienen caché del lado del servidor (TTLs detallados por endpoint).
  • Rate limiting: El endpoint de checkout tiene límite de llamadas propio.
  • Tenant: El middleware resuelve automáticamente el tenant desde el dominio o el header X-Tenant-Slug.
  • Errores estándar:
    • 400 — Validación fallida (body inválido, campo faltante)
    • 401 — JWT inválido o expirado
    • 404 — Recurso no existe o no publicado
    • 410 — Recurso ya fue procesado (ej: carrito abandonado ya recuperado)
    • 500 — Error interno del servidor

1. Sitio#

GET/api/storefront/site#

Configuración general del sitio: marca, módulos activos, analytics.

Caché: 1 hora.

Respuesta:

{
  "success": true,
  "data": {
    "site": {
      "name": "string",
      "logo": "string | null",
      "favicon": "string | null",
      "primaryColor": "#hex",
      "secondaryColor": "#hex"
    },
    "business": {
      "email": "string | null",
      "phone": "string | null",
      "address": "string | null",
      "socialLinks": {
        "facebook": "string | null",
        "instagram": "string | null",
        "twitter": "string | null",
        "linkedin": "string | null"
      }
    },
    "modules": {
      "ecommerce": true,
      "blog": true,
      "portfolio": true
    },
    "store": {
      "businessType": "PRODUCTS | SERVICES | EXPERIENCES",
      "enabledProductTypes": ["PHYSICAL_PRODUCT", "DIGITAL_PRODUCT", "SERVICE", "EXPERIENCE"],
      "checkoutEnabled": true
    },
    "analytics": {
      "ga4Id": "string | null",
      "gtmId": "string | null",
      "metaPixelId": "string | null",
      "tiktokPixelId": "string | null",
      "manyChatId": "string | null"
    },
    "siteContent": {
      "banners": [],
      "hero": {},
      "about": {},
      "gallery": [],
      "schedule": [],
      "popup": { "active": false }
    }
  }
}

GET/api/storefront/site/navigation#

Menú de navegación configurado por el tenant.

Respuesta:

{
  "success": true,
  "data": {
    "navigation": [
      {
        "id": "string",
        "label": "string",
        "url": "string",
        "type": "internal | external",
        "children": []
      }
    ]
  }
}

2. Secciones del sitio#

GET/api/storefront/site-sections#

Secciones configurables del sitio (hero, features, CTA, testimonials, FAQ, etc.).

Caché: 1 hora.

Query params:

ParamTipoDescripción
keystringFiltra por slug de sección específica

Respuesta (sin key):

{
  "success": true,
  "sections": [
    {
      "key": "hero",
      "type": "string",
      "name": "string",
      "data": {}
    }
  ]
}

Respuesta (con key):

{
  "success": true,
  "section": { "key": "hero", "type": "...", "name": "...", "data": {} }
}

3. Páginas estáticas#

GET/api/storefront/pages/:slug#

Respuesta:

{
  "success": true,
  "data": {
    "page": {
      "id": "string",
      "title": "string",
      "slug": "string",
      "content": {},
      "excerpt": "string",
      "featuredImage": "string | null",
      "seoTitle": "string | null",
      "seoDescription": "string | null",
      "publishedAt": "ISO date"
    }
  }
}

4. Productos#

GET/api/storefront/products#

Lista de productos. Caché: 30 min (variable según params).

Query params:

ParamTipoDescripción
pagenumberDefault: 1
limitnumberDefault: 20
categoryIdstringFiltrar por categoría
categorySlugstringFiltrar por slug de categoría
featuredtrue|falseSolo destacados
searchstringBúsqueda por nombre
tagSlugstringFiltrar por tag
sortstringprice-asc, price-desc, name, newest

Respuesta:

{
  "success": true,
  "products": [
    {
      "id": "string",
      "name": "string",
      "slug": "string",
      "shortDescription": "string",
      "type": "PHYSICAL_PRODUCT | DIGITAL_PRODUCT | SERVICE | EXPERIENCE",
      "price": 19990,
      "comparePrice": 29990,
      "stock": 10,
      "trackStock": true,
      "images": ["https://..."],
      "featured": true,
      "requiresScheduling": false,
      "categoryId": "string",
      "category": { "id": "string", "name": "string", "slug": "string" },
      "tags": [{ "id": "string", "name": "string", "slug": "string", "color": "#hex", "icon": "string" }],
      "createdAt": "ISO date",
      "imageUrl": "string | null"
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 100, "totalPages": 5 }
}

GET/api/storefront/products/featured#

Productos destacados. Caché: 30 min.

Query params:

ParamTipoDescripción
limitnumberDefault: 8

Respuesta: igual a productos pero sin paginación.


GET/api/storefront/products/search#

Búsqueda avanzada con filtros de precio y stock.

Query params:

ParamTipoDescripción
qstringQuery de búsqueda
categorystringFiltrar por categoría
minPricenumberPrecio mínimo
maxPricenumberPrecio máximo
inStocktrue|falseSolo con stock
featuredtrue|falseSolo destacados
pagenumber
limitnumber
sortBystring

GET/api/storefront/products/:slug#

Detalle de producto. Caché: 30 min.

Respuesta:

{
  "success": true,
  "product": {
    "id": "string",
    "name": "string",
    "slug": "string",
    "description": "string",
    "shortDescription": "string",
    "type": "PHYSICAL_PRODUCT | DIGITAL_PRODUCT | SERVICE | EXPERIENCE",
    "price": 19990,
    "comparePrice": 29990,
    "sku": "string",
    "stock": 10,
    "trackStock": true,
    "images": ["https://..."],
    "files": [],
    "featured": true,
    "requiresScheduling": false,
    "seoTitle": "string",
    "seoDescription": "string",
    "category": { "id": "string", "name": "string", "slug": "string" },
    "tags": [],
    "variants": [
      { "id": "string", "name": "string", "stock": 5, "price": 19990, "attributes": {} }
    ],
    "createdAt": "ISO date",
    "imageUrl": "string | null",
    "promotions": [
      {
        "id": "string",
        "name": "string",
        "description": "string",
        "type": "PERCENTAGE | FIXED_AMOUNT | FREE_SHIPPING | QUANTITY_DISCOUNT | BUY_X_GET_Y",
        "value": 10,
        "scope": "STORE_WIDE | CATEGORY | PRODUCT"
      }
    ],
    "bestPromotion": {
      "name": "string",
      "type": "string",
      "discountAmount": 2000,
      "finalPrice": 17990
    }
  },
  "relatedProducts": [
    {
      "id": "string",
      "name": "string",
      "slug": "string",
      "shortDescription": "string",
      "price": 15000,
      "comparePrice": null,
      "stock": 3,
      "trackStock": true,
      "images": ["https://..."],
      "imageUrl": "string | null"
    }
  ]
}

5. Categorías#

GET/api/storefront/categories#

Respuesta:

{
  "success": true,
  "categories": [
    {
      "id": "string",
      "name": "string",
      "slug": "string",
      "description": "string | null",
      "image": "string | null",
      "parentId": "string | null",
      "_count": { "products": 12 }
    }
  ]
}

6. Experiencias#

GET/api/storefront/experiences#

Query params:

ParamTipoDescripción
pagenumberDefault: 1
limitnumberDefault: 20
categoryIdstring
experienceTypestring
locationstring
searchstring
featuredtrue|false

Respuesta:

{
  "success": true,
  "experiences": [
    {
      "id": "string",
      "name": "string",
      "slug": "string",
      "description": "string",
      "shortDescription": "string",
      "price": 25000,
      "images": ["https://..."],
      "categoryId": "string",
      "featured": false,
      "createdAt": "ISO date",
      "imageUrl": "string | null",
      "translations": [{ "language": "es", "name": "string" }]
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 5, "totalPages": 1 }
}

GET/api/storefront/experiences/:slug#

Respuesta adicional (incluye slots disponibles):

{
  "success": true,
  "experience": {
    "...campos base...",
    "availableSlots": [
      {
        "id": "string",
        "startDateTime": "ISO date",
        "endDateTime": "ISO date",
        "capacity": 10,
        "bookedCount": 3,
        "price": 25000,
        "availableCapacity": 7
      }
    ]
  }
}

GET/api/storefront/experiences/:id/slots#

Slots de disponibilidad de una experiencia.

Query params:

ParamTipoDescripción
startDateISO dateDefault: ahora
endDateISO dateDefault: +60 días

7. Blog#

GET/api/storefront/posts#

Query params:

ParamTipoDescripción
pagenumber
limitnumber
categoryIdstring
searchstring

GET/api/storefront/posts/:slug#

GET/api/storefront/post-categories#


8. Portfolio / Proyectos#

GET/api/storefront/projects#

Query params:

ParamTipoDescripción
pagenumberDefault: 1
limitnumberDefault: 12
categorystringcategoryId
searchstring

GET/api/storefront/projects/:slug#

Respuesta adicional: incluye relatedProjects.


9. Sellers (Marketplace)#

GET/api/storefront/sellers#

Lista de vendedores activos. Caché: 30 min.

Respuesta:

{
  "success": true,
  "sellers": [
    {
      "id": "string",
      "name": "string",
      "slug": "string",
      "email": "string",
      "phone": "string | null",
      "logo": "string | null",
      "description": "string | null",
      "_count": { "products": 15 }
    }
  ]
}

GET/api/storefront/sellers/:slug#

Detalle de un seller.

GET/api/storefront/sellers/:slug/products#

Productos de un seller.

Query params:

ParamTipo
pagenumber
limitnumber

10. Directorio#

GET/api/storefront/directory#

Query params:

ParamTipo
categoryIdstring
featuredtrue|false
searchstring
pagenumber (default: 1)
limitnumber (default: 20)

Respuesta:

{
  "entries": [
    {
      "id": "string",
      "name": "string",
      "address": "string | null",
      "description": "string | null",
      "featured": false,
      "active": true,
      "category": { "id": "string", "name": "string", "slug": "string" }
    }
  ],
  "total": 20,
  "page": 1,
  "totalPages": 2
}

GET/api/storefront/directory/categories#

GET/api/storefront/directory/:slug#


11. Búsqueda#

GET/api/storefront/search#

Query params:

ParamTipo
qstring (requerido)
pagenumber
limitnumber
sortBystring

GET/api/storefront/search/suggestions#

Query params: q (requerido), limit.

GET/api/storefront/search/popular#

Query params: limit.


12. Checkout#

POST/api/storefront/checkout/cart/calculate#

Calcula totales del carrito con cupones y envío.

Body:

{
  "items": [
    { "productId": "string", "quantity": 2 }
  ],
  "couponCode": "DESCUENTO20",
  "shippingZoneId": "string"
}

Respuesta:

{
  "success": true,
  "cart": {
    "items": [
      {
        "productId": "string",
        "name": "string",
        "image": "string | null",
        "price": 19990,
        "quantity": 2,
        "subtotal": 39980
      }
    ],
    "subtotal": 39980,
    "discount": 7996,
    "shipping": 3990,
    "total": 35974,
    "coupon": {
      "code": "DESCUENTO20",
      "type": "PERCENTAGE",
      "value": 20,
      "discount": 7996
    },
    "promotions": [],
    "shippingInfo": {
      "zoneId": "string",
      "zoneName": "Región Metropolitana",
      "price": 3990,
      "deliveryDays": "3-5 días hábiles",
      "freeFromPromotion": false
    }
  }
}

GET/api/storefront/checkout/payment-methods#

Métodos de pago activos del tenant. Caché: 1 hora.

Respuesta:

{
  "success": true,
  "paymentMethods": [
    {
      "id": "string",
      "provider": "MERCADOPAGO | FLOW | BANK_TRANSFER | KHIPU | KLAP | PAYKU",
      "displayName": "string",
      "logo": "string | null",
      "description": "string",
      "info": "string",
      "bankDetails": {
        "bankName": "string",
        "accountType": "string",
        "accountNumber": "string",
        "accountHolder": "string",
        "rut": "string",
        "email": "string",
        "instructions": "string"
      }
    }
  ]
}

POST/api/storefront/checkout/checkout#

Crea una orden y genera el link de pago.

⚠️ Rate limited. No usar para pruebas en loop.

Body:

{
  "customer": {
    "email": "cliente@ejemplo.com",
    "name": "Nombre Apellido",
    "phone": "+56912345678"
  },
  "shipping": {
    "address": "Av. Las Condes 1234, Santiago",
    "zoneId": "zone-id-string"
  },
  "items": [
    {
      "productId": "string",
      "quantity": 2,
      "variantId": "string (opcional)",
      "slotId": "string (para experiencias con slot)",
      "scheduledDate": "2026-05-01T10:00:00Z (para servicios)",
      "scheduledEndDate": "2026-05-01T11:00:00Z",
      "notes": "string",
      "specialRequests": "string"
    }
  ],
  "couponCode": "DESCUENTO20",
  "paymentMethod": "MERCADOPAGO"
}

Respuesta 201:

{
  "success": true,
  "order": {
    "id": "string",
    "orderNumber": "ORD-2026-0042",
    "total": 35974,
    "paymentUrl": "https://..."
  }
}

GET/api/storefront/checkout/order/:orderNumber#

Estado de una orden.

Respuesta:

{
  "order": {
    "id": "string",
    "orderNumber": "ORD-2026-0042",
    "customerName": "string",
    "customerEmail": "string",
    "total": 35974,
    "status": "PENDING | PAID | PROCESSING | SHIPPED | DELIVERED | CANCELLED",
    "items": [
      {
        "id": "string",
        "quantity": 2,
        "unitPrice": 19990,
        "productSnapshot": {
          "name": "string",
          "price": 19990,
          "sku": "string",
          "images": ["https://..."]
        },
        "product": {
          "type": "PHYSICAL_PRODUCT",
          "files": []
        }
      }
    ]
  }
}

GET/api/storefront/checkout/shipping/calculate#

Calcula opciones de envío para una ubicación.

Query params:

ParamTipo
communestring
regionstring

13. Envíos#

GET/api/storefront/shipping/calculate#

Query params:

ParamTipoDescripción
communeIdstringID de comuna (ej: RM-01)
communeNamestringNombre de comuna
cartWeightnumberPeso total en kg
cartTotalnumberSubtotal en CLP

Respuesta:

{
  "success": true,
  "shippingMode": "simple | advanced",
  "zones": [
    {
      "id": "string",
      "name": "string",
      "price": 3990,
      "shippingCost": 3990,
      "isFree": false,
      "freeAbove": 50000,
      "deliveryDays": "3-5 días hábiles",
      "regions": ["Región Metropolitana"],
      "communes": []
    }
  ],
  "enviame": [
    {
      "id": "enviame-1-ECO",
      "name": "Chilexpress — Económico",
      "price": 4290,
      "shippingCost": 4290,
      "isFree": false,
      "freeAbove": null,
      "deliveryDays": "5 días",
      "carrier": "Chilexpress",
      "carrierId": 1,
      "serviceCode": "ECO",
      "source": "enviame"
    }
  ],
  "pickup": {
    "id": "pickup",
    "name": "Retiro en tienda",
    "shippingCost": 0,
    "isFree": true,
    "freeAbove": null,
    "deliveryDays": null,
    "instructions": "Retirar en Av. Ejemplo 123"
  }
}

GET/api/storefront/shipping/zones#

Zonas de envío configuradas.


14. Leads#

POST/api/storefront/leads#

Body:

{
  "name": "string",
  "email": "string",
  "phone": "string (opcional)",
  "message": "string"
}

Respuesta 201:

{
  "success": true,
  "message": "Thank you for your message. We will get back to you soon!",
  "leadId": "string"
}

15. Formulario de contacto#

POST/api/storefront/contact#

Body:

{
  "name": "string",
  "email": "string",
  "phone": "string (opcional)",
  "company": "string (opcional)",
  "message": "string (mín 10 caracteres)"
}

Respuesta 201:

{
  "success": true,
  "message": "Your message has been sent successfully. We will get back to you soon!"
}

16. Newsletter#

POST/api/storefront/newsletter/subscribe#

Body:

{
  "email": "string",
  "name": "string (opcional)",
  "preferences": {},
  "source": "string (opcional)"
}

GET/api/storefront/newsletter/verify/:token#

Verifica el email del suscriptor.

GET/api/storefront/newsletter/unsubscribe/:token#

Cancela la suscripción.

PUT/api/storefront/newsletter/preferences#

Actualiza preferencias del suscriptor.


17. Autenticación de cliente (Customer)#

Los clientes (compradores) tienen su propio sistema de auth separado del dashboard.

POST/api/storefront/customer-auth/register#

Body:

{
  "email": "string",
  "password": "string (mín 8 caracteres)",
  "name": "string",
  "phone": "string (opcional)"
}

Respuesta 201:

{
  "success": true,
  "token": "jwt",
  "user": { "id": "string", "email": "string", "name": "string" }
}

POST/api/storefront/customer-auth/login#

Body: { "email": "string", "password": "string" }

POST/api/storefront/customer-auth/verify-email#

Body: { "token": "string" }

POST/api/storefront/customer-auth/request-password-reset#

Body: { "email": "string" }

POST/api/storefront/customer-auth/reset-password#

Body: { "token": "string", "newPassword": "string (mín 8 caracteres)" }


18. Cuenta del cliente Requiere auth#

Todos los endpoints de esta sección requieren Authorization: Bearer {customer_jwt}.

GET/api/storefront/customer-account/profile#

Respuesta:

{
  "success": true,
  "user": {
    "id": "string",
    "email": "string",
    "name": "string",
    "phone": "string | null",
    "rut": "string | null",
    "birthday": "ISO date | null",
    "nationality": "string | null",
    "emailVerified": true,
    "createdAt": "ISO date"
  }
}

PUT/api/storefront/customer-account/profile#

Body (todos opcionales): name, phone, rut, birthday, nationality

POST/api/storefront/customer-account/change-password#

Body: { "currentPassword": "string", "newPassword": "string" }

GET/api/storefront/customer-account/addresses#

Respuesta:

{
  "addresses": [
    {
      "id": "string",
      "street": "string",
      "city": "string",
      "state": "string",
      "postalCode": "string",
      "country": "string",
      "isDefault": true,
      "name": "string | null",
      "phone": "string | null",
      "createdAt": "ISO date"
    }
  ]
}

POST/api/storefront/customer-account/addresses#

Body: street, city, state, postalCode, country (requeridos) + isDefault, name, phone (opcionales).

PUT/api/storefront/customer-account/addresses/:addressId#

DELETE/api/storefront/customer-account/addresses/:addressId#

GET/api/storefront/customer-account/orders#

Respuesta:

{
  "orders": [
    {
      "id": "string",
      "orderNumber": "ORD-2026-0042",
      "total": 35974,
      "status": "DELIVERED",
      "createdAt": "ISO date",
      "items": [
        {
          "id": "string",
          "quantity": 2,
          "unitPrice": 19990,
          "product": { "id": "string", "name": "string", "images": ["https://..."] }
        }
      ]
    }
  ]
}

GET/api/storefront/customer-account/orders/:orderId#


19. Lista de deseos Requiere auth#

GET/api/storefront/wishlist#

Respuesta: { "success": true, "items": [...], "count": 3 }

POST/api/storefront/wishlist#

Body: { "productId": "string" }

DELETE/api/storefront/wishlist/:productId#

DELETE/api/storefront/wishlist — Vaciar la lista#

GET/api/storefront/wishlist/check/:productId#

Respuesta: { "inWishlist": true }


20. Carrito abandonado#

POST/api/storefront/cart/abandon#

Registra un carrito abandonado para recuperación.

Body:

{
  "email": "string",
  "customerName": "string (opcional)",
  "items": [{ "productId": "string", "quantity": 2 }],
  "subtotal": 39980,
  "discount": 0,
  "total": 39980,
  "couponCode": "string (opcional)"
}

GET/api/storefront/cart/recover/:token#

Recupera un carrito abandonado por token (enviado por email).

Respuesta:

{
  "success": true,
  "data": {
    "id": "string",
    "email": "string",
    "customerName": "string | null",
    "items": [],
    "subtotal": 39980,
    "discount": 0,
    "total": 39980,
    "couponCode": "string | null",
    "recoveredAt": null,
    "createdAt": "ISO date"
  }
}

410 Gone si el carrito ya fue recuperado anteriormente.


Tipos de producto#

TipoDescripciónTiene stockTiene booking
PHYSICAL_PRODUCTProducto físico
DIGITAL_PRODUCTArchivo descargable
SERVICEServicio agendable
EXPERIENCEExperiencia con slots

21. Reseñas#

Los endpoints de escritura requieren Authorization: Bearer {customer_jwt}.

GET/api/storefront/reviews/product/:productId#

Query params:

ParamTipoDescripción
pagenumberDefault: 1
limitnumberDefault: 10
sortBystringrecent (default), helpful, rating_high, rating_low

Respuesta:

{
  "success": true,
  "reviews": [
    {
      "id": "string",
      "rating": 5,
      "title": "string | null",
      "comment": "string | null",
      "createdAt": "ISO date",
      "customer": { "name": "string" }
    }
  ],
  "pagination": { "page": 1, "limit": 10, "total": 24, "totalPages": 3 }
}

POST/api/storefront/reviews Requiere auth#

Body:

{ "productId": "string", "rating": 5, "title": "string (opcional)", "comment": "string (opcional)" }

GET/api/storefront/reviews/my-review/:productId Requiere auth#

Reseña propia del cliente para un producto.

PUT/api/storefront/reviews/:reviewId Requiere auth#

Body (todos opcionales): rating, title, comment

DELETE/api/storefront/reviews/:reviewId Requiere auth#

POST/api/storefront/reviews/:reviewId/helpful#

Marca una reseña como útil (sin auth).

GET/api/storefront/reviews/my-reviews Requiere auth#

Lista todas las reseñas del cliente autenticado.


22. Servicios#

GET/api/storefront/services#

Query params:

ParamTipoDescripción
pagenumberDefault: 1
limitnumberDefault: 20
categoryIdstring
deliveryMethodstring
requiresSchedulingtrue|false
searchstring

Respuesta:

{
  "success": true,
  "services": [
    {
      "id": "string",
      "name": "string",
      "slug": "string",
      "description": "string",
      "price": 15000,
      "images": ["https://..."],
      "categoryId": "string",
      "createdAt": "ISO date",
      "imageUrl": "string | null"
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 8, "totalPages": 1 }
}

GET/api/storefront/services/:slug#

Incluye availableSlots igual que experiencias.

GET/api/storefront/services/:id/slots#

Query params:

ParamTipoDescripción
startDateISO dateDefault: hoy
endDateISO dateDefault: +30 días

23. Bookings#

POST/api/storefront/bookings#

Crea una reserva para un servicio o experiencia.

Body:

{
  "productId": "string",
  "slotId": "string (opcional)",
  "customerName": "string",
  "customerEmail": "string",
  "customerPhone": "string",
  "participants": 2,
  "scheduledDate": "ISO date (opcional)",
  "scheduledEndDate": "ISO date (opcional)",
  "notes": "string (opcional)",
  "specialRequests": "string (opcional)"
}

Respuesta 201:

{
  "success": true,
  "booking": {
    "id": "string",
    "product": { "id": "string", "name": "string", "slug": "string", "price": 15000 },
    "slot": { "id": "string", "startDateTime": "ISO date", "endDateTime": "ISO date", "price": 15000 },
    "customerName": "string",
    "customerEmail": "string",
    "participants": 2,
    "status": "PENDING",
    "scheduledDate": "ISO date | null",
    "createdAt": "ISO date"
  },
  "message": "string"
}

GET/api/storefront/bookings/:id#

Consultar estado de una reserva.

Query params: email (requerido, para verificar identidad)

Respuesta:

{
  "success": true,
  "booking": {
    "id": "string",
    "product": { "id": "string", "name": "string", "slug": "string", "price": 15000, "images": [] },
    "slot": { "id": "string", "startDateTime": "ISO date", "endDateTime": "ISO date", "capacity": 10 },
    "customerName": "string",
    "customerEmail": "string",
    "customerPhone": "string",
    "participants": 2,
    "status": "PENDING | CONFIRMED | CANCELLED | COMPLETED | NO_SHOW",
    "scheduledDate": "ISO date | null",
    "scheduledEndDate": "ISO date | null",
    "notes": "string | null",
    "specialRequests": "string | null",
    "confirmedAt": "ISO date | null",
    "cancelledAt": "ISO date | null",
    "cancellationReason": "string | null",
    "createdAt": "ISO date"
  }
}

POST/api/storefront/bookings/:id/cancel#

Body:

{ "email": "string", "reason": "string" }

24. Rifas#

GET/api/storefront/raffles#

Query params:

ParamTipoDescripción
pagenumberDefault: 1
limitnumberDefault: 20
statusstringEstado de la rifa
featuredtrue|false
categoryIdstring
categorySlugstring
tagSlugstring
searchstring
hasAvailableTicketstrue|falseSolo rifas con tickets disponibles

GET/api/storefront/raffles/:slug#

Respuesta: incluye availableTickets (número de tickets aún disponibles).

GET/api/storefront/raffles/:slug/available-numbers#

Números de tickets aún disponibles para compra.

POST/api/storefront/raffles/:slug/purchase#

Body:

{
  "email": "string",
  "phone": "string",
  "quantity": 2,
  "ticketNumbers": [1, 2]
}

Respuesta:

{
  "success": true,
  "tickets": [],
  "totalPrice": 10000,
  "expiresAt": "ISO date",
  "message": "string"
}

GET/api/storefront/raffles/:slug/stats#

Respuesta:

{
  "ticketsSold": 150,
  "ticketsRemaining": 350,
  "totalTickets": 500,
  "progress": 30,
  "participantsCount": 80
}

POST/api/storefront/raffles/:slug/verify-draw#

Verifica la transparencia de un sorteo (hash pública).

Body: { "drawHash": "string", "drawSeed": "string" }


25. Tags#

GET/api/storefront/tags#

Respuesta:

{
  "success": true,
  "tags": [
    {
      "id": "string",
      "name": "string",
      "slug": "string",
      "color": "#hex",
      "icon": "string | null",
      "description": "string | null",
      "order": 0,
      "_count": { "productTags": 12 }
    }
  ]
}

GET/api/storefront/tags/:slug#


26. Regiones y comunas (Chile)#

Datos geográficos estáticos de Chile (16 regiones).

GET/api/storefront/regions#

Respuesta:

{
  "success": true,
  "regions": [
    { "id": "string", "code": "RM", "name": "Región Metropolitana", "communeCount": 52 }
  ]
}

GET/api/storefront/regions/:id/communes#

Respuesta:

{
  "success": true,
  "communes": [
    { "id": "string", "code": "RM-01", "name": "Santiago" }
  ]
}

27. Seguimiento de órdenes#

GET/api/storefront/orders/track/:orderNumber#

Query params: email (requerido)

Respuesta:

{
  "success": true,
  "order": {
    "orderNumber": "ORD-2026-0042",
    "status": "SHIPPED",
    "customerName": "string",
    "total": 35974,
    "items": [],
    "tracking": { "carrier": "string", "trackingNumber": "string", "trackingUrl": "string | null" }
  }
}

Errores: ORDER_NOT_FOUND (404), TRACKING_ERROR (500)

GET/api/storefront/orders/track/:orderNumber/timeline#

Query params: email (requerido)

Devuelve la línea de tiempo de eventos de la orden.

POST/api/storefront/orders/track/notify#

Suscribe al cliente para recibir notificaciones de estado de la orden.

Body: { "orderNumber": "string", "email": "string" }


28. Promociones#

GET/api/storefront/promotions#

Promociones activas del tenant.

Respuesta:

{
  "success": true,
  "promotions": [
    {
      "id": "string",
      "name": "string",
      "description": "string | null",
      "type": "PERCENTAGE | FIXED_AMOUNT | FREE_SHIPPING | QUANTITY_DISCOUNT | BUY_X_GET_Y",
      "value": 20,
      "scope": "STORE_WIDE | CATEGORY | PRODUCT",
      "minPurchase": 30000,
      "expiresAt": "ISO date | null"
    }
  ]
}

GET/api/storefront/promotions/banners#

Hasta 3 promociones activas para mostrar en banners/ribbon.


29. Eventos de analytics#

POST/api/storefront/events#

Registra un evento de comportamiento del usuario (page views, conversiones, etc.).

Body:

{
  "type": "page_view | product_view | add_to_cart | remove_from_cart | begin_checkout | add_payment_info | purchase | booking_created | form_view | form_start | lead_submit | contact_submit | newsletter_subscribe | phone_click | whatsapp_click | cta_click | search | category_view | blog_view",
  "eventId": "string (opcional)",
  "sessionId": "string (opcional)",
  "pageUrl": "string (opcional)",
  "utmSource": "string (opcional)",
  "utmMedium": "string (opcional)",
  "utmCampaign": "string (opcional)",
  "utmContent": "string (opcional)",
  "utmTerm": "string (opcional)",
  "fbclid": "string (opcional)",
  "gclid": "string (opcional)",
  "ga4ClientId": "string (opcional)",
  "email": "string (opcional)",
  "payload": {}
}

Respuesta: { "success": true }


30. Academy (módulo de cursos) Requiere auth#

Disponible solo en tenants con el módulo coursesEnabled activo.

Los endpoints marcados con Requiere auth requieren Authorization: Bearer {student_jwt}. El JWT de alumno se obtiene desde los endpoints de auth de academy (distinto al JWT de customer).


Auth de alumnos#

POST/api/academy/auth/register

Body: { "name": "string", "email": "string", "password": "string (mín 8 caracteres)", "phone": "string (opcional)", "referralCode": "string (opcional)" }

Respuesta 201:

{ "success": true, "token": "student_jwt", "student": { "id": "string", "name": "string", "email": "string" } }

POST/api/academy/auth/login

Body: { "email": "string", "password": "string" }

Respuesta: igual a register.

PATCH/api/academy/auth/profile Requiere auth

Body (todos opcionales): name, phone, avatar

POST/api/academy/auth/avatar Requiere auth

Sube una imagen de perfil (multipart/form-data, campo file, máx 2 MB, formatos JPG/PNG/WebP).

Respuesta: { "url": "https://..." }


Catálogo público#

GET/api/academy/courses

Query params:

ParamTipoDescripción
pagenumberDefault: 1
limitnumberDefault: 20

Respuesta:

{
  "courses": [
    {
      "id": "string",
      "title": "string",
      "slug": "string",
      "description": "string | null",
      "price": 49990,
      "coverImage": "string | null",
      "published": true,
      "createdAt": "ISO date"
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 5, "totalPages": 1 }
}

GET/api/academy/courses/:slug

Detalle completo del curso (secciones, lecciones, instructor, precio).


Inscripción y progreso Requiere auth#

POST/api/academy/courses/:courseId/enroll Requiere auth

Body:

{
  "backUrls": {
    "success": "https://...",
    "failure": "https://...",
    "pending": "https://..."
  },
  "referralCode": "string (opcional)",
  "useCredits": true
}

Respuesta (curso gratuito o crédito cubre total):

{ "enrolled": true, "enrollment": { "id": "string", "paymentStatus": "FREE" }, "discountApplied": 0 }

Respuesta (requiere pago):

{ "enrolled": false, "enrollment": { "id": "string", "paymentStatus": "PENDING" }, "checkoutUrl": "https://...", "discountApplied": 4999 }

GET/api/academy/enrollments/:enrollmentId Requiere auth

Estado y detalle de una inscripción.

GET/api/academy/courses/:courseId/progress Requiere auth

Progreso del alumno en el curso (lecciones completadas, porcentaje).


Lecciones Requiere auth#

GET/api/academy/lessons/:lessonId Requiere auth

Contenido de una lección (requiere estar inscrito en el curso padre).

POST/api/academy/lessons/:lessonId/complete Requiere auth

Marca una lección como completada.


Quizzes Requiere auth#

POST/api/academy/quizzes/:quizId/attempt Requiere auth

Body:

{
  "answers": [
    { "questionId": "string", "selectedOptionId": "string" }
  ]
}

Respuesta:

{
  "passed": true,
  "score": 80,
  "correctAnswers": 4,
  "totalQuestions": 5,
  "attemptNumber": 1
}

Foro Requiere auth#

GET/api/academy/courses/:courseId/forum Requiere auth

Query params: lessonId (opcional, filtra threads de esa lección)

POST/api/academy/courses/:courseId/forum/threads Requiere auth

Body: { "title": "string", "content": "string", "lessonId": "string (opcional)" }

GET/api/academy/forum/threads/:threadId Requiere auth

Detalle de un thread con sus respuestas.

POST/api/academy/forum/threads/:threadId/reply Requiere auth

Body: { "content": "string", "imageUrls": ["https://..."] }

PATCH/api/academy/forum/posts/:postId Requiere auth

Body: { "content": "string" }

DELETE/api/academy/forum/posts/:postId Requiere auth

POST/api/academy/forum/posts/:postId/react Requiere auth

Body: { "type": "LIKE | DISLIKE" }

POST/api/academy/forum/upload-image Requiere auth

Sube una imagen para el foro (multipart, campo file, máx 5 MB, solo imágenes).

Respuesta: { "url": "https://..." }


Referidos#

GET/api/academy/referral/code Requiere auth

Obtiene (o crea) el código de referido del alumno y sus estadísticas.

Respuesta:

{
  "code": "JUAN-ABC1",
  "shareUrl": "https://tudominio.com/cursos?ref=JUAN-ABC1",
  "stats": {
    "totalReferrals": 3,
    "pendingCredits": 4999,
    "availableCredits": 9998
  }
}

GET/api/academy/referral/validate?code=XXXX

Valida un código de referido (público, sin auth).

Query params: code (requerido), courseId (opcional, para calcular monto exacto de descuento)

Respuesta:

{ "valid": true, "discount": 10, "discountAmount": 4999 }

GET/api/academy/referral/history Requiere auth

Historial de referidos del alumno.


Biblioteca de recursos#

GET/api/academy/resources Requiere auth

Recursos globales del tenant accesibles para el alumno: recursos públicos globales + recursos concedidos personalmente.

Respuesta:

{
  "success": true,
  "resources": [
    {
      "id": "string",
      "title": "string",
      "description": "string | null",
      "fileUrl": "https://...",
      "fileName": "string",
      "fileType": "PDF | VIDEO | IMAGE | AUDIO | ZIP | FILE",
      "fileSize": 204800,
      "createdAt": "ISO date",
      "granted": false
    }
  ]
}

granted: true indica que fue concedido personalmente por el admin; granted: false es público.

GET/api/academy/courses/:courseId/resources Requiere auth

Recursos del curso específico (públicos + concedidos personalmente). Requiere inscripción activa o gratuita en el curso.


Webhooks (solo plan Max)#

El tenant puede configurar webhooks para recibir eventos en su URL. Eventos disponibles:

EventoDescripción
order.createdNueva orden creada
customer.createdNuevo cliente registrado
abandoned_cart.createdCarrito abandonado registrado
lead.createdNuevo lead desde formulario
contact.createdNuevo mensaje de contacto

Última actualización: 2026-04-17