Saltearse al contenido

RGPD y Privacidad

Privacidad desde el diseño

qr3.app está diseñado desde cero para cumplir con el RGPD:

  • Seudonimización de IP — Las direcciones IP se convierten inmediatamente en seudónimos diarios UTC HMAC-SHA-256 en el Edge Worker. La IP original nunca se almacena; el seudónimo sigue siendo un dato personal.
  • Minimización de datos — Solo se recopilan los datos estrictamente necesarios para el funcionamiento.

API de Privacidad

Resumen de datos (Art. 15 del RGPD)

GET /v1/account/privacy — Devuelve un resumen de todos los datos almacenados.

Ventana de terminal
curl https://qr3.app/v1/account/privacy \
-H "Authorization: Bearer qr3_sk_..."

Response:

{
"data": {
"workspace_id": "ws_xxx",
"organization_id": "org_xxx",
"account": {
"email": "[email protected]",
"name": "Alex Example",
"locale": "en",
"created_at": "2026-01-15T09:00:00.000Z"
},
"stored_data": {
"qr_codes": 42,
"scan_records": 15000,
"dpp_scan_records": 3200,
"dashboard_activity_records": 0,
"dashboard_document_activity_records": 36,
"api_operation_records": 180,
"webhook_delivery_records": 640,
"api_keys": 2,
"webhooks": 1,
"dpp_passports": 12,
"dpp_documents": 30,
"files": 4,
"gs1_identifiers": 2,
"gs1_verification_requests": 1,
"code_comments": 5,
"audit_log_entries": 0,
"dpp_import_jobs": 1,
"workspace_members": 3,
"scan_quota_alerts": 1
},
"gdpr_rights": {
"access": "GET /v1/account/privacy",
"export": "GET /v1/account/export",
"erasure": "DELETE /v1/account",
"consents": "GET /v1/account/privacy/consents"
},
"data_processing": {
"ip_storage": "Purpose-separated HMAC-SHA-256 UTC-day IP pseudonym — pseudonymous personal data",
"scan_retention": "90 days by default",
"usage_retention": "90 UTC days",
"webhook_delivery_retention": "90 UTC days",
"pii_in_export": true,
"erasure": "DELETE /v1/account erases the person's account: organizations they own with all workspaces and data (unless another org admin takes them over), their memberships in other organizations, their login and their Stripe customer. A running subscription ends immediately, without refund.",
"backup_retention": "Erased rows stay restorable from the database backups (Cloudflare D1 Time Travel) for up to 30 days, then they are gone.",
"billing_records": "Invoices stay with Stripe for the statutory retention period (7 years, § 132 BAO)."
}
},
"meta": { "request_id": "req_xxx" }
}

stored_data cuenta las entradas almacenadas. scan_records son escaneos de códigos QR y dpp_scan_records, consultas a pasaportes de producto a través del resolver DPP público. Ambos contienen el seudónimo de IP.

dashboard_activity_records y dashboard_document_activity_records cuentan los días en que un miembro abrió el dashboard (dos tablas), api_operation_records los contadores diarios de solicitudes por clave de API, operación y resultado, y webhook_delivery_records los intentos de entrega de webhooks, incluidos los de webhooks eliminados. Ninguno almacena una dirección IP ni un User-Agent; el registro de entregas sí almacena el payload del evento y el primer kilobyte (1 KB) de la respuesta del receptor. Los cuatro se eliminan tras 90 días UTC (usage_retention, webhook_delivery_retention).

account indica la persona detrás de la solicitud — en el dashboard, la persona que ha iniciado sesión; en otro caso, la persona que creó la clave — con correo electrónico, nombre, idioma y fecha de creación; para una Publishable Key es null. Los demás contadores cubren cualquier otra tabla con datos de la cuenta: pasaportes de producto, documentos DPP, archivos, identificadores GS1, comentarios, entradas de auditoría, ejecuciones de importación, miembros del workspace y los avisos de cuota de la organización (scan_quota_alerts). Las filas con borrado lógico cuentan, porque siguen almacenadas. erasure, backup_retention y billing_records describen qué borra DELETE /v1/account y qué permanece durante un tiempo; pii_in_export es true porque la exportación indica la persona (correo electrónico, nombre); los seudónimos de IP nunca salen de la base de datos.


Exportación de datos (Art. 20 del RGPD — Portabilidad de datos)

GET /v1/account/export — Descarga todos los datos como un archivo JSON.

Ventana de terminal
curl https://qr3.app/v1/account/export \
-H "Authorization: Bearer qr3_sk_..." \
-o my-data.json

La exportación contiene:

  • Todos los códigos QR (incluidos los eliminados)
  • Estadísticas de escaneo agregadas (sin hashes de IP sin procesar)
  • Consultas DPP agregadas por pasaporte de producto (dpp_scan_aggregates, igualmente sin hashes de IP)
  • Estadísticas de uso como agregados (dashboard_activity_aggregates, api_operation_aggregates): días activos y número de usuarios activos por canal del dashboard, contadores de solicitudes por operación y resultado, sin ID de usuarios ni de claves
  • Intentos de entrega de webhooks por webhook y tipo de evento (webhook_delivery_aggregates), sin payload ni respuesta
  • La persona detrás de la solicitud (account), la organización (organization; dirección de facturación y de notificaciones solo para administradores de la organización) y el workspace (workspace)
  • Pasaportes de producto, metadatos de documentos DPP y de archivos (no los archivos en sí), identificadores GS1, solicitudes de verificación de estos y ejecuciones de importación (dpp_passports, dpp_documents, files, gs1_identifiers, gs1_verification_requests, dpp_import_jobs)
  • Comentarios sin campos de autor (code_comments), entradas de auditoría como agregados (audit_log_aggregates) y miembros por rol (member_aggregates)
  • Claves de API sin ID ni hash (api_keys), webhooks sin URL ni secreto (webhooks) y los avisos de cuota de la organización (scan_quota_alerts)
  • Marcas de tiempo de creación y modificación

