8.- Validaciones de facturas

8.- Validaciones de facturas

 

1.- Validaciones previas a la descarga de facturas FACe (SFE)


Análisis basado en DownloadInvoiceFaceAction.java.
A continuación se detallan todas las validaciones que se realizan antes de invocar al servicio faceService.obtainNewInvoices(), tanto para descargas automáticas como manuales.


El proceso de descarga de facturas desde FACe hacia SFE, tal y como está implementado en DownloadInvoiceFaceAction, tiene dos modalidades:


• Descarga automática (auto): SFE solicita a FACe nuevas facturas directamente, sin fichero de entrada.
• Descarga manual (manual): El usuario sube un fichero Excel con un listado de facturas a descargar desde FACe.


Las validaciones que vamos a documentar se centran en lo que ocurre:
• En la capa de acción (DownloadInvoiceFaceAction).
• En el tratamiento del fichero Excel (XlsReader + validateProcessFile).

1.1.- Validación del tipo descarga

En la vista que nos devuelve downloadInvoiceFace.jsp el usuario tiene que elegir entre el tipo de descarga “auto” o “manual”.


En esta parte del código se valida que el usuario haya elegido un tipo de descarga, si no se elige ninguna, se añade un error de acción (noTypeDownload.advice) y no se continúa la lógica de descarga (se vuelve a la vista de descarga).

if(isNullOrEmpty(downloadType)) {

    addActionError(getText("noTypeDownload.advice"));

    return RESPONSE_DOWNLOAD;

}

En el caso de la descarga automática no hay validaciones en este archivo, la lógica de qué facturas se descargan se delega en el servicio faceService.obtainNewInvoices al que después iremos.

1.1.1.- Validaciones Descarga Manual

} else if (downloadType.equals(MANUAL)) {

    paramObtainNewInvoices.setDownloadType(MANUAL);

    List<SRCFFactura> srcfFacturaList = new ArrayList<SRCFFactura>();

    srcfFacturaList = validateProcessFile(loadFile);

    paramObtainNewInvoices.setInvoiceList(srcfFacturaList);

}

 

En esta parte se marca el tipo de descarga como manual y se procesa el fichero Excel subido por el usuario. Toda la lógica de validación del fichero está en el método validateProcessFile(...) y, a su vez, en XlsReader. En este método, encontramos una capa de validación y manejo de errores:

1-      Validación de existencia del fichero:

if (loadFile != null) {

    ...

} else {

    throw new DownloadInvoiceFaceException("noLoadFile.advice");

}

Se valida que el usuario haya subido un fichero, si no se cumple, se lanza la excepción DownloadInvoiceFaceException y se muestra el mensaje al usuario.

 

2-      Validación del contenido del fichero:

srcfFacturaList = xlsReader.processFile(loadFile);

if (srcfFacturaList.isEmpty()) {

    throw new DownloadInvoiceFaceException("emptyInvoiceFaceList.error");

}

Se valida que el fichero se procese correctamente por XlsReader y que la lista resultante de facturas no esté vacía. Si el Excel no contiene ninguna fila válida dará error (emptyInvoiceFaceList.error). Y si ocurre un error en el xlsReader.processFilese propaga una excepción (processFileLoad.error) que cubre errores de formato, errores de lectura, estructura incorrecta del Excel y otros errores controlados dentro del XlsReaderprocessFile(File file). tales como:

  • Que el fichero exista y sea accesible

  • Que sea un fichero de Excel válido

  • Que exista al menos una hoja

Si ocurre alguna de estas cosas se lanza:

DownloadInvoiceFaceException("Error al procesar el archivo")

private boolean checkExcelParameters(String idFace, String fechaRegistro, String oficinaContable) {

    boolean result = true;

    if (idFace == null || fechaRegistro == null || oficinaContable == null) {

        LOG.error("Los valores obtenidos no son correctos, " + idFace + "," + fechaRegistro + "," + oficinaContable);

        result = false;

    }

    return result;

}

 

