diff --git a/components/primitives/ParDialogo.tsx b/components/primitives/ParDialogo.tsx
new file mode 100644
index 0000000..a3b2d21
--- /dev/null
+++ b/components/primitives/ParDialogo.tsx
@@ -0,0 +1,52 @@
+'use client';
+
+import { motion } from 'framer-motion';
+import { Wordmark } from '@/components/primitives/Wordmark';
+import { fadeInUp } from '@/lib/motion';
+
+// One exchange of the page's conversation: the reader's situation, then what
+// creeer does about it.
+//
+// The sides never swap from one pair to the next. Each voice keeps its own
+// margin, the way a conversation reads on a page; what alternates going down is
+// situación → respuesta, not left → right. Flipping sides per pair would cost
+// the reader the one cue that tells them who is speaking.
+//
+// Three cues carry that distinction, never colour alone: the speaker label, the
+// column, and the rule hanging off each block. The answer also drops half a
+// step below the situation on wide screens, so the eye reads it as a reply
+// rather than as a second column of the same thought.
+//
+// No motion.li here on purpose: Framer propagates variants through React
+// context, so the two motion.div children still inherit the stagger from the
+//
the section wraps them in, even across this plain - . Making the item
+// itself a motion.li would add a third beat nobody asked for.
+
+export function ParDialogo({
+ n,
+ name,
+ situacion,
+ respuesta,
+}: {
+ n: number;
+ name: string;
+ situacion: string;
+ respuesta: string;
+}) {
+ return (
+
-
+
+
+ {String(n).padStart(2, '0')} · {name}
+
+
+ «{situacion}»
+
+
+
+
+ {respuesta}
+
+
+ );
+}
diff --git a/components/primitives/__tests__/ParDialogo.test.tsx b/components/primitives/__tests__/ParDialogo.test.tsx
new file mode 100644
index 0000000..aa01985
--- /dev/null
+++ b/components/primitives/__tests__/ParDialogo.test.tsx
@@ -0,0 +1,46 @@
+import { render, screen } from '@testing-library/react';
+import { describe, it, expect } from 'vitest';
+import { ParDialogo } from '../ParDialogo';
+
+const par = {
+ n: 1,
+ name: 'Crecer',
+ situacion: 'El siguiente salto no tiene mapa.',
+ respuesta: 'Me sumerjo hasta entender tu empresa desde dentro.',
+};
+
+describe('', () => {
+ it('renders both voices of the exchange', () => {
+ render();
+ expect(screen.getByText(/El siguiente salto no tiene mapa/)).toBeInTheDocument();
+ expect(screen.getByText(/Me sumerjo hasta entender tu empresa desde dentro/)).toBeInTheDocument();
+ });
+
+ it('attributes the answer to creeer, not to the reader', () => {
+ render();
+ // Without a speaker label the answer reads as more description of the
+ // problem. The wordmark is that label — and is the only thing
+ // allowed to draw cre≡er, never hand-typed in the JSX.
+ expect(screen.getByLabelText('creeer')).toBeInTheDocument();
+ });
+
+ it('labels the situation with its inflection point', () => {
+ render();
+ expect(screen.getByText(/01 · Crecer/)).toBeInTheDocument();
+ });
+
+ it('tells the two voices apart by more than colour', () => {
+ const { container } = render();
+ // A reader who cannot separate gold from navy still has the speaker label,
+ // the column, and the rule hanging off each block. Colour alone would fail
+ // the spec's accessibility criterion.
+ const bloques = container.querySelectorAll('li > div');
+ expect(bloques).toHaveLength(2);
+ bloques.forEach((b) => expect(b.className).toMatch(/border-l-2/));
+ });
+
+ it('is a list item, so the section can mount six of them as a list', () => {
+ render();
+ expect(screen.getAllByRole('listitem')).toHaveLength(1);
+ });
+});