sperant-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 870356b1e113b4da8ab850d488569b39b1d8ba1ef626d66d71b365abfd582935
4
+ data.tar.gz: ff69f45c39a84969c6fbbe740a10c0b5256aeb89f8342f93d7b1c84eb74b3c39
5
+ SHA512:
6
+ metadata.gz: 84410faecd8b3829e4ddd6475cd55341caafd32b8936a91433be2d96acbcd0851c862b2601ea9ed2dbfa9cb4a1d99ebb8eacbd89c3beff8f6a4436c7057711c6
7
+ data.tar.gz: fe670ebde1bef5c54286c40c4da34cb6ba35a3c35f69e949c92d5eebbb53a9339aafe9a05c3ec77e02bdecfa6858feb6ab8dabe64ee6f58779e247617c9b8fa0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Eterniasoft
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # Sperant API
2
+
3
+ Cliente Ruby para la [API v3 de Sperant](https://sperant.gitbook.io/apiv3). Permite consultar proyectos, clientes y unidades de forma sencilla, con configuración de token y entorno (prueba o producción).
4
+
5
+ ---
6
+
7
+ ## ⚠️ Esta gema no es oficial
8
+
9
+ **Esta gema no es oficial.** Es mantenida por **vitualizz** y **no está respaldada ni por Eterniasoft ni por Sperant.**
10
+
11
+ La documentación oficial de la API está en [sperant.gitbook.io/apiv3](https://sperant.gitbook.io/apiv3).
12
+
13
+ ---
14
+
15
+ ## Instalación
16
+
17
+ Añade la gema a tu `Gemfile`:
18
+
19
+ ```ruby
20
+ gem "sperant-api"
21
+ ```
22
+
23
+ Luego ejecuta:
24
+
25
+ ```bash
26
+ bundle install
27
+ ```
28
+
29
+ O instala la gema directamente:
30
+
31
+ ```bash
32
+ gem install sperant-api
33
+ ```
34
+
35
+ ## Configuración
36
+
37
+ ### Uso standalone (script o consola)
38
+
39
+ Puedes configurar la gema de forma global y luego usar el cliente sin argumentos:
40
+
41
+ ```ruby
42
+ require "sperant_api"
43
+
44
+ SperantApi.configure do |c|
45
+ c.access_token = "tu-token-entregado-por-sperant"
46
+ c.environment = :test # o :production
47
+ end
48
+
49
+ client = SperantApi::Client.new
50
+ ```
51
+
52
+ O crear un cliente con configuración explícita (sin usar la configuración global):
53
+
54
+ ```ruby
55
+ require "sperant_api"
56
+
57
+ client = SperantApi::Client.new(
58
+ access_token: "tu-token",
59
+ environment: :production
60
+ )
61
+ ```
62
+
63
+ - **`environment`**: `:test` (por defecto) usa `https://api.eterniasoft.com`; `:production` usa `https://api.sperant.com`.
64
+ - **`access_token`**: Token API Key proporcionado por Sperant (solicitar a soporte@sperant.com).
65
+
66
+ ### Uso en Ruby on Rails
67
+
68
+ Crea un inicializador, por ejemplo `config/initializers/sperant_api.rb`:
69
+
70
+ ```ruby
71
+ require "sperant_api"
72
+
73
+ SperantApi.configure do |c|
74
+ c.access_token = ENV["SPERANT_API_TOKEN"] || Rails.application.credentials.dig(:sperant, :api_token)
75
+ c.environment = Rails.env.production? ? :production : :test
76
+ end
77
+ ```
78
+
79
+ Luego en controladores o servicios:
80
+
81
+ ```ruby
82
+ client = SperantApi::Client.new
83
+ client.projects.list(q: "Prados")
84
+ ```
85
+
86
+ ## Uso básico
87
+
88
+ ### Listar proyectos
89
+
90
+ ```ruby
91
+ client = SperantApi::Client.new(access_token: "tu-token", environment: :test)
92
+
93
+ # Todos los proyectos (paginado, 20 por página)
94
+ response = client.projects.list
95
+ response.data # array de proyectos
96
+ response.meta # metadatos (p. ej. page.total)
97
+ response.links # enlaces de paginación
98
+ response.total_pages
99
+
100
+ # Con filtros
101
+ response = client.projects.list(q: "Prados")
102
+ response = client.projects.list(code: "PRADOS")
103
+ response = client.projects.list(page: 2)
104
+ ```
105
+
106
+ ### Listar clientes
107
+
108
+ ```ruby
109
+ response = client.clients.list
110
+ response = client.clients.list(q: "+51999...") # filtrar por documento, email o celular (con código de país)
111
+ response = client.clients.list(page: 1)
112
+ ```
113
+
114
+ ### Listar unidades de un proyecto
115
+
116
+ ```ruby
117
+ response = client.units.list(project_id: 7)
118
+ response = client.units.list(
119
+ project_id: 7,
120
+ block_id: 10, # ID de subdivisión (opcional)
121
+ commercial_status_id: 1, # 1=Disponible, 2=No disponible, etc. (opcional)
122
+ page: 2
123
+ )
124
+ ```
125
+
126
+ ## Respuesta paginada
127
+
128
+ Los métodos `list` devuelven un objeto `SperantApi::Response::Paginated` con:
129
+
130
+ - **`data`**: array de ítems (proyectos, clientes o unidades).
131
+ - **`meta`**: hash con metadatos (p. ej. `meta["page"]["total"]`).
132
+ - **`links`**: hash con enlaces de paginación (`prev`, `next`, `last`).
133
+ - **`total_pages`**: atajo a `meta.dig("page", "total")`.
134
+
135
+ La API devuelve 20 elementos por página por defecto.
136
+
137
+ ## Manejo de errores
138
+
139
+ La gema define excepciones específicas que puedes rescatar:
140
+
141
+ ```ruby
142
+ begin
143
+ client.projects.list
144
+ rescue SperantApi::ConfigurationError => e
145
+ # Token faltante o inválido, entorno no permitido
146
+ rescue SperantApi::RateLimitError => e
147
+ # Límite de 15 peticiones por segundo superado (429)
148
+ rescue SperantApi::ApiError => e
149
+ # Otro error HTTP (4xx/5xx)
150
+ puts "Status: #{e.status_code}, body: #{e.response_body}"
151
+ end
152
+ ```
153
+
154
+ ## Rate limit
155
+
156
+ Según la documentación de Sperant, el límite es de **15 peticiones por segundo**. Respeta este límite en tu integración para evitar `SperantApi::RateLimitError`.
157
+
158
+ ## Documentación de la API
159
+
160
+ - [Introducción y esquema](https://sperant.gitbook.io/apiv3)
161
+ - [Listar proyectos](https://sperant.gitbook.io/apiv3/proyecto/listar-proyectos)
162
+ - [Listar clientes](https://sperant.gitbook.io/apiv3/clientes/listar-cliente)
163
+ - [Listar unidades](https://sperant.gitbook.io/apiv3/unidades/listar-unidades)
164
+
165
+ ## Desarrollo y pruebas
166
+
167
+ ```bash
168
+ bundle install
169
+ bundle exec rspec
170
+ ```
171
+
172
+ ### Documentación (YARD)
173
+
174
+ La API pública está documentada con [YARD](https://yardoc.org). Para generar la documentación HTML:
175
+
176
+ ```bash
177
+ bundle exec yard doc
178
+ ```
179
+
180
+ Se generará el directorio `doc/` con la documentación. Abre `doc/index.html` en el navegador.
181
+
182
+ ## Licencia
183
+
184
+ MIT. Ver [LICENSE](LICENSE).
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SperantApi
4
+ # Punto de entrada principal: expone los recursos (proyectos, clientes, unidades)
5
+ # y usa la configuración global o la pasada en la construcción.
6
+ class Client
7
+ # @return [Configuration] Configuración usada por este cliente.
8
+ attr_reader :configuration
9
+ # @return [Resources::Projects] Recurso para listar proyectos.
10
+ attr_reader :projects
11
+ # @return [Resources::Clients] Recurso para listar clientes.
12
+ attr_reader :clients
13
+ # @return [Resources::Units] Recurso para listar unidades de un proyecto.
14
+ attr_reader :units
15
+
16
+ # Crea un cliente. La configuración se resuelve en este orden:
17
+ # 1. El objeto +configuration+ si se pasa.
18
+ # 2. Una nueva {Configuration} con +access_token+ (y opcionalmente +environment+).
19
+ # 3. La configuración global de {SperantApi.configure} (si existe).
20
+ #
21
+ # @param configuration [Configuration, nil] Configuración ya construida (opcional).
22
+ # @param access_token [String, nil] Token API (opcional si hay configuración global o +configuration+).
23
+ # @param environment [Symbol, nil] +:test+ o +:production+ (solo se usa si se pasa +access_token+).
24
+ # @return [Client]
25
+ # @raise [ConfigurationError] Si no hay configuración ni token ni configuración global.
26
+ #
27
+ # @example Con configuración global
28
+ # SperantApi.configure { |c| c.access_token = "token"; c.environment = :test }
29
+ # client = SperantApi::Client.new
30
+ #
31
+ # @example Con parámetros explícitos
32
+ # client = SperantApi::Client.new(access_token: "token", environment: :production)
33
+ def initialize(configuration: nil, access_token: nil, environment: nil)
34
+ @configuration = resolve_configuration(
35
+ configuration: configuration,
36
+ access_token: access_token,
37
+ environment: environment
38
+ )
39
+ conn = Connection.new(configuration: @configuration)
40
+ @projects = Resources::Projects.new(connection: conn)
41
+ @clients = Resources::Clients.new(connection: conn)
42
+ @units = Resources::Units.new(connection: conn)
43
+ end
44
+
45
+ private
46
+
47
+ def resolve_configuration(configuration:, access_token:, environment:)
48
+ return configuration if configuration
49
+
50
+ if access_token
51
+ opts = { access_token: access_token }
52
+ opts[:environment] = environment if environment
53
+ return Configuration.new(**opts)
54
+ end
55
+
56
+ SperantApi.configuration || raise(ConfigurationError, "No configuration. Set SperantApi.configure { ... } or pass access_token:")
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SperantApi
4
+ # Configuración del cliente: token, entorno y tamaño de página.
5
+ # Se valida el entorno al asignar y el token al asignar o al llamar a {#validate!}.
6
+ class Configuration
7
+ # @return [String, nil] Token API Key proporcionado por Sperant.
8
+ attr_accessor :access_token
9
+ # @return [Symbol] Entorno: +:test+ (api.eterniasoft.com) o +:production+ (api.sperant.com).
10
+ attr_accessor :environment
11
+ # @return [Integer] Tamaño de página por defecto para listados (p. ej. 20).
12
+ attr_accessor :page_size
13
+
14
+ # @param access_token [String, nil] Token de la API (opcional en la construcción).
15
+ # @param environment [Symbol, String] +:test+ o +:production+. Por defecto +:test+.
16
+ # @param page_size [Integer] Número de elementos por página (por defecto 20).
17
+ def initialize(access_token: nil, environment: Constants::ENVIRONMENTS.first, page_size: Constants::DEFAULT_PAGE_SIZE)
18
+ @access_token = access_token
19
+ @environment = environment.to_sym
20
+ @page_size = page_size
21
+ validate_environment!
22
+ validate_token! if access_token
23
+ end
24
+
25
+ # @param value [Symbol, String] +:test+ o +:production+.
26
+ def environment=(value)
27
+ @environment = value.to_sym
28
+ validate_environment!
29
+ end
30
+
31
+ # @param value [String, nil] Token de la API.
32
+ def access_token=(value)
33
+ @access_token = value
34
+ validate_token!
35
+ end
36
+
37
+ # URL base según el entorno configurado.
38
+ # @return [String] URL base (ej. https://api.eterniasoft.com o https://api.sperant.com).
39
+ # @raise [ConfigurationError] Si el entorno no es reconocido.
40
+ def base_url
41
+ base = Constants::BASE_URLS[environment]
42
+ raise ConfigurationError, "Unknown environment: #{environment}" unless base
43
+
44
+ base
45
+ end
46
+
47
+ # Valida token y entorno. Útil antes de realizar peticiones.
48
+ # @raise [ConfigurationError] Si el token está vacío o el entorno es inválido.
49
+ def validate!
50
+ validate_token!
51
+ validate_environment!
52
+ end
53
+
54
+ private
55
+
56
+ def validate_token!
57
+ token = access_token.to_s.strip
58
+ raise ConfigurationError, "access_token is required" if token.empty?
59
+ end
60
+
61
+ def validate_environment!
62
+ return if Constants::ENVIRONMENTS.include?(environment)
63
+
64
+ raise ConfigurationError,
65
+ "Invalid environment: #{environment}. Must be one of: #{Constants::ENVIRONMENTS.join(', ')}"
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module SperantApi
8
+ class Connection
9
+ attr_reader :configuration, :adapter
10
+
11
+ def initialize(configuration:, adapter: nil)
12
+ @configuration = configuration
13
+ @adapter = adapter
14
+ end
15
+
16
+ def get(path, query_params = {})
17
+ configuration.validate!
18
+ uri = build_uri(path, query_params)
19
+ request = build_request(uri)
20
+ response = perform_request(uri, request)
21
+ handle_response(response)
22
+ end
23
+
24
+ private
25
+
26
+ def build_uri(path, query_params)
27
+ path = path.join("/") if path.is_a?(Array)
28
+ base = configuration.base_url
29
+ version = Constants::API_VERSION
30
+ path_part = [version, path].join("/").gsub(%r{/+}, "/")
31
+ base_uri = URI.parse(base)
32
+ query_string = query_params.any? { |_, v| !v.nil? && v != "" } ? URI.encode_www_form(query_params.compact) : nil
33
+ URI::HTTPS.build(host: base_uri.host, port: base_uri.port, path: "/#{path_part}", query: query_string)
34
+ end
35
+
36
+ def build_request(uri)
37
+ request = Net::HTTP::Get.new(uri.request_uri)
38
+ request[Constants::HEADER_AUTHORIZATION] = configuration.access_token
39
+ request["Content-Type"] = "application/json"
40
+ request["Accept"] = "application/json"
41
+ request
42
+ end
43
+
44
+ def perform_request(uri, request)
45
+ http = Net::HTTP.new(uri.host, uri.port)
46
+ http.use_ssl = (uri.scheme == "https")
47
+ http.request(request)
48
+ end
49
+
50
+ def handle_response(response)
51
+ status = response.code.to_i
52
+ body = response.body.to_s
53
+
54
+ case status
55
+ when 200..299
56
+ body.empty? ? {} : JSON.parse(body)
57
+ when Constants::HTTP_STATUS_RATE_LIMIT
58
+ raise RateLimitError.new(response_body: body)
59
+ when 400..599
60
+ raise ApiError.new(
61
+ "Request failed with status #{response.code}",
62
+ status_code: status,
63
+ response_body: body
64
+ )
65
+ else
66
+ body.empty? ? {} : JSON.parse(body)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SperantApi
4
+ module Constants
5
+ ENVIRONMENTS = %i[test production].freeze
6
+
7
+ BASE_URLS = {
8
+ test: "https://api.eterniasoft.com",
9
+ production: "https://api.sperant.com"
10
+ }.freeze
11
+
12
+ API_VERSION = "v3"
13
+ DEFAULT_PAGE_SIZE = 20
14
+
15
+ HEADER_AUTHORIZATION = "Authorization"
16
+
17
+ PATH_PROJECTS = "projects"
18
+ PATH_CLIENTS = "clients"
19
+ PATH_UNITS = "units"
20
+
21
+ HTTP_STATUS_RATE_LIMIT = 429
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SperantApi
4
+ # Error base de la gema. Todas las excepciones específicas heredan de esta.
5
+ class Error < StandardError; end
6
+
7
+ # Error de configuración: token faltante o inválido, entorno no permitido, etc.
8
+ class ConfigurationError < Error; end
9
+
10
+ # Error devuelto por la API (respuesta 4xx/5xx). Incluye código HTTP y cuerpo de respuesta.
11
+ class ApiError < Error
12
+ # @return [Integer, nil] Código de estado HTTP (ej. 404, 500).
13
+ attr_reader :status_code
14
+ # @return [String, nil] Cuerpo de la respuesta HTTP.
15
+ attr_reader :response_body
16
+
17
+ # @param message [String, nil] Mensaje de error (por defecto se genera con +status_code+).
18
+ # @param status_code [Integer, nil] Código HTTP.
19
+ # @param response_body [String, nil] Cuerpo de la respuesta.
20
+ def initialize(message = nil, status_code: nil, response_body: nil)
21
+ @status_code = status_code
22
+ @response_body = response_body
23
+ super(message || "API request failed (status: #{status_code})")
24
+ end
25
+ end
26
+
27
+ # Límite de tasa superado (HTTP 429). Sperant permite 15 peticiones por segundo.
28
+ class RateLimitError < ApiError
29
+ # @param message [String] Mensaje por defecto sobre el límite de 15 req/s.
30
+ # @param status_code [Integer] 429.
31
+ # @param response_body [String, nil] Cuerpo de la respuesta.
32
+ def initialize(message = "Rate limit exceeded (15 requests per second)", status_code: 429, response_body: nil)
33
+ super(message, status_code: status_code, response_body: response_body)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SperantApi
4
+ module Resources
5
+ # Clase base para recursos que exponen listados paginados.
6
+ # Las subclases definen +list+ y usan {#get_list} con el path y parámetros adecuados.
7
+ class Base
8
+ # @return [Connection] Conexión HTTP usada para las peticiones.
9
+ attr_reader :connection
10
+
11
+ # @param connection [Connection] Conexión ya configurada.
12
+ def initialize(connection:)
13
+ @connection = connection
14
+ end
15
+
16
+ protected
17
+
18
+ # Realiza GET al path y devuelve una respuesta paginada.
19
+ # @param path [String, Array<String>] Segmentos del path (se unen con +/+).
20
+ # @param query_params [Hash] Parámetros de consulta.
21
+ # @return [Response::Paginated]
22
+ def get_list(path, query_params = {})
23
+ path = Array(path).join("/")
24
+ raw = connection.get(path, query_params)
25
+ build_paginated_response(raw)
26
+ end
27
+
28
+ def build_paginated_response(raw)
29
+ Response::Paginated.new(
30
+ data: raw["data"] || [],
31
+ meta: raw["meta"] || {},
32
+ links: raw["links"] || {}
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SperantApi
4
+ module Resources
5
+ # Recurso de clientes. Permite listar clientes con filtros opcionales.
6
+ class Clients < Base
7
+ # Lista clientes (paginado). El parámetro +q+ filtra por documento, email o celular (con código de país).
8
+ #
9
+ # @param q [String, nil] Búsqueda (ej. "+51999..." para celular).
10
+ # @param page [Integer, nil] Número de página.
11
+ # @return [Response::Paginated] Respuesta con +data+, +meta+, +links+ y +total_pages+.
12
+ #
13
+ # @example
14
+ # response = client.clients.list
15
+ # response = client.clients.list(q: "+51999123456")
16
+ def list(q: nil, page: nil)
17
+ query = { q: q, page: page }.compact
18
+ get_list(Constants::PATH_CLIENTS, query)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SperantApi
4
+ module Resources
5
+ # Recurso de proyectos. Permite listar proyectos con filtros opcionales.
6
+ class Projects < Base
7
+ # Lista proyectos (paginado, 20 por página por defecto).
8
+ #
9
+ # @param code [String, nil] Filtrar por código del proyecto.
10
+ # @param q [String, nil] Búsqueda (nombre, código, etc.).
11
+ # @param page [Integer, nil] Número de página.
12
+ # @return [Response::Paginated] Respuesta con +data+, +meta+, +links+ y +total_pages+.
13
+ #
14
+ # @example
15
+ # response = client.projects.list
16
+ # response = client.projects.list(q: "Prados", page: 2)
17
+ def list(code: nil, q: nil, page: nil)
18
+ query = { code: code, q: q, page: page }.compact
19
+ get_list(Constants::PATH_PROJECTS, query)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SperantApi
4
+ module Resources
5
+ # Recurso de unidades. Lista unidades de un proyecto (y opcionalmente por bloque o estado comercial).
6
+ class Units < Base
7
+ # Lista unidades de un proyecto (paginado).
8
+ #
9
+ # @param project_id [Integer] ID del proyecto.
10
+ # @param block_id [Integer, nil] ID de subdivisión/bloque (opcional).
11
+ # @param commercial_status_id [Integer, nil] Estado comercial (ej. 1=Disponible, 2=No disponible) (opcional).
12
+ # @param page [Integer, nil] Número de página.
13
+ # @return [Response::Paginated] Respuesta con +data+, +meta+, +links+ y +total_pages+.
14
+ #
15
+ # @example
16
+ # response = client.units.list(project_id: 7)
17
+ # response = client.units.list(project_id: 7, commercial_status_id: 1, page: 2)
18
+ def list(project_id:, block_id: nil, commercial_status_id: nil, page: nil)
19
+ path = [Constants::PATH_PROJECTS, project_id, Constants::PATH_UNITS]
20
+ query = { block_id: block_id, commercial_status_id: commercial_status_id, page: page }.compact
21
+ get_list(path, query)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SperantApi
4
+ module Response
5
+ # Respuesta paginada de los métodos +list+ de los recursos.
6
+ # Encapsula +data+, +meta+ y +links+ para no depender de hashes con keys literales.
7
+ class Paginated
8
+ # @return [Array<Hash>] Lista de ítems (proyectos, clientes o unidades).
9
+ attr_reader :data
10
+ # @return [Hash] Metadatos de la API (p. ej. +"page" => { "total" => 5 }+).
11
+ attr_reader :meta
12
+ # @return [Hash] Enlaces de paginación (+prev+, +next+, +last+, etc.).
13
+ attr_reader :links
14
+
15
+ # @param data [Array] Array de ítems.
16
+ # @param meta [Hash] Metadatos.
17
+ # @param links [Hash] Enlaces de paginación.
18
+ def initialize(data:, meta: {}, links: {})
19
+ @data = data
20
+ @meta = meta
21
+ @links = links
22
+ end
23
+
24
+ # Número total de páginas según la API.
25
+ # @return [Integer, nil] Valor de +meta["page"]["total"]+ o nil si no está presente.
26
+ def total_pages
27
+ meta.dig("page", "total")
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SperantApi
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "sperant_api/version"
4
+ require_relative "sperant_api/constants"
5
+ require_relative "sperant_api/errors"
6
+ require_relative "sperant_api/configuration"
7
+ require_relative "sperant_api/connection"
8
+ require_relative "sperant_api/response/paginated"
9
+ require_relative "sperant_api/resources/base"
10
+ require_relative "sperant_api/resources/projects"
11
+ require_relative "sperant_api/resources/clients"
12
+ require_relative "sperant_api/resources/units"
13
+ require_relative "sperant_api/client"
14
+
15
+ # Cliente Ruby no oficial para la API v3 de Sperant (proyectos, clientes, unidades).
16
+ # Configuración por token y entorno (:test o :production).
17
+ #
18
+ # @example Configuración global y uso del cliente
19
+ # SperantApi.configure do |c|
20
+ # c.access_token = "tu-token"
21
+ # c.environment = :test
22
+ # end
23
+ # client = SperantApi::Client.new
24
+ # client.projects.list(q: "Prados")
25
+ #
26
+ # @see https://sperant.gitbook.io/apiv3 Documentación oficial de la API
27
+ module SperantApi
28
+ class << self
29
+ # @return [Configuration, nil] Configuración global actual (nil hasta llamar a {configure}).
30
+ attr_accessor :configuration
31
+
32
+ # Configura la gema de forma global. Crea una instancia de {Configuration} si no existe
33
+ # y permite modificarla mediante el bloque.
34
+ #
35
+ # @yield [configuration] Bloque opcional que recibe la configuración.
36
+ # @yieldparam configuration [Configuration] Objeto de configuración a modificar.
37
+ # @return [Configuration] La configuración resultante.
38
+ #
39
+ # @example
40
+ # SperantApi.configure do |c|
41
+ # c.access_token = ENV["SPERANT_API_TOKEN"]
42
+ # c.environment = :production
43
+ # end
44
+ def configure
45
+ self.configuration ||= Configuration.new
46
+ yield(configuration) if block_given?
47
+ configuration
48
+ end
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sperant-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lee Palacios
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ description: 'Unofficial client gem for Sperant API v3: projects, clients, and units.
84
+ Configurable token and test/production environment. Not maintained by Sperant.'
85
+ email:
86
+ - vitualizz@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - LICENSE
92
+ - README.md
93
+ - lib/sperant_api.rb
94
+ - lib/sperant_api/client.rb
95
+ - lib/sperant_api/configuration.rb
96
+ - lib/sperant_api/connection.rb
97
+ - lib/sperant_api/constants.rb
98
+ - lib/sperant_api/errors.rb
99
+ - lib/sperant_api/resources/base.rb
100
+ - lib/sperant_api/resources/clients.rb
101
+ - lib/sperant_api/resources/projects.rb
102
+ - lib/sperant_api/resources/units.rb
103
+ - lib/sperant_api/response/paginated.rb
104
+ - lib/sperant_api/version.rb
105
+ homepage: https://github.com/eterniasoft/sperant-api-ruby
106
+ licenses:
107
+ - MIT
108
+ metadata:
109
+ homepage_uri: https://github.com/eterniasoft/sperant-api-ruby
110
+ source_code_uri: https://github.com/eterniasoft/sperant-api-ruby
111
+ documentation_uri: https://sperant.gitbook.io/apiv3
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: 2.7.0
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubygems_version: 3.5.3
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Unofficial Ruby client for Sperant API v3
131
+ test_files: []