openapi: 3.0.3
info:
  title: API Autenticación
  description: |
    <div class="apidoc-overview-rich">
    <div class="intro">
    <span class="eyebrow">Autenticación · OAuth 2.0</span>
    <h1>API de Autenticación</h1>
    <p class="lead">El <strong>API de Autenticación</strong> es la puerta de entrada a todas las APIs de Coordinadora. Implementa el flujo <em>OAuth 2.0 Client Credentials</em> sobre Keycloak para emitir <strong>tokens JWT</strong> de corta duración que tu aplicación debe presentar en cada llamada a las demás APIs (envíos, tracking, cotización, recolecciones).</p>
    </div>
    <div class="flow-section">
    <h2 class="flow-label">Flujo OAuth 2.0 · Client Credentials</h2>
    <div class="flow-diagram">
    <div class="flow-actors" role="img" aria-label="Diagrama del flujo OAuth 2.0 Client Credentials">
    <div class="flow-actor flow-actor--app">
    <i class="ti ti-device-laptop" aria-hidden="true"></i>
    <span class="flow-actor-name">Tu App</span>
    <span class="flow-actor-sub">Cliente</span>
    </div>
    <div class="flow-step flow-step--request">
    <span class="flow-step-label">1 · Authorization: Basic</span>
    <span class="flow-step-arrow" aria-hidden="true">→</span>
    </div>
    <div class="flow-actor flow-actor--keycloak">
    <i class="ti ti-shield-check" aria-hidden="true"></i>
    <span class="flow-actor-name">Keycloak</span>
    <span class="flow-actor-sub">OAuth 2.0 Server · POST /oauth/token</span>
    </div>
    <div class="flow-step flow-step--issue">
    <span class="flow-step-label">2 · Emite token JWT (expires_in 3600s)</span>
    <span class="flow-step-arrow" aria-hidden="true">→</span>
    </div>
    <div class="flow-actor flow-actor--token">
    <i class="ti ti-ticket" aria-hidden="true"></i>
    <span class="flow-actor-name">JWT Token</span>
    <span class="flow-actor-sub">Authorization: Bearer</span>
    </div>
    </div>
    </div>
    </div>
    <!-- CUÁNDO + REQUISITOS -->
    <div class="two-col">
    <div class="info-card">
    <div class="info-card-head">
    <i class="ti ti-help-circle" aria-hidden="true"></i>
    <h3>¿Cuándo usarlo?</h3>
    </div>
    <div class="info-card-body">
    <div class="info-row">
    <i class="ti ti-point-filled" aria-hidden="true"></i>
    <span>Al iniciar tu integración: obtén el primer <code>access_token</code>.</span>
    </div>
    <div class="info-row">
    <i class="ti ti-point-filled" aria-hidden="true"></i>
    <span>Antes de cada llamada cuando tu token haya expirado (cada ~1 hora).</span>
    </div>
    <div class="info-row">
    <i class="ti ti-point-filled" aria-hidden="true"></i>
    <span>Para rotar credenciales sin redeploy al cambiar el <code>client_secret</code>.</span>
    </div>
    </div>
    </div>
    <div class="info-card">
    <div class="info-card-head">
    <i class="ti ti-checklist" aria-hidden="true"></i>
    <h3>Requisitos previos</h3>
    </div>
    <div class="info-card-body">
    <div class="info-row">
    <div class="step-badge">1</div>
    <span>App de developer creada en <a href="/credenciales" >/credenciales</a>.</span>
    </div>
    <div class="info-row">
    <div class="step-badge">2</div>
    <span>Tener tu <code>Consumer Key</code> (client_id) y <code>Consumer Secret</code> (client_secret).</span>
    </div>
    <div class="info-row">
    <div class="step-badge">3</div>
    <span>HTTP client configurado para enviar <code>Authorization: Basic</code>.</span>
    </div>
    </div>
    </div>
    </div>
    <!-- FLUJO RECOMENDADO -->
    <div class="code-block">
    <div class="code-top">
    <div class="code-label">
    <span class="m-post">POST</span>
    /oauth/token
    </div>
    <button class="copy-btn" aria-label="Copiar"><i class="ti ti-copy"></i> Copiar</button>
    </div>
    <div class="code-pre" id="snippet"><span class="cc"># Request</span>
    <span class="ck">Authorization</span>: Basic &lt;base64(client_id:client_secret)&gt;
    <span class="ck">Content-Type</span>:  application/x-www-form-urlencoded
    <span class="ck">grant_type</span>=<span class="cs">client_credentials</span>
    <span class="cc"># Response</span>
    {
    <span class="ck">"access_token"</span>: <span class="cs">"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."</span>,
    <span class="ck">"token_type"</span>:   <span class="cs">"Bearer"</span>,
    <span class="ck">"expires_in"</span>:   3600
    }</div>
    </div>
    <!-- BUENAS PRÁCTICAS -->
    <div class="bp-grid">
    <div class="bp-card do">
    <i class="ti ti-circle-check" aria-hidden="true"></i>
    <p><strong>Cachea el token</strong> en memoria hasta 5 min antes de su expiración.</p>
    </div>
    <div class="bp-card do">
    <i class="ti ti-circle-check" aria-hidden="true"></i>
    <p><strong>Renueva en <code>401</code></strong> — implementa retry automático al recibir Unauthorized.</p>
    </div>
    <div class="bp-card dont">
    <i class="ti ti-circle-x" aria-hidden="true"></i>
    <p><strong>No expongas</strong> el <code>client_secret</code> en frontend ni repos públicos.</p>
    </div>
    <div class="bp-card info">
    <i class="ti ti-flask" aria-hidden="true"></i>
    <p>Usa <code>api-dev.coordinadora.tech</code> para pruebas; producción solo tras certificación.</p>
    </div>
    </div>
    <!-- SPEC NOTE -->
    <div class="spec-note">
    <i class="ti ti-file-code" aria-hidden="true"></i>
    <span>La especificación técnica completa, los esquemas de respuesta y la consola <strong>Try it out</strong> están en las secciones siguientes de este documento.</span>
    </div>
    </div>
  version: 1.0.0

servers:
  - url: https://api-dev.coordinadora.tech
    description: API gateway desarrollo

tags:
  - name: Token
    description: Emisión de tokens OAuth2

paths:
  /oauth/token:
    post:
      tags: [Token]
      summary: Obtener access token (client_credentials)
      description: Emite un access token JWT usando OAuth 2.0 client_credentials. Autenticación HTTP Basic con Consumer Key y Consumer Secret.
      security:
        - basicAuth: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [grant_type]
              properties:
                grant_type:
                  type: string
                  enum: [client_credentials]
                  example: client_credentials
            examples:
              default:
                summary: Solicitud típica
                value:
                  grant_type: client_credentials
      responses:
        '200':
          description: Token emitido correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '401':
          description: Credenciales inválidas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: Consumer Key y Consumer Secret obtenidos en `/credenciales`.
  schemas:
    TokenResponse:
      type: object
      properties:
        access_token: { type: string }
        token_type:   { type: string, example: Bearer }
        expires_in:   { type: integer, example: 3599 }
        scope:        { type: string }
    ErrorResponse:
      type: object
      properties:
        error: { type: string, example: invalid_client }
        error_description: { type: string }