Las Publishable Keys (qr3_pk_) reciben 403: el archivo contiene las direcciones de destino y las notas de todos los códigos.


Eliminar cuenta (Art. 17 del RGPD — Derecho al olvido)

DELETE /v1/account — Borra la cuenta de la persona detrás de la solicitud, de inmediato y de forma irreversible: en el dashboard, la persona que ha iniciado sesión; en otro caso, la persona que creó la clave. Una solicitud de borrado por correo electrónico lleva al mismo borrado: el operador elimina el inicio de sesión en Clerk y, a continuación, qr3 borra la cuenta.

Ventana de terminal
curl -X DELETE https://qr3.app/v1/account \
-H "Authorization: Bearer qr3_sk_..."

Qué ocurre:

  1. Las organizaciones que pertenecen a la persona se borran con todos sus workspaces, códigos, pasaportes de producto, archivos, estadísticas y registros, incluidos sus archivos en el almacenamiento. Si una de esas organizaciones tiene otro administrador de la organización activo, en su lugar la asume el más antiguo, con sus datos y su suscripción.
  2. Suscripción: una suscripción en curso finaliza de inmediato, sin reembolso proporcional y sin factura final; después se borra el cliente de Stripe. Las facturas permanecen en Stripe durante el plazo legal de conservación (7 años, § 132 BAO).
  3. Equipos: las membresías de la persona en otras organizaciones terminan; esos equipos conservan sus datos. Sus comentarios ya no indican autor, sus entradas de auditoría pierden el seudónimo de IP y el user agent, y las claves de API que creó allí se revocan.
  4. La persona: el inicio de sesión (Clerk) y el registro de usuario se borran.

La respuesta es 200 con status: "completed" y los contadores organizations_deleted, organizations_transferred, memberships_removed y login_deleted. En organizaciones muy grandes no basta una solicitud: la respuesta es entonces 202 con status: "in_progress", y el borrado concluye automáticamente en pocas horas.

Requiere una Secret Key o Test Key con permiso de escritura, o una sesión en el dashboard (cualquier rol, porque se trata de la propia cuenta). Las Publishable Keys y las claves de solo lectura reciben 403; una clave sin persona detrás (created_by = 'system') recibe 409. 503 significa que al despliegue le falta un secreto que el borrado necesita; no se ha borrado nada.


Gestionar consentimientos

GET /v1/account/privacy/consents — Obtener los consentimientos actuales.

POST /v1/account/privacy/consents — Actualizar los consentimientos.

Ventana de terminal
# Obtener los consentimientos actuales
curl https://qr3.app/v1/account/privacy/consents \
-H "Authorization: Bearer qr3_sk_..."
# Desactivar los correos de marketing
curl -X POST https://qr3.app/v1/account/privacy/consents \
-H "Authorization: Bearer qr3_sk_..." \
-H "Content-Type: application/json" \
-d '{ "marketing_emails": false, "analytics": true, "product_updates": true }'

Campos de consentimiento:

CampoPredeterminadoDescripción
marketing_emailsfalseBoletines informativos y correos electrónicos promocionales
analyticstrueEstadísticas de uso agregadas
product_updatestrueNovedades de productos y registros de cambios

Cambiar los consentimientos requiere permisos de escritura: el rol viewer y las Publishable Keys reciben 403.


Implementación técnica del RGPD

Seudonimización de IP (Art. 25 del RGPD)

Solicitud HTTP → Cloudflare Edge Worker
↓
Cabecera CF-Connecting-IP → HMAC-SHA-256(IP_HASH_SALT, finalidad, día UTC, IP)
↓
ip_hash (seudónimo diario) → base de datos D1
↓
La IP original nunca se almacena

IP_HASH_SALT es un secreto maestro de larga duración; la finalidad y el día UTC derivan automáticamente seudónimos diarios separados. Los seudónimos de distintos días UTC no son directamente iguales, pero quien posee el secreto puede recalcular un candidato de IP conocido para un día concreto. No se usa sal diaria en KV ni rotación manual diaria.

Retención de datos (automatizada)

PlanRetención
Todos los planes90 días

Las estadísticas de uso (actividad del dashboard, contadores de uso de la API) y los registros de entrega de webhooks también se conservan 90 días UTC: el día UTC en curso y los 89 anteriores. La misma tarea cron diaria elimina las filas más antiguas (purgeOldDashboardActivity, purgeOldApiOperationUsage, purgeOldWebhookDeliveries).

DPA de subencargados del tratamiento

Cloudflare, Inc. procesa los datos como encargado del tratamiento. El DPA está disponible en cloudflare.com/cloudflare-customer-dpa.

Cloudflare procesa los datos con una indicación de ubicación orientada a la región de la UE (entre otros, Fráncfort); actualmente no se ha contratado una jurisdicción de la UE garantizada por contrato. Dado que Cloudflare, Inc. tiene su sede en los EE. UU., se aplican las Cláusulas Contractuales Estándar (SCC) de conformidad con el Art. 46 del RGPD como base para la transferencia.


Contacto