Apariencia
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 cicloactiva(lo valida el service; intentar abrir otro → 409). Ver también "Temporada" abajo y ADR-0058 paraseasonYear/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 /cyclesconstatus: 'cerrada').endDate: fecha de cierre (cosecha final, liberación por transferencia completa, o la cargada a mano en un histórico).nullmientras 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.
parcialdeja la origenactiva(madre);completala dejaliberada.
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 destartDate(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
ciclosen el catálogo, clients["web","mobile"]. - Permisos por defecto al crear organización: Administrador todos; Técnico
ver/crear/editar; Bodeguerover. - Backfill: las orgs creadas ANTES del módulo reciben los defaults de
ciclosautomá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 }