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 +7 -0
- data/LICENSE +21 -0
- data/README.md +184 -0
- data/lib/sperant_api/client.rb +59 -0
- data/lib/sperant_api/configuration.rb +68 -0
- data/lib/sperant_api/connection.rb +70 -0
- data/lib/sperant_api/constants.rb +23 -0
- data/lib/sperant_api/errors.rb +36 -0
- data/lib/sperant_api/resources/base.rb +37 -0
- data/lib/sperant_api/resources/clients.rb +22 -0
- data/lib/sperant_api/resources/projects.rb +23 -0
- data/lib/sperant_api/resources/units.rb +25 -0
- data/lib/sperant_api/response/paginated.rb +31 -0
- data/lib/sperant_api/version.rb +5 -0
- data/lib/sperant_api.rb +50 -0
- metadata +131 -0
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
|
data/lib/sperant_api.rb
ADDED
|
@@ -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: []
|