En esta parte se valida los parámetros obligatorios por fila. Se valida que los 3 valores leídos no sean null. Si alguno es null la fila no se añade a srcfFacturaList.
Al final del processFilecontendrá una entrada SRFFactura por cada fila de Excel que se haya podido leer sin errores y tenga las 3 columnas principales no nulas (aunque podrían estar vacías como “”).
Por último, tendríamos la validación final antes de la llamada al servicio:

if (!hasActionErrors()) {

    faceService.obtainNewInvoices(paramObtainNewInvoices);

}

Se valida que no haya errores de acción acumulada (ej, falta de tipo de descarga, errores en fichero, otros problemas…). En caso de que los haya, no se llama al servicio faceService.obtainNewInvoices.
Aunque no forman parte directa del flujo de descarga, sí condicionan el acceso:
• isPermitsView() → Comprueba si el usuario puede ver el enlace de crear XPATH (CREATE_SERPA_XPATH).
• isPermitsViewDownload() → Permite ver el enlace de descarga de facturas (DOWNLOAD_INVOICE).
• isPermitsGetAllStates() → Permite acceso a consultas masivas (GET_ALL_STATES).
Estas validaciones se suelen usar en la vista (JSP/taglibs) para mostrar u ocultar funcionalidades según los permisos del usuario.

 

2.- Validaciones en la llamada al servicio


Una vez superadas todas las validaciones anteriores, se llama al servicio FaceServiceImpl.obtainNewInvoices(). Dentro de este método solo existen dos validaciones explícitas y ambas están dentro de parameterValidate(paramObtainNewInvoices).

2.1.- Validación: el parámetro de entrada no puede ser nulo

 

if (paramObtainNewInvoices == null) {

    throw new ValidationException(FaceServicesError.OBTAIN_NEW_INVOICES_300, PARAM_SEARCH_ERROR_MESSAGE);

}

Aquí se valida que la llamada al servicio incluya ParamObtainNewInvoices.

2.2.- Validación si el tipo de descarga es “DescargaDeLista”, la lista de facturas no puede estar vacía

 

if ("DescargaDeLista".equalsIgnoreCase(param.getDownloadType())

    && (param.getInvoiceList() == null || param.getInvoiceList().isEmpty())) {

    throw new ValidationException(FaceServicesError.OBTAIN_NEW_INVOICES_301, PARAM_WITHOUT_INVOICE_LIST);

}

Solo se aplica cuando downloadType = "DescargaDeLista". En ese caso, invoiceList debe existir y contener al menos una factura.

 

3.- Validaciones en el manager (FaceManagerImpl)


El manager no valida parámetros de entrada (eso ya lo hace el servicio), pero sí valida el flujo, el estado de las facturas y la coherencia de los datos durante la descarga e importación.

 

3.1.- Validaciones en obtenerNuevasFacturas

3.1.1.- Validación de ejecución simultánea

 

if (!enEjecucion) {

    enEjecucion = true;

    ...

}

El proceso solo puede ejecutarse una vez:

·       Si enEjecucion = true → NO se ejecuta nada.

·       Si enEjecucion = false → se marca como true y se inicia.

Finalidad: evitar descargas concurrentes que podrían duplicar facturas o corromper estados.

 

3.1.2.- Validación del tipo de descarga

 

if ("manual".equals(downloadType)) {

    listaObtenida = invoiceList;

} else {

    listaObtenida = faceIntegration.solicitarNuevasFacturas("");

}

 

Determina si la descarga es manual o automática.

·  Si downloadType = "manual" → se usa la lista proporcionada.

·  Si no → se llama a FACe para obtener nuevas facturas.

 

3.2.- Validaciones en procesoFacturasFace

Aquí está el grueso de validaciones del manager.

 

3.2.1.- Validación propiedad “PararProcesoDescarga”

 

