Files
Creeer-Web/lib/__tests__/content.test.ts
T
erikghandClaude Opus 5.5 89e54a6e84 feat: rediseño «El documento» en curso, contacto real y enlaces externos
Primitivas y contenido del rediseño (escala tipográfica, Revelar,
MarcaSeccion, Seccion, Antetitulo, SimboloMarca, Resplandor, ClaridadHero,
sección Proceso de ocho etapas, lib/content.ts reestructurado en seis
secciones con alias para las viejas). Mecanismo y FormaTrabajar se retiran.

Encima, lo de esta sesión:
- recorrido: creeer vuelve a responder cada situación en vez de repetirla
  (el alias apuntaba respuesta a situacion); respuestas en plural. Créditos
  de las seis fotos de México.
- contacto: WhatsApp, email (con mailto:) y LinkedIn reales; iconos de las
  redes en el cierre y el footer (IconoRed, trazos de Simple Icons, CC0).
- enlaces: lo externo abre en otra pestaña con noopener y aviso para
  lectores de pantalla (lib/enlaces.ts); mailto y anclas se quedan.
- Proceso: las ocho tarjetas no aparecían nunca en móvil (amount 0.3 sobre
  una lista de ~3000px); ahora cada una se revela sola. El ≡ de la 08 ya no
  tapa un entregable, título equilibrado, etiqueta a 12px, sin elevar al
  hover lo que no es clicable.
- Disciplinas: la frase «No son la promesa — son los medios» gana peso y
  se agrupa con título e ilustración; sin cortes huérfanos en móvil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:27:20 -06:00

102 lines
4.9 KiB
TypeScript

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<string, unknown>;
[
'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-]+\/?$/);
});
});