Skip to content

Flujo Eventos/Eventualidades

Base URL: /api/v1. Backend: camaroneras_backend (módulo eventos) ✅. Admin: camaroneras_admin (feature eventos) ✅. Mobile: camaroneras_mobile (feature eventos, offline-first) ✅. Sexto módulo de negocio; depende de piscinas (y opcionalmente ciclos). Registro de mortalidades y contratiempos productivos, visibles de inmediato a gerencia (vía RBAC). Captura por piscina con vínculo opcional al 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 ──< Event >── Cycle?   (evento por piscina, ciclo opcional)
  • Event: poolId (obligatorio), cycleId (opcional), date, category, description, affectedCount? (animales afectados, p. ej. mortalidad), notes?.
  • category (enum): mortalidad | enfermedad | clima | equipo | otro.
  • onDelete: Pool → Cascade; Cycle → SetNull (no se pierde el histórico si el ciclo se borra).
  • Sin derivados: pura captura.

RBAC

  • Módulo eventos (label "Eventos/Eventualidades") en el catálogo, clients ["web","mobile"].
  • Defaults: Administrador todo; Técnico ver/crear/editar; Bodeguero ver. Backfill automático.

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

GET /pools/:poolId/events — (eventos:ver)

Lista los eventos de una piscina (más reciente primero).

POST /pools/:poolId/events — (eventos:crear)

Registra un evento. category y description obligatorios; cycleId (si viene) debe pertenecer a esa piscina.

json
// response 400 (categoría inválida)
{ "error": "...", "message": "category must be one of the following values: ...", "statusCode": 400 }
json
// response 404 (piscina no existe / cycleId no pertenece a la piscina)
{ "error": "...", "message": "Ciclo no encontrado para esta piscina", "statusCode": 404 }

PATCH /events/:id — (eventos:editar)

Edita un evento (campos parciales). Si se cambia cycleId, se revalida contra la piscina.

DELETE /events/:id — (eventos:eliminar)

json
// response 404 (no existe o de otra organización)
{ "error": "...", "message": "Evento no encontrado", "statusCode": 404 }

Flujo admin (camaroneras_admin, feature eventos)

  • Ruta: /eventos — protegida (ProtectedRoute module="eventos" action="ver"). Sin sesión → /login; sin permiso eventos:ver → bloqueada y sin item de nav.
  • Selector de piscina (FormCombobox) desde GET /pools. Sin selección → aviso; al elegir aparece la tabla + acciones.
  • Tabla: Fecha · Categoría · Descripción · Afectados · Acciones (estados loading/empty).
  • Diálogo crear/editar: fecha, categoría (combo: mortalidad/enfermedad/clima/equipo/ otro), descripción (requerida), ciclo opcional (combo con "Ninguno" + ciclos de la piscina vía GET /cycles?poolId=), animales afectados (opcional), notas. Botón deshabilitado hasta fecha + descripción.
  • Gating RBAC: "Nuevo evento" con eventos:crear; "Editar" con eventos:editar; "Eliminar" con eventos:eliminar.

Flujo mobile (camaroneras_mobile, feature eventos, offline-first)

  • Rutas: /eventos (picker de piscina) → /eventos/:poolId (eventos). Card en Home solo con canVerEventos. La app ya exige sesión (go_router → /login).
  • Offline-first: tabla Drift Events (schema v8). Lectura siempre desde Drift; sync() refresca desde red y conserva el cache si no hay conexión. El picker lee Pools y hace su propio syncPools(). El combo de ciclo lee la tabla Cycles cacheada (sin red).
  • Escrituras online: crear/editar/eliminar requieren red (_netError → "Sin conexión…"); tras cada escritura se re-sincroniza la piscina.
  • Sheet: categoría (combo, default mortalidad), descripción (requerida), ciclo opcional ("Ninguno" + ciclos de la piscina), animales afectados (opcional), notas.
  • Gating RBAC: FAB "Registrar" con canCrearEventos; editar/eliminar con los suyos.
  • Tests: repository (Drift in-memory: mapeo + replace borra ausentes) + gating del FAB.