Notificaciones

Las notificaciones informan a cada usuario sobre eventos relevantes del club: recordatorios de turnos, tareas vencidas, vencimientos de membresías, novedades de cultivos, órdenes y avisos del sistema. En esta página vamos a recorrer los distintos endpoints BFF de notificaciones que expone la webapp de Cannahub. Veremos cómo listar las notificaciones del usuario autenticado, marcarlas como leídas, obtener el conteo de no leídas y generar recordatorios automáticos.

Todas las rutas son rutas BFF propias de la webapp (/api/notifications/...), que internamente delegan al servicio de notificaciones (Strapi). El acceso se resuelve a partir del usuario autenticado: cada usuario solo puede operar sobre sus propias notificaciones (su recipientId). El multi-tenant se resuelve a través del header Tenant-id.

El modelo de notificación

El modelo de notificación describe un aviso dirigido a un destinatario (recipientId) dentro de un tenant. Incluye el dominio funcional, el evento que la originó, un título y cuerpo legibles, la severidad, el estado de lectura y metadata adicional.

Propiedades

  • Name
    id
    Type
    string
    Description

    Identificador interno de la notificación.

  • Name
    externalId
    Type
    string
    Description

    Identificador externo estable de la notificación, usado por el cliente para marcarla como leída.

  • Name
    tenantId
    Type
    string
    Description

    Identificador del tenant (club) al que pertenece la notificación.

  • Name
    recipientId
    Type
    string
    Description

    Identificador del destinatario de la notificación (el usuario que la recibe).

  • Name
    recipientRole
    Type
    string
    Description

    Rol del destinatario al momento de generarse la notificación (por ejemplo member, grower, admin).

  • Name
    domain
    Type
    enum
    Description

    Dominio funcional de la notificación. Puede ser uno de:

    • orders
    • appointments
    • crops
    • tasks
    • memberships
    • medical
    • products
    • inventory
    • workforce
    • documents
    • system
  • Name
    event
    Type
    string
    Description

    Evento que originó la notificación (por ejemplo appointment.reminder, task.overdue).

  • Name
    title
    Type
    string
    Description

    Título legible de la notificación.

  • Name
    body
    Type
    string
    Description

    Cuerpo o descripción de la notificación.

  • Name
    severity
    Type
    enum
    Description

    Severidad de la notificación. Puede ser una de:

    • info
    • success
    • warning
    • error
  • Name
    actionUrl
    Type
    string
    Description

    URL de acción asociada a la notificación (por ejemplo, el detalle del turno o de la tarea).

  • Name
    resourceId
    Type
    string
    Description

    Identificador del recurso relacionado (turno, tarea, orden, etc.).

  • Name
    resourceType
    Type
    string
    Description

    Tipo del recurso relacionado.

  • Name
    isRead
    Type
    boolean
    Description

    Indica si la notificación fue leída.

  • Name
    readAt
    Type
    string
    Description

    Timestamp de cuándo se marcó como leída.

  • Name
    metadata
    Type
    object
    Description

    Datos adicionales arbitrarios asociados a la notificación.

  • Name
    source
    Type
    enum
    Description

    Origen de la notificación. Puede ser uno de:

    • event: Generada por un evento del sistema
    • reminder: Recordatorio programado (turnos, tareas vencidas)
    • manual: Creada manualmente
    • system: Aviso del sistema
  • Name
    expiresAt
    Type
    string
    Description

    Timestamp de expiración. Las notificaciones vencidas se limpian automáticamente.

  • Name
    createdAt
    Type
    string
    Description

    Timestamp de creación de la notificación.

  • Name
    updatedAt
    Type
    string
    Description

    Timestamp de la última actualización de la notificación.

Ejemplo de notificación

{
  "id": "12345",
  "externalId": "ntf_01HQ9REMIND001",
  "tenantId": "tnt_01HQ8CLUB001",
  "recipientId": "usr_01HQ8XYZABC123",
  "recipientRole": "member",
  "domain": "appointments",
  "event": "appointment.reminder",
  "title": "Recordatorio de turno",
  "body": "Tenés un turno mañana a las 15:00 hs en el club.",
  "severity": "info",
  "actionUrl": "/appointments/apt_01HQ9APT001",
  "resourceId": "apt_01HQ9APT001",
  "resourceType": "appointment",
  "isRead": false,
  "readAt": null,
  "metadata": {
    "date": "2026-09-10",
    "time": "15:00"
  },
  "source": "reminder",
  "expiresAt": "2026-09-11T00:00:00Z",
  "createdAt": "2026-09-09T09:00:00Z",
  "updatedAt": "2026-09-09T09:00:00Z"
}

