diff --git a/src/App.tsx b/src/App.tsx index 42004c9..6cc1a7d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -60,6 +60,11 @@ import ContentLibrary from "./pages/dashboard/guides/ContentLibrary"; import CommissionsDashboard from "./pages/dashboard/commissions/CommissionsDashboard"; import CommissionRules from "./pages/dashboard/commissions/CommissionRules"; import PaymentHistory from "./pages/dashboard/commissions/PaymentHistory"; +// CRM pages +import CRMDashboard from "./pages/dashboard/crm/CRMDashboard"; +import CRMContacts from "./pages/dashboard/crm/Contacts"; +import CRMCampaigns from "./pages/dashboard/crm/Campaigns"; +import CRMAnalytics from "./pages/dashboard/crm/Analytics"; // Tourist App import TouristApp from "./pages/TouristApp"; // Commerce pages (for retail stores) @@ -655,6 +660,39 @@ const AppRouter = () => ( } /> + {/* CRM Routes */} + + + + + + } /> + + + + + + + } /> + + + + + + + } /> + + + + + + + } /> + {/* Catch-all route */} } /> diff --git a/src/components/DashboardLayout.tsx b/src/components/DashboardLayout.tsx index 7927d30..f6c19e5 100644 --- a/src/components/DashboardLayout.tsx +++ b/src/components/DashboardLayout.tsx @@ -59,7 +59,8 @@ import { Store, Server, ShieldAlert, - UserCircle + UserCircle, + Mail } from 'lucide-react'; const DashboardLayout = ({ children }: { children: React.ReactNode }) => { @@ -128,9 +129,20 @@ const DashboardLayout = ({ children }: { children: React.ReactNode }) => { { icon: FileText, label: 'Historial', path: '/dashboard/commissions/payments' } ] }, + { + icon: Users, + label: 'CRM', + path: '/dashboard/crm', + subItems: [ + { icon: BarChart3, label: 'Dashboard', path: '/dashboard/crm/dashboard' }, + { icon: Users, label: 'Contactos', path: '/dashboard/crm/contacts' }, + { icon: Mail, label: 'Campañas', path: '/dashboard/crm/campaigns' }, + { icon: BarChart3, label: 'Analytics', path: '/dashboard/crm/analytics' } + ] + }, { icon: Settings, - label: 'Configuración', + label: 'Configuración', path: '/dashboard/config', subItems: [ { icon: Server, label: 'APIs', path: '/dashboard/config/apis' }, diff --git a/src/pages/dashboard/crm/Analytics.tsx b/src/pages/dashboard/crm/Analytics.tsx new file mode 100644 index 0000000..a4a24c0 --- /dev/null +++ b/src/pages/dashboard/crm/Analytics.tsx @@ -0,0 +1,286 @@ +import React, { useState } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { + TrendingUp, + TrendingDown, + Users, + DollarSign, + Activity, + Calendar, + Download, + ArrowUpRight, + ArrowDownRight +} from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; + +const Analytics = () => { + const [timeRange, setTimeRange] = useState('30d'); + + const metrics = [ + { + title: 'Nuevos Clientes', + value: '342', + change: '+18.2%', + trend: 'up', + icon: Users, + color: 'text-blue-500' + }, + { + title: 'Tasa de Retención', + value: '84.2%', + change: '-2.1%', + trend: 'down', + icon: Activity, + color: 'text-green-500' + }, + { + title: 'Ingresos por Cliente', + value: '$1,234', + change: '+12.5%', + trend: 'up', + icon: DollarSign, + color: 'text-orange-500' + }, + { + title: 'Engagement Score', + value: '7.8/10', + change: '+0.5', + trend: 'up', + icon: TrendingUp, + color: 'text-purple-500' + } + ]; + + const segmentPerformance = [ + { + segment: 'VIP Travelers', + customers: 342, + revenue: '$145,230', + avgSpent: '$425', + retention: '92%', + growth: '+18%' + }, + { + segment: 'Business', + customers: 456, + revenue: '$187,900', + avgSpent: '$412', + retention: '88%', + growth: '+25%' + }, + { + segment: 'Familias', + customers: 891, + revenue: '$98,450', + avgSpent: '$110', + retention: '76%', + growth: '+12%' + }, + { + segment: 'Aventureros', + customers: 623, + revenue: '$76,340', + avgSpent: '$123', + retention: '81%', + growth: '+8%' + } + ]; + + const customerJourney = [ + { stage: 'Descubrimiento', count: 5420, conversion: '100%', color: 'bg-blue-500' }, + { stage: 'Consideración', count: 3845, conversion: '71%', color: 'bg-green-500' }, + { stage: 'Decisión', count: 2156, conversion: '56%', color: 'bg-orange-500' }, + { stage: 'Compra', count: 1284, conversion: '60%', color: 'bg-purple-500' }, + { stage: 'Fidelización', count: 956, conversion: '74%', color: 'bg-pink-500' } + ]; + + const topActions = [ + { action: 'Abrir emails', count: 4523, percentage: 85 }, + { action: 'Visitar sitio web', count: 3891, percentage: 73 }, + { action: 'Ver productos', count: 2456, percentage: 61 }, + { action: 'Agregar al carrito', count: 1234, percentage: 50 }, + { action: 'Completar compra', count: 789, percentage: 64 } + ]; + + return ( +
+ {/* Header */} +
+
+

Analytics CRM

+

Análisis detallado del comportamiento de clientes

+
+
+ + +
+
+ + {/* Key Metrics */} +
+ {metrics.map((metric) => { + const Icon = metric.icon; + return ( + + + + {metric.title} + + + + +
{metric.value}
+
+ {metric.trend === 'up' ? ( + + ) : ( + + )} + + {metric.change} + + vs período anterior +
+
+
+ ); + })} +
+ + {/* Segment Performance */} + + + Rendimiento por Segmento + Métricas clave de cada grupo de clientes + + +
+ {segmentPerformance.map((segment, index) => ( +
+
+
+
+ {index + 1} +
+
+

{segment.segment}

+

{segment.customers} clientes

+
+
+ + {segment.growth} crecimiento + +
+
+
+

Ingresos Totales

+

{segment.revenue}

+
+
+

Gasto Promedio

+

{segment.avgSpent}

+
+
+

Retención

+

{segment.retention}

+
+
+

Clientes

+

{segment.customers}

+
+
+
+ ))} +
+
+
+ +
+ {/* Customer Journey */} + + + Embudo de Conversión + Journey del cliente desde descubrimiento hasta fidelización + + +
+ {customerJourney.map((stage, index) => ( +
+
+
+ + {stage.stage} +
+
+ {stage.count.toLocaleString()} + {stage.conversion} +
+
+
+
+
+
+ ))} +
+
+
+ + {/* Top Actions */} + + + Acciones Principales + Actividades más comunes de los clientes + + +
+ {topActions.map((action, index) => ( +
+
+ {action.action} + {action.count.toLocaleString()} +
+
+
+
+
+ {action.percentage}% de conversión +
+
+ ))} +
+
+
+
+
+ ); +}; + +export default Analytics; diff --git a/src/pages/dashboard/crm/CRMDashboard.tsx b/src/pages/dashboard/crm/CRMDashboard.tsx new file mode 100644 index 0000000..5e40334 --- /dev/null +++ b/src/pages/dashboard/crm/CRMDashboard.tsx @@ -0,0 +1,327 @@ +import React, { useState } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { + Users, + TrendingUp, + Mail, + Phone, + Target, + DollarSign, + Calendar, + ArrowUpRight, + ArrowDownRight, + MoreVertical +} from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; + +const CRMDashboard = () => { + const [timeRange, setTimeRange] = useState('30d'); + + const metrics = [ + { + title: 'Total Clientes', + value: '2,847', + change: '+12.5%', + trend: 'up', + icon: Users, + color: 'text-blue-500' + }, + { + title: 'Clientes Activos', + value: '1,923', + change: '+8.2%', + trend: 'up', + icon: TrendingUp, + color: 'text-green-500' + }, + { + title: 'Valor de Vida (LTV)', + value: '$4,325', + change: '+15.3%', + trend: 'up', + icon: DollarSign, + color: 'text-orange-500' + }, + { + title: 'Tasa de Retención', + value: '84.2%', + change: '-2.1%', + trend: 'down', + icon: Target, + color: 'text-purple-500' + } + ]; + + const recentActivities = [ + { + id: 1, + customer: 'María González', + action: 'Completó reserva', + value: '$450', + time: 'Hace 5 min', + type: 'booking' + }, + { + id: 2, + customer: 'Juan Pérez', + action: 'Solicitó información', + value: null, + time: 'Hace 12 min', + type: 'inquiry' + }, + { + id: 3, + customer: 'Ana Martínez', + action: 'Renovó suscripción', + value: '$89/mes', + time: 'Hace 25 min', + type: 'subscription' + }, + { + id: 4, + customer: 'Carlos López', + action: 'Dejó review', + value: '5 estrellas', + time: 'Hace 1 hora', + type: 'review' + } + ]; + + const topSegments = [ + { name: 'VIP Travelers', count: 342, revenue: '$145,230', growth: '+18%' }, + { name: 'Familias', count: 891, revenue: '$98,450', growth: '+12%' }, + { name: 'Business', count: 456, revenue: '$187,900', growth: '+25%' }, + { name: 'Aventureros', count: 623, revenue: '$76,340', growth: '+8%' } + ]; + + const activeCampaigns = [ + { + id: 1, + name: 'Promoción Verano 2024', + status: 'active', + sent: 2847, + opened: 1423, + clicked: 456, + conversions: 89 + }, + { + id: 2, + name: 'Newsletter Mensual', + status: 'scheduled', + sent: 0, + opened: 0, + clicked: 0, + conversions: 0 + }, + { + id: 3, + name: 'Reactivación Clientes', + status: 'active', + sent: 1245, + opened: 623, + clicked: 187, + conversions: 34 + } + ]; + + return ( +
+ {/* Header */} +
+
+

CRM Dashboard

+

Gestión de relaciones con clientes

+
+
+ + + + + + setTimeRange('7d')}>Últimos 7 días + setTimeRange('30d')}>Últimos 30 días + setTimeRange('1y')}>Último año + + + +
+
+ + {/* Metrics Grid */} +
+ {metrics.map((metric) => { + const Icon = metric.icon; + return ( + + + + {metric.title} + + + + +
{metric.value}
+
+ {metric.trend === 'up' ? ( + + ) : ( + + )} + + {metric.change} + + vs período anterior +
+
+
+ ); + })} +
+ +
+ {/* Recent Activities */} + + + Actividad Reciente + Últimas interacciones con clientes + + +
+ {recentActivities.map((activity) => ( +
+
+
+ {activity.customer.split(' ').map(n => n[0]).join('')} +
+
+

{activity.customer}

+

{activity.action}

+
+
+
+ {activity.value && ( +

{activity.value}

+ )} +

{activity.time}

+
+
+ ))} +
+
+
+ + {/* Top Segments */} + + + Segmentos Principales + Grupos de clientes de mayor valor + + +
+ {topSegments.map((segment, index) => ( +
+
+
+

{segment.name}

+ + {segment.count} clientes + +
+
+

Ingresos: {segment.revenue}

+ {segment.growth} +
+
+ +
+ ))} +
+
+
+
+ + {/* Active Campaigns */} + + + Campañas Activas + Estado de campañas de marketing + + +
+ {activeCampaigns.map((campaign) => ( +
+
+
+

{campaign.name}

+ + {campaign.status === 'active' ? 'Activa' : 'Programada'} + +
+ +
+
+
+

Enviados

+

{campaign.sent.toLocaleString()}

+
+
+

Abiertos

+

+ {campaign.opened.toLocaleString()} + {campaign.sent > 0 && ( + + ({((campaign.opened / campaign.sent) * 100).toFixed(1)}%) + + )} +

+
+
+

Clicks

+

+ {campaign.clicked.toLocaleString()} + {campaign.opened > 0 && ( + + ({((campaign.clicked / campaign.opened) * 100).toFixed(1)}%) + + )} +

+
+
+

Conversiones

+

+ {campaign.conversions.toLocaleString()} + {campaign.sent > 0 && ( + + ({((campaign.conversions / campaign.sent) * 100).toFixed(1)}%) + + )} +

+
+
+
+ ))} +
+
+
+
+ ); +}; + +export default CRMDashboard; diff --git a/src/pages/dashboard/crm/Campaigns.tsx b/src/pages/dashboard/crm/Campaigns.tsx new file mode 100644 index 0000000..8f2faf2 --- /dev/null +++ b/src/pages/dashboard/crm/Campaigns.tsx @@ -0,0 +1,419 @@ +import React, { useState } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { Textarea } from '@/components/ui/textarea'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { + Mail, + Plus, + Send, + Clock, + CheckCircle, + XCircle, + BarChart3, + Users, + Eye, + MousePointer, + ShoppingCart, + Calendar +} from 'lucide-react'; +import { useToast } from '@/hooks/use-toast'; +import { z } from 'zod'; + +const campaignSchema = z.object({ + name: z.string().trim().min(1, 'Nombre requerido').max(100, 'Nombre muy largo'), + subject: z.string().trim().min(1, 'Asunto requerido').max(200, 'Asunto muy largo'), + segment: z.string().min(1, 'Selecciona un segmento'), + message: z.string().trim().min(10, 'Mensaje muy corto').max(2000, 'Mensaje muy largo'), +}); + +const Campaigns = () => { + const { toast } = useToast(); + const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); + const [formData, setFormData] = useState({ + name: '', + subject: '', + segment: '', + message: '' + }); + const [formErrors, setFormErrors] = useState>({}); + + const campaigns = [ + { + id: 1, + name: 'Promoción Verano 2024', + status: 'sent', + segment: 'VIP Travelers', + sentDate: '2024-03-10', + recipients: 2847, + opened: 1423, + clicked: 456, + conversions: 89, + revenue: '$12,450' + }, + { + id: 2, + name: 'Newsletter Mensual', + status: 'scheduled', + segment: 'Todos', + sentDate: '2024-03-20', + recipients: 5234, + opened: 0, + clicked: 0, + conversions: 0, + revenue: '$0' + }, + { + id: 3, + name: 'Reactivación Clientes', + status: 'sending', + segment: 'Inactivos', + sentDate: '2024-03-15', + recipients: 1245, + opened: 623, + clicked: 187, + conversions: 34, + revenue: '$4,230' + }, + { + id: 4, + name: 'Ofertas Especiales Familias', + status: 'draft', + segment: 'Familias', + sentDate: null, + recipients: 891, + opened: 0, + clicked: 0, + conversions: 0, + revenue: '$0' + } + ]; + + const segments = ['Todos', 'VIP Travelers', 'Business', 'Familias', 'Aventureros', 'Inactivos']; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + try { + campaignSchema.parse(formData); + setFormErrors({}); + + toast({ + title: 'Campaña creada', + description: `La campaña "${formData.name}" ha sido creada exitosamente.`, + }); + + setIsCreateDialogOpen(false); + setFormData({ name: '', subject: '', segment: '', message: '' }); + } catch (error) { + if (error instanceof z.ZodError) { + const errors: Record = {}; + error.errors.forEach((err) => { + if (err.path[0]) { + errors[err.path[0].toString()] = err.message; + } + }); + setFormErrors(errors); + } + } + }; + + const getStatusBadge = (status: string) => { + const statusConfig = { + sent: { label: 'Enviada', variant: 'default' as const, icon: CheckCircle, color: 'text-green-600' }, + scheduled: { label: 'Programada', variant: 'secondary' as const, icon: Clock, color: 'text-blue-600' }, + sending: { label: 'Enviando', variant: 'outline' as const, icon: Send, color: 'text-orange-600' }, + draft: { label: 'Borrador', variant: 'outline' as const, icon: Mail, color: 'text-gray-600' }, + failed: { label: 'Fallida', variant: 'destructive' as const, icon: XCircle, color: 'text-red-600' } + }; + + return statusConfig[status as keyof typeof statusConfig] || statusConfig.draft; + }; + + return ( +
+ {/* Header */} +
+
+

Campañas

+

Gestiona tus campañas de marketing

+
+ + + + + + + Crear Nueva Campaña + + Configura tu campaña de email marketing + + +
+
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="Ej: Promoción Verano 2024" + className={formErrors.name ? 'border-red-500' : ''} + /> + {formErrors.name && ( +

{formErrors.name}

+ )} +
+
+ + setFormData({ ...formData, subject: e.target.value })} + placeholder="Asunto atractivo para tus clientes" + className={formErrors.subject ? 'border-red-500' : ''} + /> + {formErrors.subject && ( +

{formErrors.subject}

+ )} +
+
+ + + {formErrors.segment && ( +

{formErrors.segment}

+ )} +
+
+ +