Saltearse al contenido

Changelog

Changelog

Destacados seleccionados de los últimos lanzamientos. Para ver las versiones completas de la API y la política de cambios disruptivos, consulta el Versionado de API y política LTS.

Cambios detallados en endpoints individuales: Especificación OpenAPI y referencia interactiva de la API.


2026-10 · Colores en el Dashboard

  • Nuevo: la página de detalles de un código tiene la tarjeta Colores: primer plano y fondo como valor hexadecimal, fondo transparente y vista previa a través de la ruta de imagen real. La comprobación es la de PATCH /v1/codes/{id}: los pares críticos necesitan una confirmación y los pares por debajo de 1,5:1 no se pueden guardar.
  • Restablecer a negro sobre blanco vuelve a entregar en los cuatro formatos los mismos bytes que sin colores. Los códigos sin colores no cambian.
  • Los códigos transparentes aparecen sobre un tablero de ajedrez en todas las vistas previas del Dashboard. Detalles: Colores en el Dashboard.

2026-10 · Colores en PDF y EPS

  • Nuevo: qr.pdf y qr.eps dibujan fg y bg, como parámetros y como colores guardados. El negro y cualquier gris se escriben como escala de grises (solo placa negra), cualquier otro color como CMYK en porcentajes enteros, por ejemplo 1F4E79 como C74 M36 Y0 K53.
  • Fondo como en SVG: Tan pronto como se selecciona un color, se coloca una superficie opaca detrás del código y su zona de silencio, blanca o bg. bg=transparent no dibuja ninguna. El título permanece negro, la placa del logotipo blanca.
  • Sin cambios sin colores: Sin fg/bg y sin colores guardados, ambos formatos entregan los mismos bytes que antes. El PDF de un pasaporte de producto permanece sin color.
  • Límites: sin perfil ICC, sin PDF/X. Detalles: Colores en la impresión.

2026-09 · Códigos estáticos sin caché de redirección, eliminación simultánea

  • Modificado: Los códigos estáticos ya no se encuentran en la caché de redirección. Una llamada a su enlace corto (redirect_url) siempre lee el estado actual. Por lo tanto, después de DELETE /v1/account, un código estático ya no redirige en la siguiente llamada, mientras que antes lo hacía hasta por 24 horas. Un cambio de plan también tiene efecto inmediato en los códigos estáticos. La eliminación de la cuenta limpia los códigos dinámicos como antes. Los códigos estáticos impresos contienen su destino directamente y no se ven afectados.
  • Corregido: Dos peticiones DELETE /v1/codes/:id simultáneas al mismo código devuelven una vez 200 y otra vez 404. El webhook qr.deleted se envía exactamente una vez, antes dos veces.
  • Nuevo: POST y DELETE /v1/codes/:id/logo responden con 409 (errors/conflict) si otra solicitud ha modificado simultáneamente el logotipo del mismo código, y entonces no cambian nada. Antes, un logotipo subido podía permanecer guardado sin usarse. Dos peticiones DELETE /v1/codes/:id/logo simultáneas devuelven ambas 200. Detalles: Logotipo.

2026-09 · TypeScript-SDK 1.2.0

  • Novedades en @qr3/sdk 1.2.0: client.codes.update() acepta appearance y devuelve los resultados de la prueba de contraste como issues en el resultado; si no hay hallazgos, el campo no se incluye. client.codes.imageUrl() acepta fg, bg y ecc. Cada código que devuelve la API (get, list, create, update, batchCreate) incluye title y appearance.
  • Actualizar: npm install @qr3/sdk@latest. Próximamente en Python, Go y PHP. Detalles: SDKs y CLI y Guardar colores en el código.

