La República Checa recupera el registro electrónico de ventas. Con EET 2.0 (Elektronická evidence tržeb) un punto de venta comunica cada venta a la autoridad tributaria en el momento en que se produce, y la autoridad tributaria responde con un código de acuse de recibo, el pok, que es la prueba de que la venta se ha comunicado. La comunicación de ventas comienza el 1 de enero de 2027, y el entorno de pruebas (playground) donde se construyen y prueban las cajas registradoras ya está abierto.
sgcSign incorpora un nuevo componente para ello, TsgcEETClient. Valida la venta, construye el mensaje, lo firma con el certificado del contribuyente, lo envía, comprueba la firma del acuse de recibo y devuelve el resultado. Esta publicación explica qué exige EET 2.0, cómo realiza el componente el ciclo completo y el código Delphi para una primera venta, una cola sin conexión y un acuse de recibo verificado.
Una venta registrada desde la demo de Delphi, desde el modo de verificación hasta un pok real. También en YouTube.
EET 2.0 es un protocolo nuevo, no una actualización
Si construiste una caja registradora para el primer sistema EET, empieza desde cero. La versión 4.1 de la interfaz de datos no es compatible con la antigua versión 3.1, y es más sencilla: no hay código de seguridad PKP ni BKP que calcular, ni desglose de IVA, ni certificado de cliente TLS. Una venta son diez atributos de datos. Lo que queda es un servicio web SOAP 1.1 estándar:
- Un mensaje por venta, un elemento
Trzbaen el espacio de nombreshttp://fs.gov.cz/eet/schema/v4, enviado por HTTPS con TLS 1.2 o superior. - Una firma WS-Security sobre el cuerpo SOAP, con canonicalización exclusiva, SHA-256 y RSA, y el certificado del contribuyente incluido en un
wsse:BinarySecurityToken. - El certificado del contribuyente es un archivo PKCS#12, y su nombre común es el identificador del contribuyente.
- El sobre completo no debe superar 12 kB, es decir, 12.288 bytes.
- La respuesta contiene el pok, la hora de recepción y las posibles advertencias, o un código de error.
Darse de alta como contribuyente, obtener el certificado a través del portal MOJE daně y recibir un número de establecimiento asignado son trámites que se hacen antes de ejecutar cualquier código. De todo ese papeleo la biblioteca necesita un archivo PKCS#12 y dos números, el identificador del contribuyente y el identificador del establecimiento.
Cómo realiza TsgcEETClient el ciclo completo
Una llamada a Send ejecuta todos los pasos, en este orden:
- Valida cada campo del registro
TsgcEETSaleconforme a las reglas del esquema, de modo que una venta no válida se rechaza localmente con un motivo legible y nunca llega al servicio. - Construye el elemento
Trzbay lo envuelve en un sobre SOAP 1.1, con unuuid_zpravynuevo para el mensaje. - Firma el cuerpo SOAP con WS-Security usando la clave de cualquier proveedor de claves de sgcSign: un archivo PFX, el almacén de certificados de Windows, un token PKCS#11 o una tarjeta inteligente, o un servicio de claves en la nube.
- Mide el sobre terminado frente al límite de 12 kB antes de enviar nada.
- Lo envía a la autoridad tributaria. El entorno de pruebas es el endpoint predeterminado, de modo que un componente colocado en un formulario no puede presentar una venta real por accidente.
- Analiza la respuesta: el pok, la hora de recepción, el indicador de prueba, las advertencias y el código de error.
- Verifica la firma del acuse de recibo. Las respuestas de error no están firmadas por diseño, así que un rechazo nunca se convierte en un fallo de firma.
Tu primera venta en Delphi
La especificación indica a los contribuyentes que empiecen con el modo de verificación. El mensaje se comprueba por completo, exactamente igual que uno real, y después se descarta, de modo que no se presenta nada. Si supera la comprobación, el certificado, la firma, la conexión TLS y todos los campos de la venta son correctos. El código siguiente ejecuta primero esa comprobación y después presenta la venta de verdad.
var
oProvider: TsgcPFXKeyProvider;
oClient: TsgcEETClient;
oSale: TsgcEETSale;
oResponse: TsgcEETResponse;
begin
oProvider := TsgcPFXKeyProvider.Create(nil);
oClient := TsgcEETClient.Create(nil);
try
oProvider.FileName := 'CZ00000019.p12';
oProvider.Password := '...';
// Without LoadFromFile the certificate is empty and the message would
// carry no token for the tax authority to verify the signature with.
oProvider.LoadFromFile;
oClient.KeyProvider := oProvider as IsgcKeyProvider;
oClient.Environment := eetPlayground;
sgcEETInitSale(oSale);
oSale.SendDateTime := Now;
oSale.SaleDateTime := Now;
oSale.FirstSending := True;
// The common name of an EET certificate IS the taxpayer identifier.
oSale.TaxpayerEIC := oProvider.Certificate.SubjectCN;
oSale.UnitID := 11;
oSale.PosID := '1';
oSale.ReceiptNumber := '0/6460/ZQ42';
oSale.TotalAmount := 349;
// Verification mode first. Nothing is filed.
oClient.VerificationMode := True;
oResponse := oClient.Send(oSale);
if sgcEETResponseOutcome(oResponse) <> eoVerified then
raise Exception.CreateFmt('Verification failed, code %d: %s',
[oResponse.ErrorCode, oResponse.ErrorText]);
// Now for real. Only eoAcknowledged reports a sale.
oClient.VerificationMode := False;
oResponse := oClient.Send(oSale);
if sgcEETResponseOutcome(oResponse) = eoAcknowledged then
PrintReceipt(oResponse.POK, oResponse.Test) // your own routine
else
// Not filed. Store the sale and replay it later with Resend.
QueueSale(oSale); // your own routine
finally
oClient.Free;
oProvider.Free;
end;
end;
Unos pocos detalles deciden si esa primera ejecución funciona:
- Llama a
LoadFromFile. AsignarFileNameyPasswordsolo guarda los valores, yBuildMessagese niega a construir un mensaje sin certificado. TaxpayerEICdebe coincidir con el nombre común del certificado de firma, de lo contrario la respuesta incluye la advertencia 1. LeerCertificate.SubjectCNlo garantiza.- Un número de establecimiento realmente asignado tiene al menos dos dígitos y termina en 1, 2, 3 o 4. Un valor inventado como 1 supera la validación pero provoca la advertencia 6.
- Lee un importe tecleado por un cajero con
sgcEETParseAmount, no conStrToCurr, que sigue el separador decimal de la máquina.
Para el entorno de pruebas, la administración tributaria publica certificados de prueba compartidos, entre ellos CZ00000019, en eet.gov.cz. Un acuse de recibo del entorno de pruebas incluye test="true" y un pok que termina en ff, lo que no demuestra nada sobre una venta real.
Lectura de la respuesta
Esta es la parte del protocolo que más probablemente te sorprenda. Todos los resultados llegan como HTTP 200, incluido un rechazo, así que el estado HTTP no te dice nada. Y un éxito en modo de verificación llega dentro de un elemento de error con el código 0, de modo que TsgcEETResponse.IsError es True en una ejecución de verificación perfectamente correcta.
sgcEETResponseOutcome aplica ambas reglas y devuelve una de tres respuestas:
| Resultado | Significado |
|---|---|
eoAcknowledged | La venta está comunicada y el pok está en TsgcEETResponse.POK. Es el único resultado que comunica una venta. |
eoVerified | El éxito del modo de verificación. No se ha presentado nada. |
eoRejected | Todo lo demás. La venta no se ha comunicado y sigue pendiente de comunicar a la autoridad tributaria. |
Las advertencias no son críticas. Hasta diez pueden acompañar a un acuse de recibo válido, y OnWarning se dispara una vez por cada una, mientras que OnError se dispara ante un error cuyo código no es 0. Después de cada mensaje, registra LastTransactionId, la cabecera de respuesta X-Global-Transaction-Id, porque es lo primero que pide el soporte de EET. LastRequestXML y LastResponseXML conservan ambos mensajes exactamente como viajaron.
Cuando la línea está caída: una cola sin conexión
Una caja registradora tiene que seguir vendiendo cuando se cae la conexión. TsgcEETClient divide el ciclo completo para que un punto de venta pueda poner mensajes en cola y enviarlos más tarde:
BuildMessagevalida, construye, firma y mide el sobre sin enviarlo. Eluuid_zpravyque usó está enLastMessageUUID.SendRawenvía un sobre almacenado sin cambios, incluidos suuuid_zpravyy suprvni_zaslanioriginales, que es lo correcto para un mensaje que nunca salió de la máquina.Resendrepite una venta que se envió pero nunca obtuvo acuse de recibo, con unuuid_zpravynuevo yprvni_zaslania false, que es el reintento que describe la especificación.
// The line is down: sign the message now and keep it
sEnvelope := oClient.BuildMessage(oSale);
StoreInQueue(oClient.LastMessageUUID, sEnvelope); // your own storage
// The line is back: post the stored envelope exactly as it was built
oResponse := oClient.SendRaw(sEnvelope);
// Sent earlier but no answer arrived: replay the sale as a repeat
oResponse := oClient.Resend(oSale);
Conviene conocer una trampa antes de construir la cola. La hora de la venta se escribe con un desplazamiento de zona horaria, y si el registro de la venta no lleva su propio desplazamiento, la biblioteca usa el desplazamiento de la máquina en el momento en que se construye el mensaje. Una venta de julio repetida con Resend en diciembre quedaría marcada con el desplazamiento de diciembre. Guarda el desplazamiento junto con la venta, y asigna SaleOffsetMinutes y HasSaleOffsetMinutes cuando la repitas.
Verificación del acuse de recibo
VerifyResponseSignature es True por defecto, así que la firma de cada acuse de recibo se comprueba de serie. Comprobar también la cadena de certificados requiere los anclajes de confianza adecuados, y no son los evidentes. El acuse de recibo lo firma un certificado comercial de I.CA, no los certificados EET que acompañan al material de prueba del entorno de pruebas, y ninguno de los dos emisores de I.CA está en el almacén raíz de Windows. Descarga I.CA Root CA/RSA 05/2022 e I.CA Public CA/RSA 06/2022 desde ica.cz e indícalos como anclajes:
oClient.TrustedCertificates.Add('ica-root-ca-rsa-05-2022.cer');
oClient.TrustedCertificates.Add('ica-public-ca-rsa-06-2022.cer');
oClient.RequireTrustedChain := True;
Si una comprobación falla, LastVerificationDetails indica el paso que ha fallado. Cuando llega un acuse de recibo pero su firma no se verifica, Send lanza una excepción, y LastResponse sigue conteniendo la respuesta analizada con su pok, de modo que una venta ya registrada nunca se envía dos veces por error.
El límite de 12 kB
El servicio rechaza un mensaje de más de 12 kB con el código de error 7, y BuildMessage comprueba el tamaño antes de enviar nada. El esquema limita cada campo de la venta, así que la única parte del sobre cuyo tamaño varía realmente es el certificado de firma incluido en el wsse:BinarySecurityToken. Por eso también el sobre lleva exactamente una cabecera SOAP, y por eso el componente no ofrece ninguna forma de añadir otra.
C++Builder, .NET, el servidor y la línea de comandos
- C++Builder usa el mismo componente, y
Demos\CBuilder\EETreplica la demo de Delphi. - .NET tiene
TsgcEETClientcon la misma API, y una demo WinForms endemos\EET. - sgcSign Server añade
POST /api/v1/sign/eet, que construye y firma una venta con una clave que guarda el servidor, y puede enviarla y devolver la respuesta. Así una cadena de cajas registradoras puede compartir un único certificado del contribuyente guardado en el servidor, en lugar de que cada caja tenga una copia. - La herramienta de línea de comandos
sgcsigntiene el verbo correspondienteeet. Lee la venta de un archivo JSON, y su código de salida indica a un script de la caja registradora qué ha ocurrido: 6 significa que la autoridad tributaria rechazó la venta, 5 significa que el servidor no pudo firmarla o no pudo contactar con la autoridad tributaria, y 4 significa que no se pudo contactar con el propio servidor de sgcSign.
set SGCSIGN_SERVER=https://sign.shop.local:8443
set SGCSIGN_APIKEY=sgcsk_...
sgcsign eet --provider eet-taxpayer --submit sale.json
Pruébalo
La demo de Delphi en Demos\Delphi\EET recorre el ciclo completo contra el entorno de pruebas en un solo formulario. Carga un certificado de prueba, envía en modo de verificación, después desmarca esa opción y envía una venta real para obtener un acuse de recibo con su pok. Build Message (no send) muestra el sobre firmado que almacenaría una cola sin conexión, y Resend Stored Sale repite la última venta como un reenvío. Los certificados de prueba no se incluyen con la demo, porque el documento que los distribuye es de acceso restringido, así que descárgalos de eet.gov.cz.
Todas las propiedades, métodos y eventos están documentados en la ayuda en línea de sgcSign, y la sección EET 2.0 de la página de perfiles por país de sgcSign resume el componente.
Disponibilidad
TsgcEETClient se incluye en sgcSign 2026.10 para Delphi, C++Builder y .NET, junto con la ruta de sgcSign Server y el verbo eet de la herramienta de línea de comandos.
¿Preguntas, o una caja registradora que tiene que estar lista para enero? Ponte en contacto. Si algo no se comporta como esperas, envía el XML de la petición y de la respuesta junto con el X-Global-Transaction-Id, y recibirás respuesta de las personas que escribieron el código.
