Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ de errores HTTP y la descarga de documentos.
| Componente | Versión usada |
| --- | --- |
| Módulo Go | `v2` (`github.com/unkcode-org/gofip-sdk-go/v2`) |
| API GOFIP | `2.19.0` y rutas bajo `/api/v2` |
| API GOFIP | `3.0.0` y rutas bajo `/api/v2` |
| Especificación OpenAPI | `3.0.3` |
| Go | `1.22.2` o posterior |

El contrato HTTP de referencia está en
[`docs/openapi.yaml`](docs/openapi.yaml). Los campos `openapi: 3.0.3` e
`info.version: "2.19.0"` indican, respectivamente, la versión del formato OpenAPI
`info.version: "3.0.0"` indican, respectivamente, la versión del formato OpenAPI
y la versión del contrato de la API. No deben confundirse con una versión
publicada del módulo Go.

Expand Down Expand Up @@ -353,7 +353,6 @@ clienteSEP, err := gofip.NuevoClienteSEP(gofip.OpcionesClienteSEP{
})
if err != nil { return err }
solicitud := sepmodel.SolicitudCrearSesionEmbedded{
IDEmisorExterno: "elixir",
Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer},
}
sesion, err := clienteSEP.CrearSesion(ctx, solicitud)
Expand Down Expand Up @@ -412,6 +411,8 @@ go test -race ./...
```

Ver [arquitectura](docs/arquitectura.md) y [validación manual](docs/validacion.md).
El contrato de referencia es OpenAPI 2.19.0 del backend. No se publica una versión
El contrato de referencia es OpenAPI 3.0.0 del backend. No se publica una versión
como parte de estas pruebas. La secuencia prevista es `v2.0.0-rc.1` → `v2.0.0`,
con imports `/v2`, validación externa y aprobación de publicación.

SEP deriva el emisor exclusivamente de la Integration Key. CrearSesion y CrearBootstrap reciben solo permisos; la integración pertenece a un único emisor.
4 changes: 2 additions & 2 deletions contrato_importacion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ func TestContratoImportacionYProcedenciaExacta(t *testing.T) {
t.Fatal(err)
}
hash := fmt.Sprintf("%x", sha256.Sum256(yaml))
if !strings.Contains(string(provenance), hash) || !strings.Contains(string(provenance), "Versión: 2.19.0.") {
if !strings.Contains(string(provenance), hash) || !strings.Contains(string(provenance), "Versión: 3.0.0.") {
t.Fatal("copia OpenAPI sin procedencia o checksum actualizado")
}
for _, token := range []string{`version: "2.19.0"`, "/api/v2/facturas/importaciones:", "mismo emisor y punto de venta", "emisor_id+credencial_id+punto_venta_id", "legacy_import", "SolicitudImportarFactura:", "RespuestaImportarFactura:", "DecimalHistorico:", "VerificacionImportacion:", "CompletitudDocumental:"} {
for _, token := range []string{`version: "3.0.0"`, "/api/v2/facturas/importaciones:", "mismo emisor y punto de venta", "emisor_id+credencial_id+punto_venta_id", "legacy_import", "SolicitudImportarFactura:", "RespuestaImportarFactura:", "DecimalHistorico:", "VerificacionImportacion:", "CompletitudDocumental:"} {
if !strings.Contains(string(yaml), token) {
t.Fatalf("contrato sin %s", token)
}
Expand Down
9 changes: 5 additions & 4 deletions docs/CONTRATO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

- Repositorio canónico: unkcode-org/gofip-backend.
- Archivo: backend/docs/openapi.yaml.
- Versión: 2.19.0.
- Commit de implementación: 7918e9d373a8cbc298258cb9314d5d33e54d38b3.
- Base auditada de develop: ea7beffef41a91e548a1e7b8ed17e388fdd948da.
- SHA-256 de los bytes YAML copiados: 82ed564929b6c46e608d7922dc0a4fb93e1f4e1c2d8ec7bc06d434b4975d92b1.
- Versión: 3.0.0.
- Commit de implementación SEP en backend: 0fbb4e9f60b61eb2857b259b5766bbcec99d5a8b.
- Rama de revisión: codex/sep-emisor-directo.
- Base auditada de develop: dae24d3.
- SHA-256 de los bytes YAML copiados: b9580c0245564ce8cf3d11930b36650b8b6e8934b3910c492b4c9a5ae0557238.

La copia se verificó byte a byte después de estabilizar Backend/OpenAPI. Incluye
importación Legacy verificada en Producción y no agrega ambiente a documentos o
Expand Down
167 changes: 27 additions & 140 deletions docs/openapi.yaml

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions examples/sep-bootstrap/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,9 @@ func main() {
if err != nil {
log.Fatal("configuración SEP inválida")
}
emisor := os.Getenv("GOFIP_SEP_EMISOR_EXTERNO")
if emisor == "" {
log.Fatal("falta GOFIP_SEP_EMISOR_EXTERNO")
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
respuesta, err := c.CrearBootstrap(ctx, sepmodel.SolicitudCrearSesionEmbedded{IDEmisorExterno: emisor, Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer}})
respuesta, err := c.CrearBootstrap(ctx, sepmodel.SolicitudCrearSesionEmbedded{Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer}})
if err != nil {
log.Fatal("no se pudo completar la operación SEP")
}
Expand Down
6 changes: 1 addition & 5 deletions examples/sep-sesion/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,9 @@ func main() {
if err != nil {
log.Fatal("configuración SEP inválida")
}
emisor := os.Getenv("GOFIP_SEP_EMISOR_EXTERNO")
if emisor == "" {
log.Fatal("falta GOFIP_SEP_EMISOR_EXTERNO")
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
respuesta, err := c.CrearSesion(ctx, sepmodel.SolicitudCrearSesionEmbedded{IDEmisorExterno: emisor, Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer}})
respuesta, err := c.CrearSesion(ctx, sepmodel.SolicitudCrearSesionEmbedded{Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer}})
if err != nil {
log.Fatal("no se pudo completar la operación SEP")
}
Expand Down
2 changes: 1 addition & 1 deletion integration/consumer/consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestConsumidorV2(t *testing.T) {
if err != nil {
t.Fatal(err)
}
solicitud := sepmodel.SolicitudCrearSesionEmbedded{IDEmisorExterno: "elixir", Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer}}
solicitud := sepmodel.SolicitudCrearSesionEmbedded{Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer}}
sesion, err := sep.CrearSesion(ctx, solicitud)
if err != nil {
t.Fatal(err)
Expand Down
10 changes: 2 additions & 8 deletions modelos/sep/sep.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import (
"errors"
"fmt"
"log/slog"
"strings"
"time"
"unicode/utf8"
)

type PermisoEmbedded string
Expand Down Expand Up @@ -50,16 +48,12 @@ func (s *Secreto) UnmarshalJSON(b []byte) error {
}

type SolicitudCrearSesionEmbedded struct {
IDEmisorExterno string `json:"external_emisor_id"`
Permisos []PermisoEmbedded `json:"permissions"`
Permisos []PermisoEmbedded `json:"permissions"`
}

// Validar no cambia el identificador ni el orden de los permisos. El techo de
// Validar no cambia el orden de los permisos. El techo de
// autorización pertenece al backend y no puede verificarse localmente.
func (s SolicitudCrearSesionEmbedded) Validar() error {
if !utf8.ValidString(s.IDEmisorExterno) || strings.TrimSpace(s.IDEmisorExterno) == "" || utf8.RuneCountInString(s.IDEmisorExterno) > 191 {
return errors.New("el identificador externo debe tener entre 1 y 191 caracteres")
}
if len(s.Permisos) == 0 {
return errors.New("se requiere al menos un permiso Embedded")
}
Expand Down
2 changes: 1 addition & 1 deletion modelos/sep/sep_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestPermisosExactosYValidacionSinMutaciones(t *testing.T) {
t.Fatal("inventario incorrecto")
}
for _, p := range permisos {
s := SolicitudCrearSesionEmbedded{IDEmisorExterno: strings.Repeat("ñ", 191), Permisos: []PermisoEmbedded{p}}
s := SolicitudCrearSesionEmbedded{Permisos: []PermisoEmbedded{p}}
if err := s.Validar(); err != nil {
t.Fatal(err)
}
Expand Down
9 changes: 4 additions & 5 deletions sep_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const tokenSEPPrueba = "gofip_emb_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
const urlDocumentoSEPPrueba = "https://embedded.example/embed/capacidad-privada"

func solicitudSEPPrueba() sepmodel.SolicitudCrearSesionEmbedded {
return sepmodel.SolicitudCrearSesionEmbedded{IDEmisorExterno: "elixir", Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer}}
return sepmodel.SolicitudCrearSesionEmbedded{Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer}}
}

func TestNuevoClienteSEPConfiguracion(t *testing.T) {
Expand Down Expand Up @@ -78,7 +78,7 @@ func TestSEPCreacionYRecuperacionContrato(t *testing.T) {
if err := json.NewDecoder(r.Body).Decode(&b); err != nil {
t.Fatal(err)
}
if len(b.Data) != 2 || string(b.Data["external_emisor_id"]) != `"elixir"` || string(b.Data["permissions"]) != `["invoices:read"]` {
if len(b.Data) != 1 || string(b.Data["permissions"]) != `["invoices:read"]` {
t.Error("payload incorrecto")
}
w.Header().Set("Content-Type", "application/json")
Expand Down Expand Up @@ -147,9 +147,8 @@ func TestSEPValidaPermisosAntesDeEnviar(t *testing.T) {
})}
c, _ := NuevoClienteSEP(OpcionesClienteSEP{ClaveIntegracion: claveSEPPrueba, URLBase: "https://example.test", ClienteHTTP: httpClient})
for _, s := range []sepmodel.SolicitudCrearSesionEmbedded{
{}, {IDEmisorExterno: "elixir"}, {IDEmisorExterno: "elixir", Permisos: []sepmodel.PermisoEmbedded{"inventado"}},
{IDEmisorExterno: "elixir", Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer, sepmodel.PermisoFacturasLeer}},
{IDEmisorExterno: strings.Repeat("a", 192), Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer}},
{}, {Permisos: []sepmodel.PermisoEmbedded{"inventado"}},
{Permisos: []sepmodel.PermisoEmbedded{sepmodel.PermisoFacturasLeer, sepmodel.PermisoFacturasLeer}},
} {
if _, err := c.CrearSesion(context.Background(), s); err == nil {
t.Fatal("solicitud inválida aceptada")
Expand Down
Loading