Apariencia
0045. Paginación server-side opcional con meta global en el envelope
Estado
Aceptada.
Contexto
Casi todos los GET de listado del backend devolvían el array completo sin skip/take ni total (/cycles, /sectors, /pools, /roles, /users, /feeding-curves, /cycles/:id/feedings, /cycles/:id/samplings, /cycles/:id/populations, /cycles/:id/harvests, /pools/:id/events, /pools/:id/water-params, /platform/organizations, /platform/users). Funcionaba porque el volumen por ciclo/piscina es acotado (~120-150 filas), pero listados sin ese techo natural (/users, /pools, /platform/organizations a medida que crecen los tenants) no escalan indefinidamente. camaroneras_mobile (offline-first) consume varios de estos mismos endpoints esperando el array completo (data as List sin enviar query params) — cualquier diseño tenía que ser retrocompatible sin tocar el repo mobile.
Decisión
page/pageSizeson opcionales (PaginationQueryDto,src/common/dto/). Si ninguno viene, el endpoint devuelve el listado completo — comportamiento idéntico al actual, sinmeta. Asícamaroneras_mobilesigue funcionando sin cambios de código.- Cuando se envían, la respuesta agrega
meta: { total, page, pageSize }como campo hermano dedata(no reemplazadatapor un objeto{items, total}) — evita romper a los consumidores que hacendata as Listen el resto de los casos. paginationArgs/paginationMeta(src/common/utils/pagination.ts) centralizan el cálculo deskip/takey del objetometa; cada service hacePromise.all([findMany({...args}), args ? count() : undefined]).- Excepción:
alimentacion(feedings),muestreos(samplings) yraleos(harvests) tienen campos derivados que dependen del historial completo ordenado del ciclo (acumuladoKg,incremento/promIncrementocon ventana de 28 días,totalRaleoLb). Paginar la query de Prisma ahí corrompería esos cálculos, así que estos tres paginan en memoria: calculan los derivados sobre el dataset completo y recién después slicean el array de items para la página pedida. Elsummaryde cada uno sigue agregando todas las filas del ciclo, no solo la página actual. wrapResponsesInEnvelope(src/common/swagger/build-document.ts, ver ADR-0036) agregametacomo propiedad opcional (nullable: true) al envelope de toda respuesta 2xx, no solo las de listado — es fiel al contrato real (StandardResponse<T>enresponse.interceptor.tsya declarameta?: PaginationMetapara cualquier respuesta), y evita mantener dos formas de envelope en el spec.
Consecuencias
camaroneras_mobileno requirió ningún cambio: no envíapage/pageSize, así que sigue recibiendo el array completo en los endpoints que ya consumía.- La API Reference (
/api/) documentapage/pageSizecomo query params opcionales ymetacomo parte del envelope automáticamente (spec regenerado víanpm run openapi:export+sync:openapi) — los*-flow.mdde los módulos tocados no necesitaron edición manual (siguen el patrón de ADR-0036: bodies y responses 2xx viven en el spec, no a mano). - El admin y el panel de plataforma (
camaroneras_platform) quedan pendientes de migrar sus tablas a paginación server-side consumiendo estemeta(ver_planning/paginacion-server-side-plan.md); mientras tanto pueden seguir usando los endpoints sin params (comportamiento sin cambios).