String propiedadPararProceso = configurationManager

    .getConfiguration("CORE", "Properties", "PararProcesoDescarga")

    .getValue();

 

if ("true".equalsIgnoreCase(propiedadPararProceso)) {

    break;

}

 

Antes de procesar cada factura:

·       Si la propiedadPararProcesoDescarga = true → se detiene el proceso.

Finalidad: permitir detener la descarga sin reiniciar servicios.

 

3.2.2.- Validación fecha de registro FACe

try {

    fechaFace = dateFormat.parse(scrfFacturaObj.getFechaHoraRegistro());

} catch (Exception e) {

    LOG.error("Error en el formato de la fecha de la factura", e);

}

 

Detecta fechas mal formadas.

 

3.2.3.- Validación factura ya existente en base de datos

 

invoiceSearch.setIdFace(numeroFactura);

List<InvoiceBean> invoiceResult = invoiceManager.searchInvoice(invoiceSearch, null, null);

 

if (invoiceResult != null && invoiceResult.size() == 0) {

    facturaCompleta = faceIntegration.descargarFactura(numeroFactura);

} else {

    LOG.error("La factura ya se encuentra en la BD, nos saltamos esta descarga");

}

 

Se busca en la base de datos por idFace, si ya existe NO se descarga. De esta manera evita duplicados en SFE.

 

3.2.4.- Validación factura descargada correctamente

 

if (facturaCompleta != null) {

    ...

} else {

    LOG.error("La factura " + numeroFactura + " no se ha recuperado correctamente");

    rechazadasNoDescargadas.add(numeroFactura + ": La factura no se ha recuperado correctamente");

}

 

Comprueba que FACe devolvió la factura.

Si facturaCompleta == null → error técnico

 

3.2.5.- Validación contenido XML no vacío

 

if (facturaCompleta.getFactura() != null && !facturaCompleta.getFactura().isEmpty()) {

    ...

}

 

El XML de la factura debe existir y no estar vacío.

 

3.2.6.- Validación BOM en el XML

 

if (arrayb64Factura[0] == (byte) 0xEF && arrayb64Factura[1] == (byte) 0xBB && arrayb64Factura[2] == (byte) 0xBF) {

    byte[] newArrayb64Factura = new byte[arrayb64Factura.length - 3];

    System.arraycopy(arrayb64Factura, 3, newArrayb64Factura, 0, arrayb64Factura.length - 3);

    arrayb64Factura = newArrayb64Factura;

}

 

Elimina el BOM para evitar errores de parseo.

 

3.2.7.- Validación de anexos

1- MIME demasiado largo

if (mimeFichero.length() > 40) {

    mimeFichero = "application/octet-stream";

}

 

Si mimeFichero.length > 40 → se fuerza a application/octet-stream.

 

2- Extensión incorrecta

 

if (nombreFichero.indexOf(".rar") != -1) {

    nombreFichero = nombreFichero.replace(".rar", ".zip");

}

 

.rar → se convierte a .zip.

 

3- PDF sin extensión

if (!nombreFichero.contains(".pdf") && "APPLICATION/PDF".equals(mimeFichero.toUpperCase())) {

    nombreFichero = nombreFichero + ".pdf";

}

 

Si MIME = PDF pero el nombre no tiene .pdf → se añade.

 

4- Nombre normalizado

Se renombra a:

nombreFichero = "Adjunto-" + numeroFactura + "-" + contadorAdjuntos + ext;

Para evitar errores en Documentum/PAPIRO

 

3.2.8.- Validaciones de la factura tras el parseo

1- Validación de fecha de emisión vs fecha FACe

 

if (configurationManager.getConfiguration("CORE", "Habilitado", "validateDateEnabled").getValue().equals("true")) {

    if (fechaFace.after(invoice.getIssueDate())) {

        LOG.info ("Fechas: " + fechaFace + " - " + invoice.getIssueDate());

    } else {

        throw new InvoiceValidationException(...);

    }

}

 

