openapi: 3.0.3
info:
  title: Simplo API
  version: 0.2.0
  contact:
    name: Simplo
    email: hola@simplo.cl
    url: https://simplo.cl
  x-generated-note: >-
    Especificación pública generada a partir del contrato canónico de la
    plataforma Simplo. La fuente de verdad es el contrato interno de
    plataforma; ante cualquier discrepancia, prevalece el comportamiento
    del servicio en producción.
  description: |
    API REST de facturación electrónica chilena (DTE) de Simplo.

    Permite emitir y consultar Documentos Tributarios Electrónicos (DTE),
    administrar CAFs (folios autorizados por el SII), datos de la empresa
    emisora, certificado digital, webhooks de eventos, y catálogos de
    clientes y productos.

    ## Autenticación

    Dos mecanismos:

    - **API Key** (`X-Api-Key`): pensada para integraciones servidor a servidor.
    - **Bearer JWT** (`Authorization: Bearer ...`): token de sesión de la
      plataforma Simplo, usado por las aplicaciones oficiales.

    > **Beta privada**: la emisión de DTE y en general las operaciones de
    > escritura vía API key están en **beta privada**. Hoy las API keys operan
    > en modo lectura. Para solicitar acceso de escritura escriba a
    > **hola@simplo.cl**.

    Toda petición opera en el contexto de la empresa (tenant) asociada a la
    credencial. No es necesario enviar el RUT de la empresa emisora en cada
    llamada.

    ## Emisión de DTE (flujo asíncrono)

    `POST /api/v1/billing/dte` valida el documento, asigna folio desde el CAF
    activo, genera el XML, lo timbra (TED) y lo firma digitalmente. La
    respuesta `201` retorna de inmediato con estado `firmado`. El envío al SII
    ocurre de forma **asíncrona**: siga el estado con
    `GET /api/v1/billing/dte/{id}` (polling) o suscribiendo un webhook.

    ## Idempotencia

    `POST /api/v1/billing/dte` exige el header `Idempotency-Key` (UUID).
    Reintentar con la misma key retorna la respuesta original con el header
    `X-Idempotency-Replay: true`. Reutilizar una key con un body distinto
    retorna `422` (`IDEMPOTENCY_BODY_MISMATCH`); una key aún en proceso
    retorna `409` (`IDEMPOTENCY_IN_PROGRESS`).

    ## Paginación

    Los listados paginados usan cursor: parámetros `cursor` y `limit`;
    la respuesta incluye `items` (o `data` en clientes/productos),
    `next_cursor` (null cuando no hay más páginas) y, donde aplica,
    `total_count`.

    ## Errores

    Todos los errores usan un sobre común (`ErrorResponse`) con `code`
    máquina-legible y `message` descriptivo; los errores de validación
    agregan `details` por campo.
servers:
  - url: https://api.simplo.cl
    description: Producción
security:
  - ApiKeyAuth: []
  - BearerAuth: []
tags:
  - name: DTE
    description: Emisión, consulta y descarga de Documentos Tributarios Electrónicos.
  - name: CAF
    description: Códigos de Autorización de Folios otorgados por el SII.
  - name: Empresa
    description: Datos tributarios de la empresa emisora y su certificado digital.
  - name: Webhooks
    description: Suscripción a eventos (cambios de estado de DTE, folios, certificado).
  - name: Clientes
    description: Catálogo de clientes (receptores frecuentes).
  - name: Productos
    description: Catálogo de productos y servicios.
