openapi: 3.0.3
info:
  title: Hydrobit API
  description: >
    Comprehensive API documentation for the Hydrobit platform.


    This API enables management of forms, records, workers, projects, and
    related data within Hydrobit.


    ## Authentication

    - **User endpoints:** Require a JWT in the `Authorization` header (Bearer
    token).


    ## Features

    - Form management and record submission

    - Project and sector data retrieval

    - Worker management and operations

    - Production session and record management


    > **Note:** API access is available for Corporate accounts. For access or
    support, contact support@hydrobit.ag.
  version: 2.0.0
  contact:
    name: Hydrobit Support
    url: https://hydrobit.ag
    email: support@hydrobit.ag
  license:
    name: Proprietary
    url: https://www.hydrobit.ag/terms-conditions
servers:
  - url: https://api.hydrobit.ag
    description: Production server
  - url: https://iot.hydrobit.ag
    description: IoT server
  - url: wss://iot.hydrobit.ag
    description: MQTT over WebSocket endpoint
tags:
  - name: Forms
    description: Endpoints for form management and record submission
  - name: Projects
    description: Endpoints for project and sector data retrieval
  - name: Workers
    description: Endpoints for worker management and operations
  - name: Production Sessions
    description: Endpoints for managing production sessions (open, close, update)
  - name: Production Records
    description: Endpoints for creating and managing production records
  - name: Phytosanitary
    description: >-
      Endpoints for phytosanitary monitoring and analysis (pest/disease
      tracking)
  - name: Weather
    description: Endpoints for weather and climate data retrieval
  - name: Plant
    description: Endpoints for plant monitoring, growth, and health data
  - name: Irrigation
    description: Endpoints for irrigation control, logs, and automation
  - name: IoT Endpoint
    description: Endpoints for IoT devices, telemetry, and field data integration
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT token for authentication
    apiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Static or scoped API token for IoT data endpoints (weather, plant,
        irrigation)
    iotToken:
      type: apiKey
      in: header
      name: x-iot-token
      description: Short-lived token for MQTT over WebSocket authentication
  schemas:
    Error:
      type: object
      properties:
        status:
          type: string
          example: error
        message:
          type: string
          example: Error message description
        error:
          type: string
          example: Detailed error information
    Form:
      type: object
      properties:
        _id:
          type: string
          description: Form unique identifier
          example: form_abc123
        name:
          type: string
          description: Form name
          example: Hydrobit Inspection
        formKey:
          type: string
          description: Form key identifier
          example: inspection_v1
        type:
          type: string
          description: Form type
          example: inspection
        subtype:
          type: string
          description: Form subtype (e.g., monitoring, task)
          example: monitoring
        companyId:
          type: string
          description: Company identifier
          example: comp_12345
        status:
          type: string
          description: Form status (e.g., published)
          example: published
        elements:
          type: array
          description: Form field elements
          items:
            type: object
        category:
          type: string
          description: Form category name
          example: phytosanitary
        categoryId:
          type: string
          description: Category identifier
          example: cat_001
        taskId:
          type: string
          nullable: true
          description: Associated task identifier
          example: null
        isTemporal:
          type: boolean
          description: Whether the form is temporary
          example: false
    Record:
      type: object
      required:
        - _id
        - auth0Id
        - type
      properties:
        _id:
          type: string
          description: Form ID (not the record ID)
          example: form_abc123
        auth0Id:
          type: string
          description: User identifier
          example: user_123456789
        type:
          type: string
          description: Record type
          example: inspection
        latitude:
          type: number
          format: double
          description: GPS latitude
          example: 19.4326
        longitude:
          type: number
          format: double
          description: GPS longitude
          example: -99.1332
        form_id:
          type: string
          description: Form identifier
          example: form_abc123
        form_values:
          type: array
          description: Array of form field values
          items:
            type: object
        inicio:
          type: string
          format: date-time
          description: Record start time
          example: '2025-10-09T10:00:00Z'
        segundos:
          type: number
          description: Time spent on record (in seconds)
          example: 120
    RecordResponse:
      type: object
      properties:
        status:
          type: string
          example: success
        verification:
          type: object
          properties:
            project:
              type: boolean
              description: Whether the location is verified within a project
              example: true
            sector:
              type: boolean
              description: Whether the location is verified within a sector
              example: true
    ProjectWithSectors:
      type: object
      properties:
        projectId:
          type: string
          description: Project unique identifier
          example: proj_123
        projectName:
          type: string
          description: Project name
          example: Rancho San Pedro
        sectors:
          type: array
          description: List of sectors in the project
          items:
            type: object
            properties:
              name:
                type: string
                description: Sector name
                example: Lote A-1
              sectorId:
                type: string
                description: Sector unique identifier
                example: sector_001
    TimeRangeQuery:
      type: object
      required:
        - projectId
        - from
        - to
        - timezone
      properties:
        projectId:
          type: string
          description: Project identifier
          example: proj_123
        from:
          type: string
          format: date-time
          description: Start datetime in local timezone (ISO 8601)
          example: '2025-10-01T00:00:00-06:00'
        to:
          type: string
          format: date-time
          description: End datetime in local timezone (ISO 8601)
          example: '2025-10-09T23:59:59-06:00'
        timezone:
          type: string
          description: IANA timezone for interpretation of local range
          example: America/Mexico_City
    Worker:
      type: object
      properties:
        workerId:
          type: string
          description: Worker unique identifier
          example: 6507f1e3c9d8e2a4b1f9c3d5
        worker_id:
          type: string
          description: Worker business ID (INITIALS + MMDD of hire date)
          example: JS0315
        first_name:
          type: string
          description: Worker first name
          example: John
        last_name:
          type: string
          description: Worker last name
          example: Smith
        phone:
          type: string
          description: Worker phone number
          example: '+1234567890'
        email:
          type: string
          description: Worker email address
          example: john.smith@example.com
        position:
          type: string
          description: Worker position
          example: field_worker
        employment_type:
          type: string
          description: Employment type
          example: full_time
        hire_date:
          type: string
          format: date
          description: Hire date in YYYY-MM-DD format
          example: '2025-03-15'
        termination_date:
          type: string
          format: date
          nullable: true
          description: Termination date in YYYY-MM-DD format (null if still employed)
          example: null
        status:
          type: string
          description: Worker status
          example: active
        hourly_rate:
          type: number
          description: Hourly rate (if applicable)
          example: 18.5
        emergency_contact_name:
          type: string
          description: Emergency contact name
          example: Jane Smith
        emergency_contact_phone:
          type: string
          description: Emergency contact phone number
          example: '+1234567891'
        skills:
          type: array
          description: Worker skills
          items:
            type: string
          example:
            - irrigation
            - pruning
        notes:
          type: string
          description: Additional notes about the worker
          example: Experienced in avocado cultivation
        photo:
          type: string
          nullable: true
          description: URL of worker photo
          example: https://storage.example.com/photos/worker-photo.jpg
    WorkerResponse:
      type: object
      properties:
        status:
          type: string
          example: success
        data:
          type: object
          properties:
            workerId:
              type: string
              description: Worker unique identifier
              example: 6507f1e3c9d8e2a4b1f9c3d5
            worker_id:
              type: string
              description: Business worker ID
              example: JS0315
            photoUrl:
              type: string
              nullable: true
              description: URL of the uploaded photo (if photo was provided)
              example: https://storage.example.com/photos/worker-photo.jpg
    Bonus:
      type: object
      required:
        - type
        - value
      properties:
        type:
          type: string
          enum:
            - flat
            - percent
          description: Bonus type (flat amount or percentage)
          example: percent
        value:
          type: number
          minimum: 0
          description: Bonus value (0-100 for percent, any positive for flat)
          example: 10
        note:
          type: string
          description: Optional note about the bonus
          example: High quality bonus
    ProductionSession:
      type: object
      properties:
        sessionId:
          type: string
          description: Session unique identifier
          example: 6507f1e3c9d8e2a4b1f9c3d5
        companyId:
          type: string
          description: Company identifier
          example: comp_12345
        projectId:
          type: string
          description: Project identifier
          example: proj_123
        auth0Id:
          type: string
          description: User identifier who opened the session
          example: user_123456789
        sectorId:
          type: string
          description: Sector identifier
          example: sector_001
        sector_name:
          type: string
          description: Sector name
          example: Lote A-1
        timezone:
          type: string
          description: Project timezone
          example: America/Mexico_City
        day:
          type: string
          format: date-time
          description: Session day (normalized to start of day in project timezone)
          example: '2025-10-19T00:00:00.000Z'
        status:
          type: string
          enum:
            - open
            - closed
          description: Session status
          example: open
        openedAt:
          type: string
          format: date-time
          description: Timestamp when the session was opened (ISO 8601 UTC)
          example: '2025-10-19T08:00:00.000Z'
        closedAt:
          type: string
          format: date-time
          nullable: true
          description: When the session was closed (null if still open)
          example: null
        maxDurationHours:
          type: integer
          minimum: 1
          maximum: 24
          description: Maximum duration in hours
          example: 12
        samplingUnit:
          type: string
          description: Unit of measurement (e.g., box, kg, ton)
          example: box
        pricePerUnit:
          type: number
          minimum: 0
          description: Price per unit
          example: 5.5
        bonus:
          $ref: '#/components/schemas/Bonus'
        totals:
          type: object
          properties:
            totalUnits:
              type: number
              description: Total units recorded in this session
              example: 150
        notes:
          type: string
          maxLength: 500
          description: Optional notes about the session
          example: Good weather conditions
    ProductionSessionResponse:
      type: object
      properties:
        status:
          type: string
          example: success
        data:
          $ref: '#/components/schemas/ProductionSession'
    ProductionRecord:
      type: object
      required:
        - sessionId
        - workerId
        - worker_name
        - recordedAtUtc
        - clientEventId
      properties:
        sessionId:
          type: string
          description: Session identifier
          example: 6507f1e3c9d8e2a4b1f9c3d5
        workerId:
          type: string
          description: Worker identifier
          example: JS0315
        worker_name:
          type: string
          description: Worker full name
          example: John Smith
        units:
          type: number
          minimum: 0
          description: Number of units to record (defaults to 1)
          example: 5
        recordedAtUtc:
          type: string
          format: date-time
          description: Timestamp when the record was created (ISO 8601 UTC)
          example: '2025-10-19T14:30:00.000Z'
        idempotencyKey:
          type: string
          description: Idempotency key for deduplication
          example: mobile-app-key-timestamp-123456
        clientEventId:
          type: string
          description: Unique client-side event identifier for idempotency
          example: evt_1234567890abcdef
    ProductionRecordResponse:
      type: object
      properties:
        status:
          type: string
          example: success
        data:
          type: object
          properties:
            payloadPreview:
              type: object
              properties:
                recordId:
                  type: string
                  description: Generated record identifier
                  example: rec_1234567890abcdef
                sessionId:
                  type: string
                  example: 6507f1e3c9d8e2a4b1f9c3d5
                workerId:
                  type: string
                  example: JS0315
                units:
                  type: number
                  example: 5
            idempotent:
              type: boolean
              description: Whether this was an idempotent replay
              example: false
    WeatherPoint:
      type: object
      properties:
        local_time:
          type: string
          format: date-time
          example: '2025-10-09T10:00:00-06:00'
        te:
          type: number
          description: Temperature (°C)
          example: 26.4
        hr:
          type: number
          description: Relative humidity (%)
          example: 60
        wind:
          type: number
          description: Wind speed (m/s)
          example: 3.2
        vpd:
          type: number
          description: Vapor Pressure Deficit (kPa)
          example: 1.2
    WeatherSeriesResponse:
      type: object
      properties:
        status:
          type: string
          example: success
        data:
          type: array
          items:
            $ref: '#/components/schemas/WeatherPoint'
    PlantPoint:
      type: object
      properties:
        local_time:
          type: string
          format: date-time
          example: '2025-10-09T10:00:00-06:00'
        vigor_index:
          type: number
          description: Vegetation vigor index (0-1)
          example: 0.78
        incidence:
          type: number
          description: Pest/disease incidence (%)
          example: 12.5
        severity:
          type: number
          description: Severity scale (0-1)
          example: 0.32
    PlantSeriesResponse:
      type: object
      properties:
        status:
          type: string
          example: success
        data:
          type: array
          items:
            $ref: '#/components/schemas/PlantPoint'
    IrrigationEvent:
      type: object
      properties:
        start_local:
          type: string
          format: date-time
          example: '2025-10-09T07:15:00-06:00'
        end_local:
          type: string
          format: date-time
          example: '2025-10-09T08:00:00-06:00'
        duration_min:
          type: number
          example: 45
        volume_l:
          type: number
          example: 1200
        sectorId:
          type: string
          example: sector_001
    IrrigationSeriesResponse:
      type: object
      properties:
        status:
          type: string
          example: success
        data:
          type: array
          items:
            $ref: '#/components/schemas/IrrigationEvent'
    PhytosanitaryPestDisease:
      type: object
      properties:
        pest_disease_name:
          type: string
          description: Name of the pest or disease
          example: Aphids
        incidence:
          type: number
          description: Percentage of affected plants (0-100)
          example: 15.5
        severity:
          type: number
          description: Severity level (0-100)
          example: 25
    PhytosanitarySector:
      type: object
      properties:
        sector_name:
          type: string
          description: Sector name
          example: Lote A-1
        incidence:
          type: number
          description: Overall incidence for the sector (0-100)
          example: 12.3
        severity:
          type: number
          description: Overall severity for the sector (0-100)
          example: 18.5
        pests_diseases:
          type: array
          description: List of pests and diseases detected in this sector
          items:
            $ref: '#/components/schemas/PhytosanitaryPestDisease'
    PhytosanitaryProject:
      type: object
      properties:
        projectId:
          type: string
          nullable: true
          description: Project identifier
          example: proj_123
        projectName:
          type: string
          nullable: true
          description: Project name
          example: Rancho San Pedro
        sectors:
          type: array
          description: List of sectors with phytosanitary data
          items:
            $ref: '#/components/schemas/PhytosanitarySector'
    PhytosanitarySummaryResponse:
      type: object
      properties:
        status:
          type: string
          example: success
        scope:
          type: string
          enum:
            - company
            - project
            - sector
          description: Scope of the query (company, project, or sector)
          example: project
        range:
          type: object
          properties:
            startTime:
              type: string
              description: Start date in YYYY-MM-DD format
              example: '2025-10-01'
            endTime:
              type: string
              description: End date in YYYY-MM-DD format
              example: '2025-10-09'
        projectTimezone:
          type: string
          description: Timezone used for the project
          example: America/Mexico_City
        filters:
          type: object
          properties:
            companyId:
              type: string
              nullable: true
              example: comp_12345
            projectId:
              type: string
              nullable: true
              example: proj_123
            sectorId:
              type: string
              nullable: true
              example: sector_001
        totalRecords:
          type: integer
          description: Total number of phytosanitary form records
          example: 42
        totalProjects:
          type: integer
          description: Total number of projects (only for company scope)
          example: 3
        totalSectors:
          type: integer
          description: Total number of sectors (for project/sector scope)
          example: 5
        projects:
          type: array
          description: Projects with nested sectors (only for company scope)
          items:
            $ref: '#/components/schemas/PhytosanitaryProject'
        sectors:
          type: array
          description: Sectors with phytosanitary data (for project/sector scope)
          items:
            $ref: '#/components/schemas/PhytosanitarySector'
    MqttConnectInfo:
      type: object
      properties:
        url:
          type: string
          example: wss://iot.hydrobit.ag/mqtt
        deviceId:
          type: string
          example: bitRTU-01
        topic:
          type: string
          description: MQTT topic to publish/subscribe
          example: connect/comp_123/proj_123/bitRTU-01/#
        protocol:
          type: string
          example: MQTT v3.1.1 over WebSocket
