chore: baseline plantilla-proyectos como base del CRM

This commit is contained in:
Aduanasoft
2026-07-14 09:03:52 -06:00
commit c3d0eedc8d
469 changed files with 69739 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
/**
* Utility for date formatting and conversion.
* Canonical API format: DD/MM/YYYY
* Native input[type="date"] format: YYYY-MM-DD
*/
/**
* Converts DD/MM/YYYY or YYYYMMDD to YYYY-MM-DD for native date input.
*/
export function toInputDate(dateStr: string | number | null | undefined): string {
if (!dateStr) return '';
const str = String(dateStr);
if (/^\d{8}$/.test(str)) {
const y = str.substring(0, 4);
const m = str.substring(4, 6);
const d = str.substring(6, 8);
return `${y}-${m}-${d}`;
}
if (/^\d{2}\/\d{2}\/\d{4}$/.test(str)) {
const [d, m, y] = str.split('/');
return `${y}-${m}-${d}`;
}
if (/^\d{4}-\d{2}-\d{2}$/.test(str)) {
return str;
}
return '';
}
/**
* Converts YYYY-MM-DD or YYYYMMDD to DD/MM/YYYY for API.
*/
export function toDbDate(dateStr: string | null | undefined): string | null {
if (!dateStr) return null;
if (/^\d{2}\/\d{2}\/\d{4}$/.test(dateStr)) {
return dateStr;
}
if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
const [y, m, d] = dateStr.split('-');
return `${d}/${m}/${y}`;
}
if (/^\d{8}$/.test(dateStr)) {
const y = dateStr.substring(0, 4);
const m = dateStr.substring(4, 6);
const d = dateStr.substring(6, 8);
return `${d}/${m}/${y}`;
}
return null;
}

View File

@@ -0,0 +1,84 @@
/**
* Utilidades para manejar errores de API en el cliente
*/
import { toast } from 'svelte-sonner';
export interface ApiError {
detail: string;
status: number;
isForbidden?: boolean;
}
/**
* Maneja errores de API mostrando el toast apropiado
* @param error - El error a manejar (puede ser un objeto ApiError o un string)
* @returns true si se manejó un error, false si no había error
*/
export function handleApiError(error?: ApiError | string | null): boolean {
if (!error) return false;
// Si es un string, convertirlo a objeto
if (typeof error === 'string') {
// Detectar si es un error 403
if (error.includes('403') || error.toLowerCase().includes('forbidden')) {
toast.error(error, {
duration: 5000,
description: 'No tienes permisos para realizar esta acción'
});
return true;
}
// Otros errores en formato string
toast.error(error, {
duration: 4000
});
return true;
}
// Es un objeto ApiError
if (error.isForbidden || error.status === 403) {
// Mostrar el mensaje específico del backend si está disponible
const message = error.detail || 'No tienes permisos para realizar esta acción';
toast.error(message, {
duration: 5000,
description: error.detail ? 'Contacta a tu administrador si crees que esto es un error' : undefined
});
return true;
}
if (error.status === 401) {
toast.error('Sesión expirada', {
duration: 3000,
description: 'Por favor, inicia sesión nuevamente'
});
return true;
}
// Otros errores
toast.error(error.detail || 'Error en la operación', {
duration: 4000
});
return true;
}
/**
* Hook para usar en componentes Svelte con $effect
* Muestra automáticamente un toast cuando hay un error
*
* Ejemplo de uso en +page.svelte:
* ```svelte
* <script lang="ts">
* import { handleApiError } from '$lib/utils/error-handler';
* let { data } = $props();
*
* $effect(() => {
* handleApiError(data.error);
* });
* </script>
* ```
*/
export function useErrorHandler(error?: ApiError | null) {
if (error) {
handleApiError(error);
}
}

View File

@@ -0,0 +1,71 @@
/**
* Helper para verificar permisos del usuario
* Basado en el sistema de permisos RBAC del backend
*/
import { get } from 'svelte/store';
import { page } from '$app/stores';
export interface UserPermission {
module: string;
action: string;
}
/**
* Verifica si el usuario tiene un permiso específico
* @param module - El módulo (ej: 'invoices', 'pedimentos')
* @param action - La acción (ej: 'create', 'update', 'delete', 'read')
* @returns true si el usuario tiene el permiso, false si no
*/
export function hasPermission(module: string, action: string): boolean {
// TODO: Implementar verificación real contra permisos del usuario
// Por ahora retorna true para permitir desarrollo
// En producción esto debe:
// 1. Obtener los permisos del usuario desde el contexto/store
// 2. Verificar si existe un permiso con module y action
// 3. Retornar true/false basado en la verificación
console.warn('hasPermission() no está implementado - retornando true por defecto');
return true;
}
/**
* Verifica si el usuario tiene alguno de varios permisos
* @param permissions - Array de permisos a verificar
* @returns true si el usuario tiene al menos uno de los permisos
*/
export function hasAnyPermission(permissions: UserPermission[]): boolean {
return permissions.some(p => hasPermission(p.module, p.action));
}
/**
* Verifica si el usuario tiene todos los permisos especificados
* @param permissions - Array de permisos a verificar
* @returns true si el usuario tiene todos los permisos
*/
export function hasAllPermissions(permissions: UserPermission[]): boolean {
return permissions.every(p => hasPermission(p.module, p.action));
}
/**
* Guard para proteger rutas basado en permisos
* Puede ser usado en +page.server.ts o +layout.server.ts
* @param module - El módulo requerido
* @param action - La acción requerida
* @returns objeto con allowed (boolean) y redirect (string opcional)
*/
export function requirePermission(module: string, action: string): {
allowed: boolean;
redirect?: string;
} {
const allowed = hasPermission(module, action);
if (!allowed) {
return {
allowed: false,
redirect: '/dashboard?error=forbidden'
};
}
return { allowed: true };
}