Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/EdgarJr30/proyecto-de-grado-cms/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The MLM CMMS frontend implements a Progressive Web App (PWA) with service worker-based caching and Web Push notification support.
Key files:
  • Service Worker: public/sw.js
  • Push Service: src/services/pushNotificationsService.ts
  • Notification Service: src/services/notificationCenterService.ts

Service Worker Features

Caching Strategy

The service worker implements a hybrid caching approach:

Precached Resources

These files are cached during service worker installation:
// public/sw.js:9
const PRECACHE_URLS = [
  '/',
  '/index.html',
  '/offline.html',
  '/manifest.webmanifest',
  '/apple-touch-icon.png',
  '/pwa-192x192.png',
  '/pwa-512x512.png',
  '/pwa-maskable-512x512.png',
];

Push Notification Handling

Push Event Processing

When a push message arrives, the service worker:
1

Parse Payload

Extract title, body, URL, and metadata from push data:
// public/sw.js:154
self.addEventListener('push', (event) => {
  const payload = parsePushPayload(event);
  const title = typeof payload.title === 'string' && payload.title.trim()
    ? payload.title
    : 'Nueva notificación';
  const body = typeof payload.body === 'string' && payload.body.trim()
    ? payload.body
    : 'Tienes una actualización en el sistema.';
  // ...
});
2

Show Browser Notification

Display native OS notification with icon and badge:
const options = {
  body,
  icon: payload.icon || '/pwa-192x192.png',
  badge: payload.badge || '/pwa-192x192.png',
  data: {
    url: payload.url || '/notificaciones',
  },
  tag: payload.tag,
  renotify: Boolean(payload.renotify),
};
await self.registration.showNotification(title, options);
3

Broadcast to Open Tabs

Notify all open app windows to refresh notification center:
// public/sw.js:35
async function broadcastPushReceived(payload) {
  const clients = await self.clients.matchAll({
    type: 'window',
    includeUncontrolled: true,
  });
  for (const client of clients) {
    client.postMessage({
      type: 'notification:push_received',
      receivedAt: Date.now(),
      url: typeof payload.url === 'string' ? payload.url : '/notificaciones',
    });
  }
}

Notification Click Handling

When user clicks a notification:
// public/sw.js:196
self.addEventListener('notificationclick', (event) => {
  event.notification.close();

  const targetUrl = new URL(
    event.notification.data?.url || '/notificaciones',
    self.location.origin
  ).href;

  event.waitUntil(
    self.clients.matchAll({ type: 'window', includeUncontrolled: true })
      .then((clients) => {
        // Focus existing tab if URL matches
        for (const client of clients) {
          if (client.url === targetUrl && 'focus' in client) {
            return client.focus();
          }
        }
        // Otherwise open new window
        if (self.clients.openWindow) {
          return self.clients.openWindow(targetUrl);
        }
      })
  );
});

Frontend Integration

Service Worker Registration

The service worker is registered in src/main.tsx with cache-busting:
// src/main.tsx (service worker registration)
if ('serviceWorker' in navigator && window.isSecureContext) {
  navigator.serviceWorker
    .register(`/sw.js?v=${__APP_VERSION__}`, {
      updateViaCache: 'none',
    })
    .then((registration) => {
      console.log('Service Worker registered:', registration);
    })
    .catch((error) => {
      console.error('Service Worker registration failed:', error);
    });
}
The updateViaCache: 'none' option ensures the browser always fetches the latest service worker file.

Push Subscription Management

The pushNotificationsService handles subscription lifecycle:

Subscribe to Push