2026-09 · La fecha de expiración modificada se aplica en aproximadamente un minuto

  • Corregido: Un PATCH en expires_at ahora limpia la caché de redirección del código. Por lo tanto, la nueva fecha de expiración se aplica en aproximadamente un minuto en lugar de hasta 24 horas después. Anteriormente, una expiración eliminada o pospuesta seguía devolviendo 410 durante un máximo de 24 horas, y una expiración establecida para el momento actual permitía que la redirección continuara funcionando durante un máximo de 24 horas.
  • Corregido: Un código creado con expires_at ahora expira a tiempo incluso si la expiración se encuentra dentro de las primeras 24 horas.
  • Corregido: Si se elimina un código mientras se está subiendo o eliminando su logotipo, POST y DELETE /v1/codes/:id/logo responden con 404 y no modifican el código eliminado, al igual que ya lo hace PATCH.
  • Corregido: Un código estático cuyo enlace corto (redirect_url) haya sido visitado también se almacena en la caché de redirección. Modificarlo, pausarlo o eliminarlo ahora también limpia la caché, no solo en el caso de los códigos dinámicos.

2026-09 · Guardar colores en el código

  • Nuevo: PATCH /v1/codes/:id acepta appearance con foreground_color y background_color (#RRGGBB, el fondo también transparent). qr.svg y qr.png dibujan los colores guardados sin ningún parámetro; un parámetro de consulta como ?fg=000000 sigue teniendo prioridad. PDF y EPS dibujarán los colores en una versión posterior.
  • Prueba de contraste: Un par por debajo de 1,5:1 da como resultado 422. Los pares más débiles se guardan y se notifican en meta.issues como warning o critical; un fondo transparente siempre es critical, nunca se bloquea.
  • Fusión: Los campos omitidos conservan su valor guardado, null restablece. Los cambios simultáneos en el mismo código ya no se sobrescriben entre sí.
  • En cada respuesta de código: appearance está presente en cada respuesta de código y en los webhooks qr.created y qr.updated. POST, el lote y la importación lo rechazan con 422.
  • Sin cambios para los códigos existentes: Sin colores guardados, cada código devuelve los mismos bytes que antes. Detalles: Guardar colores en el código.

2026-09 · Colores y corrección de errores como parámetros de las rutas de imagen

  • Nuevo: Las cuatro rutas de imagen aceptan fg (color de primer plano), bg (color de fondo o transparent) y ecc (corrección de errores L, M, Q, H). SVG y PNG dibujan los colores; PDF y EPS los aceptan, pero no los dibujarán hasta una versión posterior. ecc se aplica en los cuatro formatos, un logotipo sigue forzando H.
  • Transparente: bg=transparent devuelve un SVG sin fondo y un PNG con canal alfa real. El fondo debe ser claro y dejar libre la zona de silencio.
  • Nunca es un error: Los valores no válidos se ignoran; la imagen se entrega exactamente igual que sin parámetros.
  • Caché: Cada parámetro efectivo devuelve Cache-Control: public, max-age=300 en lugar de las 24 horas de la imagen estándar.
  • Sin cambios de comportamiento sin parámetros: Cualquier código existente devuelve los mismos bytes que antes en los cuatro formatos.
  • Disponible en todos los planes. Detalles: Colores y corrección de errores.

2026-09 · PDF y EPS ahora también incrustan el logotipo

  • Ampliación: qr.pdf y qr.eps ahora incrustan un logotipo configurado de la misma manera que qr.svg y qr.png (ver más abajo), como una imagen incrustada (PDF a través de un Image XObject, EPS a través de un diccionario de imágenes en PostScript, allí solo con %%LanguageLevel: 3), centrado en la misma área, con el mismo aumento de la corrección de errores a H. Anteriormente, ambos formatos no incluían el logotipo; esto ya se ha solucionado.
  • Caché: Con un logotipo configurado, qr.pdf y qr.eps ahora también devuelven Cache-Control: public, max-age=300 en lugar de las 24 horas habituales, exactamente igual que SVG/PNG.
  • Alternativa: Si la propia imagen del logotipo no se puede procesar (por ejemplo, un objeto guardado dañado), la corrección de errores permanece en H, pero el área reservada se queda vacía en lugar de mostrar una imagen; nunca se produce un error de servidor.
  • Sin cambios de comportamiento sin logotipo: Los códigos sin logotipo siguen entregando qr.pdf/qr.eps de forma idéntica en bytes, con la caché de 24 horas sin cambios.
  • Detalles e instrucciones de impresión: Logo en el código QR.

2026-09 · Logo en el código QR — Dashboard y API

  • Nuevo: Un código ahora puede llevar un logo en el centro. En el Dashboard, la página de detalles de un código crea una tarjeta de logo propia: Seleccionar logo, al añadir por primera vez o al eliminar, confirmar una advertencia con una casilla de verificación obligatoria (ambas acciones cambian el patrón de puntos), luego Subir logo/Eliminar logo. Un Reemplazo no requiere confirmación; solo cambia la imagen, no el patrón. El rol viewer ve el estado y la vista previa, pero ninguna de las tres acciones.
  • API: POST /v1/codes/{id}/logo (campo multipart file; PNG, JPEG o WebP, máximo 1 MB, se rechaza SVG) crea un logo o lo reemplaza; DELETE /v1/codes/{id}/logo lo elimina de nuevo y es idempotente. Una imagen demasiado grande devuelve 413, un formato no compatible 422, el rol viewer 403.
  • Renderizado: qr.svg y qr.png incrustan el logo como píxeles reales (512 × 512, normalizado) y para ello elevan la corrección de errores a H. Por lo tanto, añadir y eliminar cambian el patrón de puntos (M ↔ H), pero un reemplazo no. qr.pdf y qr.eps no incrustan ningún logo y permanecen sin cambios. Un código ya impreso sigue funcionando en cualquier caso, porque el destino codificado no depende del logo; solo tiene que volver a imprimir quien también quiera ver la (nueva) apariencia del logo en el material, y en ese caso no debe mezclar archivos de impresión antiguos y nuevos.
  • Caché: Con un logo establecido, qr.svg y qr.png devuelven Cache-Control: public, max-age=300 en lugar de las 24 horas habituales. Detalles, límites e instrucciones de impresión: Logo en el código QR.

2026-08 · operationId para todas las 75 operaciones, y una guía de cuándo usar para agentes

  • Adición: Todas las 75 operaciones de la especificación OpenAPI ahora llevan un operationId — listCodes, createCode, getCodeStats, archiveWorkspace, etc. Hasta ahora, este campo faltaba por completo, por lo que cada generador tenía que derivar el nombre del método a partir del método HTTP y la ruta (postV1Codes). Dichos nombres dependen de la ruta y cambian con cada reestructuración de la misma. El formato del nombre es el mismo en los 75 lugares: list/get/create/update/replace/delete, o de lo contrario el verbo de la acción de negocio (validateDpp, registerGs1Identifier, pingWebhook). El verbo sigue la lógica de negocio, no el método HTTP — archiveWorkspace es un DELETE, importDpps es un POST.
  • Impacto en clientes autogenerados: Quienes generen su SDK a partir de la especificación obtendrán métodos renombrados (postV1Codes → createCode) en la próxima ejecución. Este es el propósito del cambio, pero se trata de un renombrado en código ajeno; por eso se menciona aquí explícitamente. Las rutas, parámetros, formatos de respuesta y códigos de estado no han cambiado; los SDK oficiales, la CLI y el servidor MCP no se ven afectados.
  • Inicio para agentes: https://qr3.app/llms.txt tiene una sección ## When to use qr3.app — seis tareas en lugar de seis funciones, además de la frase que indica para qué qr3.app no es la herramienta adecuada. El servidor MCP ahora proporciona la misma información en el saludo inicial: initialize contra https://mcp.qr3.app/mcp responde con un campo instructions (antes: once herramientas sin ninguna clasificación). También se incluye qué llamadas necesitan una clave API — initialize y tools/list no, pero cada tools/call sí.
  • Correcciones en llms.txt: Se contrastaron cuatro datos con producción y no resultaron válidos: (1) el SDK de Python qr3app no está publicado en PyPI — la línea pip install se ha eliminado sin reemplazo; (2) Accept: text/markdown se aplica a la página de inicio y al blog, no a todas las páginas renderizadas en el servidor; (3) la dirección de contacto es [email protected], también en el JSON-LD de la página de inicio; (4) /de/security/ es una redirección a un ancla, no una página propia. Nuevo enlace: docs.qr3.app/de/skills/.
  • Aseguramiento: Tres aserciones en openapi-spec.test.ts — exhaustividad, unicidad, lowerCamelCase —, cada una verificada mediante una mutación. Otras nueve pruebas fijan los cuatro datos corregidos para evitar que vuelvan a ocurrir.
  • Limitación conocida: Dos de las 75 operaciones están descritas en la especificación, pero responden con 404 (GET /v1/codes/{id}/stats y GET /v1/account). Se les ha asignado un nombre aquí y lo perderán de nuevo tan pronto como se decida si se construirán o se eliminarán.

2026-08 · Las reducciones de tarifa y cancelaciones ahora también afectan a los límites

  • Corrección: El webhook de Stripe solo escribía el nombre del plan al cambiar de plan o cancelar, no las cuatro columnas de límite de la organización (max_workspaces, max_members, max_dynamic_codes, max_scans_per_month). Dado que la aplicación de límites toma el máximo entre el valor guardado y la línea de base del plan, una organización con plan reducido o cancelado conservaba sus límites anteriores más altos; la reducción de tarifa no tenía efecto. Ahora, el webhook establece las columnas en la línea de base de la nueva tarifa con cada cambio real de plan, y en la línea de base gratuita en caso de cancelación, tal como ya lo hace la administración de control.
  • Comportamiento: Las actualizaciones de rutina de la suscripción (renovación, método de pago, prorrateo) siguen sin afectar a los límites; los límites más altos otorgados de forma individual se mantienen sin cambios. Solo un cambio real de plan los restablece a la línea de base de la nueva tarifa.
  • Impacto: Sin cambios en la API ni en el formato de respuesta. Las organizaciones cuya reducción de tarifa se realizó antes de esta corrección conservarán los valores antiguos hasta que se aplique el próximo cambio de plan o el soporte los ajuste.

2026-08 · Cursor keyset ahora en todos los endpoints de lista

  • Corrección: La corrección del cursor para GET /v1/codes y GET /v1/dpp (ver más abajo) se ha implementado ahora en todas las demás listas paginadas por cursor: GET /v1/qr-codes/:id/comments, /v1/workspaces, /v1/gs1/identifiers, /v1/members, /v1/audit-logs, /v1/admin/orgs, /v1/admin/users y /v1/webhooks/:id/deliveries. Todas paginaban únicamente a través de created_at; las filas con marcas de tiempo idénticas (entradas de auditoría de una operación por lotes, reintentos de webhook, importaciones de miembros) podían perderse en la página siguiente. La clave de ordenación ahora es la tupla (created_at, id) en todas partes.
  • Cambio en la API: En estas listas, meta.pagination.next_cursor es a partir de ahora también un valor opaco (base64url) en lugar de una marca de tiempo simple; los cursores ilegibles devuelven 400, y los antiguos cursores de marca de tiempo se seguirán aceptando de forma transitoria. Excepción GET /v1/webhooks/:id/deliveries: allí next_cursor sigue siendo el ID de la última entrega (ID desconocido → primera página). Los clientes que devuelven next_cursor sin cambios — Dashboard, CLI, SDKs, MCP — no necesitan cambiar nada.
  • Impacto: No se requiere migración. Si has echado en falta entradas al paginar en alguna de estas listas (por ejemplo, el registro de auditoría o el registro de entregas en el Dashboard): nunca desaparecieron; las listas las muestran por completo a partir de ahora.

2026-08 · Las marcas de tiempo vuelven a ser conformes con OpenAPI tras la edición y eliminación

  • Corrección: Tras PATCH /v1/codes/{id}, updated_at se devolvía en formato SQLite sin zona horaria (2026-08-17 09:00:00), aunque la especificación de OpenAPI promete format: date-time y la creación (POST) proporciona la marca de tiempo ISO (2026-08-17T09:00:00.000Z). Lo mismo se aplicaba a deleted_at/updated_at en el soft-delete, así como a las rutas de edición y eliminación de claves de API, organizaciones, workspaces, miembros, comentarios y webhooks, además de a last_used_at de claves de API y last_triggered_at de webhooks. Todas las rutas de escritura registran ahora en formato ISO 8601 (UTC, T y Z).
  • Impacto: Los clientes que analizan updated_at con new Date(...) (SDKs, CLI, Dashboard) leían el formato con espacio como hora local — la CLI mostraba la hora desplazada por el desfase local en los códigos editados una vez (Viena: −2 h). Esto se ha solucionado. Además, una migración de datos normaliza los valores ya almacenados en el formato antiguo a ISO, para que la ordenación y las comparaciones en conjuntos de datos mixtos sean correctas. Sin cambios en los nombres de los campos ni en la estructura de la respuesta.
  • Contexto: La misma clase de error que las dos correcciones de abajo (expiración de claves de API, límite de reescaneo): datetime('now') de SQLite escribe YYYY-MM-DD HH:MM:SS, mientras que todos los demás escritores utilizan ISO 8601. Un test de protección en el código fuente evitará que esto vuelva a ocurrir en el futuro.

2026-08 · La paginación de listas ya no pierde códigos batch

  • Corrección: GET /v1/codes y GET /v1/dpp paginaban únicamente a través de created_at. Sin embargo, los códigos de POST /v1/codes/batch, POST /v1/dpp/batch y de la importación CSV/XLSX comparten una sola marca de tiempo; tan pronto como un lote era mayor que limit (por defecto 20), la segunda página ya no entregaba las filas restantes de esa misma marca de tiempo. Los códigos existían y se podía acceder a ellos a través de GET /v1/codes/:id, pero nunca aparecían en la lista (Dashboard, CLI qr3 list, SDKs, MCP). El cursor ahora es un Keyset sobre (created_at, id).
  • Cambio en la API: meta.pagination.next_cursor es a partir de ahora un valor opaco (base64url) en lugar de una marca de tiempo simple. Quienes devuelvan el cursor sin cambios como ?cursor= — tal como lo hacen el Dashboard, la CLI, todos los SDKs y el servidor MCP — no tienen que cambiar nada. Los antiguos cursores de marca de tiempo se seguirán aceptando de forma transitoria; los cursores ilegibles ahora devuelven 400 en lugar de entregar silenciosamente la primera página.
  • Impacto: Si después de una importación por lotes veías menos códigos en la lista de los que se habían creado: los códigos nunca desaparecieron; la lista ahora los muestra por completo. No se requiere migración.

2026-08 · Los re-escaneos de seguridad vuelven a ejecutarse cada 24 horas

  • Corrección: El re-escaneo periódico de las URLs de destino y los enlaces de la página de destino (Google Web Risk) omitía códigos cuyo último escaneo se encontraba en el mismo día calendario que el límite de 24 horas; dependiendo de la hora, el re-escaneo se retrasaba hasta un día más. El límite ahora se calcula en el mismo formato ISO en el que se almacenan las marcas de tiempo de escaneo.
  • Impacto: Una URL de destino que sea clasificada como no segura después del último escaneo volverá a provocar la pausa automática del código dentro de la ventana documentada de 24 horas. Sin cambios en la API ni en el formato de respuesta.

2026-08 · Las claves de API expiran en el momento exacto de su expiración

  • Corrección: Una clave de API cuyo expires_at coincidía con el mismo día se seguía aceptando hasta la medianoche UTC. Ahora, la expiración se compara como una marca de tiempo en lugar de como una cadena de texto; una clave expirada devuelve inmediatamente 401.
  • Contexto: expires_at se almacena como una marca de tiempo ISO (2026-08-14T09:00:00Z), mientras que el lado de la comparación proporcionaba el formato con espacio (2026-08-14 09:00:00). Por lo tanto, la comparación directa de cadenas de texto solo funcionaba correctamente si la fecha ya era diferente.
  • Impacto: No se requiere migración, el formato de respuesta de GET /v1/api-keys permanece sin cambios. Los valores de expiración ilegibles ahora se consideran expirados en lugar de válidos.

2026-08 · Referencia de la API: Gestión de inquilinos documentada

  • OpenAPI: La especificación — y con ella la referencia interactiva — ahora documenta organizaciones (incl. GET /v1/organizations/usage), workspaces, miembros y roles y registros de auditoría.
  • Billing: El resumen de tarifas (GET /v1/billing/plans) es público; el checkout (POST /v1/billing/checkout) y el portal de clientes de Stripe (GET /v1/billing/portal) están designados como endpoints para administradores de la organización.
  • Exportación de escaneos: Las estadísticas de escaneo (GET /v1/codes/{id}/scans) y la exportación de datos brutos (…/scans.csv, …/scans.xlsx) están completamente documentadas — incluyendo el aviso de RGPD: el ip_hash nunca está incluido en la exportación.
  • Comportamiento de errores: También se ha documentado la respuesta 400 de la validación de solicitudes: el cuerpo es el error de Zod sin procesar, no un documento de problema RFC-7807 — sin embargo, se entrega bajo el Content-Type application/problem+json.

2026-08 · Copiar enlaces públicos de archivos

  • Dashboard: los archivos públicos en la página de detalles de un código ahora tienen un botón que copia su enlace público al portapapeles — listo para usarse como URL de destino de un código QR cuando un escaneo deba abrir de inmediato un documento concreto en lugar de la página de destino con la lista de archivos.
  • API: los endpoints de archivos (/v1/files) ahora devuelven también public_url. El campo solo está definido en archivos con visibility: public — los archivos privados no tienen dirección pública.
  • Comportamiento: el enlace no necesita inicio de sesión y abre el archivo directamente en el navegador. Reemplazar el archivo no modifica el enlace, de modo que un código impreso con él sigue siendo válido. Detalles: Archivos y fichas técnicas.

2026-07 · Roles de equipo: Colaborador sin eliminar y facturación de Administrador

  • Nuevo: rol de miembro Colaborador (sin eliminar) — crea y edita códigos QR, archivos y Digital Product Passports, pero no puede eliminar nada ni crear claves de API. Todos los endpoints destructivos comprueban el rol en el servidor (403).
  • Facturación: las mejoras de plan y el portal de clientes de Stripe (POST /v1/billing/checkout, GET /v1/billing/portal) ahora están reservados a los Administradores de la organización — todos los demás roles ven un resumen de plan de solo lectura.
  • Dashboard: las acciones no permitidas para el rol actual se ocultan: un Lector no ve, por ejemplo, botones para crear, editar o eliminar; las listas, descargas y estadísticas siguen visibles. Detalles: Equipo y roles.

2026-06 · Enlaces externos en la página de destino del código

  • Página de destino: La página de destino alojada por qr3 de un código ahora puede listar enlaces externos autoalojados ({ label, url }) además de o en lugar de los archivos subidos, por ejemplo para fichas técnicas en tu propio sitio.
  • API: POST/PATCH /v1/codes aceptan un array links (0–20 entradas, http(s), ≤ 2048 caracteres). Cada URL se verifica con Google Web Risk; una URL no segura devuelve 422. Un array vacío elimina todos los enlaces.
  • Dashboard: Añade, reordena y elimina enlaces en la página de detalles del código.
  • Seguridad: Los enlaces renderizados permanecen seguros frente a XSS (escapados, solo http(s)) y la página mantiene su encabezado noindex.

2026-04 · Analíticas del Dashboard por código QR

  • Dashboard: El botón de analíticas en la lista de códigos QR ahora abre la página de estadísticas del código QR correspondiente en /dashboard/codes/{id}.
  • Routing: El alias /dashboard/codes sigue redirigiendo a /dashboard, pero ya no intercepta rutas de detalle como /dashboard/codes/{id}.
  • API: La página de detalles carga el código QR directamente mediante GET /v1/codes/:id; de este modo, ya no depende de los límites de paginación de la lista.
  • Tests: Las pruebas de regresión cubren la redirección del alias y la carga directa del código.

2026-04 · Diálogo de eliminación en el Dashboard para códigos QR

  • Dashboard: El icono de papelera en la lista de códigos QR ahora abre un diálogo de React propio en lugar de una ventana emergente nativa del navegador.
  • Feedback: Después de la eliminación, aparece una notificación toast de éxito o error.
  • Tests: packages/dashboard/tests/dashboard.test.ts evita regresiones en confirm() en el flujo de eliminación de códigos QR.

2026-04 · Prueba de enlace corto en el Dashboard para códigos QR dinámicos

  • Dashboard: Los shortcodes en la lista de códigos QR ahora se pueden hacer clic directamente como enlaces de redirección externos. El icono de enlace externo junto a, por ejemplo, wu3qaa abre https://qr3.app/{shortCode} en una nueva pestaña.
  • i18n: Se han añadido textos de información sobre herramientas (tooltips) para alemán e inglés.
  • Tests: packages/dashboard/tests/dashboard.test.ts protege el href del enlace, el comportamiento de nueva pestaña, noopener noreferrer y el icono contra regresiones.

2026-04 · Ruta del Redirect-Worker para códigos QR dinámicos

  • Corrección: Los códigos QR dinámicos en https://qr3.app/{shortCode} vuelven a ser procesados por el Redirect-Worker. La ruta de producción ahora utiliza qr3.app/* porque las rutas de Cloudflare Workers no admiten parámetros de ruta :code.
  • Robustez: Las rutas que no coinciden se pasan al origen de la landing page para que las páginas normales como /de/pricing no sean bloqueadas por el Redirect-Worker.
  • Tests: packages/redirect/tests/unit/redirect.test.ts verifica la ruta comodín (wildcard), el procesamiento de shortcodes y el paso directo al origen (pass-through).

2026-04 · Resumen de escaneos DPP del Workspace (Q3.4.2)

  • Nuevo: GET /v1/workspace/stats/dpp?days=30 — agrega todos los dpp_scans del Workspace de la clave API (active_dpps, scans_by_day, top_dpps con nombre de producto/categoría).
  • Dashboard: Tarjeta en la página de inicio (/dashboard) con gráfico de barras de 30 días + listas principales — en paralelo a las tarjetas de códigos QR.
  • Public: Enlace corto de marketing GET /dpp/dpp_<id> (un segmento) para demostraciones en vivo, en paralelo a /dpp/{gtin}/{serial}.

2026-04 · Analíticas de escaneos DPP (Q3.4.1)

  • Nuevo: GET /v1/dpp/:id/stats?days=30 — escaneos agregados del resolver público GS1 por DPP. Campos: total_scans, period_scans, scans_by_day, top_countries, top_devices, top_representations.
  • Nuevo: Tabla dpp_scans (migración 0011) — separada de scans (Redirect-Worker). Las direcciones IP se cifran con un hash utilizando una sal (salt) rotativa diaria, las IP en bruto nunca llegan a D1.
  • Dashboard: Tarjeta de minigráfico (SVG, sin biblioteca de gráficos) en /dashboard/dpp/:dppId con barras de 30 días + desgloses de los 3 principales. Estado vacío (empty state) tan pronto como un DPP esté activo pero aún no haya tenido escaneos.

2026-04 · Simulador de conformidad de la UE en vivo (Q3.3.7)

  • Nuevo: POST /v1/dpp/:id/validate-update — simula actualizaciones parciales sin estado (stateless) (estado, lista de mercados, …) sin persistencia. La respuesta contiene eu_compliance + preview.changed_fields.
  • Dashboard: Tarjeta de simulador en el detalle del DPP (/dashboard/dpp/:dppId) — etiquetas (chips) para DE/AT/FR/IT/ES/NL + personalizadas, menú desplegable de estado, Preview EU impact / Save changes / Reset. No bloqueante a través de Remix useFetcher.
  • Robustez: Funciones auxiliares del simulador externalizadas (readUpdatePatchFromForm, marketCountriesKey) + 18 nuevas pruebas unitarias; corrección de errores: la entrada única no ISO ya no borra la lista de mercados.

2026-04 · Vista previa de conformidad de la UE en vivo en el formulario de creación (Q3.3.6)

  • Modificado: POST /v1/dpp/validate ahora también devuelve eu_compliance — el mismo validador que GET /v1/dpp/:id/eu-compliance, sin estado (stateless) antes de guardar.
  • Dashboard: Vista previa debajo del panel de validación existente + nuevo banner de protección de guardado antes de los botones de envío si hay errores/advertencias pendientes (pluralización i18n DE/EN).

2026-04 · Validador de la UE + UI textil (Q3.3.4 + Q3.3.5)

  • Nuevo: Validador de conformidad de la UE con 5 reglas textiles (TEXTILE_AGEC_REQUIRED, TEXTILE_MICROPLASTICS_CONSISTENCY, TEXTILE_SVHC_THRESHOLD, TEXTILE_GREENWASHING, TEXTILE_ESPR_READY).
  • Nuevo: GET /v1/dpp/:id/eu-compliance con compliant / espr_ready / issues[] / summary.
  • Dashboard: Sección de conformidad de la UE en el detalle del DPP (mosaicos de resumen, tarjetas de problemas agrupadas, insignia ESPR-Ready en el encabezado).

2026-04 · Esquema DPP textil (Q3.3.1–Q3.3.3)

  • Nuevo: Categoría textile con cadena obligatoria AGEC (tejeduría/punto → teñido/estampado → confección), por fibra origin_country + recycled_pct, svhc_substances[], opción de exclusión voluntaria (opt-in) de ESPR (PEF, vida útil, reciclabilidad).
  • Nuevo: Campo básico market_countries: string[] (ISO 3166-1 alpha-2) en todas las categorías DPP — controla las reglas AGEC específicas de Francia y la nota obligatoria para el consumidor francés.
  • Nuevo: Plantilla HTML para el consumidor con cuadro de advertencia de microplásticos AGEC, cadena de origen de 3 niveles (píldoras con banderas), lista SVHC, sección de durabilidad y reciclabilidad.
  • Migración: 0010_dpp_market_countries (D1).

2026-04 · Importación masiva de DPP (Q3.2.1–Q3.2.5)

  • Nuevo: POST /v1/dpp/import acepta CSV y XLSX (compatible con Workers a través de SheetJS xlsx, ~283 KB de paquete comprimido con gzip).
  • Escalado: límite basado en el plan (Free 100 → Enterprise 10k) + db.batch() fragmentado (chunked) de 100 en 100 + límite de cuerpo de 5 MB.
  • Nuevo: Informe de errores en formato CSV en el campo errors_csv de la respuesta 201; GET /v1/dpp/import/templates/:category?format=csv|xlsx proporciona plantillas listas para usar para baterías y textiles.
  • Dashboard: Carga mediante arrastrar y soltar (drag-and-drop) en /dashboard/dpp/import con proxy de plantilla y descarga de CSV en línea.

Sin cambios disruptivos — Extensiones LTS

Todos los cambios mencionados anteriormente son aditivos:

  • Los clientes existentes de POST /v1/dpp/validate ignoran el nuevo campo eu_compliance sin necesidad de realizar cambios.
  • Los flujos de battery existentes permanecen sin cambios.
  • market_countries es opcional y tiene como valor predeterminado [].

Consulta Versionado de API para conocer la política de cambios disruptivos.