Skip to content

Solucion de Problemas

Jacobo Cantorna Cigarrán edited this page Oct 12, 2026 · 6 revisions

Solución de problemas

El WSDL no se encuentra al usar el SDK como dependencia (resuelto en v1.2.0)

✅ Corregido. Este problema afectaba a versiones < 1.2.0 y ya no debería reproducirse si usas com.squareetlabs:verifactu:1.2.0 o posterior. Se deja documentado por si alguien sigue en una versión anterior.

Síntoma: al llamar a client.sendInvoice(...) desde una aplicación que consume com.squareetlabs:verifactu vía Maven Central/Gradle (no desde un checkout del propio SDK), obtienes un error como FileNotFoundException o MalformedURLException relacionado con src/main/resources/SistemaFacturacion.wsdl, y la respuesta llega con status=error.

Causa (versiones antiguas): AeatClient.performSoapCall() cargaba el WSDL desde una ruta relativa del sistema de archivos:

URL wsdlUrl = new File("src/main/resources/SistemaFacturacion.wsdl").toURI().toURL();
service = new SfVerifactu(wsdlUrl);

Esa era una ruta de fichero relativa al directorio de trabajo (user.dir) del proceso Java, no una carga desde el classpath/JAR. Aunque el fichero SistemaFacturacion.wsdl sí viaja empaquetado dentro del .jar publicado en Maven Central (en src/main/resources), aquel código no lo cargaba desde ahí — solo funcionaba si, además, existía físicamente esa ruta relativa src/main/resources/SistemaFacturacion.wsdl bajo el directorio desde el que arrancaba la aplicación. Esto se cumplía casi siempre al ejecutar los tests dentro del propio checkout del SDK, pero casi nunca en una aplicación real que solo dependía del paquete.

Solución aplicada desde la v1.2.0: AeatClient.performSoapCall() ahora carga el WSDL desde el classpath (está empaquetado como recurso dentro del propio jar de verifactu), no desde una ruta relativa del sistema de archivos:

URL wsdlUrl = AeatClient.class.getClassLoader().getResource("SistemaFacturacion.wsdl");
service = new SfVerifactu(wsdlUrl);

Esto funciona igual sin importar el working directory desde el que arranque tu aplicación (contenedor Docker, jar sombreado, tests, etc.). Si sigues en una versión < 1.2.0 y no puedes actualizar todavía, los workarounds eran: copiar manualmente src/main/resources/SistemaFacturacion.wsdl al working directory de arranque, o fijar ese working directory en tu Dockerfile/script de arranque para que la ruta relativa siempre exista.

Nota: este problema solo podía darse en modo VERI*FACTU (verifactuMode=true), porque es el único que llega a cargar/usar el WSDL para hacer la llamada SOAP real. En modo NO VERI*FACTU (verifactuMode=false) nunca se toca el WSDL — ver Firma digital XAdES.

SOAPFaultException / errores de conexión al ejecutar mvn test

Es esperado: varios tests (p. ej. los relacionados con AeatClientTest) intentan una conexión real contra el WSDL/endpoint de la AEAT sin certificado válido ni red disponible en el entorno de CI, y verifican que el resultado sea un error controlado (status=error con mensaje), no que la llamada tenga éxito. Si ves una traza de SOAPFaultException o de conexión rechazada en la salida de mvn test pero el build termina en BUILD SUCCESS, no es un fallo real.

IllegalArgumentException: thirdPartyName and thirdPartyTaxId are required...

Estás usando invoice.setIssuedByThirdPartyOrRecipient("T") sin rellenar thirdPartyName/thirdPartyTaxId. Ver Representación de terceros — ambos campos son obligatorios cuando el valor es "T".

El destinatario extranjero

Desde la 1.4.0 un destinatario con país y tipo de documento se envía en el bloque IDOtro (ver Tipos de factura). Si indicas solo nombre y NIF, se envía como NIF español y la validación local exige que sea un NIF válido; un identificador extranjero con NIF-IVA, pasaporte, etc. necesita new Recipient(nombre, id, "FR", "02").

