import { z } from 'zod'; export const signupSchema = z.object({ nombre: z.string().trim().min(1, 'Indica tu nombre.'), email: z.string().trim().toLowerCase().email('Email no válido.'), empresa: z.string().trim().min(1, 'Indica el nombre de tu empresa.'), provincia: z.string().trim().min(1, 'Indica tu provincia.'), password: z.string().min(8, 'La contraseña debe tener al menos 8 caracteres.'), optInMarketing: z.union([z.literal('on'), z.literal('')]).optional(), }); export type SignupInput = z.infer; export function slugify(value: string): string { return value .normalize('NFD') .replace(/[̀-ͯ]/g, '') .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); } // Rutas de la app que NO pueden ser slug de un reformista: colisionarían con // páginas reales o con ficheros públicos servidos en la raíz del dominio. export const RESERVED_SLUGS = new Set([ 'panel', 'login', 'logout', 'signup', 'admin', 'solicitud', 'api', 'b2b', 'b2b-assets', 'assets', '_next', 'favicon.ico', 'icon.svg', 'robots.txt', 'sitemap.xml', ]); export function isReservedSlug(slug: string): boolean { return RESERVED_SLUGS.has(slug); } export type SlugValidation = { ok: true; slug: string } | { ok: false; error: string }; // Normaliza y valida un slug propuesto para el funnel público de un reformista. // No comprueba unicidad (eso requiere BD); solo formato y palabras reservadas. export function validarSlug(raw: string): SlugValidation { const slug = slugify(raw); if (slug.length < 2) { return { ok: false, error: 'El enlace debe tener al menos 2 caracteres.' }; } if (slug.length > 40) { return { ok: false, error: 'El enlace no puede superar los 40 caracteres.' }; } if (isReservedSlug(slug)) { return { ok: false, error: 'Ese enlace está reservado. Elige otro.' }; } return { ok: true, slug }; }