openapi: 3.0.3
info:
  title: TIIX — API Interna (Roles, Permissions, Onboarding v2)
  version: "2025-09-09"
  description: |
    Especificação dos endpoints expostos por `app_v2.php` para **papéis (roles)**, **permissões/grupos** e **onboarding v2**.

servers:
  - url: https://app.tiix.com.br
    description: Produção
  - url: https://{host}
    variables:
      host:
        default: localhost

security:
  - CookieAuth: []
  - PublicToken: []  # apenas onde explicitamente permitido (IntegrationsPublic etc.)

components:
  securitySchemes:
    CookieAuth:
      type: apiKey
      in: cookie
      name: wordpress_logged_in_
    PublicToken:
      type: apiKey
      in: header
      name: X-Public-Token

  schemas:
    ApiOk:
      type: object
      properties: { ok: {type: integer, example: 1}, msg: {type: string} }
    ApiError:
      type: object
      properties: { ok: {type: integer, example: 0}, error: {type: string}, __status: {type: integer, example: 400} }

    RoleCatalog:
      type: object
      required: [slug,label]
      properties:
        slug: { type: string, example: gerente }
        label: { type: string, example: Gerente }
        multiplicidade: { type: string, enum: [single, multi], default: multi }
        scope_mask: { type: string, example: contrato }
        lens: { type: string, nullable: true }
    RoleBinding:
      type: object
      properties:
        id: { type: integer }
        role_slug: { type: string }
        scope_type: { type: string, enum: [global, contrato, equipe] }
        scope_id: { type: integer, nullable: true }
        principal_type: { type: string, enum: [usuario, equipe, grupo] }
        principal_id: { type: integer }
        is_primary: { type: integer, enum: [0,1] }
        priority: { type: integer, example: 100 }
        valid_from: { type: string, format: date-time }
        valid_to: { type: string, nullable: true, format: date-time }
        created_by: { type: integer, nullable: true }
    RoleResolveResult:
      type: object
      properties:
        candidates:
          type: array
          items:
            type: object
            properties:
              user_id: { type: integer }
              priority: { type: integer }
              source: { type: string, example: binding.equipe.usuario }
        selected:
          nullable: true
          allOf: [{ $ref: '#/components/schemas/RoleResolveCandidate' }]
    RoleResolveCandidate:
      type: object
      properties:
        user_id: { type: integer }
        priority: { type: integer }
        source: { type: string }

    PermissionSlug:
      type: object
      properties:
        id: { type: integer }
        slug: { type: string }
        nome: { type: string }
        descricao: { type: string }
        tipo: { type: string, example: acao }
    PermissionGroup:
      type: object
      properties:
        id: { type: integer }
        nome: { type: string }
        id_contrato: { type: integer }
        slug: { type: string }
        total_slugs: { type: integer }
        total_usuarios: { type: integer }

