Google Apps Script
Valida documentos de Google Drive desde Google Sheets con Apps Script y UrlFetchApp, guarda la clave en Script Properties y escribe el veredicto en la hoja.
Con Google Apps Script puedes analizar ficheros de Google Drive desde una hoja de cálculo: cada fila apunta a un
fichero, el script lo envía a Constaia en multipart/form-data y escribe el veredicto y los campos extraídos en la
misma fila. El script se ejecuta en los servidores de Google, así que la clave no llega al navegador.
Requisitos
- Una hoja de Google Sheets y ficheros en Google Drive (JPEG, PNG, WEBP, HEIC o PDF, hasta 20 MB).
- Una clave de test
ck_test_…del panel. Consulta Autenticación.
Preparar la hoja
Crea una pestaña llamada Documentos con estas columnas en la fila 1:
| A | B | C | D | E | F | G |
|---|---|---|---|---|---|---|
| ID de Drive | Tipo esperado | Veredicto | Motivos | Número | Caducidad | ID de análisis |
Rellena la columna A con el ID de cada fichero (la parte de la URL de Drive entre /d/ y /view) y la B con el
tipo esperado, por ejemplo es_dni. Los tipos disponibles están en el catálogo.
Instalar el script
Guarda la clave en Script Properties
En la hoja, abre Extensiones → Apps Script. En el editor, ve a Configuración del proyecto → Propiedades del
script y añade la propiedad CONSTAIA_API_KEY con tu clave ck_test_…. Así la clave no aparece en el código
ni se copia si alguien duplica el script.
Pega el código
Sustituye el contenido de Code.gs por:
const API = "https://api.constaia.com/v1";
const SHEET = "Documentos";
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("Constaia")
.addItem("Analizar filas pendientes", "analyzePending")
.addItem("Actualizar análisis en curso", "refreshPending")
.addToUi();
}
function apiKey_() {
const key = PropertiesService.getScriptProperties().getProperty("CONSTAIA_API_KEY");
if (!key) throw new Error("Falta la propiedad del script CONSTAIA_API_KEY");
return key;
}
function call_(path, params) {
const response = UrlFetchApp.fetch(API + path, {
...params,
headers: { Authorization: "Bearer " + apiKey_() },
muteHttpExceptions: true,
});
const code = response.getResponseCode();
const body = JSON.parse(response.getContentText());
if (code >= 400) {
const e = body.error || {};
throw new Error(`${e.code}: ${e.message} (${e.request_id})`);
}
return body;
}
function analyzeDriveFile_(fileId, expect) {
const blob = DriveApp.getFileById(fileId).getBlob();
return call_("/analyze", {
method: "post",
// Un objeto con un Blob se envía como multipart/form-data.
payload: {
file: blob,
options: JSON.stringify({ expect, language: "es", metadata: { drive_file_id: fileId } }),
},
});
}
function value_(field) {
return field && field.value != null ? field.value : "";
}
function writeResult_(sheet, row, analysis) {
const fields = analysis.fields || {};
const verdict = analysis.verdict;
sheet.getRange(row, 3, 1, 5).setValues([[
verdict ? verdict.status : analysis.status,
verdict ? verdict.reasons.filter((r) => r.severity !== "info").map((r) => r.message).join("\n") : "",
value_(fields.document_number),
value_(fields.expiry_date),
analysis.id,
]]);
}
function analyzePending() {
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET);
const rows = sheet.getDataRange().getValues();
for (let i = 1; i < rows.length; i++) {
const [fileId, expect, verdict] = rows[i];
if (!fileId || verdict) continue;
try {
writeResult_(sheet, i + 1, analyzeDriveFile_(fileId, expect || undefined));
} catch (err) {
sheet.getRange(i + 1, 3, 1, 2).setValues([["error", err.message]]);
}
Utilities.sleep(200);
}
}
function refreshPending() {
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET);
const rows = sheet.getDataRange().getValues();
for (let i = 1; i < rows.length; i++) {
const status = rows[i][2];
const id = rows[i][6];
if (!id || (status !== "queued" && status !== "processing")) continue;
try {
writeResult_(sheet, i + 1, call_("/analyses/" + encodeURIComponent(id), { method: "get" }));
} catch (err) {
sheet.getRange(i + 1, 4).setValue(err.message);
}
}
}Guarda, recarga la hoja y usa el menú Constaia → Analizar filas pendientes. La primera vez Google te pedirá autorizar el acceso a Drive, a la hoja y a servicios externos.
Revisa los resultados
La columna C recibe Válido, No válido o
Revisar como texto (valid, invalid, review) y la D los motivos que no son
informativos, en el idioma de language. Con formato condicional sobre la columna C puedes colorear las filas.
Los campos disponibles por tipo están en GET /v1/document-types (por
ejemplo, es_dni tiene document_number, birth_date, expiry_date…).
Análisis largos y resultados asíncronos
Si un análisis no termina en 30 s (PDFs de muchas páginas), la API responde 202 y la columna C queda en
queued o processing con el ID de análisis en la G. Constaia → Actualizar análisis en curso consulta
GET /v1/analyses/{id} para esas filas. Para hacerlo automáticamente, crea un activador por tiempo
(Activadores → Añadir activador → refreshPending → Según tiempo, por ejemplo cada 5 minutos).
Por qué no recibir webhooks con doPost
Una aplicación web de Apps Script (doPost(e)) no puede leer las cabeceras de la petición, así que no puede
verificar la firma de los webhooks (webhook-id, webhook-timestamp, webhook-signature).
Además, Apps Script responde a los POST con una redirección y Constaia no sigue redirecciones: la entrega se
consideraría fallida y se reintentaría. Si aun así la usas, trata el cuerpo solo como un aviso: toma data.id,
comprueba que empieza por an_ y vuelve a leer el análisis con GET /v1/analyses/{id} y tu clave, que es la
fuente de verdad, y haz que la escritura en la hoja sea idempotente. Para hojas de cálculo, el activador por tiempo
de arriba es más sencillo y fiable.
Probar en modo test
Con una clave ck_test_… no se consumen créditos y la respuesta depende del nombre del fichero en Drive (el
nombre del Blob). Sube cualquier imagen real a Drive con estos nombres:
| Fichero | Tipo esperado | Columna C |
|---|---|---|
dni_valid.jpg | es_dni | Válido número 12345678Z, caducidad 2031-03-12 |
dni_expired.jpg | es_dni | No válido motivo de caducidad (not_expired, severidad error): "Caducado el 15/06/2020." |
blurry.jpg | es_dni | Revisar motivo de calidad insuficiente (low_quality) |
También puedes renombrar el Blob en el código con blob.setName("dni_valid.jpg"). Más escenarios en
Modo test.
Seguridad
- La clave va en Script Properties, nunca en el código ni en una celda de la hoja. Quien pueda editar el proyecto de Apps Script puede ver las propiedades: comparte la hoja en modo lectura con quien no deba ver la clave.
- Usa la clave de test mientras pruebas y cambia la propiedad a
ck_live_…cuando pases a producción. - La hoja guarda los datos extraídos. Escribe solo los campos que necesites y limita quién tiene acceso.
Constaia borra el fichero al terminar con
storage: "none"(por defecto); más en Almacenamiento y privacidad.
Límites
- Constaia: 20 MB por fichero, PDFs de hasta 30 páginas en síncrono (200 con
async: true) y 2 peticiones por segundo por clave en el plan gratuito (10 en el de pago). El script procesa las filas una a una con una pausa, así que no llega al límite; ante un 429 la respuesta incluyeRetry-After. Ver Límites de uso. - Apps Script tiene cuotas propias: tiempo máximo por ejecución y número diario de llamadas con UrlFetchApp, que dependen de tu tipo de cuenta. Con muchas filas, procesa por tandas o usa lotes.
Siguientes pasos
Power Automate
Valida documentos de SharePoint u OneDrive con la acción HTTP de Power Automate, analiza la respuesta con Parse JSON y recibe eventos de Constaia.
Airtable
Valida los adjuntos de Airtable con una automatización "Run a script" que envía la URL del adjunto a Constaia y escribe el veredicto en el registro.