paths:
  /api/control/forms/getForms:
    get:
      tags:
        - Forms
      summary: Get forms for a user
      description: |
        Retrieves all forms accessible to a user based on their company context.

        Requires JWT token authentication in the Authorization header.

        - If `subtype=tasks`, returns all task forms for the user
        - Otherwise, returns monitoring forms based on user access permissions
      operationId: getForms
      security:
        - bearerAuth: []
      parameters:
        - name: auth0Id
          in: query
          required: true
          description: User identifier
          schema:
            type: string
          example: user_123456789
        - name: companyId
          in: query
          required: true
          description: Company identifier
          schema:
            type: string
          example: comp_12345
        - name: type
          in: query
          required: false
          description: Form type filter
          schema:
            type: string
          example: inspection
        - name: subtype
          in: query
          required: false
          description: Form subtype (e.g., 'tasks' for task forms)
          schema:
            type: string
          example: tasks
      responses:
        '200':
          description: Forms retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Form'
        '400':
          description: Bad request - missing required parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/control/forms/record:
    post:
      tags:
        - Forms
      summary: Submit a form record
      description: |
        Submits a new form record with field values and location data.

        This endpoint:
        - Validates the record data
        - Processes form field values
        - Determines location (project/sector) using GPS or forced location
        - Saves data to persistent storage
        - Uploads media files (photos, audio, signatures) to cloud storage
        - Updates task status if applicable
      operationId: submitRecord
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - record
              properties:
                record:
                  $ref: '#/components/schemas/Record'
            examples:
              basicRecord:
                summary: Basic record submission
                value:
                  record:
                    _id: form_abc123
                    auth0Id: user_123456789
                    type: inspection
                    latitude: 19.4326
                    longitude: -99.1332
                    form_id: form_abc123
                    form_values:
                      - field_temp: '25'
                      - field_notes: All looking good
                    inicio: '2025-10-09T10:00:00Z'
                    segundos: 120
      responses:
        '201':
          description: Record created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordResponse'
        '400':
          description: Bad request - invalid record data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/control/projects:
    get:
      tags:
        - Projects
      summary: Get projects with sectors
      description: >
        Retrieves all projects for the authenticated user's company with nested
        sector information.


        Requires JWT token authentication in the Authorization header.


        This endpoint:

        - Automatically resolves the user's company context from their JWT token

        - Returns all projects associated with the company

        - For each project, includes nested sectors with only name and sectorId
        fields

        - Only returns projectId and projectName for each project
      operationId: getProjects
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Projects retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ProjectWithSectors'
              examples:
                successResponse:
                  summary: Successful response with projects and sectors
                  value:
                    status: success
                    data:
                      - projectId: proj_123
                        projectName: Rancho San Pedro
                        sectors:
                          - name: Lote A-1
                            sectorId: sector_001
                          - name: Lote A-2
                            sectorId: sector_002
                      - projectId: proj_456
                        projectName: Finca Los Pinos
                        sectors:
                          - name: Bloque Norte
                            sectorId: sector_003
        '400':
          description: Bad request - user does not have associated companyId
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: User does not have an associated companyId
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Token has expired or is invalid
        '404':
          description: User not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: User not found
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Error fetching projects
  /api/control/workers:
    post:
      tags:
        - Workers
      summary: Create a new worker
      description: >
        Creates a new worker record with automatic ID generation and optional
        photo upload.


        Key features:

        - Worker ID is auto-generated as INITIALS + MMDD (from hire date)

        - Requires JWT token authentication in the Authorization header

        - Company context is inferred from the authenticated user

        - Validates position, employment_type, and status against template
        options

        - Ensures worker_id uniqueness per company (idempotent on duplicate)

        - Supports photo upload via base64 data URL (JPEG, PNG, WebP up to 12MB)

        - Photos are uploaded to cloud storage and URL is returned in response
      operationId: createWorker
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - auth0Id
                - first_name
                - last_name
                - hire_date
                - position
                - employment_type
              properties:
                auth0Id:
                  type: string
                  description: User identifier (internal use, derived from JWT token)
                  example: user_123456789
                first_name:
                  type: string
                  description: Worker first name
                  example: John
                last_name:
                  type: string
                  description: Worker last name
                  example: Smith
                phone:
                  type: string
                  description: Worker phone number
                  example: '+1234567890'
                email:
                  type: string
                  description: Worker email address
                  example: john.smith@example.com
                position:
                  type: string
                  description: Worker position (must match template options)
                  example: field_worker
                employment_type:
                  type: string
                  description: Employment type (must match template options)
                  example: full_time
                hire_date:
                  type: string
                  format: date
                  description: Hire date in YYYY-MM-DD format (used to generate worker_id)
                  example: '2025-03-15'
                termination_date:
                  type: string
                  format: date
                  nullable: true
                  description: Termination date in YYYY-MM-DD format (optional)
                  example: null
                status:
                  type: string
                  description: Worker status (defaults to 'active')
                  example: active
                hourly_rate:
                  type: number
                  description: Hourly rate (must be >= 0)
                  example: 18.5
                emergency_contact_name:
                  type: string
                  description: Emergency contact name
                  example: Jane Smith
                emergency_contact_phone:
                  type: string
                  description: Emergency contact phone number
                  example: '+1234567891'
                skills:
                  type: array
                  description: Array of worker skills (filtered by template options)
                  items:
                    type: string
                  example:
                    - irrigation
                    - pruning
                notes:
                  type: string
                  description: Additional notes about the worker
                  example: Experienced in avocado cultivation
                photo:
                  type: string
                  description: >
                    Worker photo in base64 data URL format (optional).

                    Supported formats: JPEG, PNG, WebP.

                    Maximum size: 12MB.

                    Format: data:image/jpeg;base64,/9j/4AAQ... or
                    data:image/png;base64,iVBORw0KG...
                  example: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...
            examples:
              basicWorker:
                summary: Basic worker creation
                value:
                  auth0Id: user_123456789
                  first_name: John
                  last_name: Smith
                  hire_date: '2025-03-15'
                  position: field_worker
                  employment_type: full_time
              fullWorker:
                summary: Complete worker with all fields
                value:
                  auth0Id: user_123456789
                  first_name: John
                  last_name: Smith
                  phone: '+1234567890'
                  email: john.smith@example.com
                  position: field_worker
                  employment_type: full_time
                  hire_date: '2025-03-15'
                  status: active
                  hourly_rate: 18.5
                  emergency_contact_name: Jane Smith
                  emergency_contact_phone: '+1234567891'
                  skills:
                    - irrigation
                    - pruning
                  notes: Experienced in avocado cultivation
              workerWithPhoto:
                summary: Worker with photo
                value:
                  auth0Id: user_123456789
                  first_name: John
                  last_name: Smith
                  hire_date: '2025-03-15'
                  position: field_worker
                  employment_type: full_time
                  photo: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...
      responses:
        '200':
          description: Worker already exists (idempotent response)
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: exists
                  message:
                    type: string
                    example: worker already exists
                  data:
                    type: object
                    properties:
                      workerId:
                        type: string
                        example: 6507f1e3c9d8e2a4b1f9c3d5
                      worker_id:
                        type: string
                        example: JS0315
        '201':
          description: Worker created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkerResponse'
              examples:
                withoutPhoto:
                  summary: Worker created without photo
                  value:
                    status: success
                    data:
                      workerId: 6507f1e3c9d8e2a4b1f9c3d5
                      worker_id: JS0315
                      photoUrl: null
                withPhoto:
                  summary: Worker created with photo
                  value:
                    status: success
                    data:
                      workerId: 6507f1e3c9d8e2a4b1f9c3d5
                      worker_id: JS0315
                      photoUrl: https://storage.example.com/photos/worker-photo.jpg
        '400':
          description: Bad request - validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingAuth0Id:
                  summary: Missing user identifier
                  value:
                    status: error
                    message: auth0Id is required at top-level body
                missingRequiredFields:
                  summary: Missing required fields
                  value:
                    status: error
                    message: >-
                      first_name, last_name, hire_date, position,
                      employment_type are required
                invalidHireDate:
                  summary: Invalid hire date format
                  value:
                    status: error
                    message: Invalid hire_date format (expected YYYY-MM-DD)
                invalidPosition:
                  summary: Invalid position value
                  value:
                    status: error
                    message: >-
                      position must be one of: field_worker, supervisor,
                      technician
                invalidHourlyRate:
                  summary: Invalid hourly rate
                  value:
                    status: error
                    message: hourly_rate must be a number >= 0
                invalidPhotoFormat:
                  summary: Invalid photo format
                  value:
                    status: error
                    message: >-
                      invalid data URL format (expect
                      data:image/jpeg|png|webp;base64,...)
        '401':
          description: Unauthorized - user not found or invalid token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: User not found
        '404':
          description: Workers database not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Workers database not found
        '413':
          description: Photo too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: photo too large (max 12MB)
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Error creating worker
                error: Detailed error message
    get:
      tags:
        - Workers
      summary: Get list of workers
      description: >
        Retrieves a paginated list of workers for the authenticated user's
        company.


        Features:

        - Requires JWT token authentication in the Authorization header

        - Company context is inferred from the authenticated user

        - Supports search by name or worker_id using the 'q' parameter

        - Filter by status, position, or employment_type

        - Pagination with customizable page size (default 25, max 100)

        - Results sorted by creation date (newest first)
      operationId: listWorkers
      security:
        - bearerAuth: []
      parameters:
        - name: auth0Id
          in: query
          required: true
          description: User identifier (internal use, can be derived from JWT token)
          schema:
            type: string
          example: user_123456789
        - name: companyId
          in: query
          required: false
          description: Company identifier (must match user's company, otherwise forbidden)
          schema:
            type: string
          example: comp_12345
        - name: q
          in: query
          required: false
          description: Search query (searches in first_name, last_name, and worker_id)
          schema:
            type: string
          example: John
        - name: status
          in: query
          required: false
          description: Filter by worker status
          schema:
            type: string
          example: active
        - name: position
          in: query
          required: false
          description: Filter by worker position
          schema:
            type: string
          example: field_worker
        - name: employment_type
          in: query
          required: false
          description: Filter by employment type
          schema:
            type: string
          example: full_time
        - name: page
          in: query
          required: false
          description: Page number (default 1)
          schema:
            type: integer
            minimum: 1
            default: 1
          example: 1
        - name: limit
          in: query
          required: false
          description: Number of items per page (default 25, max 100)
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          example: 25
      responses:
        '200':
          description: Workers retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  data:
                    type: object
                    properties:
                      total:
                        type: integer
                        description: Total number of workers matching the query
                        example: 42
                      page:
                        type: integer
                        description: Current page number
                        example: 1
                      pageSize:
                        type: integer
                        description: Number of items per page
                        example: 25
                      items:
                        type: array
                        items:
                          allOf:
                            - $ref: '#/components/schemas/Worker'
                            - type: object
                              properties:
                                _id:
                                  type: string
                                  description: unique identifier
                                  example: 6507f1e3c9d8e2a4b1f9c3d5
                                name:
                                  type: string
                                  description: Full name (first + last)
                                  example: John Smith
                                companyId:
                                  type: string
                                  example: comp_12345
                                companyName:
                                  type: string
                                  example: Hydrobit Client SA
                                createdAt:
                                  type: string
                                  format: date-time
                                  example: '2025-03-15T10:30:00.000Z'
                                updatedAt:
                                  type: string
                                  format: date-time
                                  example: '2025-03-15T10:30:00.000Z'
              examples:
                successResponse:
                  summary: Successful response with workers
                  value:
                    status: success
                    data:
                      total: 42
                      page: 1
                      pageSize: 25
                      items:
                        - _id: 6507f1e3c9d8e2a4b1f9c3d5
                          worker_id: JS0315
                          first_name: John
                          last_name: Smith
                          name: John Smith
                          phone: '+1234567890'
                          email: john.smith@example.com
                          position: field_worker
                          employment_type: full_time
                          hire_date: '2025-03-15'
                          termination_date: null
                          status: active
                          hourly_rate: 18.5
                          emergency_contact_name: Jane Smith
                          emergency_contact_phone: '+1234567891'
                          skills:
                            - irrigation
                            - pruning
                          notes: Experienced in avocado cultivation
                          photo: https://storage.example.com/photos/worker-photo.jpg
                          companyId: comp_12345
                          companyName: Hydrobit Client SA
                          createdAt: '2025-03-15T10:30:00.000Z'
                          updatedAt: '2025-03-15T10:30:00.000Z'
        '400':
          description: Bad request - missing user identifier or user has no company context
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingAuth0Id:
                  summary: Missing user identifier parameter
                  value:
                    status: error
                    message: auth0Id is required as query param
                noCompanyContext:
                  summary: User has no company context
                  value:
                    status: error
                    message: User has no company context
        '401':
          description: Unauthorized - user not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: User not found
        '403':
          description: Forbidden - companyId does not match user's company
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: 'Forbidden: companyId does not match user context'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Error fetching workers
                error: Detailed error message
  /api/control/workers/{worker}:
    get:
      tags:
        - Workers
      summary: Get a single worker
      description: >
        Retrieves detailed information about a specific worker by unique
        identifier or worker_id.


        Features:

        - Requires JWT token authentication in the Authorization header

        - Company context is inferred from the authenticated user

        - Can fetch by unique identifier or by worker_id (e.g., "JS0315")

        - Returns full worker details including all fields
      operationId: getWorker
      security:
        - bearerAuth: []
      parameters:
        - name: worker
          in: path
          required: true
          description: Worker identifier (unique id or worker_id like "JS0315")
          schema:
            type: string
          examples:
            byUniqueId:
              summary: By unique identifier
              value: 6507f1e3c9d8e2a4b1f9c3d5
            byWorkerId:
              summary: By business worker id
              value: JS0315
        - name: auth0Id
          in: query
          required: true
          description: User identifier (internal use, can be derived from JWT token)
          schema:
            type: string
          example: user_123456789
      responses:
        '200':
          description: Worker retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  data:
                    allOf:
                      - $ref: '#/components/schemas/Worker'
                      - type: object
                        properties:
                          _id:
                            type: string
                            description: unique identifier
                            example: 6507f1e3c9d8e2a4b1f9c3d5
                          name:
                            type: string
                            description: Full name (first + last)
                            example: John Smith
                          companyId:
                            type: string
                            example: comp_12345
                          companyName:
                            type: string
                            example: Hydrobit Client SA
                          createdAt:
                            type: string
                            format: date-time
                            example: '2025-03-15T10:30:00.000Z'
                          updatedAt:
                            type: string
                            format: date-time
                            example: '2025-03-15T10:30:00.000Z'
              example:
                status: success
                data:
                  _id: 6507f1e3c9d8e2a4b1f9c3d5
                  worker_id: JS0315
                  first_name: John
                  last_name: Smith
                  name: John Smith
                  phone: '+1234567890'
                  email: john.smith@example.com
                  position: field_worker
                  employment_type: full_time
                  hire_date: '2025-03-15'
                  termination_date: null
                  status: active
                  hourly_rate: 18.5
                  emergency_contact_name: Jane Smith
                  emergency_contact_phone: '+1234567891'
                  skills:
                    - irrigation
                    - pruning
                  notes: Experienced in avocado cultivation
                  photo: https://storage.example.com/photos/worker-photo.jpg
                  companyId: comp_12345
                  companyName: Hydrobit Client SA
                  createdAt: '2025-03-15T10:30:00.000Z'
                  updatedAt: '2025-03-15T10:30:00.000Z'
        '400':
          description: Bad request - missing user identifier or user has no company context
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingAuth0Id:
                  summary: Missing user identifier parameter
                  value:
                    status: error
                    message: auth0Id is required as query param
                noCompanyContext:
                  summary: User has no company context
                  value:
                    status: error
                    message: User has no company context
        '401':
          description: Unauthorized - user not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: User not found
        '404':
          description: Worker not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Worker not found
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Error fetching worker
                error: Detailed error message
  /api/control/phytosanitary/summary:
    get:
      tags:
        - Phytosanitary
      summary: Get phytosanitary monitoring summary
      description: >
        Retrieves aggregated phytosanitary data (pest and disease
        incidence/severity) for a specified time range.


        Requires API token authentication with `read:phytosanitary` scope via
        the `x-api-key` header.


        Key features:

        - Aggregates data from phytosanitary form records

        - Supports filtering by company, project, or sector

        - Returns incidence and severity metrics per sector

        - Maximum range: 180 days

        - Dates must be in YYYY-MM-DD format (local time, no timezone suffix)

        - Timezone is automatically resolved from the project settings
      operationId: getPhytosanitarySummary
      security:
        - apiKeyAuth: []
      parameters:
        - name: companyId
          in: query
          required: false
          description: >-
            Company identifier (at least one of companyId, projectId, or
            sectorId is required)
          schema:
            type: string
          example: comp_12345
        - name: projectId
          in: query
          required: false
          description: >-
            Project identifier (at least one of companyId, projectId, or
            sectorId is required)
          schema:
            type: string
          example: proj_123
        - name: sectorId
          in: query
          required: false
          description: >-
            Sector identifier (at least one of companyId, projectId, or sectorId
            is required)
          schema:
            type: string
          example: sector_001
        - name: startTime
          in: query
          required: true
          description: Start date in YYYY-MM-DD format (local time, no timezone suffix)
          schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          example: '2025-10-01'
        - name: endTime
          in: query
          required: true
          description: End date in YYYY-MM-DD format (local time, no timezone suffix)
          schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          example: '2025-10-09'
      responses:
        '200':
          description: Phytosanitary summary retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PhytosanitarySummaryResponse'
              examples:
                sectorScope:
                  summary: Summary for a single sector
                  value:
                    status: success
                    scope: sector
                    range:
                      startTime: '2025-10-01'
                      endTime: '2025-10-09'
                    projectTimezone: America/Mexico_City
                    filters:
                      companyId: null
                      projectId: null
                      sectorId: sector_001
                    totalRecords: 12
                    totalSectors: 1
                    sectors:
                      - sector_name: Lote A-1
                        incidence: 12.5
                        severity: 18.3
                        pests_diseases:
                          - pest_disease_name: Aphids
                            incidence: 15.5
                            severity: 25
                          - pest_disease_name: Powdery mildew
                            incidence: 8.2
                            severity: 12.5
                projectScope:
                  summary: Summary for a project with multiple sectors
                  value:
                    status: success
                    scope: project
                    range:
                      startTime: '2025-10-01'
                      endTime: '2025-10-09'
                    projectTimezone: America/Mexico_City
                    filters:
                      companyId: null
                      projectId: proj_123
                      sectorId: null
                    totalRecords: 42
                    totalSectors: 3
                    sectors:
                      - sector_name: Lote A-1
                        incidence: 12.5
                        severity: 18.3
                        pests_diseases:
                          - pest_disease_name: Aphids
                            incidence: 15.5
                            severity: 25
                      - sector_name: Lote A-2
                        incidence: 8
                        severity: 10.5
                        pests_diseases:
                          - pest_disease_name: Spider mites
                            incidence: 10
                            severity: 15
                companyScope:
                  summary: Summary for a company with nested projects
                  value:
                    status: success
                    scope: company
                    range:
                      startTime: '2025-10-01'
                      endTime: '2025-10-09'
                    projectTimezone: America/Monterrey
                    filters:
                      companyId: comp_12345
                      projectId: null
                      sectorId: null
                    totalRecords: 150
                    totalProjects: 2
                    projects:
                      - projectId: proj_123
                        projectName: Rancho San Pedro
                        sectors:
                          - sector_name: Lote A-1
                            incidence: 12.5
                            severity: 18.3
                            pests_diseases:
                              - pest_disease_name: Aphids
                                incidence: 15.5
                                severity: 25
                      - projectId: proj_456
                        projectName: Finca Los Pinos
                        sectors:
                          - sector_name: Bloque Norte
                            incidence: 5.2
                            severity: 8
                            pests_diseases:
                              - pest_disease_name: Whiteflies
                                incidence: 7
                                severity: 10
        '400':
          description: Bad request - missing or invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingDates:
                  summary: Missing required date parameters
                  value:
                    error: 'Faltan parámetros: startTime, endTime.'
                missingScope:
                  summary: Missing scope identifier
                  value:
                    error: >-
                      Debes proporcionar al menos uno de: companyId, projectId o
                      sectorId.
                invalidDateFormat:
                  summary: Invalid date format
                  value:
                    error: startTime y endTime deben ser YYYY-MM-DD (sin Z).
        '401':
          description: Unauthorized - invalid or missing API token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Invalid API token
        '403':
          description: Forbidden - insufficient scopes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: 'Insufficient scopes. Required: read:phytosanitary'
        '422':
          description: Unprocessable entity - invalid date range
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalidRange:
                  summary: End date before start date
                  value:
                    error: endTime no puede ser menor que startTime.
                rangeTooLarge:
                  summary: Date range exceeds maximum
                  value:
                    error: Rango máximo 180 días.
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Internal server error
  /api/control/phytosanitary/company-summary:
    get:
      tags:
        - Phytosanitary
      summary: Get phytosanitary summary for entire company
      description: >
        Retrieves aggregated phytosanitary data (pest and disease
        incidence/severity) for all projects and sectors belonging to the
        authenticated company within a specified time range.


        Requires API token authentication with `read:phytosanitary` scope via
        the `x-api-key` header.


        Key features:

        - Automatically uses companyId from the authenticated API token

        - Aggregates data from all projects and sectors belonging to the company

        - Returns incidence and severity metrics grouped by project and sector

        - Maximum range: 180 days

        - Dates must be in YYYY-MM-DD format (local time, no timezone suffix)

        - Timezone is automatically resolved from the company's projects

        - No need to specify companyId, projectId, or sectorId manually
      operationId: getPhytosanitaryCompanySummary
      security:
        - apiKeyAuth: []
      parameters:
        - name: startTime
          in: query
          required: true
          description: Start date in YYYY-MM-DD format (local time, no timezone suffix)
          schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          example: '2025-10-01'
        - name: endTime
          in: query
          required: true
          description: End date in YYYY-MM-DD format (local time, no timezone suffix)
          schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          example: '2025-10-09'
      responses:
        '200':
          description: Company phytosanitary summary retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                      - success
                    example: success
                  scope:
                    type: string
                    enum:
                      - company
                    example: company
                  range:
                    type: object
                    properties:
                      startTime:
                        type: string
                        pattern: ^\d{4}-\d{2}-\d{2}$
                        example: '2025-10-01'
                      endTime:
                        type: string
                        pattern: ^\d{4}-\d{2}-\d{2}$
                        example: '2025-10-09'
                  companyTimezone:
                    type: string
                    description: Timezone resolved from company's projects
                    example: America/Monterrey
                  companyId:
                    type: string
                    description: Company identifier from authenticated token
                    example: comp_12345
                  totalRecords:
                    type: integer
                    description: Total number of phytosanitary records found
                    example: 150
                  totalProjects:
                    type: integer
                    description: Total number of projects with data
                    example: 2
                  projects:
                    type: array
                    items:
                      type: object
                      properties:
                        projectId:
                          type: string
                          example: proj_123
                        projectName:
                          type: string
                          example: Rancho San Pedro
                        sectors:
                          type: array
                          items:
                            type: object
                            properties:
                              sector_name:
                                type: string
                                example: Lote A-1
                              incidence:
                                type: number
                                format: float
                                example: 12.5
                              severity:
                                type: number
                                format: float
                                example: 18.3
                              pests_diseases:
                                type: array
                                items:
                                  type: object
                                  properties:
                                    pest_disease_name:
                                      type: string
                                      example: Aphids
                                    incidence:
                                      type: number
                                      format: float
                                      example: 15.5
                                    severity:
                                      type: number
                                      format: float
                                      example: 25
              example:
                status: success
                scope: company
                range:
                  startTime: '2025-10-01'
                  endTime: '2025-10-09'
                companyTimezone: America/Monterrey
                companyId: comp_12345
                totalRecords: 150
                totalProjects: 2
                projects:
                  - projectId: proj_123
                    projectName: Rancho San Pedro
                    sectors:
                      - sector_name: Lote A-1
                        incidence: 12.5
                        severity: 18.3
                        pests_diseases:
                          - pest_disease_name: Aphids
                            incidence: 15.5
                            severity: 25
                      - sector_name: Lote A-2
                        incidence: 8
                        severity: 10.5
                        pests_diseases:
                          - pest_disease_name: Spider mites
                            incidence: 10
                            severity: 15
                  - projectId: proj_456
                    projectName: Finca Los Pinos
                    sectors:
                      - sector_name: Bloque Norte
                        incidence: 5.2
                        severity: 8
                        pests_diseases:
                          - pest_disease_name: Whiteflies
                            incidence: 7
                            severity: 10
        '400':
          description: Bad request - missing or invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingDates:
                  summary: Missing required date parameters
                  value:
                    error: 'Faltan parámetros: startTime, endTime.'
                invalidDateFormat:
                  summary: Invalid date format
                  value:
                    error: startTime y endTime deben ser YYYY-MM-DD (sin Z).
        '401':
          description: Unauthorized - invalid or missing API token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalidToken:
                  summary: Invalid or missing API token
                  value:
                    error: Invalid API token
                missingCompanyInfo:
                  summary: Missing company information in token
                  value:
                    error: Missing company information in authentication token.
        '403':
          description: Forbidden - insufficient scopes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: 'Insufficient scopes. Required: read:phytosanitary'
        '422':
          description: Unprocessable entity - invalid date range
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalidRange:
                  summary: End date before start date
                  value:
                    error: endTime no puede ser menor que startTime.
                rangeTooLarge:
                  summary: Date range exceeds maximum
                  value:
                    error: Rango máximo 180 días.
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Internal server error
  /api/control/production/sessions:
    post:
      tags:
        - Production Sessions
      summary: Create a new production session
      description: >
        Opens a new production session for a specific project, sector, and day.


        Requires JWT token authentication in the Authorization header.


        Key features:

        - Automatically resolves company context from authenticated user

        - Resolves project timezone for day normalization

        - Ensures uniqueness: only one open session per (company, project, day,
        sector)

        - Supports custom pricing, bonuses, and duration settings

        - Defaults are inherited from project configuration if not provided
      operationId: createProductionSession
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - auth0Id
                - projectId
                - openedAt
                - sectorId
                - sector_name
              properties:
                auth0Id:
                  type: string
                  description: User identifier (internal use, derived from JWT token)
                  example: user_123456789
                projectId:
                  type: string
                  description: Project identifier
                  example: proj_123
                openedAt:
                  type: string
                  format: date-time
                  description: >-
                    Session opening timestamp in ISO 8601 UTC format (will be
                    normalized to project timezone for day calculation)
                  example: '2025-10-19T08:00:00.000Z'
                sectorId:
                  type: string
                  description: Sector identifier
                  example: sector_001
                sector_name:
                  type: string
                  description: Sector name
                  example: Lote A-1
                samplingUnit:
                  type: string
                  description: Unit of measurement (defaults to project setting)
                  example: box
                pricePerUnit:
                  type: number
                  minimum: 0
                  description: Price per unit (defaults to project setting)
                  example: 5.5
                bonus:
                  $ref: '#/components/schemas/Bonus'
                maxDurationHours:
                  type: integer
                  minimum: 1
                  maximum: 24
                  description: Maximum session duration in hours (defaults to 12)
                  example: 12
                notes:
                  type: string
                  maxLength: 500
                  description: Optional notes
                  example: Good weather today
            examples:
              basicSession:
                summary: Basic session
                value:
                  auth0Id: user_123456789
                  projectId: proj_123
                  openedAt: '2025-10-19T08:00:00.000Z'
                  sectorId: sector_001
                  sector_name: Lote A-1
              fullSession:
                summary: Session with custom settings
                value:
                  auth0Id: user_123456789
                  projectId: proj_123
                  openedAt: '2025-10-19T08:00:00.000Z'
                  sectorId: sector_001
                  sector_name: Lote A-1
                  samplingUnit: box
                  pricePerUnit: 5.5
                  bonus:
                    type: percent
                    value: 10
                    note: High quality bonus
                  maxDurationHours: 12
                  notes: Good weather conditions
      responses:
        '201':
          description: Session created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductionSessionResponse'
        '400':
          description: Bad request - missing required fields or invalid data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingFields:
                  summary: Missing required fields
                  value:
                    status: error
                    message: >-
                      Missing required fields: projectId, openedAt, sectorId,
                      sector_name
                invalidDay:
                  summary: Invalid opening timestamp
                  value:
                    status: error
                    message: Invalid 'openedAt' or project timezone
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Conflict - session already open for this scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Session already open for that scope
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/control/production/sessions/{sessionId}:
    get:
      tags:
        - Production Sessions
      summary: Get a production session
      description: |
        Retrieves detailed information about a specific production session.

        Requires JWT token authentication in the Authorization header.

        Only the session owner (user who opened it) can view the session.
      operationId: getProductionSession
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          description: Session identifier (unique identifier)
          schema:
            type: string
          example: 6507f1e3c9d8e2a4b1f9c3d5
      responses:
        '200':
          description: Session retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductionSessionResponse'
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden - only session owner can view
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: 'Forbidden: only session owner (auth0Id) can view'
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    patch:
      tags:
        - Production Sessions
      summary: Update session metadata
      description: |
        Updates metadata of an open production session.

        Requires JWT token authentication in the Authorization header.

        Only the session owner can update. Session must be open.

        Updatable fields: samplingUnit, pricePerUnit, bonus, notes
      operationId: updateProductionSession
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          description: Session identifier (unique identifier)
          schema:
            type: string
          example: 6507f1e3c9d8e2a4b1f9c3d5
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - auth0Id
              properties:
                auth0Id:
                  type: string
                  description: User identifier (internal use, derived from JWT token)
                  example: user_123456789
                samplingUnit:
                  type: string
                  example: kg
                pricePerUnit:
                  type: number
                  minimum: 0
                  example: 6
                bonus:
                  $ref: '#/components/schemas/Bonus'
                notes:
                  type: string
                  maxLength: 500
                  example: Updated notes
      responses:
        '200':
          description: Session updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  data:
                    type: object
                    properties:
                      updated:
                        type: boolean
                        example: true
        '400':
          description: Bad request - missing auth0Id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden - only opener can edit
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Conflict - session is closed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Session is closed
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/control/production/sessions/{sessionId}/close:
    post:
      tags:
        - Production Sessions
      summary: Close a production session
      description: |
        Closes an open production session.

        Requires JWT token authentication in the Authorization header.

        Only the session owner can close it. Session must be open.
      operationId: closeProductionSession
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          description: Session identifier (unique identifier)
          schema:
            type: string
          example: 6507f1e3c9d8e2a4b1f9c3d5
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - auth0Id
              properties:
                auth0Id:
                  type: string
                  description: User identifier (internal use, derived from JWT token)
                  example: user_123456789
      responses:
        '200':
          description: Session closed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  data:
                    type: object
                    properties:
                      sessionId:
                        type: string
                        example: 6507f1e3c9d8e2a4b1f9c3d5
                      status:
                        type: string
                        example: closed
                      closedAt:
                        type: string
                        format: date-time
                        example: '2025-10-19T18:00:00.000Z'
        '400':
          description: Bad request - missing auth0Id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden - only opener can close
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: 'Forbidden: only the opener (auth0Id) can close'
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Conflict - session already closed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Session already closed
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/control/production/sessions/{sessionId}/extend:
    post:
      tags:
        - Production Sessions
      summary: Extend session duration
      description: |
        Extends the maximum duration of an open production session.

        Requires JWT token authentication in the Authorization header.

        Only the session owner can extend it. Session must be open.
      operationId: extendProductionSession
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          description: Session identifier (unique identifier)
          schema:
            type: string
          example: 6507f1e3c9d8e2a4b1f9c3d5
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - auth0Id
                - maxDurationHours
              properties:
                auth0Id:
                  type: string
                  description: User identifier (internal use, derived from JWT token)
                  example: user_123456789
                maxDurationHours:
                  type: integer
                  minimum: 1
                  maximum: 24
                  description: New maximum duration in hours
                  example: 18
      responses:
        '200':
          description: Session extended successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  data:
                    type: object
                    properties:
                      maxDurationHours:
                        type: integer
                        example: 18
        '400':
          description: Bad request - invalid maxDurationHours
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: maxDurationHours must be a positive integer
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden - only opener can extend
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Conflict - session is closed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: error
                message: Session is closed
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/control/production/records:
    post:
      tags:
        - Production Records
      summary: Create a production record
      description: |
        Creates a new production record to add units to an open session.

        Requires JWT token authentication in the Authorization header.

        Key features:
        - Only the session owner can create records
        - Session must be open
        - Supports idempotency via clientEventId and optional idempotencyKey
        - Optimized for high-performance processing
      operationId: createProductionRecord
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/ProductionRecord'
                - type: object
                  required:
                    - auth0Id
                  properties:
                    auth0Id:
                      type: string
                      description: User identifier (internal use, derived from JWT token)
                      example: user_123456789
            examples:
              basicRecord:
                summary: Basic record with 1 unit
                value:
                  auth0Id: user_123456789
                  sessionId: 6507f1e3c9d8e2a4b1f9c3d5
                  workerId: JS0315
                  worker_name: John Smith
                  recordedAtUtc: '2025-10-19T14:30:00.000Z'
                  clientEventId: evt_1234567890abcdef
              multipleUnits:
                summary: Record with multiple units
                value:
                  auth0Id: user_123456789
                  sessionId: 6507f1e3c9d8e2a4b1f9c3d5
                  workerId: JS0315
                  worker_name: John Smith
                  units: 5
                  recordedAtUtc: '2025-10-19T14:30:00.000Z'
                  idempotencyKey: mobile-app-key-12345
                  clientEventId: evt_1234567890abcdef
      responses:
        '200':
          description: Idempotent replay - record already exists
          headers:
            X-Idempotent-Replay:
              schema:
                type: string
              description: Set to "true" when returning idempotent response
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ProductionRecordResponse'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          idempotent:
                            type: boolean
                            example: true
        '201':
          description: Record created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductionRecordResponse'
        '400':
          description: Bad request - missing required fields or invalid data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden - only session owner can create records
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Conflict - session is closed or idempotency conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/control/production/records/bulk:
    post:
      tags:
        - Production Records
      summary: Bulk create production records
      description: >
        Creates multiple production records in a single request (offline sync).


        Requires JWT token authentication in the Authorization header.


        Each record is processed independently with full validation and
        idempotency.

        Returns multi-status response (207) with individual results.
      operationId: bulkCreateProductionRecords
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - auth0Id
                - records
              properties:
                auth0Id:
                  type: string
                  description: User identifier (internal use, derived from JWT token)
                  example: user_123456789
                records:
                  type: array
                  description: Array of production records to create
                  items:
                    $ref: '#/components/schemas/ProductionRecord'
            example:
              auth0Id: user_123456789
              records:
                - sessionId: 6507f1e3c9d8e2a4b1f9c3d5
                  workerId: JS0315
                  worker_name: John Smith
                  units: 5
                  recordedAtUtc: '2025-10-19T14:30:00.000Z'
                  clientEventId: evt_1234567890abcdef
                - sessionId: 6507f1e3c9d8e2a4b1f9c3d5
                  workerId: JS0315
                  worker_name: John Smith
                  units: 3
                  recordedAtUtc: '2025-10-19T15:00:00.000Z'
                  clientEventId: evt_9876543210fedcba
      responses:
        '207':
          description: Multi-status - records processed with individual results
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        index:
                          type: integer
                          description: Index of the record in the request array
                          example: 0
                        statusCode:
                          type: integer
                          description: >-
                            HTTP status code for this record (201=created,
                            200=duplicate, 4xx/5xx=error)
                          example: 201
                        payloadPreview:
                          type: object
                          properties:
                            recordId:
                              type: string
                              example: rec_1234567890abcdef
                            sessionId:
                              type: string
                              example: 6507f1e3c9d8e2a4b1f9c3d5
                            workerId:
                              type: string
                              example: JS0315
                            units:
                              type: number
                              example: 5
                        idempotent:
                          type: boolean
                          description: Present when statusCode=200 (duplicate)
                          example: false
                        error:
                          type: string
                          description: Error message (only when statusCode indicates error)
                          example: Missing sessionId or workerId
                  summary:
                    type: object
                    properties:
                      ok:
                        type: integer
                        description: Number of successfully created records
                        example: 2
                      duplicated:
                        type: integer
                        description: Number of idempotent duplicates
                        example: 0
                      failed:
                        type: integer
                        description: Number of failed records
                        example: 0
        '400':
          description: Bad request - missing auth0Id or empty records array
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/control/production/records/check-idempotency:
    head:
      tags:
        - Production Records
      summary: Check idempotency key
      description: |
        Checks if an idempotency key has been used before.

        Requires JWT token authentication in the Authorization header.

        Returns 200 if key exists, 404 if not.
      operationId: checkIdempotencyKey
      security:
        - bearerAuth: []
      parameters:
        - name: key
          in: query
          required: true
          description: Idempotency key to check
          schema:
            type: string
          example: client:6507f1e3c9d8e2a4b1f9c3d5:JS0315:evt_1234567890abcdef
      responses:
        '200':
          description: Idempotency key exists
        '401':
          description: Unauthorized - invalid or expired token
        '404':
          description: Idempotency key not found
        '500':
          description: Internal server error
  /api/control/production/records/{recordId}:
    get:
      tags:
        - Production Records
      summary: Get a production record
      description: |
        Retrieves detailed information about a specific production record.

        Requires JWT token authentication in the Authorization header.
      operationId: getProductionRecord
      security:
        - bearerAuth: []
      parameters:
        - name: recordId
          in: path
          required: true
          description: Record identifier
          schema:
            type: string
          example: rec_1234567890abcdef
      responses:
        '200':
          description: Record retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  data:
                    type: object
                    properties:
                      recordId:
                        type: string
                        example: rec_1234567890abcdef
                      sessionId:
                        type: string
                        example: 6507f1e3c9d8e2a4b1f9c3d5
                      workerId:
                        type: string
                        example: JS0315
                      units:
                        type: number
                        example: 5
                      state:
                        type: string
                        enum:
                          - pending
                          - inserted
                        description: Record insertion state
                        example: inserted
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Record not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/control/production/records/{recordId}/void:
    post:
      tags:
        - Production Records
      summary: Void a production record
      description: >
        Voids (cancels) a production record by creating a counter-movement with
        negative units.


        Requires JWT token authentication in the Authorization header.


        Only the session owner can void records. Session must be open.

        Supports idempotency - voiding the same record multiple times is safe.
      operationId: voidProductionRecord
      security:
        - bearerAuth: []
      parameters:
        - name: recordId
          in: path
          required: true
          description: Record identifier to void
          schema:
            type: string
          example: rec_1234567890abcdef
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - auth0Id
                - reason
              properties:
                auth0Id:
                  type: string
                  description: User identifier (internal use, derived from JWT token)
                  example: user_123456789
                reason:
                  type: string
                  description: Reason for voiding the record
                  example: Duplicate entry
      responses:
        '200':
          description: Record voided successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  data:
                    type: object
                    properties:
                      payloadPreview:
                        type: object
                        properties:
                          recordId:
                            type: string
                            description: New void record identifier
                            example: rec_void_9876543210
                          voidOf:
                            type: string
                            description: Original record identifier
                            example: rec_1234567890abcdef
                          idempotent:
                            type: boolean
                            description: Present when this is an idempotent replay
                            example: false
        '400':
          description: Bad request - missing recordId or reason
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden - only session opener can void records
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Record or session not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Conflict - session is closed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/iot/weather:
    get:
      tags:
        - Weather
      summary: Get weather time series for local range
      description: >
        Returns weather and microclimate time series for a project within a
        local-time range (Leaf-style).

        Auth via API token in `x-api-key`.
      operationId: getWeatherSeries
      security:
        - apiKeyAuth: []
      parameters:
        - name: projectId
          in: query
          required: true
          schema:
            type: string
        - name: from
          in: query
          required: true
          description: Start datetime in **local timezone** (ISO 8601)
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          required: true
          description: End datetime in **local timezone** (ISO 8601)
          schema:
            type: string
            format: date-time
        - name: timezone
          in: query
          required: true
          description: IANA timezone (e.g., America/Mexico_City)
          schema:
            type: string
      requestBody:
        description: Optional alternative to query params (for complex queries)
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TimeRangeQuery'
      responses:
        '200':
          description: Weather time series
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WeatherSeriesResponse'
        '400':
          description: Bad request (missing/invalid range or timezone)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized (invalid token)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/iot/plant:
    get:
      tags:
        - Plant
      summary: Get plant health and vigor series for local range
      description: >
        Returns plant KPIs (e.g., vigor, incidence, severity) aggregated for a
        project in a local-time range.

        Auth via API token in `x-api-key`.
      operationId: getPlantSeries
      security:
        - apiKeyAuth: []
      parameters:
        - name: projectId
          in: query
          required: true
          schema:
            type: string
        - name: from
          in: query
          required: true
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          required: true
          schema:
            type: string
            format: date-time
        - name: timezone
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Plant KPI series
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlantSeriesResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/iot/irrigation:
    get:
      tags:
        - Irrigation
      summary: Get irrigation events for local range
      description: |
        Returns irrigation events for a project within a local-time range.
        Auth via API token in `x-api-key`.
      operationId: getIrrigationEvents
      security:
        - apiKeyAuth: []
      parameters:
        - name: projectId
          in: query
          required: true
          schema:
            type: string
        - name: from
          in: query
          required: true
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          required: true
          schema:
            type: string
            format: date-time
        - name: timezone
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Irrigation events
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IrrigationSeriesResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /mqtt:
    get:
      tags:
        - IoT Endpoint
      summary: Establish mqtt connection over websocket
      description: >
        Upgrade to WebSocket at `wss://iot.hydrobit.ag/mqtt` and authenticate
        with `x-iot-token`.

        After upgrade, speak MQTT (v3.1.1). Use `deviceId` to scope topics.
      operationId: mqttOverWebSocket
      servers:
        - url: wss://iot.hydrobit.ag
          description: MQTT over WebSocket endpoint
      security:
        - iotToken: []
      parameters:
        - name: deviceId
          in: query
          required: true
          description: Device identifier to scope topics
          schema:
            type: string
      responses:
        '101':
          description: Switching Protocols (WebSocket upgrade to MQTT)
        '200':
          description: Handshake info for non-upgrade clients
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MqttConnectInfo'
        '400':
          description: Bad request (missing deviceId)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized (invalid or expired x-iot-token)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
