Skip to content

Flujo Ciclos — Producción (siembra, llenado, transferencias)

Base URL: /api/v1. Backend: camaroneras_backend (módulo ciclos). Admin/mobile: pendientes (tareas ciclos-admin, ciclos-mobile). Referencia de producto en referencia/produccion-flow.md. Segundo módulo de negocio; depende de piscinas.

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 ──< Cycle ──1:1── Siembra
              ├──< Filling ──< FillingEquipment >── EquipmentType
              └──< Transfer (sourceCycle) ──1:1── Cycle (destino, originTransfer)
  • Cycle: ciclo de producción de una piscina (activa | liberada | cerrada). Entidad central, trazabilidad absoluta. Invariante: una piscina tiene a lo sumo un ciclo activa (lo valida el service; intentar abrir otro → 409). Ver también "Temporada" abajo y ADR-0058 para seasonYear/seasonNumber.
    • liberada: precriadero vaciado por una transferencia completa.
    • cerrada: cosecha final ejecutada, o un ciclo histórico dado de alta ya cosechado (ver "Temporada" — POST /cycles con status: 'cerrada').
    • endDate: fecha de cierre (cosecha final, liberación por transferencia completa, o la cargada a mano en un histórico). null mientras el ciclo sigue abierto.
  • Siembra (1:1 con el ciclo): lo que lo abre. originType:
    • laboratorio: siembra directa (POST /cycles).
    • precriadero: creado por una transferencia (hereda lab/raza/código genético de la madre).
  • Filling (llenado): correlacionado con equipos de bombeo/acondicionamiento (FillingEquipment: equipmentTypeId + horas + variación). Varios por ciclo.
  • Transfer: precriadero→engorde. Crea el ciclo destino en la misma transacción. parcial deja la origen activa (madre); completa la deja liberada.

Derivados (calculados en el service, no se persisten)

  • densidadPorHa = larvas / hectáreas de la piscina.
  • diasEngorde = hoy − fecha de siembra.
  • poblacionRestante = larvas sembradas − Σ transferencias de salida.
  • diasVacio = null hasta que exista cosecha (módulo posterior).

Temporada (seasonYear / seasonNumber)

Las filas "Año" y "Ciclo" del Excel de producción de Winston (ver ADR-0058). Se persisten en Cycle y las asigna el backend:

  • seasonYear = año calendario de startDate (la siembra). Un ciclo sembrado en noviembre y cosechado en febrero pertenece al año de la siembra, no al de la cosecha.
  • seasonNumber = corrida de esa piscina dentro del año, por orden cronológico — no por conteo al momento de crear. Los ciclos duran de 2 a 4 meses y se definen caso a caso, así que el número no se deduce del calendario. Cada alta, edición de fecha o borrado que cambie el conjunto de una (piscina, año) renumera automáticamente los ciclos de esa (piscina, año); un ciclo nacido de una transferencia queda afuera de ese conteo (hereda la temporada de su madre en vez de contar como siembra propia).
  • No son editables directamente salvo como valores explícitos del DTO (ver abajo): la regla de negocio es el orden cronológico, no un contador manual.

Cargar el historial de un año (ciclos ya cosechados)

POST /cycles con status: 'cerrada' + endDate da de alta un ciclo histórico: no exige que la piscina esté libre (a diferencia de un ciclo activa), pero valida que su rango [startDate, endDate] no se solape con ningún otro ciclo de la piscina. Es el camino para cargar corridas anteriores del año en una piscina que ya tiene su ciclo activo — al guardarse, la renumeración automática corre el ciclo activo al número que le corresponde.

RBAC

  • Módulo ciclos en el catálogo, clients ["web","mobile"].
  • Permisos por defecto al crear organización: Administrador todos; Técnico ver/crear/editar; Bodeguero ver.
  • Backfill: las orgs creadas ANTES del módulo reciben los defaults de ciclos automáticamente al arrancar (solo si el rol por defecto no tenía ya permisos del módulo; conservador, no re-agrega permisos quitados a propósito en módulos existentes).

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

Ciclos

GET /cycles — (ciclos:ver)

Lista los ciclos de la organización. Filtros opcionales por query: poolId, status, seasonYear, seasonNumber.

GET /cycles/:id — (ciclos:ver)

Detalle de un ciclo: siembra, derivados, llenados (con equipos), transferencias de salida y el origen (si fue creado por una transferencia).

json
// response 404 (no existe)
{ "error": "...", "message": "Ciclo no encontrado", "statusCode": 404 }

POST /cycles — (ciclos:crear)

Abre un ciclo vía siembra directa de laboratorio. La piscina debe estar libre. Auto-asigna seasonYear/seasonNumber (ver "Temporada" arriba).

Con status: 'cerrada' + endDate da de alta un ciclo histórico en vez de abrir uno nuevo: no exige piscina libre, pero requiere endDate y valida que el rango no se solape con otro ciclo de la piscina.

json
// response 409 (piscina ocupada, ciclo nuevo activa)
{ "error": "...", "message": "La piscina ya tiene un ciclo activo", "statusCode": 409 }
json
// response 400 (histórico sin fecha de cierre)
{ "error": "...", "message": "Un ciclo histórico requiere la fecha de cierre (endDate)", "statusCode": 400 }
json
// response 409 (rango solapado con otro ciclo de la piscina)
{ "error": "...", "message": "El rango del ciclo se solapa con otro ciclo de la piscina", "statusCode": 409 }

PATCH /cycles/:id — (ciclos:editar)

Edita los datos de la siembra (campos parciales). Si cambia date, también mueve el inicio del ciclo y recalcula seasonYear/seasonNumber (renumerando la temporada afectada; dos temporadas si el ciclo cruza de año), salvo que el body los mande explícitos.

json
// response 409 (la nueva fecha solapa con otro ciclo de la piscina)
{ "error": "...", "message": "El rango del ciclo se solapa con otro ciclo de la piscina", "statusCode": 409 }

DELETE /cycles/:id — (ciclos:eliminar)

Elimina un ciclo. Solo si no tiene transferencias de salida ni fue creado por una transferencia.

json
// response 400 (tiene dependientes)
{ "error": "...", "message": "No se puede eliminar: el ciclo tiene transferencias de salida", "statusCode": 400 }

Llenado

POST /cycles/:id/fillings — (ciclos:editar)

Registra un llenado del ciclo con sus equipos de bombeo/acondicionamiento.

json
// response 400 (tipo de equipo inválido)
{ "error": "...", "message": "Tipo de equipo inválido", "statusCode": 400 }

DELETE /fillings/:id — (ciclos:eliminar)

Elimina un llenado.

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

Transferencias

POST /transfers — (ciclos:crear)

Transfiere animales de un ciclo de precriadero a una piscina de engorde (libre). Crea el ciclo destino (siembra origen=precriadero, hereda trazabilidad de la madre) en la misma transacción. parcial deja la madre activa; completa la deja liberada.

Validaciones: origen activa y de tipo precriadero; destino de tipo engorde y distinto al origen; quantity ≤ restante; parcial debe dejar restante > 0; completa debe igualar el restante.

json
// response 400 (reglas de negocio)
{ "error": "...", "message": "Solo se transfiere desde un ciclo de precriadero", "statusCode": 400 }
{ "error": "...", "message": "La piscina destino debe ser de engorde", "statusCode": 400 }
{ "error": "...", "message": "Una transferencia completa debe transferir todo el restante (400000)", "statusCode": 400 }
json
// response 409 (piscina destino ocupada)
{ "error": "...", "message": "La piscina ya tiene un ciclo activo", "statusCode": 409 }