Apariencia
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
piscinasen el catálogo, clients["web","mobile"]. - Permisos por defecto al crear organización: Administrador todos; Técnico
ver/crear/editar; Bodeguerover. - 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.sectorIddebe 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.
effectiveFromes 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
| Ruta | Tipo | Guard |
|---|---|---|
/piscinas | protegida | ProtectedRoute + permiso piscinas:ver (sin permiso → /dashboard) |
- Menú "Piscinas" visible solo con
piscinas:ver(hookusePermission). - 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
| Ruta | Tipo | Guard |
|---|---|---|
/piscinas | protegida | auth + piscinas:ver (visible en Home si tiene permiso) |
/piscinas/:id | protegida | auth + 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."
Navegació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)
| Archivo | Contenido |
|---|---|
lib/features/pools/pools_list_screen.dart | Lista con filtro + FAB + acciones |
lib/features/pools/pool_detail_screen.dart | Detalle con equipos vigentes/historial |
lib/features/pools/pool_form_sheet.dart | Crear/editar piscina (bottom sheet) |
lib/features/pools/sectors_screen.dart | Gestionar sectores |
lib/features/pools/assign_equipment_sheet.dart | Asignar equipo (bottom sheet) |
lib/features/pools/pools_notifier.dart | PoolsNotifier, SectorsNotifier, PoolDetailNotifier |
lib/features/pools/pools_repository.dart | CRUD API + sync Drift |
lib/shared/database/app_database.dart | Tablas Sectors, Pools, EquipmentTypes, PoolEquipments |
lib/core/permissions/permissions.dart | UserPermissions (guardas en UI) |