// src/services/pushNotificationsService.ts:83
export async function subscribeCurrentDeviceToPush(
  vapidPublicKey: string
): Promise<void> {
  // 1. Request notification permission
  const permission = await Notification.requestPermission();
  if (permission !== 'granted') {
    throw new Error('El permiso de notificaciones fue denegado.');
  }

  // 2. Ensure service worker is active
  const registration = await ensureServiceWorkerRegistration();

  // 3. Subscribe to push manager
  let subscription = await registration.pushManager.getSubscription();
  if (!subscription) {
    subscription = await registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
    });
  }

  // 4. Save subscription to database
  const json = subscription.toJSON();
  await supabase.from('push_subscriptions').upsert({
    user_id: userId,
    endpoint: json.endpoint,
    p256dh: json.keys.p256dh,
    auth: json.keys.auth,
    user_agent: navigator.userAgent,
    platform: navigator.userAgentData?.platform ?? navigator.platform,
    last_seen_at: new Date().toISOString(),
  }, { onConflict: 'user_id,endpoint' });
}

Unsubscribe from Push

// src/services/pushNotificationsService.ts:144
export async function unsubscribeCurrentDeviceFromPush(): Promise<void> {
  const registration = await navigator.serviceWorker.getRegistration();
  const subscription = await registration?.pushManager.getSubscription();
  
  // 1. Unsubscribe from browser
  if (subscription) {
    await subscription.unsubscribe();
  }

  // 2. Remove from database
  await supabase
    .from('push_subscriptions')
    .delete()
    .eq('user_id', userId)
    .eq('endpoint', subscription?.endpoint);
}

Browser Compatibility Check

// src/services/pushNotificationsService.ts:73
export function isPushSupported() {
  return (
    typeof window !== 'undefined' &&
    window.isSecureContext &&
    'Notification' in window &&
    'PushManager' in window &&
    'serviceWorker' in navigator
  );
}
Push notifications require:
  • HTTPS (or localhost for development)
  • Modern browser with Push API support
  • Service Worker support

Platform-Specific Considerations

iOS Safari

  • iOS 16.4+ required for Web Push
  • App must be installed to Home Screen (Add to Home Screen)
  • User must grant notification permission in Settings after install
  • Only works in standalone PWA mode, not in-browser

Android Chrome

  • Works in-browser without PWA installation
  • Notification permission prompt shown on first request
  • Background sync and notification grouping supported

Desktop (macOS/Windows/Linux)

  • Chrome, Edge, Firefox supported
  • Safari 16+ on macOS Ventura+
  • Notification permission managed by browser/OS

User Interface Integration

Notification Center

The /notificaciones page provides:
  • Unread badge: Shows count of unread notifications in navbar bell icon
  • Filter tabs: All, Unread, Tickets, Admin
  • Read/unread toggle: Click or swipe to mark
  • Push subscription toggle: Enable/disable push for current device
  • Self-test tool: Send test notification to verify setup
  • Admin test tool: Send notifications to other users (requires permissions)
See src/pages/NotificationCenter.tsx for implementation.

Realtime Updates

The notification center subscribes to database changes:
// src/services/notificationCenterService.ts:565
export function subscribeToMyNotificationDeliveries(
  userId: string,
  onChange: () => void
) {
  const channelName = `notification-deliveries:${userId}:${crypto.randomUUID()}`;

  const channel = supabase
    .channel(channelName)
    .on(
      'postgres_changes',
      {
        event: '*',
        schema: 'public',
        table: 'notification_deliveries',
        filter: `recipient_user_id=eq.${userId}`,
      },
      () => onChange()
    )
    .subscribe();

  return () => {
    void supabase.removeChannel(channel);
  };
}

Push-Triggered Refresh

When a push notification arrives while the app is open:
// src/main.tsx (message listener)
navigator.serviceWorker.addEventListener('message', (event) => {
  if (event.data?.type === 'notification:push_received') {
    // Trigger notification center refresh
    window.dispatchEvent(new CustomEvent('notification:received', {
      detail: { url: event.data.url }
    }));
  }
});

Testing Push Notifications

Test from UI

1

Enable Push

Navigate to /notificaciones and click “Activar Push en este dispositivo”
2

Send Self-Test

Click “Enviar prueba a mí mismo” button (available to all authenticated users)
3

