Apariencia
Flujo Parámetros de agua
Base URL: /api/v1. Backend: camaroneras_backend (módulo parametros) ✅. Admin: camaroneras_admin (feature parametros) ✅. Mobile: camaroneras_mobile (feature parametros, offline-first) ✅. Quinto módulo de negocio; depende de piscinas. Captura físico-química por piscina (no por ciclo).
Requests y responses 2xx: bodies (tipos, opcionales, enums) y el shape de las respuestas exitosas están en la API Reference, generada desde el spec OpenAPI. Acá quedan solo las respuestas de error (4xx/5xx) y las reglas de negocio/flujo.
Modelo
Pool ──< WaterParam (lectura de O2/temperatura/salinidad, en campo)- WaterParam:
date,dissolvedOxygen(mg/L),temperature(°C),salinity(ppt),notes. Los tres valores son opcionales pero al menos uno debe venir. - Sin derivados: pura captura.
RBAC
- Módulo
parametros(label "Parámetros de agua") en el catálogo, clients["web","mobile"]. - Defaults: Administrador todo; Técnico
ver/crear/editar; Bodeguerover. Backfill automático.
Endpoints (protegidos: AccessJwtGuard + PermissionGuard; tenant del JWT)
GET /pools/:poolId/water-params — (parametros:ver)
Lista las lecturas de una piscina (más reciente primero).
POST /pools/:poolId/water-params — (parametros:crear)
Registra una lectura. Al menos uno de dissolvedOxygen/temperature/salinity.
json
// response 400 (ningún valor)
{ "error": "...", "message": "dissolvedOxygen must be a number ...", "statusCode": 400 }json
// response 404 (piscina no existe / de otra org)
{ "error": "...", "message": "Piscina no encontrada", "statusCode": 404 }PATCH /water-params/:id — (parametros:editar)
Edita una lectura (campos parciales).
DELETE /water-params/:id — (parametros:eliminar)
json
// response 404 (no existe o de otra organización)
{ "error": "...", "message": "Lectura no encontrada", "statusCode": 404 }Flujo admin (camaroneras_admin, feature parametros)
- Ruta:
/parametros— protegida (ProtectedRoute module="parametros" action="ver"). Sin sesión →/login; sin permisoparametros:ver→ bloqueada y sin item de nav. - Selector de piscina (
FormCombobox) que carga deGET /pools. Sin selección se muestra un aviso; al elegir una piscina aparece la tabla de lecturas + acciones. - Tabla: Fecha · O₂ (mg/L) · Temp (°C) · Salinidad (ppt) · Notas · Acciones. Estados loading/empty. Diálogo crear/editar con validación "al menos un valor" en cliente (espeja el
@ValidateIfdel backend): botón deshabilitado hasta fecha + ≥1 valor. - Gating RBAC: "Nueva lectura" con
parametros:crear; "Editar" conparametros:editar; "Eliminar" conparametros:eliminar. Todo víausePermission().can('parametros', …).
Flujo mobile (camaroneras_mobile, feature parametros, offline-first)
- Rutas:
/parametros(picker de piscina) →/parametros/:poolId(lecturas). Card en Home solo concanVerParametros. La app ya exige sesión (go_router →/login). - Offline-first: tabla Drift
WaterParams(schema v7). Lectura siempre desde Drift;sync()refresca desde red y conserva el cache si no hay conexión. El picker lee la tablaPools(infra compartida) y hace su propiosyncPools()al entrar. - Escrituras online: crear/editar/eliminar requieren red (patrón
_netError→ "Sin conexión…"); tras cada escritura se re-sincroniza la piscina. - Validación "al menos un valor" en el bottom sheet (espeja el
@ValidateIfdel backend). - Gating RBAC: FAB "Registrar" con
canCrearParametros; iconos editar/eliminar concanEditar/canEliminarParametros. - Tests: repository (Drift in-memory: mapeo + replace borra lo ausente) + gating del FAB.