openapi: 3.1.0
info:
  title: "NT FactoSuite API — Mexico"
  version: "3.1.0"
  description: |
    API REST de NT FactoSuite para México.

    **Estándar fiscal:** CFDI 4.0 SAT

    Todos los endpoints requieren autenticación mediante Bearer JWT.
    Obtén tu token en `POST /auth/token` usando tu API Key de producción o sandbox.

    **Ambientes:**
    - Producción: `https://api.next-technologies.com.mx/v3`
    - Sandbox: `https://sandbox-api.next-technologies.com.mx/v3`

    **Soporte técnico:** dev@next-technologies.com.mx
  contact:
    name: NT Developer Support
    email: dev@next-technologies.com.mx
    url: https://next-technologies.com.mx/developers
  license:
    name: Uso privado — NT Group
    identifier: LicenseRef-NT-Private

servers:
  - url: "https://api.next-technologies.com.mx/v3"
    description: Producción
  - url: "https://sandbox-api.next-technologies.com.mx/v3"
    description: Sandbox (pruebas)

security:
  - BearerAuth: []

tags:
  - name: Autenticación
    description: Obtención y renovación de tokens JWT
  - name: "CFDI"
    description: "Timbrado de CFDI 4.0 ante el SAT a través de NT como PAC certificado."
  - name: Hospitality
    description: "Facturación CFDI 4.0 para hoteles. Integración Opera PMS y Micros POS."
  - name: Orders
    description: Gestión de órdenes de compra y aprobaciones
  - name: "Validación SAT"
    description: "Verificación de CFDI ante el SAT. Devuelve estado vigente/cancelado."
  - name: Webhooks
    description: Notificaciones en tiempo real de eventos fiscales

