import { describe, it, expect } from 'vitest'; import * as content from '../content'; import * as contenido from '../content'; import { apertura, dondeEstas, queCambia, elTrabajo, paraQuienQuien, empezar, puntosInflexion, dolores, fraseAncla, } from '../content'; // The landing speaks in the first person plural (Erik's decision, 6/ago/2026, // recorded in docs/superpowers/specs/2026-08-06-proceso-ocho-etapas-design.md). // Before that it was singular, and the change touched copy in six different // objects — easy to reintroduce by accident when editing one of them. // // The reader stays "tú": that half is the dueña's locked decision and is not // what this file guards. // Unambiguous first-person-singular markers: conjugated verbs and imperatives // with the enclitic "-me". Deliberately excludes anything that doubles as a // noun — "trabajo" is one ("el trabajo completo"), which is why the whole // word list is verbs and never bare stems. const SINGULAR = /\b(me sumerjo|sumerjo|cuestiono|separo|reviso|ubico|acompaño|escríbeme|cuéntame|conmigo)\b/i; describe('voz de la marca en lib/content.ts', () => { it('speaks in the plural everywhere creeer is the one speaking', () => { // `dolores` is the one exception and it is not an oversight: those four // quotes are in the first person singular because the CLIENT is talking, // not creeer ("Trabajo más y gano casi lo mismo que hace dos años"). // They are verbatim from slide 3 of the dueña's deck and must not move. const { dolores, ...resto } = content; for (const [nombre, valor] of Object.entries(resto)) { expect(JSON.stringify(valor), `«${nombre}» quedó en singular`).not.toMatch(SINGULAR); } // The eyebrow is where the old singular voice survived the plural // migration — locked now that the dueña confirmed it (19/ago/2026). expect(apertura.eyebrow).not.toMatch(/\btu estratega\b/i); // Guard the guard: if the quotes are ever rewritten, this fails and forces // a decision instead of silently weakening the check above. expect(JSON.stringify(dolores)).toMatch(/Trabajo más y gano casi lo mismo/); }); it('keeps the reader in the second person', () => { expect(JSON.stringify(content.paraQuien)).toMatch(/\btienes\b/i); expect(JSON.stringify(content.proceso)).toMatch(/\btu\b/i); }); }); describe('la estructura de seis secciones', () => { it('numera el documento del 01 al 06 sin saltos', () => { expect( [apertura, dondeEstas, queCambia, elTrabajo, paraQuienQuien, empezar].map((s) => s.numero), ).toEqual(['01', '02', '03', '04', '05', '06']); }); it('conserva verbatim las palabras de la dueña', () => { // Her doc is explicit: "they are already written in her voice — use them, // don't rewrite them." These tests fail if someone rewrites them. expect(dondeEstas.dolores).toContain('Trabajo más y gano casi lo mismo que hace dos años.'); expect(dondeEstas.remate.accent).toBe('Se resuelve con una estrategia clara.'); expect(queCambia.ancla.accent).toBe('dejan de crecer por falta de claridad.'); expect(dondeEstas.puntos.map((p) => p.name)).toEqual([ 'Crecer', 'Consolidarse', 'Recuperar rentabilidad', 'Simplificar', 'Expandirse', 'Replantear el portafolio', ]); }); it('los alias viejos siguen leyendo de la misma fuente, no de una copia', () => { // While the two structures coexist, a silent divergence between them // would mean different copy on the same page. expect(puntosInflexion.items[0].situacion).toBe(dondeEstas.puntos[0].situacion); expect(dolores.quotes).toEqual(dondeEstas.dolores); expect(fraseAncla.quote).toBe(queCambia.ancla); }); it('mantiene en pie todo lo que las secciones viejas importan', () => { // The ten old sections stay mounted until Task 13, and a single missing // name here breaks the whole build. This is the real list of names the // code imports today from `@/lib/content`. const modulo = contenido as Record; [ 'brandPillars', 'cierre', 'comoEmpezar', 'creditosFotos', 'disciplinas', 'dolores', 'estratega', 'footer', 'fraseAncla', 'header', 'hero', 'paraQuien', 'proceso', 'puntosInflexion', ].forEach((nombre) => expect(modulo[nombre], nombre).toBeDefined()); }); it('no promete escasez en la apertura', () => { expect(JSON.stringify(apertura)).not.toMatch(/cupos|cohorte|generación fundadora/i); }); it('arma los tres contactos como enlaces que abren algo', () => { // An email without `mailto:` is a relative URL: the browser navigates to // /nombre@dominio and lands on a 404 instead of opening the mail client. const { whatsapp, email, linkedin } = empezar.contacto; expect(whatsapp.href).toMatch(/^https:\/\/wa\.me\/\d{10,15}$/); expect(email.href).toMatch(/^mailto:[^\s@]+@[^\s@]+\.[^\s@]+$/); expect(linkedin.href).toMatch(/^https:\/\/(www\.)?linkedin\.com\/in\/[\w-]+\/?$/); }); });