{
  "openapi": "3.1.0",
  "info": {
    "title": "Mecha Apps — API unificada",
    "version": "1.0.0",
    "description": "Una conexión por empresa para las apps suscriptas. El servidor intersecta los scopes de la conexión con los permisos y suscripciones vigentes del usuario que la creó. Fichar usa scopes hours.read/hours.write. Este archivo describe el contrato de integración; su publicación no acredita que el backend esté habilitado. No es un servidor MCP."
  },
  "servers": [
    {
      "url": "https://mecha-apps.com"
    }
  ],
  "security": [
    {
      "MechaApiKey": []
    }
  ],
  "paths": {
    "/api/v1/operations": {
      "post": {
        "operationId": "mechaOperation",
        "summary": "Leer o modificar una app de Mecha",
        "description": "Enviar una operación y su input. Para una escritura generar Idempotency-Key UUID y conservarlo sin cambios al reintentar esa misma solicitud. No enviar Cookie junto a Bearer, credenciales por query ni campos de autoridad dentro de input. No se permiten administración de conexiones, pruebas o cobros desde la IA.",
        "x-openai-isConsequential": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Obligatorio para create/update/save. Un UUID nuevo por intención; reutilizar el mismo UUID y cuerpo sólo para reintentar la misma escritura. Las lecturas no lo requieren."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OperationRequest"
              },
              "examples": {
                "readCalendar": {
                  "value": {
                    "operation": "calendar.actions.list",
                    "input": {
                      "limit": 100,
                      "offset": 0
                    }
                  }
                },
                "createEmployee": {
                  "value": {
                    "operation": "fichar.employees.create",
                    "input": {
                      "displayName": "Empleado de prueba",
                      "status": "active"
                    }
                  }
                },
                "studyDay": {
                  "value": {
                    "operation": "fichar.time_off.create",
                    "input": {
                      "employeeId": "22222222-2222-4222-8222-222222222222",
                      "startDate": "2026-11-23",
                      "endDate": "2026-11-23",
                      "reason": "estudio",
                      "notes": "Examen final"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operación completada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResult"
                }
              }
            }
          },
          "400": {
            "description": "Solicitud inválida o Idempotency-Key faltante.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Credencial ausente o mal formada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Conexión revocada/vencida, empresa incorrecta, suscripción o permiso insuficiente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Usar POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Venció el plazo de lectura del cuerpo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Versión desactualizada, clave de idempotencia reutilizada con otra solicitud o ausencia superpuesta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Cuerpo mayor a 128 KiB, input normalizado mayor a 64 KiB o acción de Calendario normalizada mayor a 24 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Servicio sin habilitar o no disponible. No asumir que una escritura falló definitivamente: conservar su Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "MechaApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "mca_<64 hexadecimal characters>",
        "description": "Guardar la clave en el campo seguro de autenticación de la IA/integración. Nunca incluirla en mensajes, URLs o logs."
      }
    },
    "schemas": {
      "Pagination": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 100
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1000000,
            "default": 0
          }
        },
        "required": [],
        "description": "Paginación por offset. Seguir leyendo con offset + limit mientras se reciba una página completa. El orden es estable por fecha e ID; las altas concurrentes pueden cambiar las páginas."
      },
      "EmployeePagination": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 100
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1000000,
            "default": 0
          },
          "employeeId": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": []
      },
      "EmptyInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {},
        "required": []
      },
      "ChannelContent": {
        "type": "object",
        "maxProperties": 16,
        "propertyNames": {
          "pattern": "^[A-Za-z][A-Za-z0-9_-]{0,47}$"
        },
        "additionalProperties": {
          "type": "string",
          "maxLength": 10000
        },
        "description": "Contenido textual del canal. Campos habituales: content, copy, type, imageUrl, link, subject, subtitle, body, title, text y cta. No admite adjuntos como objetos ni arrays."
      },
      "CalendarDay": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID nuevo para cada día; conservarlo al editar. No repetirlo dentro de una acción."
          },
          "title": {
            "type": "string",
            "maxLength": 160
          },
          "dayOffset": {
            "type": "integer",
            "minimum": -365,
            "maximum": 365,
            "description": "Desplazamiento en días respecto de startDate."
          },
          "activeChannels": {
            "type": "array",
            "maxItems": 16,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z][A-Za-z0-9_-]{0,47}$"
            }
          },
          "channels": {
            "type": "object",
            "maxProperties": 16,
            "propertyNames": {
              "pattern": "^[A-Za-z][A-Za-z0-9_-]{0,47}$"
            },
            "additionalProperties": {
              "$ref": "#/components/schemas/ChannelContent"
            },
            "description": "Cada clave debe pertenecer a activeChannels. Ejemplos: feed_ig, stories, newsletter."
          }
        },
        "required": [
          "id",
          "title",
          "dayOffset",
          "activeChannels",
          "channels"
        ]
      },
      "CalendarActionCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 160,
            "minLength": 1
          },
          "type": {
            "type": "string",
            "maxLength": 40,
            "minLength": 1
          },
          "color": {
            "type": "string",
            "pattern": "^#[0-9a-fA-F]{6}$"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "scheduled"
            ]
          },
          "startDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Obligatoria con fecha válida para scheduled; null para un borrador sin fecha."
          },
          "days": {
            "type": "array",
            "minItems": 1,
            "maxItems": 90,
            "items": {
              "$ref": "#/components/schemas/CalendarDay"
            }
          }
        },
        "required": [
          "name",
          "type",
          "color",
          "status",
          "startDate",
          "days"
        ],
        "description": "La acción normalizada completa (name, type, color, status, startDate y days), codificada como JSON UTF-8, admite hasta 24 KiB (24576 bytes). No cuenta id ni expectedVersion. Superar el límite devuelve HTTP 413; respetar también los límites de cada campo."
      },
      "CalendarActionUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 160,
            "minLength": 1
          },
          "type": {
            "type": "string",
            "maxLength": 40,
            "minLength": 1
          },
          "color": {
            "type": "string",
            "pattern": "^#[0-9a-fA-F]{6}$"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "scheduled"
            ]
          },
          "startDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Obligatoria con fecha válida para scheduled; null para un borrador sin fecha."
          },
          "days": {
            "type": "array",
            "minItems": 1,
            "maxItems": 90,
            "items": {
              "$ref": "#/components/schemas/CalendarDay"
            }
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "expectedVersion": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740990,
            "description": "Versión recibida en la última lectura. Ante 409, releer y conciliar antes de enviar una edición nueva."
          }
        },
        "required": [
          "name",
          "type",
          "color",
          "status",
          "startDate",
          "days",
          "id",
          "expectedVersion"
        ],
        "description": "La acción normalizada completa (name, type, color, status, startDate y days), codificada como JSON UTF-8, admite hasta 24 KiB (24576 bytes). No cuenta id ni expectedVersion. Superar el límite devuelve HTTP 413; respetar también los límites de cada campo. La edición requiere id y expectedVersion actuales."
      },
      "EmployeeCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "displayName": {
            "type": "string",
            "maxLength": 120,
            "minLength": 1
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "archived"
            ],
            "default": "active"
          },
          "storeId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "default": null,
            "description": "Opcional. Omitir para Fichar independiente de una tienda. Sólo admite una tienda propia con permisos."
          }
        },
        "required": [
          "displayName"
        ]
      },
      "EmployeeUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "displayName": {
            "type": "string",
            "maxLength": 120,
            "minLength": 1
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "archived"
            ],
            "default": "active"
          },
          "storeId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "default": null,
            "description": "Opcional. Omitir para Fichar independiente de una tienda. Sólo admite una tienda propia con permisos."
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "expectedVersion": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740990
          }
        },
        "required": [
          "id",
          "expectedVersion",
          "displayName",
          "status",
          "storeId"
        ],
        "description": "Reemplaza nombre, estado y tienda. Enviar status y storeId explícitamente, conservando sus valores actuales si no se desean cambiar."
      },
      "AttendanceCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "employeeId": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "in",
              "out"
            ]
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time",
            "description": "Fecha y hora explícitas con Z u offset. Ejemplo Argentina: 2026-11-23T09:00:00-03:00."
          }
        },
        "required": [
          "employeeId",
          "kind",
          "occurredAt"
        ]
      },
      "TimeOffCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "employeeId": {
            "type": "string",
            "format": "uuid"
          },
          "startDate": {
            "type": "string",
            "format": "date"
          },
          "endDate": {
            "type": "string",
            "format": "date"
          },
          "startTime": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$",
            "default": null
          },
          "endTime": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$",
            "default": null
          },
          "reason": {
            "type": "string",
            "enum": [
              "estudio",
              "vacaciones",
              "enfermedad",
              "personal",
              "sin_causa"
            ]
          },
          "notes": {
            "type": "string",
            "maxLength": 2000,
            "default": ""
          }
        },
        "required": [
          "employeeId",
          "startDate",
          "endDate",
          "reason"
        ],
        "description": "Rango inclusivo. endDate >= startDate, diferencia máxima de 366 días. Para ausencia parcial enviar ambas horas, en un mismo día, con endTime > startTime. Se rechazan superposiciones."
      },
      "TimeOffUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "employeeId": {
            "type": "string",
            "format": "uuid"
          },
          "startDate": {
            "type": "string",
            "format": "date"
          },
          "endDate": {
            "type": "string",
            "format": "date"
          },
          "startTime": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$",
            "default": null
          },
          "endTime": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$",
            "default": null
          },
          "reason": {
            "type": "string",
            "enum": [
              "estudio",
              "vacaciones",
              "enfermedad",
              "personal",
              "sin_causa"
            ]
          },
          "notes": {
            "type": "string",
            "maxLength": 2000,
            "default": ""
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "expectedVersion": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740990
          }
        },
        "required": [
          "id",
          "expectedVersion",
          "employeeId",
          "startDate",
          "endDate",
          "reason"
        ],
        "description": "Reemplaza el registro completo con control de versión. Conservar horas/notas explícitamente si corresponde."
      },
      "AppsList": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "apps.list"
          },
          "input": {
            "$ref": "#/components/schemas/EmptyInput"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Consultar suscripciones y permisos efectivos de esta conexión."
      },
      "CalendarActionsList": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "calendar.actions.list"
          },
          "input": {
            "$ref": "#/components/schemas/Pagination"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Listar acciones editoriales. Requiere calendar.read."
      },
      "CalendarActionsCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "calendar.actions.create"
          },
          "input": {
            "$ref": "#/components/schemas/CalendarActionCreate"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Crear una acción editorial con días y canales. Requiere calendar.write e Idempotency-Key."
      },
      "CalendarActionsUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "calendar.actions.update"
          },
          "input": {
            "$ref": "#/components/schemas/CalendarActionUpdate"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Reemplazar una acción conservando su ID y verificando expectedVersion. Requiere calendar.write e Idempotency-Key."
      },
      "FicharEmployeesList": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "fichar.employees.list"
          },
          "input": {
            "$ref": "#/components/schemas/Pagination"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Consultar empleados. Requiere hours.read."
      },
      "FicharEmployeesCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "fichar.employees.create"
          },
          "input": {
            "$ref": "#/components/schemas/EmployeeCreate"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Crear empleado sin crear un usuario de acceso. Requiere hours.write e Idempotency-Key."
      },
      "FicharEmployeesUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "fichar.employees.update"
          },
          "input": {
            "$ref": "#/components/schemas/EmployeeUpdate"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Actualizar o archivar empleado con control de versión. Requiere hours.write e Idempotency-Key."
      },
      "FicharAttendanceList": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "fichar.attendance.list"
          },
          "input": {
            "$ref": "#/components/schemas/EmployeePagination"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Consultar entradas y salidas. Requiere hours.read."
      },
      "FicharAttendanceCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "fichar.attendance.create"
          },
          "input": {
            "$ref": "#/components/schemas/AttendanceCreate"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Registrar una entrada o salida de un empleado activo. Requiere hours.write e Idempotency-Key."
      },
      "FicharTimeOffList": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "fichar.time_off.list"
          },
          "input": {
            "$ref": "#/components/schemas/EmployeePagination"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Consultar ausencias. Requiere hours.read."
      },
      "FicharTimeOffCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "fichar.time_off.create"
          },
          "input": {
            "$ref": "#/components/schemas/TimeOffCreate"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Cargar una ausencia, por ejemplo un día de estudio. Requiere hours.write e Idempotency-Key."
      },
      "FicharTimeOffUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "fichar.time_off.update"
          },
          "input": {
            "$ref": "#/components/schemas/TimeOffUpdate"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional con Bearer. La empresa se obtiene de la conexión; un ID diferente es rechazado. Omitir normalmente."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Modificar una ausencia con control de versión. Requiere hours.write e Idempotency-Key."
      },
      "OperationRequest": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/AppsList"
          },
          {
            "$ref": "#/components/schemas/CalendarActionsList"
          },
          {
            "$ref": "#/components/schemas/CalendarActionsCreate"
          },
          {
            "$ref": "#/components/schemas/CalendarActionsUpdate"
          },
          {
            "$ref": "#/components/schemas/FicharEmployeesList"
          },
          {
            "$ref": "#/components/schemas/FicharEmployeesCreate"
          },
          {
            "$ref": "#/components/schemas/FicharEmployeesUpdate"
          },
          {
            "$ref": "#/components/schemas/FicharAttendanceList"
          },
          {
            "$ref": "#/components/schemas/FicharAttendanceCreate"
          },
          {
            "$ref": "#/components/schemas/FicharTimeOffList"
          },
          {
            "$ref": "#/components/schemas/FicharTimeOffCreate"
          },
          {
            "$ref": "#/components/schemas/FicharTimeOffUpdate"
          },
          {
            "$ref": "#/components/schemas/PromosStoresList"
          },
          {
            "$ref": "#/components/schemas/PromosConfigurationRead"
          },
          {
            "$ref": "#/components/schemas/PromosConfigurationSave"
          }
        ],
        "discriminator": {
          "propertyName": "operation",
          "mapping": {
            "apps.list": "#/components/schemas/AppsList",
            "calendar.actions.list": "#/components/schemas/CalendarActionsList",
            "calendar.actions.create": "#/components/schemas/CalendarActionsCreate",
            "calendar.actions.update": "#/components/schemas/CalendarActionsUpdate",
            "fichar.employees.list": "#/components/schemas/FicharEmployeesList",
            "fichar.employees.create": "#/components/schemas/FicharEmployeesCreate",
            "fichar.employees.update": "#/components/schemas/FicharEmployeesUpdate",
            "fichar.attendance.list": "#/components/schemas/FicharAttendanceList",
            "fichar.attendance.create": "#/components/schemas/FicharAttendanceCreate",
            "fichar.time_off.list": "#/components/schemas/FicharTimeOffList",
            "fichar.time_off.create": "#/components/schemas/FicharTimeOffCreate",
            "fichar.time_off.update": "#/components/schemas/FicharTimeOffUpdate",
            "promos.stores.list": "#/components/schemas/PromosStoresList",
            "promos.configuration.read": "#/components/schemas/PromosConfigurationRead",
            "promos.configuration.save": "#/components/schemas/PromosConfigurationSave"
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "OperationResult": {
        "type": "object",
        "properties": {
          "item": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "apps": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "additionalProperties": false,
        "description": "Altas/ediciones devuelven item; listas devuelven items, limit y offset; apps.list devuelve apps. Un empleado o recurso creado devuelve id; los editables incluyen version."
      },
      "PromoScope": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "storeId": {
            "type": "string",
            "format": "uuid"
          },
          "installationId": {
            "type": "string",
            "format": "uuid"
          },
          "generation": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "storeId",
          "installationId",
          "generation"
        ],
        "description": "Seleccionar IDs y generación actuales devueltos por promos.stores.list; son precondiciones, nunca autorizaciones."
      },
      "PromoDraft": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currency": {
            "type": "string",
            "const": "ARS"
          },
          "thresholdMinor": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000000000,
            "description": "Importe en unidades menores (centavos); por ejemplo ARS 30.000 = 3000000."
          },
          "basis": {
            "type": "string",
            "const": "subtotal_before_discounts"
          },
          "sdkPriceUnit": {
            "type": "string",
            "enum": [
              "major",
              "minor"
            ],
            "description": "Unidad de precios confirmada para la tienda."
          },
          "benefitLabel": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "pattern": "^[^<>\\u0000-\\u001f\\u007f]+$"
          },
          "conditions": {
            "type": "string",
            "minLength": 1,
            "maxLength": 240,
            "pattern": "^[^<>\\u0000-\\u001f\\u007f]+$"
          }
        },
        "required": [
          "currency",
          "thresholdMinor",
          "basis",
          "sdkPriceUnit",
          "benefitLabel",
          "conditions"
        ]
      },
      "PromoConfigurationSave": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "storeId": {
            "type": "string",
            "format": "uuid"
          },
          "installationId": {
            "type": "string",
            "format": "uuid"
          },
          "generation": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "expectedVersion": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740990,
            "description": "0 para crear el primer borrador; en ediciones usar version devuelta por la lectura."
          },
          "draft": {
            "$ref": "#/components/schemas/PromoDraft"
          }
        },
        "required": [
          "storeId",
          "installationId",
          "generation",
          "expectedVersion",
          "draft"
        ]
      },
      "PromosStoresList": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "promos.stores.list"
          },
          "input": {
            "$ref": "#/components/schemas/Pagination"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Omitir normalmente: la empresa se deriva de la conexión."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Listar tiendas conectadas disponibles para esta conexión. Requiere promos.read."
      },
      "PromosConfigurationRead": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "promos.configuration.read"
          },
          "input": {
            "$ref": "#/components/schemas/PromoScope"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Omitir normalmente: la empresa se deriva de la conexión."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Leer el borrador de configuración para una instalación vigente. Requiere promos.read; item puede ser null si aún no existe borrador."
      },
      "PromosConfigurationSave": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation": {
            "type": "string",
            "const": "promos.configuration.save"
          },
          "input": {
            "$ref": "#/components/schemas/PromoConfigurationSave"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "description": "Omitir normalmente: la empresa se deriva de la conexión."
          }
        },
        "required": [
          "operation",
          "input"
        ],
        "description": "Guardar un borrador con control de versión e Idempotency-Key. No publica, habilita ni aplica descuentos. Requiere promos.write."
      }
    }
  }
}