Aceptado con errores: «Codigo[2004] … FechaHoraHusoGenRegistro debe ser la fecha actual … margen de error de: 240 segundos»

El registro se generó (y se firmó) hace más de 240 s respecto al momento de enviarlo. La AEAT lo acepta, pero con errores (aeat_status = ParcialmenteCorrecto). Si lo reenvías con la misma setGenerationTimestamp para reproducir el mismo registro, mantienes la fecha vieja y el aviso. Para cualquier reintento que no sea de un envío sin respuesta, genera el registro de nuevo con la hora actual. La respuesta trae generation_timestamp_warning para detectarlo.

La respuesta es status=error con timeout=true ("Tiempo de espera agotado")

La AEAT no respondió dentro de connectTimeoutMillis (10 s por defecto) o readTimeoutMillis (30 s). El registro pudo llegar a registrarse aunque la respuesta no llegara: reenvíalo sin cambios, con la misma fecha de generación (setGenerationTimestamp) y el mismo previous, y trata un duplicado como un posible acierto (ver Respuesta de la AEAT y Encadenamiento y hash). Si guardas el registro en una transacción de base de datos, mantén los timeouts por debajo del tiempo de espera de bloqueo de tu base de datos. Puedes ajustarlos con setConnectTimeoutMillis y setReadTimeoutMillis.

La respuesta es status=error con "Validacion local: ..."

El registro no pasó InvoiceValidator y no se firmó ni se envió nada. Cada motivo está en validation_errors (ver Validación local). Corrige los datos y vuelve a enviar.

La AEAT responde Incorrecto y no sé por qué

Mira aeat_error_code, aeat_error_message y aeat_record_errors (ver Respuesta de la AEAT). Con una versión anterior a 1.3.2 la respuesta solo traía aeat_status; actualiza.

Error 4116 / NIF del ObligadoEmision con formato incorrecto

El NIF o la razón social del emisor no tienen el formato que acepta la AEAT (o son datos de prueba como CIF0000). Comprueba VeriFactuConfig#setIssuerVat con InvoiceValidator.validate(config).

Error 1244: FechaHoraHusoGenRegistro con formato incorrecto

Resuelto en la 1.3.3: el instante se envía en ISO 8601 sin fracción de segundo, con el mismo valor en el XML y en la huella.

InvoiceValidator avisa de que los importes no coinciden

Es un aviso, no un error: la AEAT acepta el registro con errores cuando el total o la cuota total difieren más de 10 € del desglose (Σ base + Σ cuota + Σ recargo), y entonces hay que subsanarlo. No se comprueba con las claves de régimen 03, 05, 06, 08 y 09. La cuota repercutida de cada línea S1 sí se rechaza si difiere más de 10 € de base × tipo.

"Una factura F1 debe identificar al destinatario"

La AEAT rechaza una F1, F3 o R1-R4 sin destinatarios, y una F2 o R5 con ellos. Si no puedes identificar al cliente, emite una factura simplificada (F2) o una F2 con la marca del art. 6.1.d (setWithoutRecipientIdentification(true), solo válida en F2 y R5).

Reenviar una factura que la AEAT rechazó

Un registro rechazado no existe en la AEAT. Para reenviarlo corregido (sin rectificativa) usa setSubsanation(true) y setPreviousRejection("X") (alta por rechazo). Ver la tabla de operaciones en el README.

¿Dónde se configura el certificado del representante vs. el del emisor?

Siempre en el constructor de AeatClient (certPath/certPassword), independientemente de si hay representación configurada o no. Si VeriFactuConfig.hasRepresentative() es true, ese certificado debe ser el del representante (quien se autentica frente a la AEAT), no el del obligado. Ver Representación de terceros.

¿Cómo reporto un bug o pido una funcionalidad?

Abre un issue en github.com/squareetlabs/verifactu-sdk/issues.

Clone this wiki locally