openapi: 3.0.3
info:
  title: LogiGho - Preferencias de usuario y administración de experiencias
  version: 1.0.0
  description: |
    Experiencia visual elegida por cada usuario y experiencias que puede usar (Sistema de Experiencias).
    La identidad es siempre el `sub` del token; la disponibilidad de cada experiencia la decide el servidor.
    Ver la guía de consumo en `preferencias.md`.
servers:
  - url: https://8u5isw5u30.execute-api.us-east-1.amazonaws.com/PreProd
    description: PreProd
  - url: https://7elgyten88.execute-api.us-east-1.amazonaws.com/Produccion
    description: Producción (todavía no montado)
security:
  - TokenCognito: []
paths:
  /preferencias/mia:
    get:
      operationId: obtenerPreferencias
      summary: Preferencia del usuario y experiencias disponibles
      description: No escribe. Sin documento guardado devuelve clasica, claro y version 0.
      responses:
        '200':
          description: Preferencia vigente
          headers:
            Cache-Control: { schema: { type: string, example: no-store } }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Preferencias' }
              examples:
                usuarioNuevo:
                  value:
                    experiencia: clasica
                    tema: claro
                    version: 0
                    disponibles: [ { id: clasica, nombre: Clásica, estado: general, orden: 10, temas: [claro], temaPorDefecto: claro } ]
        '401': { $ref: '#/components/responses/NoAutenticado' }
        '500': { $ref: '#/components/responses/ErrorInterno' }
    put:
      operationId: guardarPreferencias
      summary: Guarda la experiencia y el tema del usuario
      description: |
        Control optimista: `version` debe ser la última recibida (0 la primera vez).
        Orden de validación: token, tamaño (413), JSON estricto (400), experiencia/tema (422), versión (409).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SolicitudGuardar' }
            example: { experiencia: prueba-interna, tema: claro, version: 3 }
      responses:
        '200':
          description: Guardado; version incrementada en 1
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Preferencias' }
        '400': { $ref: '#/components/responses/Validacion' }
        '401': { $ref: '#/components/responses/NoAutenticado' }
        '409':
          description: La versión enviada no es la guardada
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
              example: { codigo: CONFLICTO_VERSION, traceId: 3fa85f6457a1 }
        '413':
          description: Cuerpo mayor a 2048 bytes
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
              example: { codigo: CUERPO_DEMASIADO_GRANDE, traceId: 3fa85f6457a1 }
        '422':
          description: Experiencia o tema no disponibles para este usuario
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
              examples:
                experiencia: { value: { codigo: EXPERIENCIA_NO_DISPONIBLE, traceId: 9c1d2e3f4a5b } }
                tema: { value: { codigo: TEMA_NO_DISPONIBLE, traceId: 9c1d2e3f4a5b } }
        '500': { $ref: '#/components/responses/ErrorInterno' }
    options:
      operationId: preflight
      summary: Preflight CORS (lo responde la Lambda)
      security: []
      responses:
        '204':
          description: Sin cuerpo. Con un origen permitido trae Access-Control-Allow-Origin con ese mismo origen.
  /experiencias:
    get:
      operationId: listarExperiencias
      summary: Experiencias visibles para el usuario (todas, con todas=true, solo admin)
      parameters:
        - { name: todas, in: query, required: false, schema: { type: string, enum: ["true", "false"] } }
      responses:
        '200':
          description: Lista ordenada por orden. Usuario común = vista pública; administrador = vista de administración
          content:
            application/json:
              schema:
                type: object
                properties:
                  experiencias: { type: array, items: { $ref: '#/components/schemas/Experiencia' } }
        '400': { $ref: '#/components/responses/Validacion' }
        '401': { $ref: '#/components/responses/NoAutenticado' }
        '403': { $ref: '#/components/responses/SinPermiso' }
    post:
      operationId: crearExperiencia
      summary: Crea una experiencia (solo administradores, ROLES_ADMIN_EXPERIENCIAS)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CrearExperiencia' }
      responses:
        '201': { description: Creada (version 1), content: { application/json: { schema: { $ref: '#/components/schemas/Experiencia' } } } }
        '400': { $ref: '#/components/responses/Validacion' }
        '401': { $ref: '#/components/responses/NoAutenticado' }
        '403': { $ref: '#/components/responses/SinPermiso' }
        '409': { description: EXPERIENCIA_YA_EXISTE, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '413': { description: Más de 8192 bytes }
    options:
      operationId: preflightExperiencias
      summary: Preflight CORS (lo responde la Lambda)
      security: []
      responses:
        '204': { description: Sin cuerpo }
  /experiencias/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { $ref: '#/components/schemas/Id' } }
    get:
      operationId: obtenerExperiencia
      summary: Una experiencia (404 si no existe o no es visible para el usuario)
      responses:
        '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Experiencia' } } } }
        '400': { $ref: '#/components/responses/Validacion' }
        '401': { $ref: '#/components/responses/NoAutenticado' }
        '404': { description: EXPERIENCIA_NO_ENCONTRADA, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
    put:
      operationId: editarExperiencia
      summary: Edita los campos editables (no el id ni el estado). Solo administradores
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EditarExperiencia' }
      responses:
        '200': { description: OK (version + 1), content: { application/json: { schema: { $ref: '#/components/schemas/Experiencia' } } } }
        '400': { $ref: '#/components/responses/Validacion' }
        '401': { $ref: '#/components/responses/NoAutenticado' }
        '403': { $ref: '#/components/responses/SinPermiso' }
        '404': { description: EXPERIENCIA_NO_ENCONTRADA }
        '409': { description: CONFLICTO_VERSION }
        '413': { description: Más de 8192 bytes }
    options:
      operationId: preflightExperiencia
      summary: Preflight CORS (lo responde la Lambda)
      security: []
      responses:
        '204': { description: Sin cuerpo }
    delete:
      operationId: borrarExperiencia
      summary: Borra solo si está en borrador o retirada y ningún usuario la tiene guardada. Solo administradores
      parameters:
        - { name: version, in: query, required: true, schema: { type: integer, minimum: 1, maximum: 1000000 } }
      responses:
        '204': { description: Borrada, sin cuerpo }
        '400': { $ref: '#/components/responses/Validacion' }
        '401': { $ref: '#/components/responses/NoAutenticado' }
        '403': { description: SIN_PERMISO o EXPERIENCIA_PROTEGIDA (clasica), content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '404': { description: EXPERIENCIA_NO_ENCONTRADA }
        '409': { description: "CONFLICTO_VERSION; ESTADO_NO_PERMITE_BORRAR (beta o general); o EXPERIENCIA_EN_USO (algún usuario la tiene guardada)", content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
  /experiencias/{id}/estado:
    parameters:
      - { name: id, in: path, required: true, schema: { $ref: '#/components/schemas/Id' } }
    put:
      operationId: cambiarEstadoExperiencia
      summary: Cambia el estado (cualquier estado a cualquier otro; clasica nunca). Solo administradores
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [estado, version]
              additionalProperties: false
              properties:
                estado: { type: string, enum: [borrador, beta, general, retirada] }
                version: { type: integer, minimum: 1, maximum: 1000000 }
      responses:
        '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Experiencia' } } } }
        '400': { $ref: '#/components/responses/Validacion' }
        '401': { $ref: '#/components/responses/NoAutenticado' }
        '413': { description: Más de 8192 bytes }
        '403': { description: SIN_PERMISO o EXPERIENCIA_PROTEGIDA, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '404': { description: EXPERIENCIA_NO_ENCONTRADA }
        '409': { description: CONFLICTO_VERSION }
    options:
      operationId: preflightEstado
      summary: Preflight CORS (lo responde la Lambda)
      security: []
      responses:
        '204': { description: Sin cuerpo }
components:
  securitySchemes:
    TokenCognito:
      type: apiKey
      in: header
      name: Token
      description: id_token de Cognito. Usar siempre este header; el authorizer del gateway no lee Authorization.
  schemas:
    Id:
      type: string
      pattern: '^[a-z][a-z0-9-]{1,39}$'
    Preferencias:
      type: object
      required: [experiencia, tema, version, disponibles]
      additionalProperties: false
      properties:
        experiencia: { $ref: '#/components/schemas/Id' }
        tema: { $ref: '#/components/schemas/Id' }
        version: { type: integer, minimum: 0 }
        disponibles:
          type: array
          items:
            type: object
            required: [id, nombre, estado, orden, temas, temaPorDefecto]
            additionalProperties: false
            properties:
              id: { $ref: '#/components/schemas/Id' }
              nombre: { type: string, minLength: 2, maxLength: 60 }
              estado: { type: string, enum: [general, beta, borrador] }
              orden: { type: integer, minimum: 0, maximum: 9999 }
              temas: { type: array, items: { $ref: '#/components/schemas/Id' } }
              temaPorDefecto: { $ref: '#/components/schemas/Id' }
    SolicitudGuardar:
      type: object
      required: [experiencia, tema, version]
      additionalProperties: false
      description: Exactamente estos tres campos; máximo 2048 bytes.
      properties:
        experiencia: { $ref: '#/components/schemas/Id' }
        tema: { $ref: '#/components/schemas/Id' }
        version: { type: integer, minimum: 0, maximum: 1000000 }
    Experiencia:
      type: object
      description: Vista pública (usuario común) o de administración (incluye rolesAcceso, version y auditoría)
      required: [id, nombre, descripcion, estado, temas, temaPorDefecto, orden]
      properties:
        id: { $ref: '#/components/schemas/Id' }
        nombre: { type: string }
        descripcion: { type: string }
        estado: { type: string, enum: [borrador, beta, general, retirada] }
        temas: { type: array, items: { $ref: '#/components/schemas/Id' } }
        temaPorDefecto: { $ref: '#/components/schemas/Id' }
        orden: { type: integer }
        rolesAcceso: { type: array, items: { type: string }, description: Solo administradores }
        version: { type: integer, description: Solo administradores }
        creadoEn: { type: string, format: date-time }
        creadoPor: { type: string }
        actualizadoEn: { type: string, format: date-time }
        actualizadoPor: { type: string }
    CrearExperiencia:
      type: object
      required: [id, nombre]
      additionalProperties: false
      properties:
        id: { $ref: '#/components/schemas/Id' }
        nombre: { type: string, minLength: 2, maxLength: 60 }
        descripcion: { type: string, maxLength: 200 }
        estado: { type: string, enum: [borrador, beta, general, retirada], default: borrador }
        rolesAcceso: { type: array, maxItems: 20, items: { type: string, maxLength: 60 } }
        temas: { type: array, minItems: 1, maxItems: 10, items: { $ref: '#/components/schemas/Id' } }
        temaPorDefecto: { $ref: '#/components/schemas/Id' }
        orden: { type: integer, minimum: 0, maximum: 9999, default: 100 }
    EditarExperiencia:
      type: object
      required: [nombre, rolesAcceso, temas, temaPorDefecto, orden, version]
      additionalProperties: false
      properties:
        nombre: { type: string, minLength: 2, maxLength: 60 }
        descripcion: { type: string, maxLength: 200 }
        rolesAcceso: { type: array, maxItems: 20, items: { type: string, maxLength: 60 } }
        temas: { type: array, minItems: 1, maxItems: 10, items: { $ref: '#/components/schemas/Id' } }
        temaPorDefecto: { $ref: '#/components/schemas/Id' }
        orden: { type: integer, minimum: 0, maximum: 9999 }
        version: { type: integer, minimum: 1, maximum: 1000000 }
    Error:
      type: object
      required: [codigo, traceId]
      properties:
        codigo:
          type: string
          enum: [VALIDACION, SIN_TOKEN, TOKEN_INVALIDO, USUARIO_NO_ENCONTRADO, RUTA_NO_ENCONTRADA, METODO_NO_PERMITIDO,
                 CONFLICTO_VERSION, CUERPO_DEMASIADO_GRANDE, EXPERIENCIA_NO_DISPONIBLE, TEMA_NO_DISPONIBLE, ERROR_INTERNO,
                 SIN_PERMISO, EXPERIENCIA_NO_ENCONTRADA, EXPERIENCIA_YA_EXISTE, EXPERIENCIA_PROTEGIDA, ESTADO_NO_PERMITE_BORRAR, EXPERIENCIA_EN_USO]
        traceId: { type: string, pattern: '^[0-9a-f]{12}$' }
  responses:
    SinPermiso:
      description: El usuario no tiene un rol de ROLES_ADMIN_EXPERIENCIAS (o pidió todas=true sin ser administrador)
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { codigo: SIN_PERMISO, traceId: 3fa85f6457a1 }
    Validacion:
      description: JSON inválido, campo faltante/extra/repetido, tipo o formato incorrecto
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { codigo: VALIDACION, traceId: 3fa85f6457a1 }
    NoAutenticado:
      description: Sin token válido (también lo responde el authorizer del gateway como {"message":"Unauthorized"})
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { codigo: SIN_TOKEN, traceId: 3fa85f6457a1 }
    ErrorInterno:
      description: Fallo no controlado o configuración inválida; sin detalles
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { codigo: ERROR_INTERNO, traceId: 3fa85f6457a1 }