paths:
  ########################################
  ## Roles
  ########################################
  /adm/api/Roles/ping:
    get:
      summary: Diagnóstico
      tags: [Roles]
      security: []
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: object, properties: { pong: {type: boolean}, controller: {type: string} } } } } }

  /adm/api/Roles/catalog:
    get:
      summary: Lista catálogo de papéis
      tags: [Roles]
      parameters:
        - in: query; name: q; schema: { type: string }
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: object, properties: { items: { type: array, items: { $ref: '#/components/schemas/RoleCatalog' } } } } } } }

  /adm/api/Roles/catalog_save:
    post:
      summary: Cria/atualiza papel no catálogo
      tags: [Roles]
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, properties: { data: { $ref: '#/components/schemas/RoleCatalog' } } }
      responses:
        '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/ApiOk' } } } }
        '400': { description: Erro, content: { application/json: { schema: { $ref: '#/components/schemas/ApiError' } } } }

  /adm/api/Roles/catalog_delete:
    post:
      summary: Exclui papel do catálogo
      tags: [Roles]
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, properties: { data: { type: object, required: [slug], properties: { slug: { type: string } } } } }
      responses:
        '200': { description: OK }
        '400': { description: Erro }

  /adm/api/Roles/bindings_list:
    get:
      summary: Lista bindings (nomeações)
      tags: [Roles]
      parameters:
        - in: query; name: role_slug; schema: { type: string }
        - in: query; name: scope_type; schema: { type: string, enum: [global, contrato, equipe] }
        - in: query; name: scope_id;   schema: { type: integer }
        - in: query; name: principal_type; schema: { type: string, enum: [usuario, equipe, grupo] }
        - in: query; name: principal_id; schema: { type: integer }
        - in: query; name: active_only; schema: { type: boolean, default: true }
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: object, properties: { items: { type: array, items: { $ref: '#/components/schemas/RoleBinding' } } } } } } }

  /adm/api/Roles/bindings_save:
    post:
      summary: Cria/atualiza binding
      tags: [Roles]
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, properties: { data: { $ref: '#/components/schemas/RoleBinding' } } }
      responses:
        '200': { description: OK }
        '400': { description: Erro }

  /adm/api/Roles/bindings_delete:
    post:
      summary: Exclui binding
      tags: [Roles]
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, properties: { data: { type: object, required: [id], properties: { id: { type: integer } } } } }
      responses:
        '200': { description: OK }
        '400': { description: Erro }

  /adm/api/Roles/resolve:
    get:
      summary: Resolve ocupantes de um papel (precedência)
      tags: [Roles]
      parameters:
        - in: query; name: role_slug; required: true; schema: { type: string }
        - in: query; name: id_contrato; schema: { type: integer }
        - in: query; name: equipe_id; schema: { type: integer }
        - in: query; name: return; schema: { type: string, enum: [one, all], default: one }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { type: object, properties: { candidates: { type: array, items: { $ref: '#/components/schemas/RoleResolveCandidate' } }, selected: { nullable: true, allOf: [ { $ref: '#/components/schemas/RoleResolveCandidate' } ] } } }

  /adm/api/Roles/userRoles:
    get:
      summary: Lista roles diretos do usuário (bindings principal=usuario)
      tags: [Roles]
      parameters:
        - in: query; name: user_id; required: true; schema: { type: integer }
      responses:
        '200': { description: OK }

  /adm/api/Roles/applyGroupMap:
    post:
      summary: Aplica mapeamento papel→grupo (atribuir/remover no RBAC)
      tags: [Roles]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                data:
                  type: object
                  required: [role_slug, user_id]
                  properties:
                    role_slug: { type: string }
                    user_id: { type: integer }
                    assign: { type: boolean, default: true }
      responses:
        '200': { description: OK }

  ########################################
  ## Permissions (resumo)
  ########################################
  /adm/api/Permissions/ping:
    get:
      summary: Ping
      tags: [Permissions]
      responses: { '200': { description: OK } }

  /adm/api/Permissions/slugs:
    get:
      summary: Lista de slugs de permissão
      tags: [Permissions]
      parameters: [{ in: query, name: q, schema: { type: string } }, { in: query, name: tipo, schema: { type: string, enum: [acao, tela] } }]
      responses: { '200': { description: OK } }

  /adm/api/Permissions/groups:
    get:
      summary: Grupos de um contrato
      tags: [Permissions]
      parameters: [{ in: query, name: id_contrato, schema: { type: integer } }, { in: query, name: q, schema: { type: string } }]
      responses: { '200': { description: OK } }

  /adm/api/Permissions/groupSlugs:
    get:
      summary: Slugs vinculados a um grupo
      tags: [Permissions]
      parameters: [{ in: query, name: group_id, required: true, schema: { type: integer } }]
      responses: { '200': { description: OK } }

  /adm/api/Permissions/groupSlugs_save:
    post:
      summary: Salva kit de slugs do grupo
      tags: [Permissions]
      requestBody:
        required: true
        content: { application/json: { schema: { type: object, properties: { data: { type: object, properties: { group_id: { type: integer }, slugs: { type: array, items: { type: string } } } } } } } }
      responses: { '200': { description: OK } }

  /adm/api/Permissions/applyTemplate:
    post:
      summary: Aplica template de permissões por contrato
      tags: [Permissions]
      requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { data: { type: object, required: [id_contrato, template], properties: { id_contrato: { type: integer }, template: { type: string } } } } } } } }
      responses: { '200': { description: OK } }

  /adm/api/Permissions/sync:
    post:
      summary: Diagnóstico/limpeza de slugs (unused/gaps)
      tags: [Permissions]
      requestBody: { required: false, content: { application/json: { schema: { type: object, properties: { data: { type: object, properties: { id_contrato: { type: integer }, mode: { type: string, enum: [by_assignment, dry_run_pairs, all] }, commit: { type: boolean } } } } } } } }
      responses: { '200': { description: OK } }

  /adm/api/Permissions/kpis:
    get:
      summary: KPIs básicos de ACL
      tags: [Permissions]
      parameters: [{ in: query, name: id_contrato, schema: { type: integer } }]
      responses: { '200': { description: OK } }

  /adm/api/Permissions/auditTrail:
    get:
      summary: Trilha de auditoria para ações de ACL
      tags: [Permissions]
      parameters:
        - { in: query, name: ini, schema: { type: string, format: date } }
        - { in: query, name: fim, schema: { type: string, format: date } }
        - { in: query, name: id_contrato, schema: { type: integer } }
      responses: { '200': { description: OK } }

  ########################################
  ## Onboarding v2 (resumo)
  ########################################
  /adm/api/Onboarding/ping:
    get:
      summary: Ping
      tags: [Onboarding]
      responses: { '200': { description: OK } }

  /adm/api/Onboarding/lookupCep:
    get:
      summary: Consulta CEP (ViaCEP)
      tags: [Onboarding]
      parameters: [{ in: query, name: cep, required: true, schema: { type: string, example: "01311000" } }]
      responses: { '200': { description: OK } }

  /adm/api/Onboarding/submit:
    post:
      summary: Submit de etapas do onboarding v2
      tags: [Onboarding]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                data:
                  type: object
                  required: [step]
                  properties:
                    step: { type: string, example: prof_taxonomias }
                    id_contrato: { type: integer }
                    user_id: { type: integer }
                    profissional_id: { type: integer }
                    categoria_id: { type: integer }
                    profissoes: { type: array, items: { type: integer } }
                    profissoes_academico: { type: array, items: { type: integer } }
                    especialidades: { type: array, items: { type: integer } }
                    atuacoes: { type: array, items: { type: integer } }
      responses:
        '200': { description: OK }
        '400': { description: Erro }