Si fechaFace < issueDateerror (“La fecha de la factura no puede ser superior a la fecha en FACe”).

 

3.2.9.- Validaciones durante importación SFE

Durante la importación de la factura en SFE se ejecutan diversas validaciones técnicas y funcionales. Cada una de ellas puede generar un error específico (IMPORT_INVOICE_XXX) que el manager interpreta posteriormente. Las validaciones reales que se realizan dentro de importInvoice (importResult = invoiceManager.importInvoice(...))son las siguientes:

1- Validación de firma electrónica (SIFE/SignManager)

Se verifica la firma de la factura.

  • Si la firma es inválida → IMPORT_INVOICE_102

  • Si falla la comunicación con SIFE → IMPORT_INVOICE_212

  • Algunos errores SHA‑512 se consideran “controlados” y no bloquean la importación.

Las comprobaciones incluyen:

  • Validación criptográfica de la firma

  • Validación del formato XAdES

  • Detección de errores SHA‑512

  • Detección de firma inválida o corrupta

  • Validación de la respuesta de SIFE

  • Validación de elevación de firma (XAdES‑A)

  • Detección de errores de comunicación con SIFE

 

2- Validación de factura rectificativa

invoiceToBeRectified = getRectificativeImportedInvoice(invoice);

 

  • Si la factura es rectificativa, se comprueba que exista la factura original y se obtiene.

  • Si no existe o hay problemas, lo habitual es que getRectificativeImportedInvoice acabe provocando un error que se transforma (en otra capa) en IMPORT_INVOICE_132 (la factura a rectificar no existe), que el manager trata específicamente.

 

3- Validación CCSV (almacenamiento documental)

 

Se valida que la factura pueda almacenarse correctamente en CCSV.

  • Si falla la inserción o validación documental → IMPORT_INVOICE_100 u otros códigos CCSV.

 

4- Validación SRT (registro telemático)

 

Se valida el registro de la factura en SRT y la generación del justificante.

  • Si falla la integración → IMPORT_INVOICE_110–115

 

5- Validación SIU (organización/DIR3/multientidad)

 

Se validan los organismos asociados a la factura y la correspondencia DIR3–SIU.

  • Si falla la integración o los datos son incorrectos → IMPORT_INVOICE_160–164

 

6- Validación SERPA (solo si el organismo usa ERP SERPA)

 

Si la factura pertenece a un organismo cuyo ERP es SERPA, se ejecuta una prevalidación funcional.

  • Si falla la validación → IMPORT_INVOICE_150–152

  • Si fallan los XPaths necesarios → IMPORT_INVOICE_153

 

7- Validación SIFE (hash, HTML, transformaciones)

 

Errores técnicos al generar hash, HTML o transformaciones XSLT.

  • Hash incorrecto → IMPORT_INVOICE_101

  • Error generando HTML → IMPORT_INVOICE_103

 

3.2.10.- Validaciones de estado en FACe

Durante el proceso de importación, SFE debe actualizar el estado de la factura en FACe. Las validaciones que se realizan en esta fase son las siguientes:

 

1- Validaciones de cambio de estado

estado = faceIntegration.cambiarEstadoFactura(oficinaContable, numeroFactura, "2600", mensajeError);

if (!"2600".equals(estado.getCodigo())) {

rechazadasPendientes.add(...);

}

 

  FACe debe devolver un estado cuyo código coincida con el solicitado.

  Si FACe no devuelve el estado esperado → la factura queda como rechazo pendiente manual.

 

2- Validaciones de respuesta correcta FACe

 

if (OK.equals(response.getResultado().getCodigo())) {

    resultado = response.getFactura();

} else {

    throw new FACeException(...);

}

 

  FACe debe devolver código OK.

  Si FACe devuelve un error funcional → se lanza FACeException

 

