import { describe, it, expect } from 'vitest'; import { conversacionSchema, perfilSchema, calificacionSchema, intentoSchema, } from '@/lib/funnel/bot-schemas'; describe('conversacionSchema', () => { it('acepta un turno mínimo (rol + mensaje)', () => { const r = conversacionSchema.safeParse({ rol: 'user', mensaje: 'Hola' }); expect(r.success).toBe(true); }); it('acepta media y estado opcionales', () => { const r = conversacionSchema.safeParse({ rol: 'assistant', mensaje: 'Te mando una foto', mediaType: 'image', mediaUrl: 'https://x/y.jpg', estadoWa: 'entregado', botStep: 'pide_fotos', }); expect(r.success).toBe(true); }); it('rechaza mensaje vacío', () => { expect(conversacionSchema.safeParse({ rol: 'user', mensaje: ' ' }).success).toBe(false); }); it('rechaza un rol fuera del enum', () => { expect(conversacionSchema.safeParse({ rol: 'bot', mensaje: 'Hola' }).success).toBe(false); }); it('rechaza un estadoWa inválido', () => { const r = conversacionSchema.safeParse({ rol: 'user', mensaje: 'Hola', estadoWa: 'visto' }); expect(r.success).toBe(false); }); }); describe('perfilSchema', () => { it('acepta una actualización parcial con un solo campo', () => { expect(perfilSchema.safeParse({ botStep: 'espacio' }).success).toBe(true); }); it('acepta campos normalizados del motor de presupuesto', () => { const r = perfilSchema.safeParse({ tipoReforma: 'cocina', m2Suelo: 12.5, calidadGlobal: 'premium', urgencia: 'alta', presupuestoTarget: 1500000, viable: true, }); expect(r.success).toBe(true); }); it('acepta fotosSolicitadasAt como datetime ISO', () => { const r = perfilSchema.safeParse({ fotosSolicitadasAt: '2026-06-07T10:00:00.000Z' }); expect(r.success).toBe(true); }); it('rechaza un cuerpo vacío', () => { expect(perfilSchema.safeParse({}).success).toBe(false); }); it('rechaza tipoReforma fuera del enum', () => { expect(perfilSchema.safeParse({ tipoReforma: 'jardin' }).success).toBe(false); }); it('rechaza m2Suelo no positivo', () => { expect(perfilSchema.safeParse({ m2Suelo: 0 }).success).toBe(false); expect(perfilSchema.safeParse({ m2Suelo: -3 }).success).toBe(false); }); it('rechaza fotosSolicitadasAt que no es datetime', () => { expect(perfilSchema.safeParse({ fotosSolicitadasAt: 'ayer' }).success).toBe(false); }); }); describe('calificacionSchema', () => { it('acepta score + nivel + criterios libres', () => { const r = calificacionSchema.safeParse({ score: 78, nivel: 'B', criterios: { presupuesto: 'ok', urgencia: 'media' }, notasAgente: 'Lead caliente', }); expect(r.success).toBe(true); }); it('acepta solo notasAgente', () => { expect(calificacionSchema.safeParse({ notasAgente: 'Pendiente de fotos' }).success).toBe(true); }); it('rechaza un cuerpo vacío', () => { expect(calificacionSchema.safeParse({}).success).toBe(false); }); it('rechaza score fuera de 0-100', () => { expect(calificacionSchema.safeParse({ score: 120 }).success).toBe(false); expect(calificacionSchema.safeParse({ score: -1 }).success).toBe(false); }); it('rechaza un nivel fuera del enum', () => { expect(calificacionSchema.safeParse({ nivel: 'E' }).success).toBe(false); }); }); describe('intentoSchema', () => { it('acepta un intento mínimo (canal + numeroIntento)', () => { const r = intentoSchema.safeParse({ canal: 'whatsapp', numeroIntento: 1 }); expect(r.success).toBe(true); }); it('acepta resultado, duración y metadata', () => { const r = intentoSchema.safeParse({ canal: 'llamada', resultado: 'no_contesta', completado: false, numeroIntento: 2, duracionSeg: 0, metadata: { retellCallId: 'call_123' }, }); expect(r.success).toBe(true); }); it('rechaza canal fuera del enum', () => { expect(intentoSchema.safeParse({ canal: 'email', numeroIntento: 1 }).success).toBe(false); }); it('rechaza numeroIntento menor que 1', () => { expect(intentoSchema.safeParse({ canal: 'whatsapp', numeroIntento: 0 }).success).toBe(false); }); it('rechaza resultado fuera del enum', () => { const r = intentoSchema.safeParse({ canal: 'llamada', resultado: 'colgado', numeroIntento: 1 }); expect(r.success).toBe(false); }); });