Skip to content

Reporte ciudadano de validez de puntos — EPIC #120 (plan + backend + API + web)#119

Merged
vgpastor merged 11 commits into
mainfrom
claude/practical-ritchie-pocjs4
Jun 28, 2026
Merged

Reporte ciudadano de validez de puntos — EPIC #120 (plan + backend + API + web)#119
vgpastor merged 11 commits into
mainfrom
claude/practical-ritchie-pocjs4

Conversation

@vgpastor

@vgpastor vgpastor commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Permite que un usuario autenticado reporte un punto de acopio como cerrado, ya no existe, mudado o desactualizado. Tras N = 3 reportes de usuarios distintos el punto se marca "dudoso" (sigue visible, con aviso) y un coordinador lo resuelve (confirmar cierre · marcar inválido · descartar).

Implementación completa de la EPIC #120.

Closes #121
Closes #122
Closes #123
Closes #124

Backend (#121)

Resource.disputed/disputedAt + flagDisputed/clearDispute/markInvalid + eventos; entidad ResourceValidityReport; migración 0031 (índice único parcial = un reporte abierto por usuario). Casos de uso con dedup, umbral y resolución de 3 vías (devuelve MutationAuditResult → activity trail de #135).

API (#122)

  • POST /resources/:id/validity-reports (ciudadano) · POST /resources/:id/dispute/resolve (coordinador, motivo obligatorio) · GET /resources/:id/validity-reports · GET /emergencies/:id/coordination/disputed.
  • disputed/disputedAt en los 4 read models públicos · pnpm gen:api.

Web ciudadano (#123)

  • DisputedBadge ("En verificación") en tarjeta y ficha; aviso en el popup del mapa.
  • CTA "¿Algo va mal? Avísanos" en tarjeta y ficha → /recursos/[id]/reportar-estado (formulario auth-gated: 4 motivos · nota · fotos).

Web coordinación (#124)

  • Pestaña "Puntos en duda" + entrada en el hub con contador.
  • Cola con desglose por motivo, nº de reportantes y última fecha; acciones Confirmar cierre / Marcar inválido / Descartar con motivo → audit trail.

Gate

Verde de punta a punta: api build · lint · prettier · test (1066) · e2e (8) — web build · lint — gen:api.

Follow-ups opcionales (menores)

  • Evidencia por reporte (notas/fotos) en un drawer de coordinación (GET …/validity-reports ya existe).
  • Enlace "reportar" directo dentro del popup del mapa (hoy el popup solo informa del estado).

Ficha 15: un usuario autenticado puede reportar un punto de acopio como
cerrado, inexistente, mudado o desactualizado. Tras N reportes de usuarios
distintos el punto se marca "dudoso" (sigue visible) y un coordinador
confirma el cierre o lo descarta.

Concepto propio en el contexto resources (entidad ResourceValidityReport
+ flag `disputed` en Resource), con API, modelo de datos Drizzle, plan de
frontend (Atomic Design) y decisiones abiertas. Incluye la entrada en el
indice del backlog.
@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
response-grid Ready Ready Preview, Comment Jun 28, 2026 9:39pm

Request Review

Convierte §8 (decisiones abiertas) en decisiones resueltas: concepto propio
en resources, umbral N=3, fotos solo coordinación, sin TTL en MVP, excluir
al dueño del recuento, texto de badge "En verificación · posible cierre" y
sin cooldown en el MVP.
Añade el tracking (EPIC #120 · sub-issues #121#124) en la cabecera de la
ficha y en el índice del backlog.
)

Un usuario autenticado puede reportar un punto como cerrado / ya no existe /
mudado / desactualizado; tras N=3 reportes de usuarios distintos el recurso
pasa a `disputed` (sigue visible, con aviso). Coordinación resuelve en 3 vías:
confirmar cierre, marcar inválido (rejected) o descartar.

- Dominio: Resource.disputed/disputedAt + flagDisputed/clearDispute/markInvalid
  + eventos resource.disputed / resource.dispute_resolved; entidad
  ResourceValidityReport (+ enums ValidityReason/ValidityReportStatus).
- Migración 0031: tabla resource_validity_reports (índice único parcial:
  un reporte abierto por usuario) + columnas disputed/disputed_at en resources.
- Casos de uso: ReportResourceValidity (upsert + dedup + umbral, excluye al
  dueño), ResolveResourceDispute (devuelve MutationAuditResult para el activity
  trail), GetDisputedResources, GetResourceValidityReports. Repos
  (puerto/Drizzle/memoria) + wiring del módulo.
- 23 tests nuevos; gate verde (build/lint/prettier/test).

Parte de la EPIC #120. Ajustes de diseño en docs/features/15 §9.
@vgpastor vgpastor changed the title docs: plan de reporte ciudadano de validez de puntos (cerrado/no existe) Reporte ciudadano de validez de puntos: plan (EPIC #120) + backend (#121) Jun 28, 2026
…gen:api (#122)

- Endpoints: POST /resources/:id/validity-reports (ciudadano autenticado),
  POST /resources/:id/dispute/resolve (coordinador; motivo obligatorio →
  audit trail vía setAuditContext), GET /resources/:id/validity-reports y
  GET /emergencies/:id/coordination/disputed (cola de coordinación).
- `disputed`/`disputedAt` expuestos en los read models públicos
  (ResourceView + ResourceViewDto → aparece en list/in-bounds/nearby/detalle).
- ReportResourceValidity pasa a ser resource-scoped (sin emergencyId en el
  comando; se deriva del recurso).
- Errores de dominio nuevos mapeados en el filtro HTTP (403/409).
- pnpm gen:api: openapi.json + packages/api-client/src/schema.ts regenerados
  (paths nuevos verificados).
- e2e: reporte → dudoso (umbral 3) → resolver (cerrar/descartar), authz
  (dueño 403, sin token 401, no-coordinador 403) y públicos exponen disputed.

Parte de la EPIC #120.
@vgpastor vgpastor changed the title Reporte ciudadano de validez de puntos: plan (EPIC #120) + backend (#121) Reporte ciudadano de validez de puntos: plan (EPIC #120) + backend (#121) + API (#122) Jun 28, 2026
- DisputedBadge (variante 'disputed' en Badge) en la tarjeta de punto y la
  ficha de detalle cuando el punto está en duda ("En verificación").
- CTA "¿Algo va mal? Avísanos" en la tarjeta (listados) y en la ficha → lleva
  al formulario de reporte.
- Ruta /e/[slug]/recursos/[resourceId]/reportar-estado: formulario (motivo de
  4 opciones, nota y fotos opcionales) con server action que llama a
  POST /resources/:id/validity-reports; auth-gated (redirige a login?next=).
- i18n es/en (resource_card.disputed_label/report_cta + sección reportar_validez).

Parte de la EPIC #120 (#123). Pendiente del front: estilo de marcador/popup de
mapa para puntos en duda.
…, #123)

- #124: pestaña "Puntos en duda" en coordinación + entrada en el hub con
  contador. Cola (DisputedQueue) con desglose por motivo, nº de reportantes y
  última fecha; acciones de resolución (confirmar cierre / marcar inválido /
  descartar) con motivo obligatorio → POST /resources/:id/dispute/resolve
  (queda en el audit trail). Server action resolveDispute.
- #123: el mapa muestra "En verificación · posible cierre" en el popup de los
  puntos en duda (disputed enhebrado en MapPoint desde landing + in-bounds).
- i18n es/en.

Parte de la EPIC #120.
@vgpastor vgpastor changed the title Reporte ciudadano de validez de puntos: plan (EPIC #120) + backend (#121) + API (#122) Reporte ciudadano de validez de puntos — EPIC #120 (plan + backend + API + web) Jun 28, 2026
…untos (ficha 15)

Revisión en profundidad de la disputa de puntos y del código colindante.

Bugs:
- La ingesta externa (re-import de acopiove) ya no resetea disputed/disputedAt
  ni deja reportes abiertos huérfanos: se preservan como campos local-owned.
- flagDisputed() es idempotente (no duplica evento ni reinicia disputedAt).
- La cola "Puntos en duda" filtra por visibilidad: un punto disputado que luego
  se cierra/oculta deja de aparecer aunque el flag persista.
- Re-reportar cambiando solo el motivo ya no borra la nota/fotos previas
  (se reenvían solo cuando el cliente las aporta).
- Primer reporte concurrente del mismo usuario: se captura la violación 23505
  del índice parcial one_open_per_user y se pliega en un update (antes 500).
- Antes de marcar disputed se re-lee el recurso para no emitir dos eventos
  resource.disputed si dos ciudadanos cruzan el umbral a la vez.

Refactors:
- DomainExceptionFilter: ternario anidado -> tabla error->status por instanceof.
- ResolveResourceDispute: un unico switch (accion + targetStatus); guardado de
  reportes en paralelo. GetDisputedResources: lecturas por recurso en paralelo.
- Predicado de visibilidad extraido a Resource.isPubliclyVisible().
- DTO byReason tipado como Record<string,number> en el cliente generado.
- Web: la CTA "avisar de un problema" vive solo en la ficha, no en cada tarjeta
  de la lista; revalidatePath tras reportar; se quita aria-label redundante.

Tests: idempotencia, preservacion de nota/fotos, exclusion de cerrados de la
cola, doble resolucion, re-disputa tras descartar, diff de auditoria en dismiss,
preservacion de disputed en re-ingesta, e int-spec del pliegue del 23505.
@vgpastor vgpastor marked this pull request as ready for review June 28, 2026 21:39
@vgpastor vgpastor enabled auto-merge (squash) June 28, 2026 21:39
@vgpastor vgpastor merged commit 83f7d81 into main Jun 28, 2026
6 checks passed
@vgpastor vgpastor deleted the claude/practical-ritchie-pocjs4 branch June 28, 2026 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment