Seguimiento de #226 (PR #314) · Parte de la épica #228
Problema o valor
El backfill de soft-links supplyId (#226) cubre las 4 tablas relacionales con columna supply_id (need_items, offer_items, resource_items, donation_intake_lines) y las líneas jsonb de containers.lines. En la auditoría de cierre se detectó que shipments.items es el único almacén de SupplyLine con supplyId que quedó fuera: la carga suelta de una expedición (logistics/infrastructure/drizzle/schema.ts, jsonb items) persiste líneas canónicas con supplyId, pero el backfill no las recorre. Las expediciones legacy con carga suelta quedan sin enlazar al catálogo y fuera de la agregación/previsión.
Propuesta
Añadir shipment_items como sexta fuente del backfill existente:
- Nuevo literal en
SupplyLineSource (domain/ports/supply-link-backfill.repository.ts).
- En
DrizzleSupplyLinkBackfillRepository: mismo tratamiento que containers.lines (jsonb) — listUnlinked con filtro EXISTS sobre jsonb_array_elements, y relink read-modify-write con SELECT … FOR UPDATE reutilizando relinkContainerLines (o extrayendo el helper a nombre neutro).
- El caso de uso
BackfillSupplyLinks no cambia: es agnóstico de la fuente.
Decidir si tiene sentido excluir expediciones en estados terminales o enlazarlas igualmente (en #226 se decidió enlazar también contenedores sellados: el soft-link es metadato de catálogo, no altera el contenido declarado — probablemente aplique el mismo criterio).
Alcance mínimo
Solo la fuente nueva en puerto + adapter + tests (unit del caso de uso con la fuente nueva e int-spec del jsonb de shipments). Sin cambios de endpoint ni DTO (la fuente aparece sola en sources del informe; regenerar pnpm gen:api por el enum del DTO).
Criterios de aceptación
Dependencias o riesgos
Depende de #226 (mergeado en 122544d). Riesgo compartido con containers: el agregado Shipment persiste su jsonb completo en save() sin bloqueo optimista, así que un save() que leyó antes del commit del backfill puede revertir los supplyId recién enlazados (solo el metadato; recuperable re-ejecutando). El fix de raíz (versionado optimista de los agregados con jsonb) queda fuera de alcance.
Problema o valor
El backfill de soft-links
supplyId(#226) cubre las 4 tablas relacionales con columnasupply_id(need_items,offer_items,resource_items,donation_intake_lines) y las líneas jsonb decontainers.lines. En la auditoría de cierre se detectó queshipments.itemses el único almacén deSupplyLineconsupplyIdque quedó fuera: la carga suelta de una expedición (logistics/infrastructure/drizzle/schema.ts, jsonbitems) persiste líneas canónicas consupplyId, pero el backfill no las recorre. Las expediciones legacy con carga suelta quedan sin enlazar al catálogo y fuera de la agregación/previsión.Propuesta
Añadir
shipment_itemscomo sexta fuente del backfill existente:SupplyLineSource(domain/ports/supply-link-backfill.repository.ts).DrizzleSupplyLinkBackfillRepository: mismo tratamiento quecontainers.lines(jsonb) —listUnlinkedcon filtroEXISTSsobrejsonb_array_elements, y relink read-modify-write conSELECT … FOR UPDATEreutilizandorelinkContainerLines(o extrayendo el helper a nombre neutro).BackfillSupplyLinksno cambia: es agnóstico de la fuente.Decidir si tiene sentido excluir expediciones en estados terminales o enlazarlas igualmente (en #226 se decidió enlazar también contenedores sellados: el soft-link es metadato de catálogo, no altera el contenido declarado — probablemente aplique el mismo criterio).
Alcance mínimo
Solo la fuente nueva en puerto + adapter + tests (unit del caso de uso con la fuente nueva e int-spec del jsonb de shipments). Sin cambios de endpoint ni DTO (la fuente aparece sola en
sourcesdel informe; regenerarpnpm gen:apipor el enum del DTO).Criterios de aceptación
GET /admin/supplies/backfilllista los textos sin enlazar deshipments.itemscon su recuento.POST /admin/supplies/backfillenlaza las líneas de carga suelta que casan (exacto/alias) y respeta la idempotencia (supplyIdya fijado nunca se toca).Shipmentno pierden líneas (mismo patrónFOR UPDATE+ filtroEXISTSquecontainers).Dependencias o riesgos
Depende de #226 (mergeado en
122544d). Riesgo compartido concontainers: el agregadoShipmentpersiste su jsonb completo ensave()sin bloqueo optimista, así que unsave()que leyó antes del commit del backfill puede revertir lossupplyIdrecién enlazados (solo el metadato; recuperable re-ejecutando). El fix de raíz (versionado optimista de los agregados con jsonb) queda fuera de alcance.