Verify Receipt

  • Check browser notification appears
  • Verify in-app notification list updates
  • Check outbox status transitions to sent

Test from Database

-- Send test notification to specific user
SELECT send_self_test_notification(
  'Prueba manual',
  'Este es un mensaje de prueba desde SQL',
  true  -- send_push
);

-- Admin test (requires permissions)
SELECT admin_send_test_notification(
  '<recipient-user-id>'::uuid,
  'Prueba de admin',
  'Mensaje enviado por administrador',
  '/tickets/123',
  true  -- send_push
);

Debug Push Subscription

Inspect subscription details in browser console:
// Check if service worker is registered
navigator.serviceWorker.getRegistration().then(reg => {
  console.log('SW registration:', reg);
});

// Check push subscription
navigator.serviceWorker.ready.then(reg => {
  reg.pushManager.getSubscription().then(sub => {
    console.log('Push subscription:', sub?.toJSON());
  });
});

// Check notification permission
console.log('Notification permission:', Notification.permission);

Troubleshooting

”Permission Denied” Errors

  1. Check notification permission:
    • Chrome: chrome://settings/content/notifications
    • Firefox: about:preferences#privacy → Permissions → Notifications
    • Safari: System Preferences → Notifications
  2. Reset permission (requires browser-specific steps):
    • Chrome: Site settings → Notifications → Reset
    • Safari: Remove and re-add to Home Screen

Service Worker Not Updating

  1. Check update strategy:
    navigator.serviceWorker.getRegistrations().then(regs => {
      regs.forEach(reg => reg.update());
    });
    
  2. Hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (macOS)
  3. Clear service worker:
    • Chrome DevTools → Application → Service Workers → Unregister

Push Not Received

  1. Verify subscription exists:
    SELECT * FROM push_subscriptions WHERE user_id = '<user-uuid>';
    
  2. Check outbox status:
    SELECT * FROM notification_outbox 
    WHERE delivery_id IN (
      SELECT id FROM notification_deliveries 
      WHERE recipient_user_id = '<user-uuid>'
    )
    ORDER BY created_at DESC LIMIT 10;
    
  3. Verify Edge Function is deployed (see Edge Functions)
  4. Check browser console for errors during subscription

Performance Optimization

Reduce Cache Size

Adjust runtime cache limit:
// public/sw.js:6
const MAX_RUNTIME_ENTRIES = 120;  // Reduce to 60 for slower devices

Disable Push for Low-End Devices

Check device capabilities before offering push:
if (!isPushSupported()) {
  // Hide push subscription UI
  return;
}

// Optional: Check connection speed
if (navigator.connection?.effectiveType === '2g') {
  // Warn user about data usage
}

Optimize Notification Payload

Keep push payload minimal (4KB limit):
// Only include essential fields
const payload = {
  title: notification.title,
  body: notification.message.slice(0, 200),  // Truncate long messages
  url: notification.url,
  tag: notification.deliveryId,  // For deduplication
};

Security Considerations

  • Never expose VAPID_PRIVATE_KEY in client code
  • Only store VAPID_PUBLIC_KEY in frontend .env
  • Validate notification URLs to prevent open redirects
  • Use HTTPS in production (required for Push API)

URL Validation

The system enforces internal URLs:
// src/services/notificationCenterService.ts:145
function resolveNotificationUrl(
  payload: Record<string, unknown>,
  entityType: string,
  entityId: string
) {
  const payloadUrl = payload.url;
  // Only allow internal paths starting with '/'
  if (typeof payloadUrl === 'string' && payloadUrl.startsWith('/')) {
    return payloadUrl;
  }
  // Default safe fallback
  if (entityType === 'ticket') {
    return `/tickets/${entityId}`;
  }
  return '/inicio';
}

Next Steps

Notification Setup

Return to setup guide for database configuration

Edge Functions

Review Edge Function deployment and monitoring

Build docs developers (and LLMs) love