3- Validaciones de errores de firma del mensaje

} catch (Fault e) {

    if (e.getCause() instanceof InvoiceManagerException) {

        throw new FACeException(Apps.SIFE.getValue(), e.getMessage());

    }

 

Si el error proviene de la firma del mensaje SOAP → se clasifica como error SIFE.

 

4- Validaciones de errores al procesar el XML en SAFE

else {

    throw new FACeException(Apps.SFE.getValue(), e.getMessage());

}

 

Si el error no es de firma, se considera error interno de SFE al generar o procesar el XML enviado a FACe.

 

5- Validaciones de errores genéricos

Cualquier excepción no controlada se clasifica como error SFE.

 

Resumen:

Si la factura se importa correctamente:

·       Se confirma descarga en FACe

·       Se cambia estado a FACE_VERIFICADA_RFC

·       Solo si la factura NO va a SERPA

Si falla:

·       Se rechaza con estado 2600

·       Si FACe no acepta → rechazo pendiente manual

 

3.2.11.- Validaciones de anulaciones

 

Durante la gestión de anulaciones, SFE realiza dos llamadas a FACe:

  1. solicitarNuevasAnulaciones → para obtener las anulaciones pendientes

  •   Se valida que FACe devuelva código OK.

  •   Si FACe devuelve error → no se procesan anulaciones.

  •   Si ocurre un error técnico (SOAP, red, parsing) → se descarta la respuesta.

  1. gestionarSolicitudAnulacionFactura → para confirmar la anulación en FACe

 

FACe debe devolver un código OK. Si no:

  • La anulación no se considera gestionada.

  • Se registra el error, pero no se lanza excepción (flujo no interrumpido).

Si ocurre cualquier excepción:

  • Se registra el error.

  • La anulación queda no gestionada.

  • El proceso continúa con el resto de facturas.

 

3.3.- Validaciones en validateAndParseInvoice

 

Este método es el núcleo de validación técnica y funcional del proceso.

Realiza 4 fases de validación, todas obligatorias.

 

3.3.1.- Validación sintáctica del XML

 

Método:syntaxValidate(data)

Valida:

·       XML bien formado

·       Cumplimiento del XSD FacturaE

·       Determinación de versión (3.2, 3.2.1, 3.2.2)

Errores:

·       InvoiceValidationException

·       SAXException

·       ValidationException

 

3.3.2.- Validación estructural (unmarshal)

es.mityc.facturae321.Facturae facturae = unmarshal321(data);

Valida:

·       Tipos correctos

·       Campos obligatorios

·       Estructura interna del XML

 

3.3.3.- Validación semántica

El método semanticValidate321(data, facturae) realiza dos niveles de validación:

 

1- Validación contable y normativa FacturaE (ValidaciónEfactura321)

Comprueba:

·       Coherencia de importes

·       Fechas

·       NIF/CIF

·       Reglas del estándar FacturaE

·       Validación de facturas rectificativas

Si falla → InvoiceValidationException (VALIDATION_33).

 

2- Validación propia SFE (sfeValidate)

Comprueba:

·       Reglas internas de SFE

·       Datos obligatorios adicionales

·       Coherencia de emisor/receptor

·       Validaciones específicas no cubiertas por FacturaE

Si falla → InvoiceValidationException con la lista de errores.

 

4.- Validaciones en FaceIntegration


 

Los métodos de FACeIntegration realizan únicamente validaciones relacionadas con la respuesta del servicio FACe y errores técnicos. No realizan validaciones funcionales sobre la factura.

 

1- consultarFactura (idFace)

  • FACe devuelve código OK

if (OK.equals(response.getResultado().getCodigo()))

  • Si FACe devuelve error → se registra en log (no lanza excepción)

  • Si ocurre cualquier excepción → result = null

 

2- consultarEstados()

  • FACe devuelve código OK

  • Si FACe devuelve error → solo log