Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI

on:
pull_request:
workflow_dispatch:

# Permisos minimos: el workflow solo necesita leer el codigo.
permissions:
contents: read

# Maximo una ejecucion a la vez en todo el repositorio. Las demas esperan en
# cola y nunca se cancela una en curso (las pruebas golpean el servicio de pruebas).
concurrency:
group: sw-sdk-go-ci
cancel-in-progress: false

jobs:
pruebas:
runs-on: ubuntu-latest
timeout-minutes: 20
env:
SW_USER: ${{ secrets.SDKTEST_USER }}
SW_PASSWORD: ${{ secrets.SDKTEST_PASSWORD }}
SW_TOKEN: ${{ secrets.SDKTEST_TOKEN }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod

- run: go build ./...

- run: go vet ./...

- run: go run golang.org/x/vuln/cmd/govulncheck@v1.9.0 ./...

# En serie para no colisionar en el servicio de pruebas.
- run: go test -p 1 ./...
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

SDK oficial de SW (Servicios Web) para Go que permite interactuar con los servicios.

**Versión:** 1.1.0
**Versión:** 1.2.0
**Licencia:** MIT

## Características
Expand All @@ -27,7 +27,7 @@ go get github.com/lunasoft/sw-sdk-go

### Requisitos

- Go 1.21.0 o superior
- Go: versión mínima compatible 1.21.0. Se recomienda compilar con una versión de Go con parches de seguridad vigentes (1.26.10 o superior)
- Acceso a los servicios de SW (test o producción)
- Credenciales válidas o token infinito

Expand Down Expand Up @@ -61,6 +61,7 @@ SW_TOKEN=tu-token-infinito-aqui
| `SW_USER` | Usuario para autenticación | (vacío) |
| `SW_PASSWORD` | Contraseña para autenticación | (vacío) |
| `SW_TOKEN` | Token infinito/fijo | (vacío) |
| `SW_TIMEOUT` | Tiempo máximo de cada petición HTTP, en segundos (`0` = sin límite) | `30` |

### 🔄 Configuración Programática

Expand Down Expand Up @@ -426,14 +427,15 @@ go test ./swsdk/validacion -v

- Las pruebas de **emisión** y **timbrado** requieren XML sellado válido
- Las pruebas de **cancelación** requieren certificados válidos
- Las pruebas de **usuarios** tienen algunos tests con `t.Skip()` por restricciones del API
- Las pruebas de **usuarios** usan una cuenta de pruebas fija; las de crear y eliminar tienen `t.Skip()`
- Las pruebas de **balance** funcionan correctamente y muestran información detallada
- Las pruebas de **validación** funcionan correctamente con archivos XML reales
- Las pruebas se ejecutan con la hora de la Ciudad de México, la que valida el servicio
- Para skipear pruebas problemáticas: `go test -skip`

## Requisitos

- Go 1.19 o superior
- Go 1.21.0 o superior
- Acceso a los servicios de SW (credenciales válidas)

## Licencia
Expand Down Expand Up @@ -497,6 +499,12 @@ Para soporte técnico o reportar bugs:

## Changelog

### v1.2.0 (2026-10-27)
- Toolchain actualizado a Go 1.26.10
- Timeout configurable (`SW_TIMEOUT`) en todos los clientes HTTP
- Pruebas: hora de México y cuenta de usuario fija
- CI con GitHub Actions

### v1.1.0 (2025-01-04)
- 🔑 **NUEVO**: Sistema de Token Infinito
- Soporte para tokens fijos/infinitos
Expand Down
10 changes: 8 additions & 2 deletions VERSION
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
SW SDK Go - Version Information
================================

Version: 1.1.0
Release Date: 2025-01-04
Version: 1.2.0
Release Date: 2026-10-27
Go Version: 1.21.0+

Developer Information:
Expand Down Expand Up @@ -33,6 +33,12 @@ Features:

Changelog:
----------
v1.2.0 (2026-10-27):
- Toolchain actualizado a Go 1.26.10
- Timeout configurable (SW_TIMEOUT) en todos los clientes HTTP
- Pruebas: hora de México y cuenta de usuario fija
- CI con GitHub Actions

v1.1.0 (2025-01-04):
- 🔑 Sistema de Token Infinito
- ✅ Fallback automático a autenticación tradicional
Expand Down
3 changes: 3 additions & 0 deletions env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ SW_PASSWORD=tu-password
# Token infinito (recomendado para producción)
SW_TOKEN=tu-token-infinito-aqui

# Tiempo máximo de cada petición HTTP, en segundos (0 = sin límite; por defecto 30)
# SW_TIMEOUT=30

# Notas:
# - Si SW_TOKEN está configurado, se usará automáticamente
# - Si SW_TOKEN está vacío, se usará autenticación tradicional (SW_USER + SW_PASSWORD)
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ module github.com/lunasoft/sw-sdk-go

go 1.21.0

toolchain go1.24.1
toolchain go1.26.10

require github.com/joho/godotenv v1.5.1
require github.com/joho/godotenv v1.5.1
25 changes: 25 additions & 0 deletions internal/testsupport/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Package testsupport reúne utilidades solo para las pruebas del SDK.
package testsupport

import (
"os"
"time"
_ "time/tzdata" // zona horaria incluida, no depende del equipo
)

const (
baseURLPruebas = "https://services.test.sw.com.mx"
apiBaseURLPruebas = "https://api.test.sw.com.mx"
zonaServicio = "America/Mexico_City"
)

func init() {
// Siempre contra el ambiente de pruebas
os.Setenv("SW_BASE_URL", baseURLPruebas)
os.Setenv("SW_API_BASE_URL", apiBaseURLPruebas)

// Hora del servicio, solo dentro del proceso de pruebas
if zona, err := time.LoadLocation(zonaServicio); err == nil {
time.Local = zona
}
}
12 changes: 12 additions & 0 deletions internal/testsupport/env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package testsupport

import (
"testing"
"time"
)

func TestZonaHorariaDeLasPruebas(t *testing.T) {
if got := time.Now().Location().String(); got != zonaServicio {
t.Fatalf("zona de las pruebas = %q, se esperaba %q", got, zonaServicio)
}
}
1 change: 1 addition & 0 deletions swsdk/autenticacion/authentication_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package autenticacion

import (
_ "github.com/lunasoft/sw-sdk-go/internal/testsupport"
"testing"
)

Expand Down
1 change: 1 addition & 0 deletions swsdk/balance/balance_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package balance

import (
_ "github.com/lunasoft/sw-sdk-go/internal/testsupport"
"testing"
)

Expand Down
1 change: 1 addition & 0 deletions swsdk/cancelacion/cancelacion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cancelacion

import (
"fmt"
_ "github.com/lunasoft/sw-sdk-go/internal/testsupport"
"os"
"path/filepath"
"testing"
Expand Down
9 changes: 5 additions & 4 deletions swsdk/cancelacion/request_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/lunasoft/sw-sdk-go/swsdk"
"io"
"mime/multipart"
"net/http"
Expand Down Expand Up @@ -36,7 +37,7 @@ func (r *RequestHelper) SendCancelacionUUIDRequest(baseURL, token, rfc, uuid, mo
req.Header.Set("Accept", "*/*")

// Ejecutar la petición
client := &http.Client{}
client := swsdk.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error en petición HTTP: %w", err)
Expand Down Expand Up @@ -88,7 +89,7 @@ func (r *RequestHelper) SendCancelacionCSDRequest(baseURL, token string, request
req.Header.Set("Accept", "*/*")

// Ejecutar la petición
client := &http.Client{}
client := swsdk.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error en petición HTTP: %w", err)
Expand Down Expand Up @@ -140,7 +141,7 @@ func (r *RequestHelper) SendCancelacionPFXRequest(baseURL, token string, request
req.Header.Set("Accept", "*/*")

// Ejecutar la petición
client := &http.Client{}
client := swsdk.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error en petición HTTP: %w", err)
Expand Down Expand Up @@ -212,7 +213,7 @@ func (r *RequestHelper) SendCancelacionXMLRequest(baseURL, token, xmlPath string
req.Header.Set("Accept", "*/*")

// Ejecutar la petición
client := &http.Client{}
client := swsdk.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error en petición HTTP: %w", err)
Expand Down
26 changes: 25 additions & 1 deletion swsdk/config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package swsdk

import (
"net/http"
"os"
"strconv"
"time"

"github.com/joho/godotenv"
Expand Down Expand Up @@ -50,7 +52,7 @@ func LoadConfig() *Config {
User: user,
Password: password,
Token: token,
Timeout: time.Duration(30) * time.Second,
Timeout: timeoutDesdeEntorno(),

AuthEndpoint: "/security/authenticate",
IssueEndpoint: "/cfdi33/issue",
Expand All @@ -70,3 +72,25 @@ func getEnv(key, defaultValue string) string {
}
return defaultValue
}

// timeoutPorDefecto es el tiempo máximo de cada petición HTTP.
const timeoutPorDefecto = 30 * time.Second

// timeoutDesdeEntorno lee SW_TIMEOUT (segundos enteros). Con 0 no hay límite;
// un valor ausente o inválido usa el valor por defecto.
func timeoutDesdeEntorno() time.Duration {
valor := os.Getenv("SW_TIMEOUT")
if valor == "" {
return timeoutPorDefecto
}
segundos, err := strconv.Atoi(valor)
if err != nil || segundos < 0 {
return timeoutPorDefecto
}
return time.Duration(segundos) * time.Second
}

// NewHTTPClient crea un cliente HTTP con el timeout de la configuración.
func NewHTTPClient() *http.Client {
return &http.Client{Timeout: LoadConfig().Timeout}
}
31 changes: 31 additions & 0 deletions swsdk/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package swsdk

import (
"testing"
"time"
)

func TestTimeoutDesdeEntorno(t *testing.T) {
casos := []struct {
nombre string
valor string
quiere time.Duration
}{
{"sin definir usa el valor por defecto", "", 30 * time.Second},
{"segundos válidos", "90", 90 * time.Second},
{"cero significa sin límite", "0", 0},
{"valor inválido usa el valor por defecto", "abc", 30 * time.Second},
{"negativo usa el valor por defecto", "-5", 30 * time.Second},
}
for _, c := range casos {
t.Run(c.nombre, func(t *testing.T) {
t.Setenv("SW_TIMEOUT", c.valor)
if got := LoadConfig().Timeout; got != c.quiere {
t.Fatalf("SW_TIMEOUT=%q: timeout %v, se esperaba %v", c.valor, got, c.quiere)
}
if got := NewHTTPClient().Timeout; got != c.quiere {
t.Fatalf("NewHTTPClient: timeout %v, se esperaba %v", got, c.quiere)
}
})
}
}
1 change: 1 addition & 0 deletions swsdk/consultaestatus/consultaestatus_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package consultaestatus

import (
_ "github.com/lunasoft/sw-sdk-go/internal/testsupport"
"testing"
)

Expand Down
1 change: 1 addition & 0 deletions swsdk/issue/issue_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package issue

import (
_ "github.com/lunasoft/sw-sdk-go/internal/testsupport"
"os"
"testing"

Expand Down
3 changes: 2 additions & 1 deletion swsdk/issue/request_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/lunasoft/sw-sdk-go/swsdk"
"io"
"mime/multipart"
"net/http"
Expand Down Expand Up @@ -61,7 +62,7 @@ func (r *RequestHelper) SendStampRequest(baseURL, token, xmlPath, version, endpo
req.Header.Set("Authorization", "bearer "+token)

// Ejecutar la petición
client := &http.Client{}
client := swsdk.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error en petición HTTP: %w", err)
Expand Down
3 changes: 2 additions & 1 deletion swsdk/stamp/request_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/lunasoft/sw-sdk-go/swsdk"
"io"
"mime/multipart"
"net/http"
Expand Down Expand Up @@ -61,7 +62,7 @@ func (r *RequestHelper) SendStampRequest(baseURL, token, xmlPath, version, endpo
req.Header.Set("Authorization", "bearer "+token)

// Ejecutar la petición
client := &http.Client{}
client := swsdk.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error en petición HTTP: %w", err)
Expand Down
Loading
Loading