Skip to content

feat: [TESIS-147] show and operate the failed events queue - #63

Merged
LauAubert merged 2 commits into
masterfrom
TESIS-999006-failed-events-queue
Oct 5, 2026
Merged

LauAubert merged 2 commits into
masterfrom
TESIS-999006-failed-events-queue

Conversation

@LauAubert

@LauAubert LauAubert commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Ticket de Jira

https://proyectofinalfrlp.atlassian.net/browse/TESIS-147

ID provisorio: se reemplaza por la clave real al cargar la card en Jira. Card: cards/006.md.


Descripción

El motor de reintentos (RF-16, Módulo E del alcance) está completo en la API: cada webhook entrante o pedido saliente que falla queda en failed_events, se reintenta con backoff exponencial y hay endpoints para listar, reintentar y descartar. Pero no se veía en ningún lado: el operador no se enteraba de que una venta no entró, y en la demo no había forma de mostrar el criterio de aceptación «los webhooks fallidos son recuperados automáticamente sin pérdida de datos» (E4b §2.6). Este PR agrega la sección Eventos fallidos (/failed-events).

Decisiones que conviene mirar:

Sin maqueta. Es la DataTable del DS con pestañas por estado, como el catálogo: Todos, Pendientes, Agotados, Resueltos y Descartados, con su contador (una consulta de una fila por pestaña, que lee meta.total). processing no tiene pestaña: dura segundos mientras un worker lo intenta, y aparece en «Todos». Las filas agotadas se resaltan.

Sólo las acciones que la API acepta. utils/actions.ts es el espejo de RequeueFailedEvent::REQUEUEABLE_STATUSES: reintentar en pending, dead y discarded; descartar en todo lo no resuelto ni descartado. Van como botones en una columna y no en el menú de la DataTable, que no admite opciones por fila. processing no ofrece reintento: la API lo acepta sólo si el claim del worker venció, y eso el front no lo puede ver (si quedó colgado, el barrido lo rescata solo).

El tipo de evento, en palabras. webhooks.order_ingestion → «Venta recibida de un canal», etc. Un tipo desconocido se muestra tal cual: mejor un nombre técnico que esconder que existe.

Un solo «ahora» por lectura. «Próximo reintento» y «Registrado» se calculan contra el dataUpdatedAt de la query, no contra la hora de cada render: si no, un re-render cualquiera movería los «dentro de 4 minutos» sin que la tabla haya cambiado.

Sin payload. La API no lo expone a propósito (puede traer datos del cliente). Para diagnosticar quedan el último error y el status HTTP.

  • Agrega features/failed-events con api.ts, types.ts, queryKeys.ts, hooks/useFailedEvents.ts, content.ts y utils/actions.ts.
  • Agrega FailedEventsPage: tabla paginada por el backend, pestañas con contadores, reintento y descarte con confirmación.
  • La mutación invalida la feature también al fallar: un 422 quiere decir que el evento cambió de estado con la tabla abierta, y la fila tiene que mostrarlo.
  • Registra /failed-events con su entrada «Eventos fallidos» en el Sidebar y actualiza routes.test.tsx.
  • Suma la feature a docs/guidelines/architecture.md.

Evidencia visual

Pendiente de captura con la API levantada. El comportamiento está cubierto por los tests de la página y de las reglas de acciones.


Cómo probar

Precondición: API de master, bin/rails db:seed, login con un usuario de Norte, bin/jobs corriendo.

  1. Forzar un fallo: POST /api/v1/webhooks/integrations/<token> con un payload de orden cuyo producto no está vinculado (o apagar la red del courier en desarrollo).
  2. Sidebar → «Eventos fallidos» → el evento aparece como «Pendiente», con 1 / 5 intentos, el próximo reintento y el último error.
  3. Esperar a que agote los intentos (o bajar max_attempts desde la consola) → pasa a «Agotado», resaltado, y cuenta en la pestaña.
  4. «Reintentar» → toast «El evento volvió a la cola…», vuelve a «Pendiente» con 0 / 5.
  5. «Descartar» → confirmación destructiva → pasa a «Descartado» y sólo ofrece «Reintentar».

Verificación: npm run test (690 tests, 0 fallas), npm run lint, npm run format:check y npm run build limpios.


Impacto y consideraciones

¿Introduce breaking changes?
No

¿Requiere nuevas variables de entorno?
No

¿Afecta la arquitectura o genera un nuevo patrón?
No. Una feature nueva con la estructura estándar.

Conflicto esperable: proyecto-web#62 (depósitos) también agrega una entrada al Sidebar en routes.tsx y su test. Es un conflicto de líneas vecinas, se resuelve dejando las dos.

🤖 Generated with Claude Code