GET/api/notifications

Listar notificaciones

Retorna una lista paginada de las notificaciones del usuario autenticado. El destinatario (recipientId) se resuelve automáticamente a partir de la sesión, por lo que cada usuario solo recibe sus propias notificaciones. Podés filtrar por dominio, estado de lectura y origen.

Query params opcionales

  • Name
    domain
    Type
    enum
    Description

    Filtra por dominio funcional (orders, appointments, crops, tasks, memberships, medical, products, inventory, workforce, documents, system).

  • Name
    isRead
    Type
    boolean
    Description

    Filtra por estado de lectura. Usá true para leídas o false para no leídas.

  • Name
    source
    Type
    enum
    Description

    Filtra por origen de la notificación (event, reminder, manual, system).

  • Name
    page
    Type
    integer
    Description

    Número de página. Por defecto 1.

  • Name
    limit
    Type
    integer
    Description

    Cantidad de notificaciones por página. Por defecto 20.

Request

GET
/api/notifications
curl -G https://api.cannahub.tech/api/notifications \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -d domain=appointments \
  -d isRead=false \
  -d page=1 \
  -d limit=20

Response

{
  "notifications": [
    {
      "id": "12345",
      "externalId": "ntf_01HQ9REMIND001",
      "tenantId": "tnt_01HQ8CLUB001",
      "recipientId": "usr_01HQ8XYZABC123",
      "recipientRole": "member",
      "domain": "appointments",
      "event": "appointment.reminder",
      "title": "Recordatorio de turno",
      "body": "Tenés un turno mañana a las 15:00 hs en el club.",
      "severity": "info",
      "actionUrl": "/appointments/apt_01HQ9APT001",
      "resourceId": "apt_01HQ9APT001",
      "resourceType": "appointment",
      "isRead": false,
      "source": "reminder",
      "createdAt": "2026-09-09T09:00:00Z",
      "updatedAt": "2026-09-09T09:00:00Z"
    },
    {
      "id": "12346",
      "externalId": "ntf_01HQ9MEMBER07",
      "tenantId": "tnt_01HQ8CLUB001",
      "recipientId": "usr_01HQ8XYZABC123",
      "recipientRole": "member",
      "domain": "memberships",
      "event": "membership.expiring",
      "title": "Tu membresía está por vencer",
      "body": "Tu membresía Premium vence el 15/01. Renovala para no perder acceso.",
      "severity": "warning",
      "actionUrl": "/membership",
      "resourceId": "mbr_01HQ9ABC456",
      "resourceType": "membership",
      "isRead": false,
      "source": "reminder",
      "createdAt": "2026-09-08T09:00:00Z",
      "updatedAt": "2026-09-08T09:00:00Z"
    }
  ],
  "count": 2,
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 2
  }
}

GET/api/notifications/unread-count

Conteo de no leídas

Retorna la cantidad de notificaciones no leídas del usuario autenticado. Es una ruta liviana pensada para alimentar el badge del ícono de notificaciones. En la webapp se refresca periódicamente (cada 30 segundos) y al recuperar el foco de la ventana.

El recipientId se resuelve a partir de la sesión; no requiere ningún parámetro adicional.

Response

  • Name
    count
    Type
    number
    Description

    Cantidad de notificaciones no leídas del usuario.

Request

GET
/api/notifications/unread-count
curl https://api.cannahub.tech/api/notifications/unread-count \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

{
  "count": 5
}

POST/api/notifications/mark-read

Marcar como leídas

Marca una notificación específica como leída, o bien todas las notificaciones del usuario autenticado. El body debe incluir o notificationId o all: true. Si no se envía ninguno de los dos, la ruta responde con un error de validación.

Body

  • Name
    notificationId
    Type
    string
    Description

    Identificador (externalId) de la notificación a marcar como leída. Requerido si no se envía all.

  • Name
    all
    Type
    boolean
    Description

    Si es true, marca como leídas todas las notificaciones del usuario. Requerido si no se envía notificationId.

Request

POST
/api/notifications/mark-read
curl -X POST https://api.cannahub.tech/api/notifications/mark-read \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{
    "notificationId": "ntf_01HQ9REMIND001"
  }'

Response (notificationId)

