Skip to content

0059. FCA por talla reusa los tramos del criterio FCA; Prioridades es página propia con endpoint dedicado

Estado

Aceptada

Contexto

dashboard-ejecutivo-v4 (backlog, tarea 4 de la agrupación del Excel v4) cierra las dos hojas ejecutivas que quedaban del WL_Aqua_Intelligence_v4: completar Dashboard (índice de salud promedio, conteo de atención prioritaria, resumen por sector, top-5) y construir Prioridades (ranking operativo de las 47 lagunas). Ambas consumen el motor de scoring de ADR-0057, ya implementado — esta tarea es consumidora, no lo modifica.

Winston pidió además, el 2026-08-04, un alcance nuevo que no está en el Excel: FCA sectorizado por tallas en producción. El método del FCA consolidado (alimento total ÷ biomasa total, sin ponderar) ya estaba confirmado sin cambios; lo que faltaba definir era cómo agrupar las piscinas en tallas.

Dos decisiones no obvias quedaron al planificar, antes de escribir código.

Decisión

1. La talla de cada piscina es el tramo vigente del criterio fca del KpiThresholdSet de su organización — no una tabla de tallas propia. Se reusa findBracket() (kpi-scoring.ts) tal cual, agrupando por peso actual (pesoActG).

Se descartó un corte de tallas independiente (ej. tramos fijos 10/15/20/25/+ g, mencionados como alternativa en el backlog) porque:

  • No agrega configuración nueva: los tramos del criterio FCA ya son editables por organización y ya expresan la noción de "talla" del negocio (Winston los definió para el semáforo de FCA por tramo de peso).
  • Evita una inconsistencia visible: si el corte de tallas fuera distinto al del semáforo, una piscina podría aparecer en un grupo del bloque "FCA por talla" y en un tramo distinto en su propio semáforo de FCA — mismo peso, dos fronteras diferentes, sin razón de negocio para la diferencia.

Consecuencia aceptada: el bloque "FCA por talla" cambia si el admin edita los umbrales de FCA en KpiThresholdsPage. No es un bug — es la misma fuente de verdad que ya gobierna el semáforo — pero hay que documentarlo explícito (flow doc, FcrPorTallaTable.tsx) para que no se lea como inconsistencia.

Las piscinas sin pesoActG (sin muestreo, o sin ciclo) van a un grupo sin_dato propio, no al último tramo. Esto es deliberadamente distinto de findBracket(), que sí manda los pesos null al último tramo — ahí tiene sentido porque un semáforo necesita algún valor con el que evaluar; acá mezclar esas piscinas con el tramo de mayor peso inflaría o desinflaría un grupo real con piscinas que en realidad no se pudieron tallar.

2. Prioridades es una página admin propia (/prioridades) con endpoint dedicado (GET /reports/priorities), no una sección del dashboard ni una vista sobre /reports/dashboard.

Se descartaron dos alternativas:

  • Sección dentro de DashboardProduccionPage: el ranking completo de las 47 lagunas es una vista operativa con vida propia (se linkea, se ordena, se consulta aparte del panorama ejecutivo) — meterlo ahí hace crecer una página que ya agrega kpis, donut, barras, línea y tabla resumen.
  • Página propia consumiendo /reports/dashboard: evita el endpoint nuevo pero obliga a traer series y sectors, que Prioridades no usa — pagar ese payload solo para reusar el fetch no compensa la simplicidad de un endpoint dedicado y liviano.

GET /reports/priorities reusa el mismo permiso dashboard:ver que /reports/dashboard (misma superficie ejecutiva, no la sábana de reportes:ver) y el mismo fetch Prisma interno (fetchSectorsWithCycles + buildRow), solo que aplanado y ordenado por índice de salud ascendente, sin series ni agregación de sectores/kpis.

Consecuencias

  • dashboard.calc.ts gana tres funciones puras nuevas —computeFcrPorTalla, computeSectorSummaries, computeTopPools— y DashboardKpiRow se extiende con poolId/poolCode/pesoActG/indiceSalud/prioridad, ya calculados por computeKpiScoring en buildRow; no hay recomputación.
  • GET /reports/dashboard gana campos aditivos (kpis.indiceSaludPromedio, kpis.prioridadCounts, kpis.atencionPrioritariaCount, sectors[].indiceSaludPromedio /.atencionPrioritariaCount/.fcr, fcrPorTalla[], topPools[]) — no rompe consumidores existentes.
  • Nuevo endpoint GET /reports/priorities, protegido igual que el dashboard.
  • El índice de salud promedio y los conteos de prioridad, tanto en kpis globales como por sector, se calculan solo entre piscinas con ciclo — una piscina vacía siempre da índice 100 (los 5 criterios caen en sin_dato, que penaliza 0 en ADR-0057), así que incluirla infla el promedio y esconde atención prioritaria real.
  • El admin gana src/shared/types/kpi.ts y src/shared/components/KpiBadges.tsx (+ src/shared/lib/kpi-badges.ts por la regla react-refresh/only-export-components, que exige separar constantes de componentes): antes KpiEstado/KpiPrioridad y los badges de estado/prioridad vivían duplicados dentro de la feature reportes (ReporteSemanalPage.tsx); con dos features nuevas necesitándolos (dashboard-produccion extendido y prioridades), y la regla del proyecto de no importar entre features, se extrajeron a shared/ReporteSemanalPage.tsx se refactorizó para consumir la misma fuente.

Referencias

  • Tarea dashboard-ejecutivo-v4 (2026-08-04), _planning/dashboard-ejecutivo-v4-plan.md / -progress.md.
  • ADR-0057 — motor de scoring que esta tarea consume.
  • ADR-0046 — precedente de ruta propia + kpis no filtrables para el dashboard de producción.
  • camaroneras_backend/src/modules/reportes/dashboard.calc.ts, dashboard.calc.spec.ts, reportes.service.ts, reportes.controller.ts.
  • camaroneras_admin/src/features/dashboard-produccion/, camaroneras_admin/src/features/prioridades/, camaroneras_admin/src/shared/{types/kpi.ts,lib/kpi-badges.ts,components/KpiBadges.tsx}.
  • camaroneras_docs/modulos/09-dashboard-produccion-flow.md, camaroneras_docs/modulos/11-prioridades-flow.md.