Documentation Index
Fetch the complete documentation index at: https://mintlify.com/elegroag/nuxt-credito-caja/llms.txt
Use this file to discover all available pages before exploring further.
Comfaca Créditos en Línea incluye un sistema de notificaciones in-app que mantiene informado al afiliado sobre los cambios de estado de sus solicitudes, la finalización de firmas, documentos requeridos y otras acciones relevantes del proceso crediticio. Las notificaciones se almacenan en base de datos y se consultan desde el cliente mediante polling periódico configurable.
Composable useNotifications
El composable useNotifications gestiona el ciclo de vida completo de las notificaciones en el cliente: carga, actualización del contador de no leídas, marcado como leídas y eliminación.
Estado reactivo
const notifications = ref<Notification[]>([]);
const unreadCount = ref(0);
const loading = ref(false);
const error = ref<string | null>(null);
const pollingEnabled = ref(true);
const pollingInterval = ref(100000); // ~100 segundos
Propiedades computadas
const hasUnread = computed(() => unreadCount.value > 0);
const unreadNotifications = computed(() =>
notifications.value.filter((n) => !n.read_at)
);
const readNotifications = computed(() =>
notifications.value.filter((n) => n.read_at)
);
Métodos disponibles
// Carga notificaciones — opcionalmente solo las no leídas
await loadNotifications(onlyUnread?: boolean, limit?: number);
// Actualiza únicamente el contador (más liviano que cargar todas)
await updateUnreadCount();
// Marca una notificación como leída y actualiza el contador localmente
await markAsRead(notificationId: string): Promise<boolean>;
// Marca todas como leídas y pone unreadCount en 0
await markAllAsRead(): Promise<boolean>;
// Elimina una notificación (ajusta unreadCount si estaba sin leer)
await deleteNotification(notificationId: string): Promise<boolean>;
// Control del polling
startPolling(); // Inicia el intervalo automático
stopPolling(); // Detiene el intervalo y limpia el timer
togglePolling(); // Alterna entre activo e inactivo
Polling automático
El composable actualiza el contador de no leídas periódicamente en segundo plano. El polling se detiene automáticamente en onUnmounted:
const startPolling = (): void => {
if (pollingTimer) {
clearInterval(pollingTimer);
}
pollingEnabled.value = true;
pollingTimer = setInterval(() => {
if (pollingEnabled.value) {
updateUnreadCount();
}
}, pollingInterval.value);
};
onUnmounted(() => {
stopPolling();
});
Los helpers de formato están en app/composables/notifications/notificationFormat.ts y se mantienen separados del composable principal para que los componentes presentacionales (NotificationItem) puedan usarlos sin instanciar useNotifications ni disparar efectos secundarios como el polling.
Interfaz Notification
interface Notification {
id: string;
type: string;
data: Record<string, unknown> & {
titulo: string;
mensaje: string;
solicitud_id?: string; // ID de solicitud relacionada (si aplica)
url?: string; // URL de navegación al hacer clic
};
read_at: string | null; // null = no leída
created_at: string; // ISO 8601
}
Tipos de notificación y sus iconos
const icons: Record<string, string> = {
firma_completada: "lucide:check-circle",
firma_rechazada: "lucide:x-circle",
firma_expirada: "lucide:alert-circle",
solicitud_aprobada: "lucide:thumbs-up",
solicitud_rechazada: "lucide:thumbs-down",
documento_requerido: "lucide:file-text",
estado_actualizado: "lucide:refresh-cw",
solicitud_estado_actualizado: "lucide:refresh-cw",
comentario_nuevo: "lucide:message-circle",
recordatorio: "lucide:bell"
};
Colores por tipo
const colors: Record<string, string> = {
firma_completada: "text-green-600",
firma_rechazada: "text-red-600",
firma_expirada: "text-orange-600",
solicitud_aprobada: "text-green-600",
solicitud_rechazada: "text-red-600",
documento_requerido: "text-blue-600",
estado_actualizado: "text-blue-600",
solicitud_estado_actualizado: "text-blue-600",
comentario_nuevo: "text-purple-600",
recordatorio: "text-yellow-600"
};
Función de tiempo relativo
export function formatRelativeTime(date: string): string {
const diff = Date.now() - new Date(date).getTime();
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (seconds < 60) return "Hace unos segundos";
if (minutes < 60) return `Hace ${minutes} minuto${minutes > 1 ? "s" : ""}`;
if (hours < 24) return `Hace ${hours} hora${hours > 1 ? "s" : ""}`;
if (days < 7) return `Hace ${days} día${days > 1 ? "s" : ""}`;
return new Date(date).toLocaleDateString("es-CO", {
year: "numeric", month: "short", day: "numeric"
});
}
Servicio de Notificaciones
server/services/notification.service.ts es la capa de acceso a datos para las notificaciones. Utiliza Prisma para interactuar con la tabla notifications.
Crear una notificación
const service = notificationService();
await service.createNotification({
owner_username: "juan.perez",
type: "solicitud_estado_actualizado",
data: {
titulo: "Tu solicitud fue actualizada",
mensaje: "La solicitud 000001-2026-01 pasó a estado FIRMADO.",
solicitud_id: "000001-2026-01",
url: "/dash/solicitudes/000001-2026-01"
}
});
Métodos del servicio
| Método | Descripción |
|---|
getNotifications(username) | Retorna las últimas 50 notificaciones del usuario, ordenadas por created_at desc |
getUnreadCount(username) | Cuenta las notificaciones donde read_at IS NULL |
markAsRead(id, username) | Establece read_at = new Date() con validación de propietario |
markAllAsRead(username) | updateMany donde read_at IS NULL para el usuario dado |
createNotification(data) | Inserta una nueva notificación con UUID generado por crypto.randomUUID() |
Esquema de la notificación en base de datos
// Campos escritos por createNotification()
{
id: string, // UUID v4
owner_username: string, // Usuario destinatario
type: string, // Tipo de notificación
data: JSON, // { titulo, mensaje, solicitud_id?, url? }
notifiable_type: "User",
notifiable_id: "1",
read_at: null, // null al crear
created_at: Date,
updated_at: Date
}
API de Notificaciones
Listar notificaciones
Contador no leídas
Marcar como leída
Marcar todas como leídas
Eliminar notificación
GET /api/notifications?limit=50&unread=false
Authorization: Bearer <token>
Parámetros de query:
limit (opcional, default 50) — número máximo de notificaciones a retornar
unread (opcional, "true") — retornar solo las no leídas
Respuesta:{
"success": true,
"data": {
"notifications": [...],
"unread_count": 3,
"total": 15
}
}
GET /api/notifications/unread-count
Authorization: Bearer <token>
Respuesta:{
"success": true,
"data": { "unread_count": 3 }
}
PUT /api/notifications/:id/read
Authorization: Bearer <token>
Respuesta:{
"success": true,
"message": "Notificación marcada como leída"
}
PUT /api/notifications/mark-all-read
Authorization: Bearer <token>
Respuesta:{
"success": true,
"data": { "marked_count": 5 },
"message": "Todas las notificaciones marcadas como leídas"
}
DELETE /api/notifications/:id
Authorization: Bearer <token>
Respuesta:{
"success": true,
"message": "Notificación eliminada"
}
Ejemplo de uso en un componente Vue
<script setup lang="ts">
import {
getNotificationIcon,
getNotificationColor,
formatRelativeTime
} from "~/composables/notifications/notificationFormat";
const {
notifications,
unreadCount,
hasUnread,
loading,
loadNotifications,
markAsRead,
markAllAsRead,
deleteNotification,
startPolling
} = useNotifications();
// Cargar al montar e iniciar polling
onMounted(async () => {
await loadNotifications();
startPolling();
});
</script>
<template>
<div class="notification-panel">
<!-- Badge de no leídas -->
<span v-if="hasUnread" class="badge">{{ unreadCount }}</span>
<!-- Botón "Marcar todas como leídas" -->
<button v-if="hasUnread" @click="markAllAsRead">
Marcar todas como leídas
</button>
<!-- Lista de notificaciones -->
<ul>
<li
v-for="notif in notifications"
:key="notif.id"
:class="{ unread: !notif.read_at }"
@click="markAsRead(notif.id)"
>
<!-- Icono según tipo -->
<Icon
:name="getNotificationIcon(notif.type)"
:class="getNotificationColor(notif.type)"
/>
<div>
<p class="font-semibold">{{ notif.data.titulo }}</p>
<p class="text-sm text-gray-600">{{ notif.data.mensaje }}</p>
<span class="text-xs text-gray-400">
{{ formatRelativeTime(notif.created_at) }}
</span>
</div>
<!-- Eliminar -->
<button @click.stop="deleteNotification(notif.id)">×</button>
</li>
</ul>
<p v-if="loading">Cargando notificaciones...</p>
</div>
</template>
Las notificaciones se generan automáticamente por el servidor en los siguientes eventos: cambio de estado de una solicitud (cualquier transición), finalización de firma digital (FIRMADO), rechazo de firma, solicitud de documentos adicionales y aprobación o rechazo final del crédito. El tipo de notificación determina el icono y el color mostrado en la interfaz, según los mapas definidos en notificationFormat.ts.