diff --git a/README.md b/README.md index e6ca032..a388522 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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) @@ -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. diff --git a/contrato_importacion_test.go b/contrato_importacion_test.go index 880f433..01f12ab 100644 --- a/contrato_importacion_test.go +++ b/contrato_importacion_test.go @@ -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) } diff --git a/docs/CONTRATO.md b/docs/CONTRATO.md index 2c478b8..4c07e43 100644 --- a/docs/CONTRATO.md +++ b/docs/CONTRATO.md @@ -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 diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 39c187d..814792a 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: GO ARCA API - version: "2.19.0" + version: "3.0.0" description: >- API REST para gestión de emisores, puntos de venta, credenciales API y facturación electrónica con ARCA. Todos los cuerpos JSON de entrada usan el @@ -42,7 +42,7 @@ tags: - name: Admin description: Operaciones globales reservadas a usuarios con permisos administrativos. - name: Integraciones Embedded - description: Administración de clientes externos, mapeos de emisores y credenciales M2M para la futura superficie Embedded. + description: Administración de integraciones de un único emisor y credenciales M2M para la futura superficie Embedded. - name: API Embedded description: Operaciones de navegador autenticadas exclusivamente mediante Authorization EmbeddedSession y acotadas al emisor de la sesión. - name: ARCA Catálogos @@ -304,7 +304,7 @@ paths: - Integraciones SEP operationId: listarIntegracionesSEP summary: Lista Integraciones SEP - description: "Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP con mapping canónico único; los clientes globales no son accesibles. " + description: "Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP pertenecientes directamente al emisor; los clientes globales no son accesibles. " security: - CookieAuth: [] parameters: @@ -352,7 +352,7 @@ paths: - Integraciones SEP operationId: crearIntegracionSEP summary: Crea una Integración SEP - description: "Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP con mapping canónico único; los clientes globales no son accesibles. Crea desactivada, sin permisos, y asocia automáticamente el emisor. No acepta IDs de ownership. Los bodies son estrictos; no aceptan emisor, usuario, cliente interno ni scopes. " + description: "Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP pertenecientes directamente al emisor; los clientes globales no son accesibles. Crea desactivada, sin permisos, y asocia automáticamente el emisor. No acepta IDs de ownership. Los bodies son estrictos; no aceptan emisor, usuario, cliente interno ni scopes. " security: - CookieAuth: [] parameters: @@ -394,7 +394,7 @@ paths: - Integraciones SEP operationId: obtenerIntegracionSEP summary: Obtiene una Integración SEP - description: "Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP con mapping canónico único; los clientes globales no son accesibles. " + description: "Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP pertenecientes directamente al emisor; los clientes globales no son accesibles. " security: - CookieAuth: [] parameters: @@ -436,7 +436,7 @@ paths: - Integraciones SEP operationId: actualizarIntegracionSEP summary: Actualiza una Integración SEP - description: Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP con mapping canónico único; los clientes globales no son accesibles. Los bodies son estrictos; no aceptan emisor, usuario, cliente interno ni scopes. Se aplica a nuevas autenticaciones/autorizaciones. Las sesiones y capacidades ya emitidas conservan su vencimiento actual. + description: Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP pertenecientes directamente al emisor; los clientes globales no son accesibles. Los bodies son estrictos; no aceptan emisor, usuario, cliente interno ni scopes. Se aplica a nuevas autenticaciones/autorizaciones. Las sesiones y capacidades ya emitidas conservan su vencimiento actual. security: - CookieAuth: [] parameters: @@ -485,7 +485,7 @@ paths: - Integraciones SEP operationId: actualizarPermisosSEP summary: Reemplaza los permisos de la integración - description: Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP con mapping canónico único; los clientes globales no son accesibles. Los bodies son estrictos; no aceptan emisor, usuario, cliente interno ni scopes. Se aplica a nuevas autenticaciones/autorizaciones. Las sesiones y capacidades ya emitidas conservan su vencimiento actual. + description: Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP pertenecientes directamente al emisor; los clientes globales no son accesibles. Los bodies son estrictos; no aceptan emisor, usuario, cliente interno ni scopes. Se aplica a nuevas autenticaciones/autorizaciones. Las sesiones y capacidades ya emitidas conservan su vencimiento actual. security: - CookieAuth: [] parameters: @@ -534,7 +534,7 @@ paths: - Integraciones SEP operationId: listarCredencialesSEP summary: Lista credenciales de la integración - description: "Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP con mapping canónico único; los clientes globales no son accesibles. " + description: "Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP pertenecientes directamente al emisor; los clientes globales no son accesibles. " security: - CookieAuth: [] parameters: @@ -589,7 +589,7 @@ paths: - Integraciones SEP operationId: crearCredencialSEP summary: Crea una credencial de integración - description: "Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP con mapping canónico único; los clientes globales no son accesibles. Los bodies son estrictos; no aceptan emisor, usuario, cliente interno ni scopes. " + description: "Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP pertenecientes directamente al emisor; los clientes globales no son accesibles. Los bodies son estrictos; no aceptan emisor, usuario, cliente interno ni scopes. " security: - CookieAuth: [] parameters: @@ -644,7 +644,7 @@ paths: - Integraciones SEP operationId: revocarCredencialSEP summary: Revoca una credencial de integración - description: Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP con mapping canónico único; los clientes globales no son accesibles. Se aplica a nuevas autenticaciones/autorizaciones. Las sesiones y capacidades ya emitidas conservan su vencimiento actual. + description: Requiere CookieAuth y pertenencia obligatoria del emisor. Una integración o credencial fuera del padre responde 404. Sólo integraciones SEP pertenecientes directamente al emisor; los clientes globales no son accesibles. Se aplica a nuevas autenticaciones/autorizaciones. Las sesiones y capacidades ya emitidas conservan su vencimiento actual. security: - CookieAuth: [] parameters: @@ -3082,66 +3082,6 @@ paths: "404": {$ref: "#/components/responses/Error"} "500": {$ref: "#/components/responses/Error"} - /api/v2/admin/integration-clients/{integration-client-id}/emisor-mappings: - get: - tags: [Integraciones Embedded] - operationId: listarMapeosEmisorIntegracion - summary: Lista mapeos de emisores - description: Devuelve únicamente mapeos del cliente padre, incluida la autorización Embedded máxima de cada emisor. - security: [{CookieAuth: []}] - parameters: [{$ref: "#/components/parameters/IdClienteIntegracion"}] - responses: - "200": - description: Mapeos del cliente - content: - application/json: - schema: {$ref: "#/components/schemas/RespuestaMapeosIntegracion"} - "401": {$ref: "#/components/responses/Error"} - "403": {$ref: "#/components/responses/Error"} - "404": {$ref: "#/components/responses/Error"} - "500": {$ref: "#/components/responses/Error"} - post: - tags: [Integraciones Embedded] - operationId: crearMapeoEmisorIntegracion - summary: Crea un mapeo de emisor - description: Asocia un identificador externo único con un emisor GOFIP y persiste su techo de permisos Embedded cerrado. - security: [{CookieAuth: []}] - parameters: [{$ref: "#/components/parameters/IdClienteIntegracion"}] - requestBody: - required: true - content: - application/json: - schema: {$ref: "#/components/schemas/CuerpoSolicitudCrearMapeoIntegracion"} - responses: - "201": - description: Mapeo creado - content: - application/json: - schema: {$ref: "#/components/schemas/MapeoEmisorIntegracion"} - "400": {$ref: "#/components/responses/Error"} - "401": {$ref: "#/components/responses/Error"} - "403": {$ref: "#/components/responses/Error"} - "404": {$ref: "#/components/responses/Error"} - "409": {$ref: "#/components/responses/Error"} - "500": {$ref: "#/components/responses/Error"} - - /api/v2/admin/integration-clients/{integration-client-id}/emisor-mappings/{mapping-id}: - delete: - tags: [Integraciones Embedded] - operationId: eliminarMapeoEmisorIntegracion - summary: Elimina un mapeo de emisor - description: Elimina el mapeo parent-scoped y sus permisos por cascada, sin eliminar el emisor GOFIP. - security: [{CookieAuth: []}] - parameters: - - {$ref: "#/components/parameters/IdClienteIntegracion"} - - {$ref: "#/components/parameters/IdMapeoIntegracion"} - responses: - "204": {description: Mapeo y permisos eliminados} - "401": {$ref: "#/components/responses/Error"} - "403": {$ref: "#/components/responses/Error"} - "404": {$ref: "#/components/responses/Error"} - "500": {$ref: "#/components/responses/Error"} - /api/v2/embedded/bootstrap/exchange: post: tags: [Integraciones Embedded] @@ -3183,7 +3123,7 @@ paths: tags: [Integraciones Embedded] operationId: crearBootstrapEmbedded summary: Crea un bootstrap de recuperación Embedded - description: Autentica exclusivamente la IntegrationKey y vuelve a resolver el mapeo confiable de emisor y el techo de permisos. No acepta Document Capability, no crea un documento y no crea una sesión de navegador. + description: Autentica exclusivamente la IntegrationKey y vuelve a resolver el emisor y el techo de permisos de la integración autenticada. No acepta Document Capability, no crea un documento y no crea una sesión de navegador. security: [{IntegrationKeyAuth: []}] requestBody: required: true @@ -3211,8 +3151,7 @@ paths: summary: Crea un bootstrap Embedded de un solo uso description: >- Autentica exclusivamente una IntegrationKey M2M con el alcance - `embedded_session:create`. El emisor GOFIP se resuelve únicamente desde - `external_emisor_id` dentro del cliente autenticado; nunca enviar `emisor_id` + `embedded_session:create`. El emisor GOFIP se obtiene exclusivamente de la integración autenticada; nunca enviar `emisor_id` ni `integration_client_id`. `bootstrap_token` es secreto, se devuelve una sola vez, vence pronto y se canjea una sola vez mediante `POST /api/v2/embedded/bootstrap/exchange`; aún no representa una sesión de navegador. @@ -4156,12 +4095,6 @@ components: required: true description: UUIDv7 de una credencial M2M perteneciente al cliente de integración padre. schema: {type: string, format: uuid} - IdMapeoIntegracion: - name: mapping-id - in: path - required: true - description: UUIDv7 de un mapeo de emisor perteneciente al cliente de integración padre. - schema: {type: string, format: uuid} IdSesionWhatsApp: name: sesion-id in: path @@ -5126,7 +5059,6 @@ components: - allowed_origin - status - permissions - - reference - created_at - updated_at properties: @@ -5151,10 +5083,6 @@ components: uniqueItems: true items: $ref: "#/components/schemas/PermisoEmbedded" - reference: - type: string - description: Referencia pública estable generada por GOFIP. - readOnly: true created_at: &a1 type: string description: Fecha de creación. @@ -5311,7 +5239,7 @@ components: enum: ["embedded_session:create"] PermisoEmbedded: type: string - description: Capacidad máxima que una futura sesión Embedded podrá solicitar para el mapeo de emisor. + description: Capacidad máxima que una futura sesión Embedded podrá solicitar para la integración. enum: - "invoices:read" - "invoices:create" @@ -5329,14 +5257,12 @@ components: type: string description: >- Código estable de Embedded: 18000 cliente no encontrado; 18001 cliente deshabilitado; - 18002 origen inválido; 18003 datos de cliente inválidos; 18004 mapeo no encontrado; - 18005 ID externo duplicado; 18006 emisor duplicado para el cliente; 18007 datos de - mapeo inválidos; 18008 credencial no encontrada; 18009 credencial revocada; 18010 + 18002 origen inválido; 18003 datos de cliente inválidos; 18008 credencial no encontrada; 18009 credencial revocada; 18010 credencial vencida; 18011 credencial inválida; 18012 datos de credencial inválidos; 18013 alcance inválido; 18014 alcance requerido ausente; 18015 permiso Embedded inválido; 18016 solicitud de bootstrap inválida; 18017 permiso no autorizado; 18018 bootstrap no disponible; 18019 fallo de persistencia del bootstrap. - enum: ["18000", "18001", "18002", "18003", "18004", "18005", "18006", "18007", "18008", "18009", "18010", "18011", "18012", "18013", "18014", "18015", "18016", "18017", "18018", "18019"] + enum: ["18000", "18001", "18002", "18003", "18008", "18009", "18010", "18011", "18012", "18013", "18014", "18015", "18016", "18017", "18018", "18019"] DetallesErrorIntegracionEmbedded: allOf: - {$ref: "#/components/schemas/DetallesError"} @@ -5346,9 +5272,11 @@ components: code: {$ref: "#/components/schemas/CodigoErrorIntegracionEmbedded"} ClienteIntegracion: type: object - required: [id, name, status, allowed_origin, created_at, updated_at] + required: [id, emisor_id, permissions, name, status, allowed_origin, created_at, updated_at] properties: id: {type: string, format: uuid, description: UUIDv7 del cliente de integración.} + emisor_id: {type: string, format: uuid, description: Emisor propietario inmutable.} + permissions: {type: array, uniqueItems: true, items: {$ref: "#/components/schemas/PermisoEmbedded"}, description: Techo de permisos de la integración.} name: {type: string, description: Nombre administrativo de la plataforma externa.} status: {$ref: "#/components/schemas/EstadoClienteIntegracion"} allowed_origin: {type: string, format: uri, description: "Origen exacto reservado para la futura política de embedding; nunca incluye ruta, consulta, fragmento ni comodín."} @@ -5368,9 +5296,10 @@ components: properties: data: type: object - description: Datos del nuevo cliente de integración. - required: [name, allowed_origin] + description: Datos del nuevo cliente de integración, asociado a un único emisor. + required: [emisor_id, name, allowed_origin] properties: + emisor_id: {type: string, format: uuid, description: Emisor propietario elegido por el administrador al crear la integración.} name: {type: string, minLength: 1, maxLength: 120, description: Nombre administrativo de la plataforma externa.} allowed_origin: {type: string, format: uri, description: "Origen HTTP(S) exacto sin path, query, fragmento ni comodín."} CuerpoSolicitudActualizarClienteIntegracion: @@ -5380,50 +5309,12 @@ components: data: type: object minProperties: 1 - description: Campos administrativos a modificar. + description: Campos administrativos a modificar. El emisor propietario es inmutable. properties: name: {type: string, minLength: 1, maxLength: 120, description: Nuevo nombre administrativo.} status: {$ref: "#/components/schemas/EstadoClienteIntegracion"} allowed_origin: {type: string, format: uri, description: "Nuevo origen HTTP(S) exacto sin path, query, fragmento ni comodín."} - MapeoEmisorIntegracion: - type: object - required: [id, integration_client_id, emisor_id, external_emisor_id, permissions, created_at, updated_at] - properties: - id: {type: string, format: uuid, description: UUIDv7 del mapeo.} - integration_client_id: {type: string, format: uuid, description: Cliente de integración propietario.} - emisor_id: {type: string, format: uuid, description: Emisor GOFIP resuelto exclusivamente en el backend.} - external_emisor_id: {type: string, description: Identificador estable usado por la plataforma externa.} - permissions: - type: array - uniqueItems: true - description: Techo persistido de permisos de la futura sesión Embedded para este mapeo. - items: {$ref: "#/components/schemas/PermisoEmbedded"} - created_at: {type: string, format: date-time, description: Fecha de creación.} - updated_at: {type: string, format: date-time, description: Fecha de última actualización.} - RespuestaMapeosIntegracion: - type: object - required: [emisor_mappings] - properties: - emisor_mappings: - type: array - description: Mapeos pertenecientes al cliente solicitado. - items: {$ref: "#/components/schemas/MapeoEmisorIntegracion"} - CuerpoSolicitudCrearMapeoIntegracion: - type: object - required: [data] - properties: - data: - type: object - description: Asociación y autorización máxima del emisor externo. - required: [emisor_id, external_emisor_id, permissions] - properties: - emisor_id: {type: string, format: uuid, description: Emisor GOFIP existente que se asociará.} - external_emisor_id: {type: string, minLength: 1, maxLength: 191, description: Identificador del emisor en la plataforma externa.} - permissions: - type: array - uniqueItems: true - description: Techo cerrado de permisos Embedded para este emisor; no se deriva de los alcances M2M. - items: {$ref: "#/components/schemas/PermisoEmbedded"} + permissions: {type: array, uniqueItems: true, items: {$ref: "#/components/schemas/PermisoEmbedded"}, description: Nuevo techo de permisos de la integración.} CredencialIntegracion: type: object required: [id, integration_client_id, secret_prefix, status, scopes, created_at, updated_at] @@ -5437,7 +5328,7 @@ components: scopes: type: array uniqueItems: true - description: Alcances M2M de la credencial; son independientes de los permisos Embedded del mapeo. + description: Alcances M2M de la credencial; son independientes de los permisos Embedded de la integración. items: {$ref: "#/components/schemas/AlcanceIntegracion"} created_at: {type: string, format: date-time, description: Fecha de creación.} updated_at: {type: string, format: date-time, description: Fecha de última actualización.} @@ -5466,6 +5357,7 @@ components: CuerpoSolicitudCrearSesionEmbedded: type: object required: [data] + additionalProperties: false properties: data: $ref: "#/components/schemas/SolicitudCrearSesionEmbedded" @@ -5487,19 +5379,14 @@ components: description: Capacidad opaca, breve y de un único uso. Enviar sólo en este cuerpo POST; no usar query parameters. SolicitudCrearSesionEmbedded: type: object - required: [external_emisor_id, permissions] + required: [permissions] additionalProperties: false properties: - external_emisor_id: - type: string - minLength: 1 - maxLength: 191 - description: Identificador del emisor en el ERP. GOFIP lo resuelve sólo dentro del cliente autenticado. permissions: type: array minItems: 1 uniqueItems: true - description: Permisos exactos solicitados para la futura sesión; deben ser subconjunto del techo del mapeo. + description: Permisos exactos solicitados para la futura sesión; deben ser subconjunto del techo de la integración. items: {$ref: "#/components/schemas/PermisoEmbedded"} RespuestaCrearSesionEmbedded: type: object diff --git a/examples/sep-bootstrap/main.go b/examples/sep-bootstrap/main.go index c9b02fe..01a0c22 100644 --- a/examples/sep-bootstrap/main.go +++ b/examples/sep-bootstrap/main.go @@ -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") } diff --git a/examples/sep-sesion/main.go b/examples/sep-sesion/main.go index e8d95d5..39c092a 100644 --- a/examples/sep-sesion/main.go +++ b/examples/sep-sesion/main.go @@ -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") } diff --git a/integration/consumer/consumer_test.go b/integration/consumer/consumer_test.go index 083d900..abcde6e 100644 --- a/integration/consumer/consumer_test.go +++ b/integration/consumer/consumer_test.go @@ -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) diff --git a/modelos/sep/sep.go b/modelos/sep/sep.go index 52d2d82..9d0b9ab 100644 --- a/modelos/sep/sep.go +++ b/modelos/sep/sep.go @@ -6,9 +6,7 @@ import ( "errors" "fmt" "log/slog" - "strings" "time" - "unicode/utf8" ) type PermisoEmbedded string @@ -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") } diff --git a/modelos/sep/sep_test.go b/modelos/sep/sep_test.go index d443056..344805b 100644 --- a/modelos/sep/sep_test.go +++ b/modelos/sep/sep_test.go @@ -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) } diff --git a/sep_test.go b/sep_test.go index 0732ae9..685a00e 100644 --- a/sep_test.go +++ b/sep_test.go @@ -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) { @@ -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") @@ -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")