The retry engine (RF-16) records every failed webhook and outbound request,
retries it with exponential backoff and exposes list, retry and discard
endpoints, but nothing showed it: the operator could not tell a sale failed
to come in, and the resilience acceptance criterion could not be shown.

The new Eventos fallidos section lists the queue with tabs per status and
their counts, names each event type in words, and shows the attempts, the
next automatic retry and the last error with its HTTP status. Each row offers
only the actions the API accepts for its status; discarding asks first, and a
422 on retry is explained because the event changed status meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TomasMartin2004

Copy link
Copy Markdown
Contributor

Revisado. Lo veo bien para implementar.

Mismo caso que #62 y por el mismo motivo: RF-16 (motor de reintentos / DLQ) está completo en la API y no se veía en ningún lado. Verifiqué que en master no hay carpeta features/failed-events. El operador no se entera de que una venta no entró, y en la defensa no hay forma de mostrar el criterio de aceptación de resiliencia de E4b, que es uno de los diferenciadores que el proyecto vende.

Lo que verifiqué

  • utils/actions.ts es el núcleo y está bien resuelto: el mapa ACTIONS coincide exactamente con Webhooks::RequeueFailedEvent::REQUEUEABLE_STATUSES de la API (pending, dead, discarded), que fui a chequear contra el código. Ofrecer un botón que la API va a rechazar con 422 es prometer algo que no pasa, y acá no pasa.
  • Que processing ofrezca sólo descarte está bien pensado: la API acepta el reintento sólo si venció el claim del worker, y eso el front no lo puede saber. La alternativa —mostrar el botón y cruzar los dedos— sería peor.
  • Misma estructura de feature que el resto, architecture.md y routes.test.tsx actualizados.
  • La regla queda en una función pura con sus tests, no embebida en el JSX de la fila.

Para la demo

Esta pantalla sin eventos no muestra nada. Si va a usarse para defender el criterio de resiliencia, o se provoca un fallo a mano antes (un webhook con un producto sin mapear alcanza) o entra proyecto-api#111, que siembra actividad. Lo digo para que no se descubra el día de la presentación.

@LauAubert LauAubert changed the title feat: [TESIS-999006] show and operate the failed events queue feat: [TESIS-147] show and operate the failed events queue Oct 3, 2026
@LauAubert LauAubert closed this Oct 3, 2026
@LauAubert
LauAubert deleted the TESIS-999006-failed-events-queue branch October 3, 2026 23:18
@LauAubert
LauAubert restored the TESIS-999006-failed-events-queue branch October 3, 2026 23:22
@LauAubert LauAubert reopened this Oct 3, 2026
@LauAubert
LauAubert marked this pull request as ready for review October 3, 2026 23:25
@LauAubert
LauAubert requested a review from a team as a code owner October 3, 2026 23:25
@LauAubert
LauAubert requested review from LoLoo03 and removed request for a team October 3, 2026 23:25

@TomasMartin2004 TomasMartin2004 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revisión del diff completo. El código es idéntico al que leí cuando los PRs estaban en draft —ningún commit nuevo—, así que lo que sigue es el veredicto formal.

✅ Aprobado

Mismo caso que #62 y por el mismo motivo: RF-16 (motor de reintentos / DLQ) está completo en la API y no se veía en ningún lado. Verifiqué que en master no hay carpeta features/failed-events. El operador no se entera de que una venta no entró, y en la defensa no hay forma de mostrar el criterio de aceptación de resiliencia de E4b, que es uno de los diferenciadores que el proyecto vende.

Lo que verifiqué

  • utils/actions.ts es el núcleo y está bien resuelto: el mapa ACTIONS coincide exactamente con Webhooks::RequeueFailedEvent::REQUEUEABLE_STATUSES (pending, dead, discarded), que fui a chequear contra el código del backend. Ofrecer un botón que la API va a rechazar con 422 es prometer algo que no pasa, y acá no pasa.
  • Que processing ofrezca sólo descarte está bien pensado: la API acepta el reintento sólo si venció el claim del worker, y eso el front no lo puede saber. La alternativa —mostrar el botón y cruzar los dedos— sería peor.
  • Misma estructura de feature que el resto, architecture.md y routes.test.tsx actualizados.
  • La regla vive en una función pura con sus tests, no embebida en el JSX de la fila.

🟡 Para la demo

Esta pantalla sin eventos no muestra nada. Si va a usarse para defender el criterio de resiliencia, o se provoca un fallo a mano antes —un webhook con un producto sin mapear alcanza— o entra proyecto-api#111, que siembra actividad. Lo digo para que no se descubra el día de la presentación.

…-events-queue

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LauAubert
LauAubert merged commit d36cca6 into master Oct 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants