feat(crm): frontend del CRM (clientes API, navegación, dashboard + Kanban) y rebrand
- Clientes API tipados por entidad en src/lib/api/crm - Navegación CRM en el sidebar - Páginas: panel (KPIs + embudo), cuentas, contactos, prospectos, actividades - Kanban de oportunidades con drag & drop (mueve entre etapas) y siembra de embudo - Rebrand plantilla → CRM (.env, docker-compose name, package.json, README) - Fix: CORE_DB_HOST=postgres (coincide con el servicio del compose) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
66
frontend/src/lib/components/crm/format.ts
Normal file
66
frontend/src/lib/components/crm/format.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Utilidades de formato y etiquetas legibles para el CRM.
|
||||
*/
|
||||
|
||||
export function formatMoney(value: number | null | undefined, currency = 'MXN'): string {
|
||||
if (value === null || value === undefined) return '—';
|
||||
return new Intl.NumberFormat('es-MX', { style: 'currency', currency }).format(Number(value));
|
||||
}
|
||||
|
||||
export function formatDate(value: string | null | undefined): string {
|
||||
if (!value) return '—';
|
||||
const d = new Date(value);
|
||||
if (Number.isNaN(d.getTime())) return '—';
|
||||
return d.toLocaleDateString('es-MX', { year: 'numeric', month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
export const ACCOUNT_TYPES: { value: string; label: string }[] = [
|
||||
{ value: 'importador', label: 'Importador' },
|
||||
{ value: 'exportador', label: 'Exportador' },
|
||||
{ value: 'immex', label: 'IMMEX / Maquila' },
|
||||
{ value: 'agencia_aduanal', label: 'Agencia aduanal' },
|
||||
{ value: 'transportista', label: 'Transportista' },
|
||||
{ value: 'otro', label: 'Otro' }
|
||||
];
|
||||
|
||||
export const ACCOUNT_STATUS: { value: string; label: string }[] = [
|
||||
{ value: 'active', label: 'Activa' },
|
||||
{ value: 'prospect', label: 'Prospecto' },
|
||||
{ value: 'inactive', label: 'Inactiva' }
|
||||
];
|
||||
|
||||
export const LEAD_SOURCES: { value: string; label: string }[] = [
|
||||
{ value: 'web', label: 'Web' },
|
||||
{ value: 'referido', label: 'Referido' },
|
||||
{ value: 'evento', label: 'Evento' },
|
||||
{ value: 'llamada', label: 'Llamada' },
|
||||
{ value: 'email', label: 'Email' },
|
||||
{ value: 'otro', label: 'Otro' }
|
||||
];
|
||||
|
||||
export const LEAD_STATUS: { value: string; label: string }[] = [
|
||||
{ value: 'new', label: 'Nuevo' },
|
||||
{ value: 'contacted', label: 'Contactado' },
|
||||
{ value: 'qualified', label: 'Calificado' },
|
||||
{ value: 'unqualified', label: 'No calificado' },
|
||||
{ value: 'converted', label: 'Convertido' }
|
||||
];
|
||||
|
||||
export const ACTIVITY_TYPES: { value: string; label: string }[] = [
|
||||
{ value: 'call', label: 'Llamada' },
|
||||
{ value: 'meeting', label: 'Reunión' },
|
||||
{ value: 'task', label: 'Tarea' },
|
||||
{ value: 'email', label: 'Correo' },
|
||||
{ value: 'note', label: 'Nota' }
|
||||
];
|
||||
|
||||
export const ACTIVITY_STATUS: { value: string; label: string }[] = [
|
||||
{ value: 'pending', label: 'Pendiente' },
|
||||
{ value: 'completed', label: 'Completada' },
|
||||
{ value: 'canceled', label: 'Cancelada' }
|
||||
];
|
||||
|
||||
export function labelOf(list: { value: string; label: string }[], value: string | null): string {
|
||||
if (!value) return '—';
|
||||
return list.find((x) => x.value === value)?.label ?? value;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Settings2,
|
||||
Users,
|
||||
Shield,
|
||||
Briefcase,
|
||||
} from '@lucide/svelte';
|
||||
|
||||
export type SystemContext = 'fixed_asset' | 'inventory';
|
||||
@@ -34,6 +35,19 @@ export function getNavMain(): NavMainItem[] {
|
||||
url: '/dashboard',
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: 'CRM',
|
||||
url: '/dashboard/crm',
|
||||
icon: Briefcase,
|
||||
items: [
|
||||
{ title: 'Panel', url: '/dashboard/crm' },
|
||||
{ title: 'Cuentas', url: '/dashboard/crm/cuentas' },
|
||||
{ title: 'Contactos', url: '/dashboard/crm/contactos' },
|
||||
{ title: 'Prospectos', url: '/dashboard/crm/prospectos' },
|
||||
{ title: 'Oportunidades', url: '/dashboard/crm/oportunidades' },
|
||||
{ title: 'Actividades', url: '/dashboard/crm/actividades' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Usuarios',
|
||||
url: '/dashboard/users',
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
|
||||
<Sidebar.Group>
|
||||
<Sidebar.GroupLabel class="flex items-center gap-2">
|
||||
<span>Anexo-76</span>
|
||||
<span>CRM</span>
|
||||
{#if $permissionsRefreshing}
|
||||
<span
|
||||
class="size-1.5 shrink-0 animate-pulse rounded-full bg-primary"
|
||||
|
||||
Reference in New Issue
Block a user