{
	"openapi": "3.0.3",
	"info": {
		"title": "Mini Bodegas San Pedro API",
		"version": "1.3.0",
		"description": "API pública de solo lectura, sin autenticación. Expone el catálogo de tamaños de bodega y los datos del negocio (contacto, ubicación, horario, preguntas frecuentes). Los precios no son públicos: cada tamaño se cotiza por WhatsApp con el enlace quoteUrl de cada unidad. Todas las respuestas son JSON con CORS abierto.\n\nVersionado: la versión va en la ruta (/api/v1/). Las rutas sin versión (/api/units, /api/business) son alias permanentes de la versión estable más reciente. Los cambios compatibles (campos nuevos) llegan a v1 sin aviso; un cambio incompatible se publica como /api/v2/ sin tocar v1. Política de deprecación: si una versión va a retirarse, sus respuestas llevan los encabezados Deprecation y Sunset al menos 90 días antes de la fecha de retiro, y el retiro se anuncia en https://www.minibodegassanpedro.com/deprecation-policy/ y en /docs/.\n\nLímites de uso: 60 solicitudes por minuto por dirección IP (orientativo). Cada respuesta incluye los encabezados RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset y RateLimit-Policy; al exceder el límite el API responde 429 con Retry-After.\n\nDeprecation policy (English): this API is URL-versioned (/api/v1/); /api/units and /api/business are permanent aliases of the latest stable version. Backward-compatible changes land in v1 without notice; a breaking change ships as /api/v2/. If a version is scheduled for retirement, its responses carry the Deprecation (RFC 9745) and Sunset (RFC 8594) headers at least 90 days before the sunset date, and the retirement is announced at https://www.minibodegassanpedro.com/deprecation-policy/. No version is currently deprecated.",
		"contact": {
			"name": "Mini Bodegas San Pedro",
			"url": "https://www.minibodegassanpedro.com/contacto/"
		}
	},
	"externalDocs": {
		"description": "Guía para agentes y desarrolladores",
		"url": "https://www.minibodegassanpedro.com/docs/"
	},
	"servers": [
		{
			"url": "https://www.minibodegassanpedro.com"
		}
	],
	"tags": [
		{
			"name": "informacion",
			"description": "Información pública del negocio"
		}
	],
	"paths": {
		"/api/v1/units": {
			"get": {
				"operationId": "listStorageUnits",
				"tags": [
					"informacion"
				],
				"summary": "Catálogo de tamaños de bodega",
				"description": "Devuelve todos los tamaños disponibles (locker de 1 m² a bodegas de 100 m², parqueo y a la medida) con su descripción y un enlace de cotización por WhatsApp. No incluye precios: el modelo de precios es por cotización. Alias sin versión: /api/units.",
				"parameters": [
					{
						"name": "minM2",
						"in": "query",
						"required": false,
						"description": "Área mínima en metros cuadrados. Filtra el catálogo a las unidades cuya área máxima alcanzable (maxAreaM2 cuando existe, si no areaM2) es mayor o igual a este valor; parqueo y a la medida no tienen área y quedan fuera. Un valor no numérico o negativo produce un 400 invalid_parameter.",
						"schema": {
							"type": "number",
							"minimum": 0,
							"example": 12
						}
					}
				],
				"responses": {
					"200": {
						"description": "Catálogo de unidades.",
						"headers": {
							"RateLimit-Limit": {
								"description": "Solicitudes permitidas por ventana (60).",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Remaining": {
								"description": "Solicitudes restantes en la ventana actual.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Reset": {
								"description": "Segundos hasta que la ventana se reinicia.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Policy": {
								"description": "Política de límite: 60;w=60.",
								"schema": {
									"type": "string"
								}
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "Catálogo completo de tamaños de bodega.",
									"required": [
										"business",
										"pricing",
										"units"
									],
									"properties": {
										"business": {
											"type": "object",
											"required": [
												"name",
												"url"
											],
											"properties": {
												"name": {
													"type": "string"
												},
												"url": {
													"type": "string",
													"format": "uri"
												}
											}
										},
										"pricing": {
											"type": "object",
											"description": "Modelo de precios: cotización por WhatsApp, sin lista pública.",
											"required": [
												"model",
												"note"
											],
											"properties": {
												"model": {
													"type": "string",
													"enum": [
														"quote"
													]
												},
												"note": {
													"type": "string"
												}
											}
										},
										"filters": {
											"type": "object",
											"description": "Eco de los filtros activos. Solo aparece cuando se envió un parámetro de filtro.",
											"properties": {
												"minM2": {
													"type": "number",
													"minimum": 0
												}
											}
										},
										"units": {
											"type": "array",
											"items": {
												"type": "object",
												"description": "Un tamaño de bodega que se puede cotizar.",
												"required": [
													"id",
													"name",
													"sizeLabel",
													"areaM2",
													"description",
													"quoteUrl"
												],
												"properties": {
													"id": {
														"type": "string",
														"description": "Identificador estable de la unidad.",
														"example": "m-12"
													},
													"name": {
														"type": "string",
														"description": "Nombre comercial.",
														"example": "Mediana (M)"
													},
													"sizeLabel": {
														"type": "string",
														"description": "Tamaño tal como se muestra en el sitio.",
														"example": "12mts²"
													},
													"areaM2": {
														"type": "number",
														"nullable": true,
														"description": "Área en metros cuadrados; para un grupo de tamaños combinados es el área mínima del grupo; null para parqueo o a la medida."
													},
													"maxAreaM2": {
														"type": "number",
														"description": "Solo en grupos de tamaños combinados: el área máxima que alcanza el grupo."
													},
													"description": {
														"type": "string",
														"description": "Para qué sirve este tamaño."
													},
													"quoteUrl": {
														"type": "string",
														"format": "uri",
														"description": "Enlace de WhatsApp con el mensaje de cotización prellenado."
													}
												}
											}
										},
										"docs": {
											"type": "string",
											"format": "uri"
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "Parámetro inválido: minM2 no numérico o negativo.",
						"headers": {
							"RateLimit-Limit": {
								"description": "Solicitudes permitidas por ventana (60).",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Remaining": {
								"description": "Solicitudes restantes en la ventana actual.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Reset": {
								"description": "Segundos hasta que la ventana se reinicia.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Policy": {
								"description": "Política de límite: 60;w=60.",
								"schema": {
									"type": "string"
								}
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"405": {
						"description": "Método no permitido (solo GET).",
						"headers": {
							"RateLimit-Limit": {
								"description": "Solicitudes permitidas por ventana (60).",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Remaining": {
								"description": "Solicitudes restantes en la ventana actual.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Reset": {
								"description": "Segundos hasta que la ventana se reinicia.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Policy": {
								"description": "Política de límite: 60;w=60.",
								"schema": {
									"type": "string"
								}
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"429": {
						"description": "Límite de solicitudes excedido. Espere lo que indique Retry-After y reintente.",
						"headers": {
							"RateLimit-Limit": {
								"description": "Solicitudes permitidas por ventana (60).",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Remaining": {
								"description": "Solicitudes restantes en la ventana actual.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Reset": {
								"description": "Segundos hasta que la ventana se reinicia.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Policy": {
								"description": "Política de límite: 60;w=60.",
								"schema": {
									"type": "string"
								}
							},
							"Retry-After": {
								"description": "Segundos que hay que esperar antes de reintentar.",
								"schema": {
									"type": "string"
								}
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"default": {
						"description": "Cualquier otro error generado por la aplicación: misma estructura tipada { error, code, hint, docs, openapi }. Los errores de plataforma (5xx del CDN) pueden no seguir esta estructura.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					}
				}
			}
		},
		"/api/v1/business": {
			"get": {
				"operationId": "getBusinessInfo",
				"tags": [
					"informacion"
				],
				"summary": "Datos del negocio",
				"description": "Devuelve identidad, contacto (WhatsApp, teléfono, formulario de reserva), ubicación con coordenadas, horario de acceso y preguntas frecuentes. Con el parámetro fields la respuesta incluye solo las secciones pedidas más name y url. Alias sin versión: /api/business.",
				"parameters": [
					{
						"name": "fields",
						"in": "query",
						"required": false,
						"description": "Lista separada por comas de secciones a incluir: description, contact, location, hours, faq, docs. name y url siempre se incluyen. Nombres desconocidos se ignoran; sin el parámetro se devuelve todo.",
						"schema": {
							"type": "string",
							"example": "contact,location"
						}
					}
				],
				"responses": {
					"200": {
						"description": "Datos del negocio.",
						"headers": {
							"RateLimit-Limit": {
								"description": "Solicitudes permitidas por ventana (60).",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Remaining": {
								"description": "Solicitudes restantes en la ventana actual.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Reset": {
								"description": "Segundos hasta que la ventana se reinicia.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Policy": {
								"description": "Política de límite: 60;w=60.",
								"schema": {
									"type": "string"
								}
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "Identidad, contacto, ubicación, horario y preguntas frecuentes del negocio.",
									"required": [
										"name",
										"url"
									],
									"properties": {
										"name": {
											"type": "string",
											"example": "Mini Bodegas San Pedro"
										},
										"description": {
											"type": "string"
										},
										"url": {
											"type": "string",
											"format": "uri"
										},
										"contact": {
											"type": "object",
											"required": [
												"whatsapp",
												"phone",
												"bookingForm",
												"languages"
											],
											"properties": {
												"whatsapp": {
													"type": "object",
													"required": [
														"number",
														"url"
													],
													"properties": {
														"number": {
															"type": "string",
															"example": "+50688252148"
														},
														"url": {
															"type": "string",
															"format": "uri"
														}
													}
												},
												"phone": {
													"type": "string",
													"description": "Teléfono en formato E.164."
												},
												"bookingForm": {
													"type": "string",
													"format": "uri",
													"description": "Formulario web de reserva."
												},
												"languages": {
													"type": "array",
													"items": {
														"type": "string"
													},
													"description": "Idiomas de atención."
												}
											}
										},
										"location": {
											"type": "object",
											"required": [
												"address",
												"geo",
												"mapsUrl"
											],
											"properties": {
												"address": {
													"type": "string",
													"description": "Dirección completa en una línea."
												},
												"streetAddress": {
													"type": "string"
												},
												"locality": {
													"type": "string"
												},
												"region": {
													"type": "string"
												},
												"country": {
													"type": "string",
													"description": "Código de país ISO 3166-1 alpha-2."
												},
												"geo": {
													"type": "object",
													"required": [
														"latitude",
														"longitude"
													],
													"properties": {
														"latitude": {
															"type": "number"
														},
														"longitude": {
															"type": "number"
														}
													}
												},
												"mapsUrl": {
													"type": "string",
													"format": "uri"
												}
											}
										},
										"hours": {
											"type": "object",
											"required": [
												"access"
											],
											"properties": {
												"access": {
													"type": "string",
													"example": "24/7"
												},
												"note": {
													"type": "string"
												}
											}
										},
										"faq": {
											"type": "array",
											"description": "Preguntas frecuentes con sus respuestas.",
											"items": {
												"type": "object",
												"required": [
													"question",
													"answer"
												],
												"properties": {
													"question": {
														"type": "string"
													},
													"answer": {
														"type": "string"
													}
												}
											}
										},
										"docs": {
											"type": "string",
											"format": "uri"
										}
									}
								}
							}
						}
					},
					"405": {
						"description": "Método no permitido (solo GET).",
						"headers": {
							"RateLimit-Limit": {
								"description": "Solicitudes permitidas por ventana (60).",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Remaining": {
								"description": "Solicitudes restantes en la ventana actual.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Reset": {
								"description": "Segundos hasta que la ventana se reinicia.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Policy": {
								"description": "Política de límite: 60;w=60.",
								"schema": {
									"type": "string"
								}
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"429": {
						"description": "Límite de solicitudes excedido. Espere lo que indique Retry-After y reintente.",
						"headers": {
							"RateLimit-Limit": {
								"description": "Solicitudes permitidas por ventana (60).",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Remaining": {
								"description": "Solicitudes restantes en la ventana actual.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Reset": {
								"description": "Segundos hasta que la ventana se reinicia.",
								"schema": {
									"type": "string"
								}
							},
							"RateLimit-Policy": {
								"description": "Política de límite: 60;w=60.",
								"schema": {
									"type": "string"
								}
							},
							"Retry-After": {
								"description": "Segundos que hay que esperar antes de reintentar.",
								"schema": {
									"type": "string"
								}
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"default": {
						"description": "Cualquier otro error generado por la aplicación: misma estructura tipada { error, code, hint, docs, openapi }. Los errores de plataforma (5xx del CDN) pueden no seguir esta estructura.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					}
				}
			}
		}
	},
	"components": {
		"schemas": {
			"StorageUnit": {
				"type": "object",
				"description": "Un tamaño de bodega que se puede cotizar.",
				"required": [
					"id",
					"name",
					"sizeLabel",
					"areaM2",
					"description",
					"quoteUrl"
				],
				"properties": {
					"id": {
						"type": "string",
						"description": "Identificador estable de la unidad.",
						"example": "m-12"
					},
					"name": {
						"type": "string",
						"description": "Nombre comercial.",
						"example": "Mediana (M)"
					},
					"sizeLabel": {
						"type": "string",
						"description": "Tamaño tal como se muestra en el sitio.",
						"example": "12mts²"
					},
					"areaM2": {
						"type": "number",
						"nullable": true,
						"description": "Área en metros cuadrados; para un grupo de tamaños combinados es el área mínima del grupo; null para parqueo o a la medida."
					},
					"maxAreaM2": {
						"type": "number",
						"description": "Solo en grupos de tamaños combinados: el área máxima que alcanza el grupo."
					},
					"description": {
						"type": "string",
						"description": "Para qué sirve este tamaño."
					},
					"quoteUrl": {
						"type": "string",
						"format": "uri",
						"description": "Enlace de WhatsApp con el mensaje de cotización prellenado."
					}
				}
			},
			"UnitsResponse": {
				"type": "object",
				"description": "Catálogo completo de tamaños de bodega.",
				"required": [
					"business",
					"pricing",
					"units"
				],
				"properties": {
					"business": {
						"type": "object",
						"required": [
							"name",
							"url"
						],
						"properties": {
							"name": {
								"type": "string"
							},
							"url": {
								"type": "string",
								"format": "uri"
							}
						}
					},
					"pricing": {
						"type": "object",
						"description": "Modelo de precios: cotización por WhatsApp, sin lista pública.",
						"required": [
							"model",
							"note"
						],
						"properties": {
							"model": {
								"type": "string",
								"enum": [
									"quote"
								]
							},
							"note": {
								"type": "string"
							}
						}
					},
					"filters": {
						"type": "object",
						"description": "Eco de los filtros activos. Solo aparece cuando se envió un parámetro de filtro.",
						"properties": {
							"minM2": {
								"type": "number",
								"minimum": 0
							}
						}
					},
					"units": {
						"type": "array",
						"items": {
							"type": "object",
							"description": "Un tamaño de bodega que se puede cotizar.",
							"required": [
								"id",
								"name",
								"sizeLabel",
								"areaM2",
								"description",
								"quoteUrl"
							],
							"properties": {
								"id": {
									"type": "string",
									"description": "Identificador estable de la unidad.",
									"example": "m-12"
								},
								"name": {
									"type": "string",
									"description": "Nombre comercial.",
									"example": "Mediana (M)"
								},
								"sizeLabel": {
									"type": "string",
									"description": "Tamaño tal como se muestra en el sitio.",
									"example": "12mts²"
								},
								"areaM2": {
									"type": "number",
									"nullable": true,
									"description": "Área en metros cuadrados; para un grupo de tamaños combinados es el área mínima del grupo; null para parqueo o a la medida."
								},
								"maxAreaM2": {
									"type": "number",
									"description": "Solo en grupos de tamaños combinados: el área máxima que alcanza el grupo."
								},
								"description": {
									"type": "string",
									"description": "Para qué sirve este tamaño."
								},
								"quoteUrl": {
									"type": "string",
									"format": "uri",
									"description": "Enlace de WhatsApp con el mensaje de cotización prellenado."
								}
							}
						}
					},
					"docs": {
						"type": "string",
						"format": "uri"
					}
				}
			},
			"BusinessInfo": {
				"type": "object",
				"description": "Identidad, contacto, ubicación, horario y preguntas frecuentes del negocio.",
				"required": [
					"name",
					"url"
				],
				"properties": {
					"name": {
						"type": "string",
						"example": "Mini Bodegas San Pedro"
					},
					"description": {
						"type": "string"
					},
					"url": {
						"type": "string",
						"format": "uri"
					},
					"contact": {
						"type": "object",
						"required": [
							"whatsapp",
							"phone",
							"bookingForm",
							"languages"
						],
						"properties": {
							"whatsapp": {
								"type": "object",
								"required": [
									"number",
									"url"
								],
								"properties": {
									"number": {
										"type": "string",
										"example": "+50688252148"
									},
									"url": {
										"type": "string",
										"format": "uri"
									}
								}
							},
							"phone": {
								"type": "string",
								"description": "Teléfono en formato E.164."
							},
							"bookingForm": {
								"type": "string",
								"format": "uri",
								"description": "Formulario web de reserva."
							},
							"languages": {
								"type": "array",
								"items": {
									"type": "string"
								},
								"description": "Idiomas de atención."
							}
						}
					},
					"location": {
						"type": "object",
						"required": [
							"address",
							"geo",
							"mapsUrl"
						],
						"properties": {
							"address": {
								"type": "string",
								"description": "Dirección completa en una línea."
							},
							"streetAddress": {
								"type": "string"
							},
							"locality": {
								"type": "string"
							},
							"region": {
								"type": "string"
							},
							"country": {
								"type": "string",
								"description": "Código de país ISO 3166-1 alpha-2."
							},
							"geo": {
								"type": "object",
								"required": [
									"latitude",
									"longitude"
								],
								"properties": {
									"latitude": {
										"type": "number"
									},
									"longitude": {
										"type": "number"
									}
								}
							},
							"mapsUrl": {
								"type": "string",
								"format": "uri"
							}
						}
					},
					"hours": {
						"type": "object",
						"required": [
							"access"
						],
						"properties": {
							"access": {
								"type": "string",
								"example": "24/7"
							},
							"note": {
								"type": "string"
							}
						}
					},
					"faq": {
						"type": "array",
						"description": "Preguntas frecuentes con sus respuestas.",
						"items": {
							"type": "object",
							"required": [
								"question",
								"answer"
							],
							"properties": {
								"question": {
									"type": "string"
								},
								"answer": {
									"type": "string"
								}
							}
						}
					},
					"docs": {
						"type": "string",
						"format": "uri"
					}
				}
			},
			"Error": {
				"type": "object",
				"description": "Estructura de todo error del API.",
				"required": [
					"error",
					"code"
				],
				"properties": {
					"error": {
						"type": "string",
						"description": "Mensaje del error, en español."
					},
					"code": {
						"type": "string",
						"description": "Código estable del error. Códigos actuales: not_found, method_not_allowed, rate_limited, invalid_parameter. La lista puede crecer sin cambio de versión; trate valores desconocidos como error genérico."
					},
					"hint": {
						"type": "string",
						"description": "Cómo resolver el error."
					},
					"docs": {
						"type": "string",
						"format": "uri",
						"description": "Guía para agentes y desarrolladores."
					},
					"openapi": {
						"type": "string",
						"format": "uri",
						"description": "URL de esta especificación."
					}
				}
			}
		},
		"headers": {
			"Deprecation": {
				"description": "Presente solo cuando la versión del API que respondió está deprecada (RFC 9745). Hoy no se envía: ninguna versión está deprecada.",
				"schema": {
					"type": "string"
				}
			},
			"Sunset": {
				"description": "Fecha de retiro de una versión deprecada (RFC 8594). Se envía junto con Deprecation al menos 90 días antes del retiro.",
				"schema": {
					"type": "string"
				}
			}
		}
	}
}