Cloudflare Worker que automatitza la paperassa de regularització administrativa per a Reus Refugi, una entitat sense ànim de lucre que acompanya persones migrades a regularitzar la seva situació a Espanya.
Estat: en producció des d'abril 2026. L'usen voluntaris no-tècnics diàriament. Aquest repo està obert per transparència i com a referència per a altres entitats del Tercer Sector que vulguin replicar fluxos similars.
Tota la paperassa de regularització passa per quatre baules connectades:
- Tally captura les dades inicials del sol·licitant en una primera trobada, sense haver d'exposar Venus (la base d'Airtable) a persones sense preparació tècnica ni accés operatiu. El formulari de Tally escriu directament a Airtable.
- Venus (Airtable) actua com a core de dades. És la font de veritat única: cada cas, document, factor de vulnerabilitat i relació familiar viu aquí.
- Venus genera l'informe de vulnerabilitat amb un clic i, a la mateixa fila, prepara un esborrany de Gmail amb el PDF adjunt llest per enviar al usuari.
- Un userscript de Tampermonkey automatitza la pujada de dades de Venus
cap a Mercurio, omplint els ~144 camps del formulari telemàtic EX-31/EX-32
- pujada de arxius perquè el voluntari només hagi de revisar i signar amb AutoFirma.
Tally → Venus (Airtable) ─┬─→ Informe de vulnerabilitat (PDF) + draft Gmail
├─→ Dossier EX-31 / EX-32 (PDF)
└─→ Mercurio (auto-fill via Tampermonkey)
POST /anexo2 — el voluntari prem un botó a la taula Informes de Vulnerabilitat
d'Airtable i, en pocs segons, es genera un certificat de vulnerabilitat
oficial (Anexo II del procediment EX-32) signat amb les dades de l'entitat
acreditada (RECEX), llest per adjuntar a la sol·licitud i per enviar com a
esborrany de Gmail amb un altre clic.
Detall tècnic: el template és el PDF buit oficial del Ministeri (descarregat
d'inclusion.gob.es) sense AcroForm; pintem totes les dades — incloent el
segell i la firma del representant — a coordenades absolutes a runtime.
El PDF lliurat no és editable i renderitza idèntic a tot arreu (Adobe, Chrome,
Drive, Samsung Notes…). Vegeu src/anexo2.ts i
src/anexo2-coords.ts.
Mercurio és la plataforma oficial per presentar EX-31 i EX-32 telemàticament: ~144 camps de formulari que els voluntaris havien de copiar-pegar manualment des d'Airtable cas per cas.
Un userscript de Tampermonkey servit per GET /mercurio.user.js:
- Detecta quan ets a la pantalla d'EX-31 o EX-32 a Mercurio.
- Mostra un panell flotant amb cercador de casos d'Airtable Venus.
- Quan cliques un cas, omple els 144 camps automàticament — incloent les cascades AJAX (província → municipi → localitat), radio buttons dinàmics, checkboxes condicionals i el bloc reagrupant (DA 21ª) si el cas té un referent familiar.
- No submiteja. El voluntari revisa les dades i prem "Firmar y registrar" amb AutoFirma.
És beta: codis confirmats per a DA 21ª Laboral/Familiar/Vulnerabilitat; codis de DA 20ª (PI) encara s'estan validant amb casos reals. La pujada de documents adjunts (passaport, antecedents, etc.) està implementada.
Endpoints relacionats:
GET /mercurio.user.js— userscript autoinstal·lable (auto-update diari).GET /mercurio/cases?q=text— cerca casos a Airtable.GET /mercurio/payload?caso=recXXX— retorna els 144 camps mapats per a un cas.
Codi a src/mercurio/:
mapping.ts— Airtable case → payload Mercurio (143 camps + lògica DA 20ª/DA 21ª).catalogs.ts— codis estàtics del DOM Mercurio (sexe, província, parentesco…).userscriptCode.ts— template del userscript Tampermonkey.
També hi ha codi al Worker per a aquestes coses, però no són el focus del projecte ara mateix. Els documentem aquí perquè existeixen i funcionen:
POST /gmail-draft— proxy a Google Apps Script. Crea un esborrany a Gmail amb el PDF adjunt. Existeix perquè GAS retorna 302 que el navegador no pot seguir; aquest endpoint fa la crida server-side.
Les dades inicials del cas (la primera trobada amb el sol·licitant) es capturen amb un formulari de Tally que els voluntaris fan servir com a portafoli d'entrada. El formulari escriu directament a la base Venus d'Airtable via la integració nativa Tally → Airtable.
A partir d'aquí, els voluntaris enriqueixen el cas dins d'Airtable (documents, situació familiar, factors de vulnerabilitat, etc.) i el Worker treballa sobre aquestes dades.
┌──────────────────┐ ┌──────────────────┐ ┌────────────────────────┐
│ Tally (entrada) │ │ Airtable (Venus) │ │ Mercurio (gov.es) │
└────────┬─────────┘ └────────┬─────────┘ └──────────┬─────────────┘
│ webhook │ REST API │ userscript injecta DOM
└────────────►──────────┘ │
│ │
┌─────────▼─────────────────────▼──────────┐
│ Cloudflare Worker (aquest repo) │
│ - /generate dossier EX-31/EX-32 │
│ - /anexo2 informe vulnerabilitat │
│ - /mercurio/* cerca + payload + JS │
│ - /gmail-draft proxy a GAS │
│ secrets: AIRTABLE_TOKEN, SHARED_SECRET │
│ assets: plantilles PDF oficials │
└──────────────────────────────────────────┘
- Node.js 20+
- Compte de Cloudflare (el pla gratuït cobre el volum d'una entitat petita).
- Personal Access Token d'Airtable amb scopes
data.records:readidata.records:writelimitats a la teva base.
# 1. Clona i instal·la
git clone https://github.com/andratwiro/reus-refugi-pdf-worker
cd reus-refugi-pdf-worker
npm install
# 2. Login a Cloudflare (obre el navegador)
npx wrangler login
# 3. Defineix els secrets bàsics
npx wrangler secret put AIRTABLE_TOKEN # Personal Access Token
npx wrangler secret put SHARED_SECRET # openssl rand -hex 32
# 4. Defineix el presentador (representant acreditat de la teva entitat)
# — només cal si vols usar la integració Mercurio
npx wrangler secret put PRESENTADOR_NOMBRE # "COGNOM1 COGNOM2 NOM" majúscules
npx wrangler secret put PRESENTADOR_NIE
npx wrangler secret put PRESENTADOR_TIPODOC # NF (NIE) | NV (DNI) | PA (passaport)
npx wrangler secret put PRESENTADOR_MOBIL
npx wrangler secret put PRESENTADOR_EMAIL
# 5. Defineix les dades PII del representant legal de l'entitat
# — necessari per omplir les seccions 2/3 dels EX-31/EX-32
npx wrangler secret put REPRESENTANT_NOM # "NOM COGNOM1 COGNOM2" majúscules
npx wrangler secret put REPRESENTANT_DNI # DNI/NIE del representant legal
npx wrangler secret put REPRESENTANT_TITOL # ex. "PRESIDENTE", "SECRETARIO"
# 6. Defineix els secrets de Gmail (opcional, només si uses /gmail-draft)
npx wrangler secret put GAS_WEBAPP_URL
npx wrangler secret put GAS_SHARED_SECRET
# 7. Edita wrangler.toml amb els IDs de la teva base d'Airtable
# (AIRTABLE_BASE_ID, CASOS_TABLE_ID, INFORMES_VULN_TABLE_ID, etc.)
# 8. Edita ENTITAT_REUS_REFUGI_BASE a src/mappings.ts amb les dades
# públiques de la teva entitat (nom, NIF, adreça, RECEX, tipusEntitat).
# 9. Posa segell + firma del representant a src/private/ i puja'ls a KV
# (gitignored — no han d'estar mai al repo públic; vegeu src/private/README.md)
cp /ruta/al/teu/segell.png src/private/entity-stamp.png
cp /ruta/a/la/teva/firma.png src/private/representative-signature.png
npx wrangler kv namespace create PRIVATE_BINARIES
# (descomenta el bloc [[kv_namespaces]] de wrangler.toml amb l'ID retornat)
npx wrangler kv key put --binding PRIVATE_BINARIES "entity-stamp" \
--path src/private/entity-stamp.png
npx wrangler kv key put --binding PRIVATE_BINARIES "representative-signature" \
--path src/private/representative-signature.png
# 10. Push a main → Cloudflare Workers Builds desplega automàticament.
git push origin maincurl https://<el-teu-worker>.workers.dev/
# → {"ok":true,"service":"reus-refugi-pdf-worker"}
curl https://<el-teu-worker>.workers.dev/mercurio.user.js | head -30
# → ha de retornar el userscript de Tampermonkey amb @match a MercurioPer a /generate: crea un camp Button a la taula Casos que dispari una
Automation tipus "Run script". Enganxa el codi de
airtable-automation.js i actualitza les dues
primeres línies amb el teu WORKER_URL i SHARED_SECRET.
Per a /anexo2: a una Dashboard d'Airtable afegeix una Scripting Extension
i enganxa el codi de
airtable-extension-anexo2.js (mateixes
dues primeres constants). El voluntari selecciona la fila de
Informes de Vulnerabilitat i clica Run.
Per a Mercurio: instal·la Tampermonkey al navegador, obre la URL
https://<el-teu-worker>.workers.dev/mercurio.user.js i Tampermonkey detectarà
el script automàticament.
src/
index.ts ← router principal (totes les rutes HTTP)
airtable.ts ← client Airtable (read, listRecords, uploadAttachment)
mappings.ts ← IDs de camps Airtable + dades públiques d'entitat
fillPdf.ts ← omplir dossier EX-31/EX-32 (decision tree per Via legal)
anexo2.ts ← omplir Informe de Vulnerabilitat (paint a coords absolutes)
anexo2-coords.ts ← coordenades hardcoded dels camps de l'Annex II
mercurio/
mapping.ts ← Airtable case → 144 camps Mercurio
catalogs.ts ← codis DOM (sexe, província, parentesco…)
userscriptCode.ts ← template del userscript Tampermonkey
private/ ← gitignored — segell + firma de l'entitat (vegeu README intern)
assets/ ← públicament accessible via env.ASSETS.fetch()
EX31_oficial.pdf, EX31_seccion5.pdf
EX32_oficial.pdf, EX32_seccion5.pdf
A2_certificado_vulnerabilidad.pdf ← versió oficial buida del Ministeri
scripts/
extract-anexo2-coords.ts ← regenera anexo2-coords.ts si canvies de template
optimize-airtable-pdfs.ts
mock/ ← fixtures de casos reals + sintètics per a tests
airtable-automation.js ← script per Automation /generate (botó Casos)
airtable-extension-anexo2.js ← script per Scripting Extension /anexo2 (Dashboard)
wrangler.toml ← config del Worker (vars públiques, assets binding, data rules)
L'esquema de Venus està publicat com a plantilla pública a Airtable Universe:
🔗 Venus — Plantilla regularització (RD 1155/2024)
Pots clonar-la al teu workspace amb un clic — t'estalvies recrear taules, camps, vistes i automatitzacions des de zero. Després només cal:
- Connectar el formulari de Tally (o el teu propi punt d'entrada) a la taula
Casos. - Substituir els IDs de base i taula a
wrangler.tomlpels de la teva còpia. - Configurar els secrets de l'entitat (
PRESENTADOR_*,REPRESENTANT_*).
-
El presentador (representant acreditat que signa les sol·licituds via Mercurio) es configura via els 5 secrets
PRESENTADOR_*. Cada entitat ha de fer-ho amb les seves pròpies dades — no hi ha valors per defecte al codi. -
El representant legal de l'entitat (qui signa els PDFs EX-31/EX-32) es configura via els 3 secrets
REPRESENTANT_*. Cap valor PII queda al codi. -
Les dades públiques de l'entitat (nom, NIF, domicili, telèfon i email del registre d'associacions, RECEX,
tipusEntitat) viuen aENTITAT_REUS_REFUGI_BASEasrc/mappings.ts— cada fork ha d'editar aquesta constant amb les dades de la seva entitat. PosatipusEntitat: "admin_publica"si ets una administració pública competent en assistència social, o"tercer_sector"si ets una entitat del Tercer Sector inscrita al RECEX. -
El segell de l'entitat i la firma manuscrita del representant que apareixen a la zona de signatura de l'Annex II viuen a un KV namespace privat de Cloudflare (no al repo, no al binding ASSETS). El directori
src/private/és la font local dels PNGs (gitignored) i només s'utilitza per pujar els bytes a KV un sol cop ambwrangler kv key put. El worker els llegeix a runtime via el bindingPRIVATE_BINARIES. Vegeusrc/private/README.mdper al setup pas a pas.🔒 Per què KV i no
assets/o un binding[[rules]] type="Data"?assets/s'exposa a la URL pública del worker — qualsevol amb la URL podria descarregar el segell i estampar-lo a documents falsos.[[rules]] type="Data"empotra els fitxers al bundle del worker però requereix que estiguin al disc al moment delwrangler deployo el build de Cloudflare Workers Builds. Com que els PNGs són gitignored, el clon de CF Builds no els tindria → build fail. KV té els bytes al servei privat Cloudflare i el worker els llegeix només via el binding — mai exposats per cap URL.
-
wrangler.tomlapunta a la base d'Airtable Venus de Reus Refugi (appWuXncpGWaFTR4M). Cada entitat té la seva pròpia base; cal canviar tots els IDs. -
L'esquema d'Airtable reflecteix el flux operatiu de Reus Refugi i s'ha anat construint segons les necessitats dels voluntaris. La plantilla pública és un punt de partida raonable, però potser voldràs adaptar-la.
Si treballes en una entitat similar i vols adaptar això, obre un issue —
mirarem d'extreure les peces reutilitzables (sobretot mercurio/catalogs.ts,
que conté codis del DOM Mercurio que són universals).
- Els secrets viuen només a Cloudflare; mai al repo.
/generate,/anexo2,/gmail-draft,/mercurio/casesi/mercurio/payloadexigeixenAuthorization: Bearer <SHARED_SECRET>.GET /mercurio.user.jsés obert (qualsevol pot baixar el userscript). ElSHARED_SECRETqueda embedded al JS servit; el threat model assumeix que l'entitat acreditada és de confiança i que Cloudflare té audit logs si calgués investigar abús.- L'Airtable token ha de tenir scope limitat a la base (no a totes les bases).
- No es loguegen dades personals — només IDs d'Airtable i mètriques.
MIT. Pots forkar, modificar i redistribuir lliurement — inclòs ús comercial — sempre que mantinguis l'avís de copyright. Sense garanties.
Construït per Reus Refugi amb assistència
de Claude Code. Si trobes alguna cosa útil aquí o vols col·laborar, escriu
a regularitzacio@reusrefugi.cat.