Introducción

Cuando los equipos legales o los analistas forenses necesitan demostrar que un documento no ha sido manipulado, simplemente observar el contenido visible no es suficiente. Las propiedades ocultas —como autor, fecha de creación o número de revisión— pueden revelar quién tocó un archivo y cuándo. Detectar estos cambios sutiles entre versiones de documentos es un punto de dolor común que a menudo requiere inspección manual de cada propiedad, una tarea que consume tiempo y es propensa a errores.

GroupDocs.Metadata para Java ofrece una forma programática de extraer cada campo de metadatos y calcular una diferencia estructurada entre dos versiones. En este tutorial compararemos tres enfoques prácticos: una diferencia completa de metadatos, detección enfocada de cambios de titularidad y análisis del historial de revisiones. Cada método se muestra con código conciso, listo para copiar y pegar, y también veremos cómo exportar los resultados a CSV o JSON para informes de auditoría.

Me encontré con este problema al revisar un contrato que había sido editado por múltiples partes durante meses; el texto visible parecía idéntico, pero los campos de titularidad habían cambiado silenciosamente.

¿Cómo puedo saber qué campos de metadatos cambiaron entre dos versiones de un documento?

GroupDocs.Metadata carga cada archivo, extrae todas las propiedades accesibles en un mapa y luego itera sobre las claves para clasificar adiciones, eliminaciones y modificaciones. La biblioteca maneja etiquetas incorporadas y personalizadas, por lo que obtienes una visión completa sin escribir analizadores específicos de formato. El resultado es un objeto MetadataDiff que puedes consultar o serializar para informes de cumplimiento.

Requisitos previos

  • Java 8 o superior
  • GroupDocs.Metadata para Java 24.7 ([licencia temporal][TEMP_LICENSE_URL])
  • Dos archivos de documento que deseas comparar (por ejemplo, contract_v1.pdf y contract_v2.pdf)

Instalación

Agrega la dependencia mediante Maven:

<dependency>
    <groupId>com.groupdocs</groupId>
    <artifactId>groupdocs-metadata</artifactId>
    <version>24.7</version>
</dependency>

Método 1 – Diferencia completa de metadatos

Este método extrae todos los metadatos de ambas versiones y reporta las entradas añadidas, eliminadas y modificadas.

// CompareMetadataSets.run – returns a MetadataDiff object
Map<String, String> v1 = ExtractAllMetadata.run(pathV1);
Map<String, String> v2 = ExtractAllMetadata.run(pathV2);
MetadataDiff diff = new MetadataDiff();

// Detect added and changed properties
for (Map.Entry<String, String> e : v2.entrySet()) {
    String key = e.getKey();
    String val = e.getValue();
    if (!v1.containsKey(key)) {
        diff.added.put(key, val);               // New property in v2
    } else if (!v1.get(key).equals(val)) {
        diff.changed.put(key, new String[]{v1.get(key), val}); // Value changed
    }
}
// Detect removed properties
for (Map.Entry<String, String> e : v1.entrySet()) {
    if (!v2.containsKey(e.getKey())) {
        diff.removed.put(e.getKey(), e.getValue());
    }
}
return diff;

Puntos clave:

  • Integral: Captura todas las etiquetas, incluidas las personalizadas.
  • Lógica de mapa simple: No se requiere una biblioteca externa de diferencias.
  • Objeto de resultado: Los mapas added, removed y changed están listos para procesamiento adicional.

💡 Consejo: Usa este método cuando necesites una pista de auditoría completa para cumplimiento regulatorio.

Método 2 – Detectar cambios de titularidad

Los litigios legales a menudo dependen de quién creó o editó un documento. Este método se centra en etiquetas relacionadas con personas, como Creator, Editor, Manager y Company.

// DetectOwnershipChanges.run – returns a map of changed ownership fields
Map<String, String> v1 = readOwnership(pathV1);
Map<String, String> v2 = readOwnership(pathV2);
Set<String> allKeys = new HashSet<>(v1.keySet());
allKeys.addAll(v2.keySet());
Map<String, String[]> changes = new LinkedHashMap<>();
for (String key : allKeys) {
    String oldVal = v1.getOrDefault(key, "<missing>");
    String newVal = v2.getOrDefault(key, "<missing>");
    if (!oldVal.equals(newVal)) {
        changes.put(key, new String[]{oldVal, newVal});
    }
}
return changes;

readOwnership extrae solo las etiquetas relevantes:

Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(path)) {
    if (metadata.getFileFormat() == FileFormat.Unknown) return result;
    for (MetadataProperty p : metadata.findProperties(
            new ContainsTagSpecification(Tags.getPerson().getCreator())
                .or(new ContainsTagSpecification(Tags.getPerson().getEditor()))
                .or(new ContainsTagSpecification(Tags.getPerson().getManager()))
                .or(new ContainsTagSpecification(Tags.getCorporate().getCompany())))) {
        String value = "";
        if (p.getValue() != null && p.getValue().getRawValue() != null) {
            value = String.valueOf(p.getValue().getRawValue());
        }
        result.put(p.getName(), value);
    }
}
return result;

Puntos clave:

  • Enfocado: Solo se examinan las propiedades que identifican a personas.
  • Salida clara: Devuelve un mapa donde cada entrada muestra valores [antiguo, nuevo].
  • Listo para cumplimiento: Perfecto para e‑discovery o disputas de titularidad de contratos.

💡 Consejo: Combínalo con la diferencia completa si necesitas tanto amplitud como profundidad.

Método 3 – Detectar cambios en el historial de revisiones

Los números de revisión, marcas de tiempo de edición y fechas de impresión son invisibles para los usuarios finales pero cruciales para líneas de tiempo forenses. Este método aísla las etiquetas relacionadas con el tiempo.

Map<String, String> v1 = readRevision(pathV1);
Map<String, String> v2 = readRevision(pathV2);
Set<String> allKeys = new HashSet<>(v1.keySet());
allKeys.addAll(v2.keySet());
Map<String, String[]> changes = new LinkedHashMap<>();
for (String key : allKeys) {
    String oldVal = v1.getOrDefault(key, "<missing>");
    String newVal = v2.getOrDefault(key, "<missing>");
    if (!oldVal.equals(newVal)) {
        changes.put(key, new String[]{oldVal, newVal});
    }
}
return changes;

readRevision extrae las marcas de tiempo Modified, Created y Printed:

Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(path)) {
    if (metadata.getFileFormat() == FileFormat.Unknown) return result;
    for (MetadataProperty p : metadata.findProperties(
            new ContainsTagSpecification(Tags.getTime().getModified())
                .or(new ContainsTagSpecification(Tags.getTime().getCreated()))
                .or(new ContainsTagSpecification(Tags.getTime().getPrinted())))) {
        String value = "";
        if (p.getValue() != null && p.getValue().getRawValue() != null) {
            value = String.valueOf(p.getValue().getRawValue());
        }
        result.put(p.getName(), value);
    }
}
return result;

Puntos clave:

  • Reconstrucción de línea de tiempo: Muestra cuántas veces se editó o imprimió un archivo.
  • Deltas numéricos: Útil para detectar revisiones sospechosamente rápidas.
  • Ligero: Solo se consultan tres etiquetas, manteniendo la ejecución rápida.

**💡 Consejo