paths:
  /api/v1/billing/dte:
    get:
      operationId: listarDTEs
      summary: Listar DTEs
      description: |-
        Listado con paginación basada en cursor. Soporta filtros por tipo de
        documento, estado, rango de fechas de emisión y RUT del receptor.
      tags:
        - DTE
      parameters:
        - name: tipo_dte
          in: query
          description: Filtrar por tipo de DTE
          schema:
            $ref: '#/components/schemas/TipoDTE'
        - name: estado
          in: query
          description: Filtrar por estado del ciclo de vida
          schema:
            $ref: '#/components/schemas/EstadoDTE'
        - name: fecha_desde
          in: query
          description: Fecha de emisión mínima (inclusive)
          schema:
            type: string
            format: date
        - name: fecha_hasta
          in: query
          description: Fecha de emisión máxima (inclusive)
          schema:
            type: string
            format: date
        - name: rut_receptor
          in: query
          description: RUT del receptor con dígito verificador
          schema:
            type: string
            example: 76543210-K
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Lista de DTEs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DTEListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: emitirDTE
      summary: Emitir DTE
      description: |-
        Emite un Documento Tributario Electrónico. Soporta los 12 tipos SII:
        facturas (33, 34), boletas (39, 41), liquidación factura (43), factura
        de compra (46), guía de despacho (52), notas de débito/crédito (56, 61)
        y documentos de exportación (110, 111, 112).

        Flujo: validación, asignación de folio desde el CAF activo, generación
        del XML, timbre electrónico (TED), firma digital y persistencia. La
        respuesta `201` retorna inmediatamente con estado `firmado`; el envío
        al SII ocurre de forma **asíncrona**. Use webhooks o polling en
        `GET /api/v1/billing/dte/{id}` para seguir el estado.

        Requiere el header `Idempotency-Key` (UUID). Un reintento con la misma
        key retorna la respuesta original con `X-Idempotency-Replay: true`.

        > **Beta privada**: la emisión y las escrituras vía API key están en
        > beta privada (solicitar acceso: hola@simplo.cl); las API keys hoy
        > operan en modo lectura.
      tags:
        - DTE
      x-codeSamples:
        - lang: sh
          label: cURL
          source: |
            curl -X POST "https://api.simplo.cl/api/v1/billing/dte" \
              -H "X-Api-Key: $SIMPLO_API_KEY" \
              -H "Content-Type: application/json" \
              -H "Idempotency-Key: $(uuidgen)" \
              -d '{
                "tipo_dte": 33,
                "fecha_emision": "2026-08-01",
                "receptor": {
                  "rut": "76543210-K",
                  "razon_social": "Empresa Ejemplo SpA",
                  "giro": "Desarrollo de software",
                  "direccion": "Av. Providencia 1234",
                  "comuna": "Providencia",
                  "ciudad": "Santiago"
                },
                "detalle": [
                  {
                    "nombre": "Servicio de consultoria TI",
                    "cantidad": 10,
                    "unidad": "UN",
                    "precio": 50000
                  }
                ]
              }'
        - lang: js
          label: JavaScript
          source: |
            const res = await fetch("https://api.simplo.cl/api/v1/billing/dte", {
              method: "POST",
              headers: {
                "X-Api-Key": process.env.SIMPLO_API_KEY,
                "Content-Type": "application/json",
                "Idempotency-Key": crypto.randomUUID(),
              },
              body: JSON.stringify({
                tipo_dte: 33,
                fecha_emision: "2026-08-01",
                receptor: {
                  rut: "76543210-K",
                  razon_social: "Empresa Ejemplo SpA",
                  giro: "Desarrollo de software",
                  direccion: "Av. Providencia 1234",
                  comuna: "Providencia",
                  ciudad: "Santiago",
                },
                detalle: [
                  { nombre: "Servicio de consultoria TI", cantidad: 10, unidad: "UN", precio: 50000 },
                ],
              }),
            });

            const dte = await res.json();
            console.log(dte.folio, dte.estado);
        - lang: cs
          label: C#
          source: |
            using var http = new HttpClient();
            http.DefaultRequestHeaders.Add("X-Api-Key",
                Environment.GetEnvironmentVariable("SIMPLO_API_KEY"));
            http.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());

            var body = """
            {
              "tipo_dte": 33,
              "fecha_emision": "2026-08-01",
              "receptor": {
                "rut": "76543210-K",
                "razon_social": "Empresa Ejemplo SpA",
                "giro": "Desarrollo de software",
                "direccion": "Av. Providencia 1234",
                "comuna": "Providencia",
                "ciudad": "Santiago"
              },
              "detalle": [
                { "nombre": "Servicio de consultoria TI", "cantidad": 10, "unidad": "UN", "precio": 50000 }
              ]
            }
            """;

            var res = await http.PostAsync(
                "https://api.simplo.cl/api/v1/billing/dte",
                new StringContent(body, Encoding.UTF8, "application/json"));
            res.EnsureSuccessStatusCode();
            Console.WriteLine(await res.Content.ReadAsStringAsync());
        - lang: java
          label: Java
          source: |
            var body = """
                {
                  "tipo_dte": 33,
                  "fecha_emision": "2026-08-01",
                  "receptor": {
                    "rut": "76543210-K",
                    "razon_social": "Empresa Ejemplo SpA",
                    "giro": "Desarrollo de software",
                    "direccion": "Av. Providencia 1234",
                    "comuna": "Providencia",
                    "ciudad": "Santiago"
                  },
                  "detalle": [
                    { "nombre": "Servicio de consultoria TI", "cantidad": 10, "unidad": "UN", "precio": 50000 }
                  ]
                }
                """;

            var client = HttpClient.newHttpClient();
            var request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.simplo.cl/api/v1/billing/dte"))
                .header("X-Api-Key", System.getenv("SIMPLO_API_KEY"))
                .header("Content-Type", "application/json")
                .header("Idempotency-Key", UUID.randomUUID().toString())
                .POST(HttpRequest.BodyPublishers.ofString(body))
                .build();

            var response = client.send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println(response.body());
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmitirDTERequest'
            examples:
              factura_afecta:
                summary: Factura electrónica (tipo 33)
                value:
                  tipo_dte: 33
                  fecha_emision: '2026-08-01'
                  receptor:
                    rut: 76543210-K
                    razon_social: Empresa Ejemplo SpA
                    giro: Desarrollo de software
                    direccion: Av. Providencia 1234
                    comuna: Providencia
                    ciudad: Santiago
                  detalle:
                    - nombre: Servicio de consultoria TI
                      cantidad: 10
                      unidad: UN
                      precio: 50000
              nota_credito_anula:
                summary: Nota de crédito (tipo 61) que anula una factura
                value:
                  tipo_dte: 61
                  receptor:
                    rut: 76543210-K
                    razon_social: Empresa Ejemplo SpA
                  detalle:
                    - nombre: Servicio de consultoria TI
                      cantidad: 10
                      precio: 50000
                  referencias:
                    - tipo_doc_ref: 33
                      folio_ref: 1042
                      fecha_ref: '2026-08-01'
                      codigo_ref: 1
                      razon_ref: Anula factura por devolucion
      responses:
        '201':
          description: DTE emitido exitosamente
          headers:
            X-Idempotency-Replay:
              $ref: '#/components/headers/X-Idempotency-Replay'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmitirDTEResponse'
              example:
                id: 4f9c9c2e-8a49-4f2e-9d5f-0d8f3a1b2c3d
                folio: 1042
                tipo_dte: 33
                estado: firmado
                monto_total: 595000
                created_at: '2026-08-01T14:30:00Z'
        '400':
          description: Error de validación
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: VALIDATION_ERROR
                message: Error de validacion en los campos enviados
                details:
                  - field: receptor.rut
                    code: INVALID_RUT
                    message: 'RUT invalido: digito verificador no coincide'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: |-
            Conflicto. Posibles causas:
            - Folio duplicado
            - Idempotency-Key ya está siendo procesada (código `IDEMPOTENCY_IN_PROGRESS`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: >-
            Idempotency-Key reutilizada con un body de request diferente al
            original (código `IDEMPOTENCY_BODY_MISMATCH`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          description: Servicio de idempotencia temporalmente no disponible. Reintentar.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/billing/dte/{id}:
    get:
      operationId: obtenerDTE
      summary: Obtener DTE por ID
      description: >-
        Retorna el DTE completo con estado actual, tracking del envío al SII y
        último detalle de consulta de estado. Con `include_xml=true` incluye el
        XML firmado en el campo `xml_documento`.
      tags:
        - DTE
      parameters:
        - $ref: '#/components/parameters/DTEId'
        - name: include_xml
          in: query
          description: Incluir XML firmado en la respuesta
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: DTE encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DTEResponse'
              example:
                id: 4f9c9c2e-8a49-4f2e-9d5f-0d8f3a1b2c3d
                tipo_dte: 33
                folio: 1042
                estado: aceptado
                rut_receptor: 76543210-K
                razon_social_receptor: Empresa Ejemplo SpA
                fecha_emision: '2026-08-01'
                monto_neto: 500000
                monto_exento: 0
                tasa_iva: 19.0
                iva: 95000
                monto_total: 595000
                detalle:
                  - nombre: Servicio de consultoria TI
                    cantidad: 10
                    unidad: UN
                    precio: 50000
                tracking:
                  track_id: '1234567890'
                  estado_envio: aceptado
                  informados: 1
                  aceptados: 1
                  rechazados: 0
                  reparos: 0
                  intentos: 1
                created_at: '2026-08-01T14:30:00Z'
                updated_at: '2026-08-01T15:05:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/billing/dte/{id}/pdf:
    get:
      operationId: descargarDTEPdf
      summary: Descargar PDF del DTE
      description: >-
        Genera y retorna la representación impresa del DTE en PDF, con timbre
        electrónico PDF417 y datos tributarios.
      tags:
        - DTE
      parameters:
        - $ref: '#/components/parameters/DTEId'
        - name: cedible
          in: query
          required: false
          description: >-
            Si es `true`, genera la copia cedible con acuse de recibo para los
            tipos de DTE que corresponden.
          schema:
            type: boolean
            default: false
        - name: copy
          in: query
          required: false
          description: Alternativa semántica para solicitar `copy=cedible`.
          schema:
            type: string
            enum:
              - cedible
      responses:
        '200':
          description: PDF del DTE
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/billing/dte/{id}/xml:
    get:
      operationId: descargarDTEXml
      summary: Descargar XML del DTE
      description: Retorna el XML firmado del DTE en encoding ISO-8859-1.
      tags:
        - DTE
      parameters:
        - $ref: '#/components/parameters/DTEId'
      responses:
        '200':
          description: XML firmado del DTE
          content:
            application/xml:
              schema:
                type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/billing/caf:
    get:
      operationId: listarCAFs
      summary: Listar CAFs
      description: >-
        Lista los CAFs (Códigos de Autorización de Folios) de la empresa con la
        cantidad de folios disponibles por cada uno.
      tags:
        - CAF
      parameters:
        - name: tipo_dte
          in: query
          description: Filtrar por tipo de DTE
          schema:
            $ref: '#/components/schemas/TipoDTE'
        - name: is_active
          in: query
          description: Filtrar por CAFs activos/inactivos
          schema:
            type: boolean
      responses:
        '200':
          description: Lista de CAFs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CAFListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: subirCAF
      summary: Subir CAF XML
      description: |-
        Sube un Código de Autorización de Folios obtenido desde el SII.
        Acepta el XML en base64 o raw. El sistema parsea y valida el CAF, y
        extrae el rango de folios y la clave RSA pública.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - CAF
      x-codeSamples:
        - lang: sh
          label: cURL
          source: |
            curl -X POST "https://api.simplo.cl/api/v1/billing/caf" \
              -H "X-Api-Key: $SIMPLO_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{
                "tipo_dte": 33,
                "caf_xml": "'"$(base64 -i CAF33.xml)"'"
              }'
        - lang: js
          label: JavaScript
          source: |
            import { readFile } from "node:fs/promises";

            const cafXml = await readFile("CAF33.xml", "base64");

            const res = await fetch("https://api.simplo.cl/api/v1/billing/caf", {
              method: "POST",
              headers: {
                "X-Api-Key": process.env.SIMPLO_API_KEY,
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ tipo_dte: 33, caf_xml: cafXml }),
            });

            const caf = await res.json();
            console.log(caf.folio_desde, caf.folio_hasta);
        - lang: cs
          label: C#
          source: |
            using var http = new HttpClient();
            http.DefaultRequestHeaders.Add("X-Api-Key",
                Environment.GetEnvironmentVariable("SIMPLO_API_KEY"));

            var cafXml = Convert.ToBase64String(File.ReadAllBytes("CAF33.xml"));
            var body = JsonSerializer.Serialize(new { tipo_dte = 33, caf_xml = cafXml });

            var res = await http.PostAsync(
                "https://api.simplo.cl/api/v1/billing/caf",
                new StringContent(body, Encoding.UTF8, "application/json"));
            res.EnsureSuccessStatusCode();
            Console.WriteLine(await res.Content.ReadAsStringAsync());
        - lang: java
          label: Java
          source: |
            var cafXml = Base64.getEncoder()
                .encodeToString(Files.readAllBytes(Path.of("CAF33.xml")));
            var body = """
                { "tipo_dte": 33, "caf_xml": "%s" }
                """.formatted(cafXml);

            var client = HttpClient.newHttpClient();
            var request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.simplo.cl/api/v1/billing/caf"))
                .header("X-Api-Key", System.getenv("SIMPLO_API_KEY"))
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(body))
                .build();

            var response = client.send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println(response.body());
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubirCAFRequest'
      responses:
        '201':
          description: CAF registrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CAFResponse'
              example:
                id: 9b7e6a3c-2f1d-4e5a-8b9c-0d1e2f3a4b5c
                tipo_dte: 33
                folio_desde: 1
                folio_hasta: 200
                folios_disponibles: 158
                ambiente: produccion
                fecha_autorizacion: '2026-07-15'
                is_active: true
                created_at: '2026-07-15T10:00:00Z'
        '400':
          description: CAF inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: CAF con rango de folios ya registrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/billing/caf/{id}:
    get:
      operationId: obtenerCAF
      summary: Detalle CAF
      description: Retorna el detalle de un CAF con rango de folios y disponibilidad.
      tags:
        - CAF
      parameters:
        - name: id
          in: path
          required: true
          description: UUID del CAF
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: CAF encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CAFResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/billing/empresa:
    get:
      operationId: obtenerEmpresa
      summary: Obtener empresa actual
      description: >-
        Retorna los datos tributarios de la empresa asociada a la credencial
        actual (JWT o API key).
      tags:
        - Empresa
      responses:
        '200':
          description: Datos de la empresa
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmpresaResponse'
              example:
                id: 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d
                rut: 76123456-7
                razon_social: Empresa Ejemplo SpA
                giro: Servicios de software
                direccion: Av. Apoquindo 1234
                comuna: Las Condes
                ciudad: Santiago
                ambiente: produccion
                fecha_resolucion: '2026-01-10'
                numero_resolucion: 0
                inbound_email: 761234567@dte.simplo.cl
                inbound_status: active
        '401':
          $ref: '#/components/responses/Unauthorized'
    put:
      operationId: actualizarEmpresa
      summary: Actualizar datos empresa
      description: |-
        Actualiza datos tributarios y de contacto de la empresa emisora.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - Empresa
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ActualizarEmpresaRequest'
            example:
              razon_social: Empresa Ejemplo SpA
              giro: Servicios de software
              direccion: Av. Apoquindo 1234
              comuna: Las Condes
              ciudad: Santiago
      responses:
        '200':
          description: Empresa actualizada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmpresaResponse'
        '400':
          description: Error de validación
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/billing/empresa/certificado:
    get:
      operationId: estadoCertificado
      summary: Ver estado certificado
      description: >-
        Retorna el estado del certificado digital de firma (vigencia, RUT del
        firmante) sin exponer claves privadas.
      tags:
        - Empresa
      responses:
        '200':
          description: Estado del certificado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CertificadoStatusResponse'
              example:
                has_certificado: true
                rut_firmante: 12345678-9
                fecha_vencimiento: '2027-06-30'
                dias_para_vencimiento: 307
                is_active: true
                uploaded_at: '2026-06-30T12:00:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: No hay certificado cargado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/billing/empresa/certificado/pfx:
    post:
      operationId: subirCertificadoPFX
      summary: Subir certificado digital (PFX/P12)
      description: |-
        Sube un certificado digital en formato `.pfx` o `.p12` vía
        `multipart/form-data`. El servidor convierte el PFX internamente y
        almacena el certificado encriptado; la clave privada nunca se expone
        en la API. Requiere rol admin. Tamaño máximo del archivo: 50KB.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - Empresa
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
                - password
                - rut_firmante
              properties:
                file:
                  type: string
                  format: binary
                  description: Archivo .pfx o .p12 del certificado digital (max 50KB)
                password:
                  type: string
                  description: Password del certificado PFX
                rut_firmante:
                  type: string
                  description: RUT del firmante (con dígito verificador)
                  example: 12345678-9
      responses:
        '201':
          description: Certificado subido y procesado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CertificadoStatusResponse'
        '400':
          description: PFX inválido, password incorrecto, o archivo demasiado grande
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Requiere rol admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/billing/webhooks:
    get:
      operationId: listarWebhooks
      summary: Listar webhooks
      description: Lista los webhooks registrados para la empresa.
      tags:
        - Webhooks
      responses:
        '200':
          description: Lista de webhooks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WebhookResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: registrarWebhook
      summary: Registrar webhook
      description: |-
        Registra una URL HTTPS para recibir eventos vía webhook. Los eventos se
        firman con HMAC-SHA256 usando el `secret` del webhook; el secret solo
        se retorna completo al momento de crear el webhook.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookRequest'
            example:
              url: https://mi-app.cl/webhooks/simplo
              events:
                - dte.accepted
                - dte.rejected
      responses:
        '201':
          description: Webhook registrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookResponse'
              example:
                id: 7c8d9e0f-1a2b-4c3d-8e5f-6a7b8c9d0e1f
                url: https://mi-app.cl/webhooks/simplo
                events:
                  - dte.accepted
                  - dte.rejected
                secret: whsec_ejemplo_no_real
                is_active: true
                created_at: '2026-08-01T14:30:00Z'
        '400':
          description: URL inválida o eventos no soportados
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/billing/webhooks/{id}:
    delete:
      operationId: eliminarWebhook
      summary: Eliminar webhook
      description: |-
        Elimina un webhook registrado. Deja de entregar eventos de inmediato.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - Webhooks
      parameters:
        - name: id
          in: path
          required: true
          description: UUID del webhook
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Webhook eliminado
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/billing/clientes:
    get:
      operationId: listClientes
      summary: Listar clientes
      description: Listado de clientes con paginación basada en cursor.
      tags:
        - Clientes
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - name: limit
          in: query
          description: Cantidad máxima de resultados por página
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Lista de clientes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClienteListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createCliente
      summary: Crear cliente
      description: |-
        Crea un cliente (receptor frecuente) en el catálogo de la empresa. El
        RUT debe ser valido y único dentro de la empresa.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - Clientes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateClienteRequest'
            example:
              rut: 76543210-K
              razon_social: Empresa Ejemplo SpA
              giro: Desarrollo de software
              direccion: Av. Providencia 1234
              comuna: Providencia
              ciudad: Santiago
              email: facturacion@ejemplo.cl
      responses:
        '201':
          description: Cliente creado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClienteResponse'
        '400':
          description: Datos inválidos (RUT inválido, razon_social vacía)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Cliente con este RUT ya existe para la empresa
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/billing/clientes/search:
    get:
      operationId: searchClientes
      summary: Buscar clientes por nombre o RUT
      description: Búsqueda rápida de clientes por razón social o RUT.
      tags:
        - Clientes
      parameters:
        - name: q
          in: query
          required: true
          description: Texto de búsqueda (nombre o RUT)
          schema:
            type: string
        - name: limit
          in: query
          description: Cantidad máxima de resultados
          schema:
            type: integer
            default: 10
      responses:
        '200':
          description: Resultados de búsqueda
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ClienteResponse'
        '400':
          description: Parámetro q requerido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/billing/clientes/{id}:
    get:
      operationId: getCliente
      summary: Obtener cliente por ID
      description: Retorna un cliente del catálogo por su UUID.
      tags:
        - Clientes
      parameters:
        - $ref: '#/components/parameters/ClienteId'
      responses:
        '200':
          description: Cliente encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClienteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      operationId: updateCliente
      summary: Actualizar cliente
      description: |-
        Actualiza datos de un cliente existente. El RUT no es modificable.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - Clientes
      parameters:
        - $ref: '#/components/parameters/ClienteId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateClienteRequest'
      responses:
        '200':
          description: Cliente actualizado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClienteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteCliente
      summary: Eliminar cliente (soft delete)
      description: |-
        Elimina un cliente del catálogo (soft delete). Los DTEs ya emitidos a
        ese receptor no se ven afectados.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - Clientes
      parameters:
        - $ref: '#/components/parameters/ClienteId'
      responses:
        '200':
          description: Cliente eliminado
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/billing/productos:
    get:
      operationId: listProductos
      summary: Listar productos
      description: >-
        Listado de productos con paginación basada en cursor. Soporta filtro
        por categoría.
      tags:
        - Productos
      parameters:
        - name: categoria
          in: query
          description: Filtrar por categoría
          schema:
            type: string
        - $ref: '#/components/parameters/Cursor'
        - name: limit
          in: query
          description: Cantidad máxima de resultados por página
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Lista de productos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductoListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createProducto
      summary: Crear producto
      description: |-
        Crea un producto o servicio en el catálogo de la empresa. El `codigo`
        (si se envía) debe ser único dentro de la empresa.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - Productos
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProductoRequest'
            example:
              codigo: SERV-001
              nombre: Hora de consultoria TI
              precio_unitario: 50000
              unidad: HR
              es_exento: false
              categoria: Servicios
      responses:
        '201':
          description: Producto creado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductoResponse'
        '400':
          description: Datos inválidos (nombre vacio, precio negativo)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Producto con este código ya existe para la empresa
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/billing/productos/search:
    get:
      operationId: searchProductos
      summary: Buscar productos por nombre o código
      description: Búsqueda rápida de productos por nombre o código.
      tags:
        - Productos
      parameters:
        - name: q
          in: query
          required: true
          description: Texto de búsqueda (nombre o código)
          schema:
            type: string
        - name: limit
          in: query
          description: Cantidad máxima de resultados
          schema:
            type: integer
            default: 10
      responses:
        '200':
          description: Resultados de búsqueda
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProductoResponse'
        '400':
          description: Parámetro q requerido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/billing/productos/{id}:
    get:
      operationId: getProducto
      summary: Obtener producto por ID
      description: Retorna un producto del catálogo por su UUID.
      tags:
        - Productos
      parameters:
        - $ref: '#/components/parameters/ProductoId'
      responses:
        '200':
          description: Producto encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductoResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      operationId: updateProducto
      summary: Actualizar producto
      description: |-
        Actualiza datos de un producto existente.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - Productos
      parameters:
        - $ref: '#/components/parameters/ProductoId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateProductoRequest'
      responses:
        '200':
          description: Producto actualizado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductoResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteProducto
      summary: Eliminar producto (soft delete)
      description: |-
        Elimina un producto del catálogo (soft delete). Los DTEs ya emitidos
        con ese item no se ven afectados.

        > **Beta privada**: las escrituras vía API key están en beta privada
        > (solicitar acceso: hola@simplo.cl); las API keys hoy operan en modo
        > lectura.
      tags:
        - Productos
      parameters:
        - $ref: '#/components/parameters/ProductoId'
      responses:
        '200':
          description: Producto eliminado
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
      description: >-
        API key de integración servidor a servidor. La emisión y en general
        las operaciones de escritura vía API key están en **beta privada**
        (solicitar acceso: hola@simplo.cl); hoy las keys operan en modo
        lectura.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        JWT de sesión de la plataforma Simplo (RS256). Contiene el contexto de
        la empresa (tenant) para multi-tenancy.
  headers:
    X-Idempotency-Replay:
      description: >-
        Presente con valor `true` cuando la respuesta es un replay de una
        ejecución anterior (misma Idempotency-Key).
      schema:
        type: string
        enum:
          - 'true'
  parameters:
    DTEId:
      name: id
      in: path
      required: true
      description: UUID del DTE
      schema:
        type: string
        format: uuid
    ClienteId:
      name: id
      in: path
      required: true
      description: UUID del cliente
      schema:
        type: string
        format: uuid
    ProductoId:
      name: id
      in: path
      required: true
      description: UUID del producto
      schema:
        type: string
        format: uuid
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        UUID único para garantizar idempotencia. Obligatorio. Reintentar con la
        misma key retorna la respuesta original con el header
        `X-Idempotency-Replay: true`. Las keys completadas se retienen 24
        horas.
      schema:
        type: string
        format: uuid
        example: 550e8400-e29b-41d4-a716-446655440000
    Cursor:
      name: cursor
      in: query
      description: Cursor de paginación retornado en `next_cursor` de la página anterior
      schema:
        type: string
    Limit:
      name: limit
      in: query
      description: Cantidad máxima de resultados por página
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
  responses:
    Unauthorized:
      description: Token o API key inválido o ausente
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Recurso no encontrado
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Rate limit excedido
      headers:
        Retry-After:
          description: Segundos hasta poder reintentar
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    TipoDTE:
      type: integer
      description: >-
        Código SII del tipo de Documento Tributario Electrónico (33 factura,
        39 boleta, 52 guía de despacho, 56/61 notas, 110-112 exportación).
        Detalle de cada tipo en la guía
        [Tipos de DTE](https://docs.simplo.cl/referencia/tipos-de-dte/).
      enum:
        - 33
        - 34
        - 39
        - 41
        - 43
        - 46
        - 52
        - 56
        - 61
        - 110
        - 111
        - 112
    EstadoDTE:
      type: string
      description: |
        Estado del DTE en su ciclo de vida.

        Importante:
        - la API serializa estos valores exactamente como aparecen aquí
        - use estos mismos literales para renderizar UI, hacer exhaustiveness
          checks y filtrar por `estado` en `GET /api/v1/billing/dte`

        Valores:
        - borrador: Creado pero aún no firmado
        - generado: Documento construido, previo a firma
        - firmado: Firmado digitalmente, pendiente de envío
        - enviando: Sobre aceptado por la plataforma, esperando procesamiento SII
        - procesando: SII recibió el envío y se está consultando estado final del DTE
        - aceptado: Aceptado por el SII
        - rechazado: Rechazado por el SII
        - con_reparos: Aceptado por el SII con observaciones
      enum:
        - borrador
        - generado
        - firmado
        - enviando
        - procesando
        - aceptado
        - rechazado
        - con_reparos
    Receptor:
      type: object
      required:
        - rut
        - razon_social
      properties:
        rut:
          type: string
          description: RUT del receptor con dígito verificador
          example: 76543210-K
        razon_social:
          type: string
          maxLength: 200
          example: Empresa Ejemplo SpA
        giro:
          type: string
          maxLength: 200
          example: Desarrollo de software
        direccion:
          type: string
          maxLength: 300
          example: Av. Providencia 1234
        comuna:
          type: string
          maxLength: 100
          example: Providencia
        ciudad:
          type: string
          maxLength: 100
          example: Santiago
    LineaDetalle:
      type: object
      required:
        - nombre
      properties:
        tipo_doc_liq:
          type: string
          maxLength: 3
          description: >-
            Tipo de documento liquidado. Requerido en Liquidación Factura
            (tipo 43).
          example: '33'
        ind_exe:
          type: integer
          enum:
            - 1
          description: Marca una línea como exenta cuando el tipo de DTE lo requiere.
        cod_imp_adic:
          type: integer
          description: >-
            Código de impuesto adicional por línea. Para factura de compra con
            retención total de IVA se usa `15`.
          example: 15
        nombre:
          type: string
          maxLength: 200
          description: Nombre del item o servicio
          example: Servicio de consultoria TI
        dsc_item:
          type: string
          maxLength: 1000
          description: >-
            Descripción adicional de la línea. En notas con `codigo_ref=2` y
            monto cero se serializa como `DscItem` y se omiten
            `QtyItem`/`PrcItem`.
          example: Detalle adicional del servicio
        cantidad:
          type: number
          minimum: 1.0e-06
          description: >-
            Cantidad (soporta decimales para unidades fraccionarias). Puede
            omitirse solo en correcciones de texto `codigo_ref=2` sin monto.
          example: 10
        unidad:
          type: string
          maxLength: 4
          description: >-
            Unidad de medida opcional del item, por ejemplo `UN`, `Kg` o `Lt`
          example: UN
        precio:
          type: integer
          minimum: 0
          description: >-
            Precio unitario en pesos chilenos. Puede omitirse solo en
            correcciones de texto `codigo_ref=2` sin monto.
          example: 50000
        monto_item:
          type: integer
          description: >-
            Monto total de la línea. Uso acotado (por ejemplo liquidaciones)
            cuando el valor de línea no debe recalcularse como cantidad por
            precio.
          example: 329448
        descuento_pct:
          type: number
          minimum: 0
          maximum: 100
          description: Descuento porcentual sobre la línea (0-100)
          example: 10
        recargo_pct:
          type: number
          minimum: 0
          maximum: 100
          description: Recargo porcentual sobre la línea (0-100)
          example: 0
    Referencia:
      type: object
      required:
        - tipo_doc_ref
        - folio_ref
        - fecha_ref
        - razon_ref
      properties:
        tipo_doc_ref:
          oneOf:
            - $ref: '#/components/schemas/TipoDTE'
            - type: string
              minLength: 1
              maxLength: 3
              description: >-
                Código libre para referencias de exportación, por ejemplo
                `MIC`, `DUS`, `AWB` o `SNA`.
        folio_ref:
          type: integer
          description: Folio del documento referenciado
          example: 123
        fecha_ref:
          type: string
          format: date
          description: Fecha del documento referenciado
        codigo_ref:
          type: integer
          description: |
            Código de referencia SII:
            - 1: Anula documento referenciado
            - 2: Corrige texto del documento referenciado
            - 3: Corrige montos del documento referenciado
          enum:
            - 1
            - 2
            - 3
        razon_ref:
          type: string
          maxLength: 200
          description: Razón de la referencia
          example: Anula factura por devolucion
    DscRcgGlobal:
      type: object
      required:
        - tpo_mov
        - tpo_valor
        - valor_dr
      properties:
        nro_lin_dr:
          type: integer
          description: Número de línea del descuento/recargo
        tpo_mov:
          type: string
          enum:
            - D
            - R
          description: D = descuento, R = recargo
        tpo_valor:
          type: string
          enum:
            - '%'
            - '$'
          description: '% = porcentaje, $ = monto fijo'
        valor_dr:
          type: number
          description: >-
            Valor del descuento/recargo. En exportación puede incluir
            decimales.
          example: 5.0
        glosa_dr:
          type: string
          maxLength: 45
          description: Glosa descriptiva
          example: Descuento por volumen
        ind_exe_dr:
          type: integer
          enum:
            - 1
            - 2
          description: >-
            1 = descuento/recargo global no afecto; 2 = no facturable. Para DTE
            exentos/exportación se infiere 1 si se omite.
    ComisionDTE:
      type: object
      required:
        - tipo_movim
        - glosa
        - val_com_neto
        - val_com_exe
      properties:
        tipo_movim:
          type: string
          enum:
            - C
            - O
          description: >-
            `C` para cobro/comisión positiva, `O` para otros movimientos o
            rebajas.
        glosa:
          type: string
          example: NETO COMISION FIJA
        tasa_comision:
          type: number
          example: 2.5
        val_com_neto:
          type: integer
          example: 1538
        val_com_exe:
          type: integer
          example: 0
        val_com_iva:
          type: integer
          example: 292
    ImpuestoRetencion:
      type: object
      required:
        - tipo_imp
      properties:
        tipo_imp:
          type: integer
          description: >-
            Código SII del impuesto retenido. Para IVA retenido total en
            factura de compra se usa `15`.
          example: 15
        tasa_imp:
          type: number
          description: >-
            Tasa usada para calcular el monto retenido cuando `monto_imp` no
            viene informado.
          example: 19
        monto_imp:
          type: integer
          description: >-
            Monto retenido explícito. Si se omite y hay `tasa_imp`, se calcula
            sobre el neto.
          example: 19000
    ExportData:
      type: object
      description: Datos de exportación para tipos 110, 111, 112
      required:
        - tpo_moneda
      properties:
        tpo_moneda:
          type: string
          description: >-
            Código de moneda según tabla Aduanas (e.g., "DOLAR USA", "EURO")
          example: EURO
        tpo_cambio:
          type: number
          description: >-
            Tipo de cambio fijado por el Banco Central para OtraMoneda cuando
            se informa
          example: 950.5
        mnt_export:
          type: number
          description: Monto auxiliar de compatibilidad para OtraMoneda
          example: 90000
        nacionalidad:
          type: string
          description: Código o nombre de país para Receptor/Extranjero/Nacionalidad
          example: ESPANA
        num_id:
          type: string
          description: >-
            Número de identificación del receptor extranjero/turista para
            Exportaciones/Extranjero/NumId
          example: '0'
        tipo_doc_id:
          type: integer
          deprecated: true
          description: >-
            No usar en tipos 110, 111, 112; el XSD de Exportaciones no permite
            TipoDocID dentro de Receptor/Extranjero
          example: 1
        forma_pago_exp:
          type: integer
          description: Código Aduana de forma de pago de exportación
          example: 21
        modalidad_venta:
          type: integer
          description: Código Aduana de modalidad de venta
          example: 4
        clausula_venta:
          type: string
          description: >-
            Cláusula Aduana, acepta código numérico o etiqueta conocida como
            CIF, CFR, FOB
          example: CIF
        total_clausula:
          type: number
          description: Total de la cláusula de venta
          example: 1535.87
        via_transporte:
          type: integer
          description: Código Aduana de vía de transporte
          example: 4
        puerto_embarque:
          type: string
          description: Código o nombre de puerto de embarque
          example: CALDERA
        puerto_desembarque:
          type: string
          description: Código o nombre de puerto de desembarque
          example: SIDNEY
        tara:
          type: integer
          description: Tara
          example: 0
        unidad_tara:
          type: integer
          description: Código Aduana de unidad de medida de tara
          example: 10
        peso_bruto:
          type: number
          description: Peso bruto
          example: 0
        unidad_peso_bruto:
          type: integer
          description: Código Aduana de unidad de peso bruto
          example: 8
        peso_neto:
          type: number
          description: Peso neto
          example: 0
        unidad_peso_neto:
          type: integer
          description: Código Aduana de unidad de peso neto
          example: 8
        tipo_bulto:
          type: string
          description: Tipo de bulto, acepta código numérico o etiqueta conocida
          example: PALLETS
        total_bultos:
          type: integer
          description: Total de bultos
          example: 36
        marcas:
          type: string
          description: >-
            Marcas informadas dentro de TipoBultos cuando la operación lo
            exige
          example: ROLLOS
        mnt_flete:
          type: number
          description: Monto de flete en moneda de venta
          example: 393.15
        mnt_seguro:
          type: number
          description: Monto de seguro en moneda de venta
          example: 100.64
        pais_recep:
          type: string
          description: Código o nombre de país receptor según tabla Aduanas
          example: AUSTRALIA
        pais_dest:
          type: string
          description: Código o nombre de país destino según tabla Aduanas
          example: AUSTRALIA
    EmitirDTERequest:
      type: object
      required:
        - tipo_dte
        - receptor
        - detalle
      properties:
        tipo_dte:
          $ref: '#/components/schemas/TipoDTE'
        fecha_emision:
          type: string
          format: date
          description: |
            Fecha de emisión tributaria del DTE en formato YYYY-MM-DD. Si se
            omite, se usa la fecha de negocio en Chile. Para exportación con
            moneda extranjera, debe coincidir con una fecha que tenga tipo de
            cambio oficial publicado cuando se informa `export_data.tpo_cambio`.
        receptor:
          $ref: '#/components/schemas/Receptor'
        detalle:
          type: array
          minItems: 1
          maxItems: 60
          items:
            $ref: '#/components/schemas/LineaDetalle'
        comisiones:
          type: array
          items:
            $ref: '#/components/schemas/ComisionDTE'
          description: Comisiones de Liquidación Factura (tipo 43)
        impuestos_retenciones:
          type: array
          items:
            $ref: '#/components/schemas/ImpuestoRetencion'
          description: >-
            Impuestos retenidos en Totales/ImptoReten. Usado por factura de
            compra (tipo 46) y sus notas.
        referencias:
          type: array
          maxItems: 40
          items:
            $ref: '#/components/schemas/Referencia'
          description: >-
            Requerido para notas de crédito (61), notas de débito (56), y
            exportación (111, 112)
        ind_traslado:
          type: integer
          enum:
            - 1
            - 2
            - 3
            - 4
            - 5
            - 6
            - 7
            - 8
            - 9
          description: |
            Tipo de traslado para Guía de Despacho (tipo 52):
            - 1: Operación constituye venta
            - 2: Venta por efectuar
            - 3: Consignación
            - 4: Entrega gratuita
            - 5: Traslado interno
            - 6: Otros traslados no venta
            - 7: Guía de devolución
            - 8: Traslado para exportación (no venta)
            - 9: Venta para exportación
        tipo_despacho:
          type: integer
          enum:
            - 1
            - 2
            - 3
          description: |
            Modo de despacho para Guía de Despacho (tipo 52):
            - 1: Despacho por cuenta del comprador
            - 2: Despacho por cuenta del emisor a instalaciones del comprador
            - 3: Despacho por cuenta del emisor a otras instalaciones
        ind_servicio:
          type: integer
          enum:
            - 1
            - 2
            - 3
            - 4
            - 5
            - 6
          description: |
            Indicador de servicio:
            - 1: Facturación de servicios periódicos domiciliarios
            - 2: Facturación de otros servicios periódicos
            - 3: Factura de servicio; en exportación, servicio calificado por Aduana
            - 4: Factura de exportación por servicios de hotelería
            - 5: Factura de exportación por transporte terrestre internacional
            - 6: Factura de exportación por servicios prestados y utilizados totalmente en el extranjero
        descuentos_globales:
          type: array
          maxItems: 20
          items:
            $ref: '#/components/schemas/DscRcgGlobal'
          description: Descuentos y recargos globales aplicados sobre el neto
        auto_send:
          type: boolean
          default: true
          description: |
            Cuando es `true` (default), el DTE se encola para envío individual
            al SII inmediatamente después de emitirse. Cuando es `false`, el
            DTE queda en estado `firmado` para un envío posterior.
        export_data:
          $ref: '#/components/schemas/ExportData'
        metadata:
          type: object
          additionalProperties: true
          description: Metadata extensible definida por el integrador
    EmitirDTEResponse:
      type: object
      required:
        - id
        - folio
        - tipo_dte
        - estado
        - monto_total
        - created_at
      properties:
        id:
          type: string
          format: uuid
        folio:
          type: integer
          example: 1042
        tipo_dte:
          $ref: '#/components/schemas/TipoDTE'
        estado:
          $ref: '#/components/schemas/EstadoDTE'
        monto_total:
          type: integer
          description: Monto total en pesos chilenos
          example: 595000
        created_at:
          type: string
          format: date-time
    TrackingInfo:
      type: object
      description: Información de tracking del envío al SII
      properties:
        envio_id:
          type: string
          format: uuid
          nullable: true
        track_id:
          type: string
          nullable: true
          description: Track ID asignado por el SII
        estado_envio:
          type: string
          nullable: true
        codigo_sii:
          type: string
          nullable: true
          description: Código de respuesta del SII
        glosa_sii:
          type: string
          nullable: true
          description: Glosa descriptiva de la respuesta SII
        informados:
          type: integer
          description: Cantidad total de documentos informados por el SII
        aceptados:
          type: integer
          description: Cantidad de documentos aceptados
        rechazados:
          type: integer
          description: Cantidad de documentos rechazados
        reparos:
          type: integer
          description: Cantidad de documentos con reparos
        intentos:
          type: integer
    SIIStatusInfo:
      type: object
      description: Último detalle fino por documento consultado al SII.
      properties:
        source:
          type: string
          description: Fuente del dato consultado en SII
        track_id:
          type: string
          nullable: true
        estado_sii:
          type: string
          nullable: true
          description: Estado crudo del documento devuelto por SII
        glosa:
          type: string
          nullable: true
          description: Glosa consolidada
        glosa_estado:
          type: string
          nullable: true
          description: Glosa principal asociada al estado
        err_code:
          type: string
          nullable: true
        glosa_err:
          type: string
          nullable: true
        num_atencion:
          type: string
          nullable: true
        checked_at:
          type: string
          format: date-time
          nullable: true
        raw_xml:
          type: string
          nullable: true
          description: Respuesta XML cruda relevante, cuando está disponible.
    DTEResponse:
      type: object
      required:
        - id
        - tipo_dte
        - folio
        - estado
        - rut_receptor
        - razon_social_receptor
        - fecha_emision
        - monto_total
        - detalle
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        tipo_dte:
          $ref: '#/components/schemas/TipoDTE'
        folio:
          type: integer
        estado:
          $ref: '#/components/schemas/EstadoDTE'
        rut_receptor:
          type: string
          example: 76543210-K
        razon_social_receptor:
          type: string
          example: Empresa Ejemplo SpA
        fecha_emision:
          type: string
          format: date
        monto_neto:
          type: integer
          nullable: true
        monto_exento:
          type: integer
          nullable: true
        tasa_iva:
          type: number
          example: 19.0
        iva:
          type: integer
          nullable: true
        monto_total:
          type: integer
        detalle:
          type: array
          items:
            $ref: '#/components/schemas/LineaDetalle'
        referencias:
          type: array
          items:
            $ref: '#/components/schemas/Referencia'
        tracking:
          $ref: '#/components/schemas/TrackingInfo'
        sii_status:
          $ref: '#/components/schemas/SIIStatusInfo'
        xml_documento:
          type: string
          nullable: true
          description: XML firmado (solo si se solicita con include_xml=true)
        metadata:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    DTEListResponse:
      type: object
      required:
        - items
        - total_count
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/DTEResponse'
        next_cursor:
          type: string
          nullable: true
          description: Cursor para la siguiente página. Null si no hay más resultados.
        total_count:
          type: integer
          description: Total de registros que coinciden con los filtros
    SubirCAFRequest:
      type: object
      required:
        - tipo_dte
        - caf_xml
      properties:
        tipo_dte:
          $ref: '#/components/schemas/TipoDTE'
        caf_xml:
          type: string
          description: XML del CAF en base64 o raw XML
    CAFResponse:
      type: object
      required:
        - id
        - tipo_dte
        - folio_desde
        - folio_hasta
        - folios_disponibles
        - ambiente
        - fecha_autorizacion
        - is_active
        - created_at
      properties:
        id:
          type: string
          format: uuid
        tipo_dte:
          $ref: '#/components/schemas/TipoDTE'
        folio_desde:
          type: integer
          example: 1
        folio_hasta:
          type: integer
          example: 200
        folios_disponibles:
          type: integer
          description: Cantidad de folios aún no asignados
          example: 158
        ambiente:
          type: string
          enum:
            - certificacion
            - produccion
        fecha_autorizacion:
          type: string
          format: date
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
    CAFListResponse:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/CAFResponse'
    EmpresaResponse:
      type: object
      required:
        - id
        - rut
        - razon_social
        - giro
        - direccion
        - comuna
        - ciudad
        - ambiente
        - fecha_resolucion
        - numero_resolucion
      properties:
        id:
          type: string
          format: uuid
        rut:
          type: string
          example: 76123456-7
        razon_social:
          type: string
          example: Empresa Ejemplo SpA
        giro:
          type: string
          example: Servicios de software
        direccion:
          type: string
          example: Av. Apoquindo 1234
        comuna:
          type: string
          example: Las Condes
        ciudad:
          type: string
          example: Santiago
        ambiente:
          type: string
          description: Ambiente SII en que opera la empresa
          enum:
            - certificacion
            - produccion
        fecha_resolucion:
          type: string
          format: date
          description: Fecha de la resolución SII que autoriza la emisión electrónica
        numero_resolucion:
          type: integer
          description: Número de la resolución SII (puede ser 0)
        inbound_email:
          type: string
          description: >-
            Casilla de recepción de DTE de proveedores asignada a la empresa
          example: 761234567@dte.simplo.cl
        inbound_status:
          type: string
          example: active
    ActualizarEmpresaRequest:
      type: object
      properties:
        razon_social:
          type: string
          maxLength: 200
        giro:
          type: string
          maxLength: 200
        direccion:
          type: string
          maxLength: 300
        comuna:
          type: string
          maxLength: 100
        ciudad:
          type: string
          maxLength: 100
        ambiente:
          type: string
          enum:
            - certificacion
            - produccion
        fecha_resolucion:
          type: string
          format: date
        numero_resolucion:
          type: integer
        webhook_url:
          type: string
          format: uri
        config:
          type: object
          additionalProperties: true
    CertificadoStatusResponse:
      type: object
      required:
        - has_certificado
      properties:
        has_certificado:
          type: boolean
        rut_firmante:
          type: string
          nullable: true
          example: 12345678-9
        fecha_vencimiento:
          type: string
          format: date
          nullable: true
        dias_para_vencimiento:
          type: integer
          nullable: true
          description: Días restantes hasta vencimiento
        is_active:
          type: boolean
        uploaded_at:
          type: string
          format: date-time
          nullable: true
    WebhookRequest:
      type: object
      required:
        - url
        - events
      properties:
        url:
          type: string
          format: uri
          description: URL HTTPS donde se enviarán los eventos
          example: https://mi-app.cl/webhooks/simplo
        events:
          type: array
          minItems: 1
          items:
            type: string
            enum:
              - dte.created
              - dte.accepted
              - dte.rejected
              - dte.repaired
              - caf.low_stock
              - certificado.expiring
          description: Eventos a los que suscribirse
        secret:
          type: string
          description: >-
            Secret para firma HMAC-SHA256 (se genera automáticamente si no se
            envía)
    WebhookResponse:
      type: object
      required:
        - id
        - url
        - events
        - is_active
        - created_at
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
        secret:
          type: string
          description: Solo se retorna al crear el webhook. Después se enmascara.
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
    CreateClienteRequest:
      type: object
      required:
        - rut
        - razon_social
      properties:
        rut:
          type: string
          description: RUT del cliente con dígito verificador
          example: 76543210-K
        razon_social:
          type: string
          example: Empresa Ejemplo SpA
        giro:
          type: string
          example: Desarrollo de software
        direccion:
          type: string
        comuna:
          type: string
        ciudad:
          type: string
        email:
          type: string
          format: email
        telefono:
          type: string
        contacto:
          type: string
    UpdateClienteRequest:
      type: object
      properties:
        razon_social:
          type: string
        giro:
          type: string
        direccion:
          type: string
        comuna:
          type: string
        ciudad:
          type: string
        email:
          type: string
        telefono:
          type: string
        contacto:
          type: string
        notas:
          type: string
    ClienteResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        rut:
          type: string
          example: 76543210-K
        razon_social:
          type: string
          example: Empresa Ejemplo SpA
        giro:
          type: string
        direccion:
          type: string
        comuna:
          type: string
        ciudad:
          type: string
        email:
          type: string
        telefono:
          type: string
        contacto:
          type: string
        notas:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ClienteListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ClienteResponse'
        next_cursor:
          type: string
          description: Cursor para la siguiente página
    CreateProductoRequest:
      type: object
      required:
        - nombre
      properties:
        codigo:
          type: string
          example: PROD-001
        nombre:
          type: string
          example: Hora de consultoria TI
        descripcion:
          type: string
        precio_unitario:
          type: integer
          format: int64
          description: Precio en CLP (entero, sin decimales)
          example: 50000
        unidad:
          type: string
          default: UN
          enum:
            - UN
            - HR
            - KG
            - LT
            - MT
            - M2
            - M3
        es_exento:
          type: boolean
          default: false
        categoria:
          type: string
    UpdateProductoRequest:
      type: object
      properties:
        codigo:
          type: string
        nombre:
          type: string
        descripcion:
          type: string
        precio_unitario:
          type: integer
          format: int64
        unidad:
          type: string
        es_exento:
          type: boolean
        categoria:
          type: string
    ProductoResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        codigo:
          type: string
          example: PROD-001
        nombre:
          type: string
          example: Hora de consultoria TI
        descripcion:
          type: string
        precio_unitario:
          type: integer
          format: int64
          example: 50000
        unidad:
          type: string
          example: HR
        es_exento:
          type: boolean
        categoria:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ProductoListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ProductoResponse'
        next_cursor:
          type: string
          description: Cursor para la siguiente página
    ValidationError:
      type: object
      required:
        - field
        - code
        - message
      properties:
        field:
          type: string
          description: Campo con error (dot notation para nested)
          example: receptor.rut
        code:
          type: string
          description: Código de validación
          example: INVALID_RUT
        message:
          type: string
          description: Mensaje descriptivo
          example: 'RUT invalido: digito verificador no coincide'
    ErrorResponse:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Código de error máquina-legible
          example: VALIDATION_ERROR
        message:
          type: string
          description: >-
            Mensaje descriptivo del error. Este es el campo canónico para
            clientes nuevos.
          example: Error de validacion en los campos enviados
        error:
          type: string
          description: Alias de compatibilidad de `message` mantenido hacia atrás.
          deprecated: true
        details:
          type: array
          items:
            $ref: '#/components/schemas/ValidationError'
        action:
          type: string
          description: Acción recomendada para el cliente
        severity:
          type: string
          enum:
            - critical
            - error
            - warning
            - info
          description: Severidad opcional del error
        context:
          type: object
          additionalProperties: true
          description: Contexto estructurado opcional para debugging