{
  "notification": {
    "id": "12345",
    "externalId": "ntf_01HQ9REMIND001",
    "tenantId": "tnt_01HQ8CLUB001",
    "recipientId": "usr_01HQ8XYZABC123",
    "recipientRole": "member",
    "domain": "appointments",
    "event": "appointment.reminder",
    "title": "Recordatorio de turno",
    "severity": "info",
    "isRead": true,
    "readAt": "2026-09-09T12:30:00Z",
    "source": "reminder",
    "createdAt": "2026-09-09T09:00:00Z",
    "updatedAt": "2026-09-09T12:30:00Z"
  }
}

Response (all: true)

{
  "updated": 5
}

POST/api/notifications/generate-reminders

Generar recordatorios

Endpoint programado (cron) que genera notificaciones de recordatorio para todos los tenants. No usa autenticación de usuario: se protege con el secreto CRON_API_SECRET, que se envía en el header Authorization como Bearer {CRON_API_SECRET}. Se ejecuta diariamente (a las 09:00 UTC) desde un GitHub Action.

En cada corrida, por cada tenant, la ruta:

  • Genera recordatorios de turnos para los turnos confirmados del día siguiente (appointment.reminder).
  • Genera recordatorios de tareas vencidas para tareas PENDING o IN_PROGRESS con vencimiento hasta hoy (task.overdue).
  • Limpia las notificaciones vencidas (según su expiresAt).

Incluye control de idempotencia: si ya existe un recordatorio del mismo evento y recurso generado hoy para el destinatario, se omite.

Autenticación

  • Name
    Authorization
    Type
    header
    Description

    Debe ser Bearer {CRON_API_SECRET}. Cualquier otro valor responde 401 Unauthorized.

Response

  • Name
    tenants
    Type
    number
    Description

    Cantidad de tenants procesados.

  • Name
    reminders
    Type
    object
    Description

    Cantidad de recordatorios generados.

    • Name
      appointments
      Type
      number
      Description

      Recordatorios de turnos generados.

    • Name
      tasks
      Type
      number
      Description

      Recordatorios de tareas vencidas generados.

  • Name
    expired
    Type
    number
    Description

    Cantidad de notificaciones vencidas eliminadas.

Request

POST
/api/notifications/generate-reminders
curl -X POST https://api.cannahub.tech/api/notifications/generate-reminders \
  -H "Authorization: Bearer {CRON_API_SECRET}"

Response

{
  "tenants": 3,
  "reminders": {
    "appointments": 12,
    "tasks": 5
  },
  "expired": 8
}

React Query Hooks

La webapp de Cannahub provee hooks de React Query pre-armados para gestionar notificaciones. Estos hooks consumen las rutas BFF y manejan el cacheo, la invalidación y las actualizaciones optimistas automáticamente.

Query Keys

// features/shared/notifications/hooks/useNotifications.ts
export const notificationKeys = {
  all: ['notifications'] as const,
  lists: () => [...notificationKeys.all, 'list'] as const,
  list: (filters: StrapiNotificationFilters) => [...notificationKeys.lists(), filters] as const,
  unreadCount: () => [...notificationKeys.all, 'unread-count'] as const,
}

Queries

import { useNotifications, useUnreadCount } from '@/features/shared/notifications/hooks/useNotifications'

// Listado paginado (useInfiniteQuery) con filtros opcionales
const { data, fetchNextPage, hasNextPage } = useNotifications({
  domain: 'appointments',
  isRead: false,
  limit: 20,
})

// Conteo de no leídas (para el badge). Refresca cada 30s.
const { data: unread } = useUnreadCount()

Mutations

import { useMarkAsRead, useMarkAllAsRead } from '@/features/shared/notifications/hooks/useNotifications'

// Marcar una notificación como leída (por externalId)
const { mutate: markAsRead } = useMarkAsRead()
markAsRead('ntf_01HQ9REMIND001')

// Marcar todas como leídas
const { mutate: markAllAsRead } = useMarkAllAsRead()
markAllAsRead()

Actualizaciones optimistas

Las mutaciones useMarkAsRead y useMarkAllAsRead aplican cambios optimistas en el cache antes de que responda el servidor: actualizan el estado isRead de las notificaciones en el listado y decrementan (o ponen en cero) el conteo de no leídas. Si la request falla, se hace rollback al estado previo. Al finalizar, se invalidan las queries relevantes:

queryClient.invalidateQueries({ queryKey: notificationKeys.lists() })
queryClient.invalidateQueries({ queryKey: notificationKeys.unreadCount() })

Was this page helpful?