paths:
  /auth/token:
    post:
      tags: [Autenticación]
      summary: Obtener JWT
      description: |
        Intercambia tu API Key por un Bearer JWT con expiración de 1 hora.
        El token debe enviarse en el header `Authorization: Bearer <token>`.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [api_key, api_secret]
              properties:
                api_key:
                  type: string
                  example: "nt_live_pk_abc123xyz"
                  description: API Key pública de tu cuenta NT
                api_secret:
                  type: string
                  example: "nt_live_sk_•••••••••"
                  description: API Secret privado (nunca expongas esto en frontend)
      responses:
        "200":
          description: Token generado exitosamente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TokenResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /auth/token/refresh:
    post:
      tags: [Autenticación]
      summary: Renovar JWT
      description: Renueva un token próximo a expirar sin necesidad de re-autenticarse.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [refresh_token]
              properties:
                refresh_token:
                  type: string
                  example: "nt_rt_xxxxxxxxxxxxxx"
      responses:
        "200":
          description: Token renovado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TokenResponse"

  /cfdi/emitir:
    post:
      tags: ["CFDI"]
      summary: Emitir comprobante
      description: |
        Genera y timbra un comprobante fiscal electrónico.
        El documento es sellado digitalmente y enviado al SAT/DGII/DIAN/AEAT.
        Responde con el UUID y el XML timbrado en Base64.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmitirRequest"
            example:
              emisor:
                rfc: "ABC010101XYZ"
                nombre: "EMPRESA DEMO SA DE CV"
                regimen_fiscal: "601"
              receptor:
                rfc: "XAXX010101000"
                nombre: "PUBLICO EN GENERAL"
                uso_cfdi: "G03"
              conceptos:
                - descripcion: "Servicio de software empresarial"
                  unidad: "E48"
                  cantidad: 1
                  valor_unitario: 10000.00
                  importe: 10000.00
                  traslados:
                    - impuesto: "002"
                      tipo: "Tasa"
                      tasa: 0.16
                      importe: 1600.00
      responses:
        "201":
          description: Comprobante emitido y timbrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ComprobanteEmitido"
        "422":
          $ref: "#/components/responses/ValidationError"

  /cfdi/{uuid}:
    get:
      tags: ["CFDI"]
      summary: Consultar comprobante
      parameters:
        - name: uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: "6128396f-c09b-4ec6-8699-43c5f7e3b230"
      responses:
        "200":
          description: Detalle del comprobante
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ComprobanteEmitido"
        "404":
          $ref: "#/components/responses/NotFound"

  /cfdi/{uuid}/cancelar:
    post:
      tags: ["CFDI"]
      summary: Cancelar comprobante
      parameters:
        - name: uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [motivo]
              properties:
                motivo:
                  type: string
                  enum: ["01", "02", "03", "04"]
                  description: |
                    Motivo de cancelación:
                    - 01: Comprobante emitido con errores con relación
                    - 02: Comprobante emitido con errores sin relación
                    - 03: No se llevó a cabo la operación
                    - 04: Operación nominativa relacionada en una factura global
                uuid_relacionado:
                  type: string
                  format: uuid
                  description: UUID del comprobante sustituto (requerido para motivo 01)
      responses:
        "200":
          description: Solicitud de cancelación enviada
          content:
            application/json:
              schema:
                type: object
                properties:
                  estado_cancelacion:
                    type: string
                    enum: [cancelado, en_proceso, rechazado]
                  mensaje:
                    type: string

  /hospitality/factura:
    post:
      tags: [Hospitality]
      summary: Factura de hotel/restaurante
      description: |
        Genera factura electrónica a partir de un folio de hospedaje o consumo.
        Compatible con Opera PMS, Micros POS y otros sistemas vía webhook.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/HospitalityFacturaRequest"
      responses:
        "201":
          description: Factura generada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ComprobanteEmitido"

  /hospitality/autofactura:
    post:
      tags: [Hospitality]
      summary: Portal de autofactura
      description: |
        Emite comprobante a partir de datos ingresados por el huésped en el portal
        de autofactura personalizable del hotel.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [folio, rfc_receptor, email]
              properties:
                folio:
                  type: string
                  example: "H-2026-00841"
                rfc_receptor:
                  type: string
                  example: "ABC010101XYZ"
                email:
                  type: string
                  format: email
                  example: "administracion@empresa.com"
      responses:
        "201":
          description: Comprobante enviado al email del receptor
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ComprobanteEmitido"

  /orders/orden:
    post:
      tags: [Orders]
      summary: Crear orden de compra
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OrdenRequest"
      responses:
        "201":
          description: Orden creada en estado pendiente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrdenResponse"

  /orders/{id}:
    get:
      tags: [Orders]
      summary: Consultar orden
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Detalle de la orden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrdenResponse"

  /orders/{id}/aprobar:
    patch:
      tags: [Orders]
      summary: Aprobar o rechazar orden
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [accion]
              properties:
                accion:
                  type: string
                  enum: [aprobar, rechazar]
                comentario:
                  type: string
      responses:
        "200":
          description: Estado de la orden actualizado

  /valid/verificar:
    post:
      tags: ["Validación SAT"]
      summary: Verificar comprobante
      description: "Verificación de CFDI ante el SAT. Devuelve estado vigente/cancelado."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [uuid]
              properties:
                uuid:
                  type: string
                  format: uuid
                  example: "6128396f-c09b-4ec6-8699-43c5f7e3b230"
      responses:
        "200":
          description: Estado de validación
          content:
            application/json:
              schema:
                type: object
                properties:
                  uuid:
                    type: string
                  estado:
                    type: string
                    enum: [vigente, cancelado, no_encontrado]
                  fecha_emision:
                    type: string
                    format: date-time
                  emisor_rfc:
                    type: string
                  receptor_rfc:
                    type: string
                  total:
                    type: number

  /webhooks:
    get:
      tags: [Webhooks]
      summary: Listar webhooks configurados
      responses:
        "200":
          description: Lista de webhooks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Webhook"
    post:
      tags: [Webhooks]
      summary: Registrar webhook
      description: |
        Configura una URL para recibir notificaciones en tiempo real cuando:
        - Un comprobante es timbrado exitosamente
        - Un comprobante es cancelado
        - Una orden de compra cambia de estado
        - Un comprobante falla el timbrado
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookCreate"
      responses:
        "201":
          description: Webhook registrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"

  /webhooks/{id}:
    delete:
      tags: [Webhooks]
      summary: Eliminar webhook
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Webhook eliminado

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT obtenido en POST /auth/token

  responses:
    Unauthorized:
      description: No autenticado o token expirado
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            codigo: "AUTH_001"
            mensaje: "Token inválido o expirado. Obtén uno nuevo en POST /auth/token."
    NotFound:
      description: Recurso no encontrado
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ValidationError:
      description: Error de validación en los datos enviados
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ValidationErrorResponse"

  schemas:
    TokenResponse:
      type: object
      properties:
        access_token:
          type: string
          example: "eyJhbGciOiJSUzI1NiJ9..."
        token_type:
          type: string
          example: "Bearer"
        expires_in:
          type: integer
          example: 3600
          description: Segundos hasta la expiración
        refresh_token:
          type: string
          example: "nt_rt_xxxxxxxxxxxxxx"

    EmitirRequest:
      type: object
      required: [emisor, receptor, conceptos]
      properties:
        emisor:
          type: object
          required: [rfc, nombre, regimen_fiscal]
          properties:
            rfc:
              type: string
              example: "ABC010101XYZ"
            nombre:
              type: string
            regimen_fiscal:
              type: string
              example: "601"
        receptor:
          type: object
          required: [rfc, nombre]
          properties:
            rfc:
              type: string
            nombre:
              type: string
            uso_cfdi:
              type: string
              example: "G03"
        conceptos:
          type: array
          minItems: 1
          items:
            type: object
            required: [descripcion, cantidad, valor_unitario, importe]
            properties:
              descripcion:
                type: string
              cantidad:
                type: number
              valor_unitario:
                type: number
              importe:
                type: number
        serie:
          type: string
          example: "A"
        folio:
          type: string
          example: "1001"

    ComprobanteEmitido:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
          example: "6128396f-c09b-4ec6-8699-43c5f7e3b230"
        serie:
          type: string
        folio:
          type: string
        fecha:
          type: string
          format: date-time
        sello_sat:
          type: string
        noCertificadoSAT:
          type: string
        estado:
          type: string
          enum: [vigente, cancelado, en_proceso]
        xml_base64:
          type: string
          format: byte
          description: XML timbrado codificado en Base64
        pdf_url:
          type: string
          format: uri
          description: URL temporal del PDF (24 horas)
        total:
          type: number

    HospitalityFacturaRequest:
      type: object
      required: [folio_pms, emisor_rfc, receptor_rfc]
      properties:
        folio_pms:
          type: string
          example: "RES-2026-4821"
          description: Número de reservación o folio del PMS (Opera, Innsist, etc.)
        emisor_rfc:
          type: string
          example: "ABC010101XYZ"
        receptor_rfc:
          type: string
        receptor_nombre:
          type: string
        receptor_email:
          type: string
          format: email
        uso_cfdi:
          type: string
          example: "D10"

    OrdenRequest:
      type: object
      required: [proveedor_id, conceptos]
      properties:
        proveedor_id:
          type: string
          example: "prov_abc123"
        centro_costo:
          type: string
          example: "CC-OPERACIONES"
        conceptos:
          type: array
          items:
            type: object
            properties:
              descripcion:
                type: string
              cantidad:
                type: number
              precio_unitario:
                type: number

    OrdenResponse:
      type: object
      properties:
        id:
          type: string
          example: "ord_2026_00491"
        estado:
          type: string
          enum: [pendiente, aprobada, rechazada, cancelada]
        total:
          type: number
        creada_en:
          type: string
          format: date-time
        aprobador:
          type: string

    Webhook:
      type: object
      properties:
        id:
          type: string
          example: "wh_xyz789"
        url:
          type: string
          format: uri
        eventos:
          type: array
          items:
            type: string
            enum: [cfdi.emitido, cfdi.cancelado, orden.aprobada, cfdi.error]
        activo:
          type: boolean
        creado_en:
          type: string
          format: date-time

    WebhookCreate:
      type: object
      required: [url, eventos]
      properties:
        url:
          type: string
          format: uri
          example: "https://tuapp.com/webhooks/nt"
        eventos:
          type: array
          items:
            type: string
            enum: [cfdi.emitido, cfdi.cancelado, orden.aprobada, cfdi.error]
          example: [cfdi.emitido, cfdi.cancelado]
        secret:
          type: string
          description: Secreto HMAC-SHA256 para verificar autenticidad del payload

    Error:
      type: object
      properties:
        codigo:
          type: string
        mensaje:
          type: string

    ValidationErrorResponse:
      type: object
      properties:
        codigo:
          type: string
          example: "VALIDATION_ERROR"
        mensaje:
          type: string
        errores:
          type: array
          items:
            type: object
            properties:
              campo:
                type: string
              mensaje:
                type: string
