{"openapi":"3.0.3","info":{"title":"Quilmes Corrugados Quote API","description":"API pública para cotización de cajas de cartón corrugado. Diseñada para integración con LLMs, agentes de IA y sistemas B2B.\n\n## Información del Negocio\n\nQuilmes Corrugados es una fábrica argentina de cajas de cartón corrugado ubicada en Lugones 219, Quilmes, Buenos Aires.\n\n## Cómo cotizar\n\nGET con parámetros en la URL para una medida, POST para hasta 10 medidas juntas. Los dos devuelven el mismo precio, que es el que paga un cliente real.\n\n## Dos canales\n\n- De 500 a 1.000 m²: medidas estándar de catálogo, sin impresión, entrega más rápida. Se cotiza en /cajas y el pedido se cierra por WhatsApp\n- Desde 1.000 m²: producción a medida, con precio por m² que baja según el volumen\n\nLa respuesta incluye el campo `channel` indicando cuál corresponde.\n\n## Restricciones\n\n- Solo fabricamos para Argentina\n- Solo cartón corrugado\n- Mínimo de compra: 500 m² de cartón desplegado. Se mide en superficie, no en cantidad de cajas\n- Medida mínima por caja: 200 x 200 x 100 mm; ancho + alto no puede superar 1200 mm (rollo) y largo + ancho no puede superar 2000 mm (largo de plancha)\n- Cajas cuyo desarrollo de una pieza supera los 2050 mm se fabrican en dos mitades pegadas: el precio ya incluye ese proceso (material extra y 25% de recargo) y la respuesta lo indica en `boxes[].pieces` y `boxes[].pieces_note`\n- Precios en ARS (Peso Argentino) sin IVA\n\n## Rate Limiting\n\n- Sin API key: 10 requests/minuto\n- Con API key: 100 requests/minuto","version":"1.1.0","contact":{"name":"Quilmes Corrugados - Ventas","email":"ventas@quilmescorrugados.com.ar","url":"https://www.quilmescorrugados.com.ar"},"license":{"name":"Propietario","url":"https://www.quilmescorrugados.com.ar/terminos"}},"servers":[{"url":"https://www.quilmescorrugados.com.ar/api/v1","description":"Producción"}],"paths":{"/box-template":{"servers":[{"url":"https://www.quilmescorrugados.com.ar/api","description":"Producción"}],"get":{"summary":"Descargar la plantilla de impresión (PDF)","description":"Genera al instante el PDF de la caja desplegada: líneas de corte, líneas de plegado y las áreas donde puede ir el diseño.\n\nEl cliente descarga el PDF, ubica su arte sobre las áreas marcadas y lo envía a ventas@quilmescorrugados.com.ar o por WhatsApp. Con ese archivo se produce. No hay que solicitar la plantilla ni esperar respuesta: se arma sola con las medidas.\n\nLa impresión aplica desde 1.000 m² (producción a medida), hasta 3 colores. El costo de la impresión está incluido en el precio por m²: aparte solo se cobra el polímero, que es la matriz de impresión, una por color y una sola vez por diseño. Por debajo de ese volumen se venden medidas estándar de catálogo, sin imprimir.","operationId":"getBoxTemplate","tags":["Impresión"],"parameters":[{"name":"length","in":"query","required":true,"description":"Largo en milímetros (mínimo 200)","schema":{"type":"integer","minimum":200,"example":400}},{"name":"width","in":"query","required":true,"description":"Ancho en milímetros (mínimo 200)","schema":{"type":"integer","minimum":200,"example":600}},{"name":"height","in":"query","required":true,"description":"Alto en milímetros (mínimo 100). Ancho + alto no puede superar 1200.","schema":{"type":"integer","minimum":100,"example":600}}],"responses":{"200":{"description":"PDF de la plantilla, listo para ubicar el diseño encima","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Medidas por debajo del mínimo, o ancho + alto mayor a 1200 mm"}}}},"/quote":{"post":{"summary":"Calcular cotización","description":"Calcula el precio de cajas de cartón corrugado según dimensiones, cantidad y opciones de impresión.","operationId":"createQuote","tags":["Cotización"],"security":[{},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteRequest"},"examples":{"simple":{"summary":"Caja simple sin impresión","value":{"boxes":[{"length_mm":400,"width_mm":300,"height_mm":200,"quantity":1000}]}},"withPrinting":{"summary":"Caja con impresión a 2 colores","value":{"boxes":[{"length_mm":500,"width_mm":400,"height_mm":300,"quantity":5000,"has_printing":true,"printing_colors":2}]}},"multiple":{"summary":"Múltiples tipos de cajas","value":{"boxes":[{"length_mm":400,"width_mm":300,"height_mm":200,"quantity":2000},{"length_mm":600,"width_mm":400,"height_mm":400,"quantity":1000,"has_printing":true,"printing_colors":1}]}}}}}},"responses":{"200":{"description":"Cotización calculada exitosamente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponse"}}},"headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Límite de requests por minuto"},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests restantes en la ventana actual"},"X-RateLimit-Reset":{"schema":{"type":"string","format":"date-time"},"description":"Fecha/hora de reset del rate limit"}}},"400":{"description":"Error de validación","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":"Validation failed","errors":["boxes[0].length_mm must be between 200 and 1800"]}}}},"429":{"description":"Rate limit excedido","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":"Rate limit exceeded. Please wait before making more requests.","rate_limit":{"remaining":0,"reset_at":"2025-01-19T12:01:00.000Z"}}}}},"500":{"description":"Error interno del servidor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"summary":"Cotizar una medida (query params)","description":"Devuelve el precio real de una medida usando parámetros en la URL. Pensado para asistentes de IA, que navegan con GET y no pueden enviar un cuerpo JSON. Sin parámetros devuelve la documentación del endpoint.\n\nEjemplo:\n`/api/v1/quote?length_cm=40&width_cm=60&height_cm=60&quantity=3000`\n\nPara cotizar varias medidas de una vez, usar POST.","operationId":"getQuote","tags":["Cotización"],"security":[{},{"ApiKeyAuth":[]}],"parameters":[{"name":"length_mm","in":"query","description":"Largo en milímetros. Alternativa: length_cm (o largo_cm) en centímetros. Regla combinada: largo + ancho no puede superar 2000 mm.","schema":{"type":"integer","minimum":200,"maximum":1800,"example":400}},{"name":"width_mm","in":"query","description":"Ancho en milímetros. Alternativa: width_cm (o ancho_cm). Reglas combinadas: largo + ancho ≤ 2000 mm y ancho + alto ≤ 1200 mm.","schema":{"type":"integer","minimum":200,"maximum":1100,"example":600}},{"name":"height_mm","in":"query","description":"Alto en milímetros. Alternativa: height_cm (o alto_cm). Regla combinada: ancho + alto no puede superar 1200 mm.","schema":{"type":"integer","minimum":100,"maximum":1000,"example":600}},{"name":"quantity","in":"query","description":"Cantidad de cajas. Alias: cantidad, qty.","schema":{"type":"integer","minimum":1,"example":3000}},{"name":"printing_colors","in":"query","description":"Colores de impresión, hasta 3. No suma al precio por m²: la impresión está incluida y aparte solo se cobra el polímero. Si el pedido no llega al volumen mínimo de impresión, se cotiza igual pero sin imprimir, y quote.printing.price_note lo explica.","schema":{"type":"integer","minimum":0,"maximum":3,"default":0}}],"responses":{"200":{"description":"Cotización calculada, o la documentación del endpoint si no se pasaron parámetros","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponse"}}}},"400":{"description":"Faltan parámetros o están fuera de rango"},"429":{"description":"Se superó el límite de consultas por minuto"}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key para rate limit extendido (100 req/min)"}},"schemas":{"QuoteRequest":{"type":"object","required":["boxes"],"properties":{"boxes":{"type":"array","minItems":1,"maxItems":10,"description":"Lista de cajas a cotizar (máximo 10)","items":{"$ref":"#/components/schemas/BoxInput"}}}},"BoxInput":{"type":"object","required":["length_mm","width_mm","height_mm","quantity"],"properties":{"length_mm":{"type":"integer","minimum":200,"maximum":1800,"description":"Largo de la caja en milímetros. Regla combinada: largo + ancho ≤ 2000 mm (largo máximo de plancha, incluso fabricando en dos mitades)."},"width_mm":{"type":"integer","minimum":200,"maximum":1100,"description":"Ancho de la caja en milímetros. Reglas combinadas: largo + ancho ≤ 2000 mm y ancho + alto ≤ 1200 mm (ancho del rollo)."},"height_mm":{"type":"integer","minimum":100,"maximum":1000,"description":"Alto de la caja en milímetros. Regla combinada: ancho + alto ≤ 1200 mm."},"quantity":{"type":"integer","minimum":1,"description":"Cantidad de cajas"},"has_printing":{"type":"boolean","default":false,"description":"Si la caja tiene impresión"},"printing_colors":{"type":"integer","minimum":0,"maximum":3,"default":0,"description":"Cantidad de colores de impresión (0-3). La impresión está incluida en el precio por m²; aparte solo se cobra el polímero. Si el pedido no llega al volumen mínimo de impresión, se cotiza igual pero sin imprimir, y quote.printing.price_note lo explica"}}},"QuoteResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"quote":{"$ref":"#/components/schemas/Quote"},"rate_limit":{"$ref":"#/components/schemas/RateLimitInfo"}}},"Quote":{"type":"object","description":"Resultado de cotizar. Es una unión discriminada por `cotizable`: cuando `cotizable=true` el pedido se puede vender y `subtotal`, `tax_amount` y `total_with_tax` traen números; cuando `cotizable=false` esos tres campos vienen en null y el `impedimento` explica por qué no se puede tomar el pedido. Nunca leer el precio sin haber chequeado `cotizable` antes: si es false, no hay precio a comunicar y no hay que invitar a negociar cantidades.","required":["cotizable","impedimento","subtotal","tax_amount","total_with_tax","tax_rate","subtotal_includes_tax"],"discriminator":{"propertyName":"cotizable"},"properties":{"cotizable":{"type":"boolean","description":"Discriminador. `true` cuando el pedido se puede vender (llega al mínimo, la medida se fabrica, la cantidad alcanza para el canal correspondiente). `false` cuando no se puede: mirar `impedimento` para saber por qué."},"impedimento":{"description":"Por qué el pedido no se puede tomar. `null` cuando `cotizable=true`; un objeto `Impedimento` cuando `cotizable=false`.","oneOf":[{"$ref":"#/components/schemas/Impedimento"},{"type":"null"}]},"boxes":{"type":"array","items":{"$ref":"#/components/schemas/BoxResult"}},"total_m2":{"type":"number","description":"Total de metros cuadrados de cartón desplegado del pedido"},"subtotal":{"type":["number","null"],"description":"Precio total SIN IVA en ARS. Es null cuando `cotizable=false`: si el pedido no se puede vender no hay precio, y comunicar un cero o un aproximado abre una negociación de cantidad que el mínimo excluyente existe para evitar."},"tax_amount":{"type":["number","null"],"description":"Monto del IVA en ARS, calculado sobre `subtotal` (21%). Va explícito para que quien lea la respuesta no tenga que multiplicar por su cuenta. Es null cuando `cotizable=false`."},"total_with_tax":{"type":["number","null"],"description":"Total en ARS con IVA 21% incluido. Es lo que paga el cliente. Va calculado, no dejado como cuenta para el consumidor de la API: un modelo que tiene que hacer la multiplicación alguna vez la hace mal, y este es un número que va en la orden de compra. Es null cuando `cotizable=false`."},"tax_rate":{"type":"number","example":0.21,"description":"Alícuota del IVA (21% general de Argentina). `subtotal` NUNCA incluye IVA; `total_with_tax` SIEMPRE lo incluye."},"subtotal_includes_tax":{"type":"boolean","enum":[false],"description":"Explícito para que nadie asuma lo contrario: el subtotal va sin IVA."},"currency":{"type":"string","enum":["ARS"],"description":"Moneda (siempre ARS)"},"estimated_days":{"type":"integer","description":"Días hábiles de producción estimados"},"valid_until":{"type":"string","format":"date","description":"Fecha hasta la cual el precio es válido"},"minimum_m2":{"type":"integer","example":500,"description":"Mínimo de m² de cartón para poder vender el pedido. Una medida propia, troquelada o impresa, arranca en 1.000 m²"},"meets_minimum":{"type":"boolean","description":"Si el pedido cumple con el mínimo requerido"}}},"Impedimento":{"type":"object","description":"Por qué un pedido no se puede tomar. Son tres motivos distintos, discriminados por `tipo`, y conviene no mezclarlos:\n- `bajo_minimo`: el pedido no llega al piso de 500 m². Se arregla comprando más de la misma medida.\n- `medida_propia_sin_volumen`: pidió una medida fuera del catálogo con un volumen que solo alcanza para stock. Se arregla eligiendo una medida estándar o subiendo bastante más el volumen (mínimo 1.000 m² para producción a medida).\n- `no_fabricable`: la caja no entra en el rollo (por ejemplo ancho + alto > 1.200 mm, o medida por debajo del mínimo). No se arregla comprando más: no hay cantidad que la haga fabricable.","required":["tipo","motivo","alternativas"],"properties":{"tipo":{"type":"string","enum":["bajo_minimo","medida_propia_sin_volumen","no_fabricable"],"description":"Discriminador del impedimento."},"motivo":{"type":"string","description":"Explicación en castellano de por qué no se puede tomar el pedido, lista para leerle al usuario."},"cajas_necesarias":{"type":["integer","null"],"description":"Cuántas cajas de ESTA medida hacen falta para poder avanzar. Solo aparece con valor en `bajo_minimo` y `medida_propia_sin_volumen`; en `no_fabricable` viene null o ausente porque ninguna cantidad resuelve el problema."},"m2_faltantes":{"type":"number","description":"Cuántos m² faltan para llegar al mínimo aplicable. Solo tiene sentido en `bajo_minimo` y `medida_propia_sin_volumen`; en `no_fabricable` no se debe usar como sugerencia."},"alternativas":{"type":"array","description":"Medidas de catálogo parecidas ya cotizadas al mínimo. Existe para que decir 'no se puede' venga siempre acompañado de un 'esto sí', con precio incluido, sin obligar al cliente a pedir otra cotización.","items":{"$ref":"#/components/schemas/AlternativaDeCatalogo"}}}},"AlternativaDeCatalogo":{"type":"object","description":"Una medida de catálogo que sí se puede vender, con su precio al mínimo. Se ofrece como alternativa cuando la medida pedida trae un impedimento.","properties":{"length_mm":{"type":"integer","description":"Largo en mm"},"width_mm":{"type":"integer","description":"Ancho en mm"},"height_mm":{"type":"integer","description":"Alto en mm"},"cantidad":{"type":"integer","description":"Cuántas cajas de ESTA medida son el mínimo de compra."},"m2":{"type":"number","description":"m² de cartón desplegado para esa cantidad."},"precio_por_caja":{"type":"number","description":"Precio unitario por caja en ARS, sin IVA."},"subtotal":{"type":"number","description":"Subtotal en ARS, sin IVA."},"total_con_iva":{"type":"number","description":"Total en ARS con IVA 21% incluido."},"stock":{"type":"integer","description":"Stock disponible de esta medida de catálogo."},"diferencia_mm":{"type":"integer","description":"Qué tan distinta es de la medida pedida, sumando las tres dimensiones en mm."},"entra":{"type":"boolean","description":"Si lo que iba a entrar en la caja pedida entra en esta. NO filtra ni reordena: viaja como dato para que quien conteste pueda decir 'esta es un poco más chica' en vez de decidir por el cliente."}}},"BoxResult":{"type":"object","description":"Datos de una caja en la respuesta. Los tres campos de precio (`price_per_m2`, `unit_price`, `subtotal`) vienen en null cuando `Quote.cotizable=false`: si el pedido no se puede vender, esta caja tampoco tiene precio individual. Son null y no cero a propósito, para que un cero no se imprima como '$0' y pase por una oferta rota.","properties":{"length_mm":{"type":"integer"},"width_mm":{"type":"integer"},"height_mm":{"type":"integer"},"quantity":{"type":"integer"},"has_printing":{"type":"boolean"},"printing_colors":{"type":"integer"},"sheet_width_mm":{"type":"integer","description":"Ancho de la plancha desplegada en mm"},"sheet_length_mm":{"type":"integer","description":"Largo de CADA plancha en mm. Si pieces=1 es el desarrollo completo; si pieces=2 es el largo de cada mitad (medio perímetro más su solapa)."},"pieces":{"type":"integer","enum":[1,2],"description":"1 = la caja sale de una plancha. 2 = se fabrica en dos mitades pegadas porque el desarrollo de una pieza supera el largo máximo de plancha (2050 mm); el precio ya incluye el material de la segunda solapa y un 25% por el pegado."},"pieces_note":{"type":"string","description":"Solo presente cuando pieces=2: explicación en castellano del proceso de dos mitades, lista para mostrarle al cliente."},"sqm_per_box":{"type":"number","description":"Metros cuadrados por caja"},"total_sqm":{"type":"number","description":"Total m² para esta caja × cantidad"},"price_per_m2":{"type":["number","null"],"description":"Precio por m² aplicado en ARS, sin IVA. Null cuando el pedido no es cotizable."},"unit_price":{"type":["number","null"],"description":"Precio unitario por caja en ARS, sin IVA. Null cuando el pedido no es cotizable."},"subtotal":{"type":["number","null"],"description":"Subtotal para este tipo de caja en ARS, sin IVA. Null cuando el pedido no es cotizable."}}},"RateLimitInfo":{"type":"object","properties":{"remaining":{"type":"integer","description":"Requests restantes en la ventana actual"},"reset_at":{"type":"string","format":"date-time","description":"Fecha/hora de reset del rate limit"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","description":"Mensaje de error principal"},"errors":{"type":"array","items":{"type":"string"},"description":"Lista detallada de errores de validación"},"rate_limit":{"$ref":"#/components/schemas/RateLimitInfo"}}}}},"tags":[{"name":"Cotización","description":"Endpoints para calcular cotizaciones de cajas"},{"name":"Información","description":"Información sobre la API"}],"externalDocs":{"description":"Documentación completa","url":"https://www.quilmescorrugados.com.ar/api/v1/docs"}}