Skip to content

Flujo Piscinas — Sectores, Piscinas y Equipos

Base URL: /api/v1. Backend: camaroneras_backend (módulo pools). Admin: camaroneras_admin (feature pools). Primer módulo de negocio; referencia de producto en referencia/produccion-flow.md.

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

Organization ──< Sector ──< Pool ──< PoolEquipment >── EquipmentType
  • Sector: agrupa piscinas; por organización (@@unique(organizationId, name)).
  • Pool: code, hectares, type (engorde|precriadero), maxLbPerHa (tope lb/ha, opcional), sectorId. @@unique(organizationId, code).
  • EquipmentType: catálogo global sembrado al arrancar (aireador eléctrico/mecánico, alimentador automático, hidrófono Shrimp Talk). Idempotente vía onModuleInit.
  • PoolEquipment: asignación de equipo con fecha (poolId, equipmentTypeId, quantity, effectiveFrom). La última por (pool, tipo) es la vigente; el conjunto es el historial. Equipo fijo = una asignación que no cambia; móvil = nuevas asignaciones al trasladar.

RBAC

  • Módulo piscinas en el catálogo, clients ["web","mobile"].
  • Permisos por defecto al crear organización: Administrador todos; Técnico ver/crear/editar; Bodeguero ver.
  • Orgs creadas ANTES de existir el módulo no reciben los permisos automáticamente: el admin se los asigna desde la matriz de Roles (cache Redis → aplica al instante).

Endpoints (todos protegidos: AccessJwtGuard + PermissionGuard; tenant del JWT)

GET /equipment-types — (piscinas:ver)

Catálogo global de tipos de equipo.

GET /sectors — (piscinas:ver)

Lista de sectores de la organización con conteo de piscinas.

POST /sectors — (piscinas:crear)

Crear sector.

  • name único por organización → 409 si existe.

PATCH /sectors/:id — (piscinas:editar)

Renombrar sector.

DELETE /sectors/:id — (piscinas:eliminar)

Eliminar sector (solo si no tiene piscinas).

json
// response 400 (tiene piscinas)
{ "error": "...", "message": "No se puede eliminar: el sector tiene piscinas", "statusCode": 400 }

GET /pools — (piscinas:ver)

Lista de piscinas de la organización.

POST /pools — (piscinas:crear)

Crear piscina.

  • code único por organización → 409.
  • sectorId debe pertenencer a la org → 404.

PATCH /pools/:id — (piscinas:editar)

Editar piscina (campos parciales).

DELETE /pools/:id — (piscinas:eliminar)

Eliminar piscina.


GET /pools/:id/equipment — (piscinas:ver)

Equipos vigentes e historial de una piscina.

PUT /pools/:id/equipment — (piscinas:editar)

Asignar/actualizar equipo en piscina. La nueva asignación se vuelve vigente; la anterior queda en historial.

  • effectiveFrom es opcional; por defecto = ahora (servidor).
  • Crea historial automáticamente: la anterior (si existe) se marca como histórica, la nueva como vigente.

Flujo cliente — Admin

RutaTipoGuard
/piscinasprotegidaProtectedRoute + permiso piscinas:ver (sin permiso → /dashboard)
  • Menú "Piscinas" visible solo con piscinas:ver (hook usePermission).
  • Lista de piscinas: tabla con código, sector, hectáreas, tipo (badge), tope lb/ha, acciones (Equipos, Editar, Eliminar) según permiso.
  • Diálogo piscina (crear/editar): código, hectáreas, tipo (select), tope lb/ha (opcional), sector (select). Requiere sectores creados.
  • Diálogo sectores: crear, listar (con conteo de piscinas), eliminar (deshabilitado si tiene piscinas).
  • Diálogo equipos (por piscina): vigentes + formulario de asignación (tipo, cantidad, fecha) + historial. Cada asignación nueva queda como vigente y conserva el historial.

Flujo cliente — Mobile

Rutas

RutaTipoGuard
/piscinasprotegidaauth + piscinas:ver (visible en Home si tiene permiso)
/piscinas/:idprotegidaauth + piscinas:ver

Permisos en UI

Los permisos vienen en el response de POST /auth/refresh. El notifier los almacena en AuthState.permissions. Botones de crear/editar/eliminar se ocultan o deshabilitan según piscinas:crear, piscinas:editar, piscinas:eliminar.

Offline

  • Lecturas offline: Sectors, Pools, PoolEquipments y EquipmentTypes cacheados en Drift (tablas locales). Las pantallas leen de Drift siempre.
  • Sync de lectura: fetch API → upsert Drift al abrir la pantalla + pull-to-refresh.
  • Escrituras online: crear/editar/eliminar van directo al API; sin red → mensaje "Sin conexión. Necesitas internet para esta operación."
Home (card "Piscinas" si piscinas:ver)
  → /piscinas (lista)
    → filtro por sector (chips)
    → pull-to-refresh → sync API → Drift
    → FAB "Nueva piscina" (si piscinas:crear)
    → ••• Editar / Eliminar (si permisos)
    → botón sectores → SectorsScreen
      → crear sector (si piscinas:crear)
      → eliminar sector (si piscinas:eliminar, deshabilitado si poolCount > 0)
    → tap piscina → /piscinas/:id (detalle)
      → info + equipos vigentes + historial
      → "Asignar equipo" (si piscinas:editar) → bottom sheet
      → Editar / Eliminar (si permisos)

Archivos clave (mobile)

ArchivoContenido
lib/features/pools/pools_list_screen.dartLista con filtro + FAB + acciones
lib/features/pools/pool_detail_screen.dartDetalle con equipos vigentes/historial
lib/features/pools/pool_form_sheet.dartCrear/editar piscina (bottom sheet)
lib/features/pools/sectors_screen.dartGestionar sectores
lib/features/pools/assign_equipment_sheet.dartAsignar equipo (bottom sheet)
lib/features/pools/pools_notifier.dartPoolsNotifier, SectorsNotifier, PoolDetailNotifier
lib/features/pools/pools_repository.dartCRUD API + sync Drift
lib/shared/database/app_database.dartTablas Sectors, Pools, EquipmentTypes, PoolEquipments
lib/core/permissions/permissions.dartUserPermissions (guardas en UI)