Skip to content

Odontogram Tier 2 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Implement Tier 2 clinical charting features: Periogram View Toggle, Print-to-PDF export, and Tooth History Timeline, driven entirely by TDD.

Architecture:

  1. Tooth History Timeline: Enhance the existing history section in ToothPanel.tsx into a vertical timeline. We will add visual timeline nodes, and assume the API provides created_at and doctor_name (or mock them).
  2. Print-to-PDF: Introduce a @media print block in Odontogram.css (or index.css) that hides interactive panels/buttons, expands the chart, and reveals a hidden-by-default signature line. Add a Print button to the UI.
  3. Periogram View: Add a view toggle to Odontogram.tsx (Odontogram vs. Periodontogram). Create a Periogram placeholder component that renders a specialized grid for pocket depths, BOP, and recession.

Architecture Diagram:

graph TD
classDef main fill:#007AFF0D,stroke:#007AFF,stroke-width:1px;
classDef secondary fill:#00000008,stroke:#0000001A,stroke-width:1px;
subgraph OdontogramView ["Odontogram View"]
direction TB
O[Odontogram Orchestrator]:::main --> T[ToothSVG Chart]:::main
O --> P[ToothPanel]:::main
P --> H[History Timeline]:::main
end
subgraph PeriogramView ["Periogram View"]
direction TB
O --> Peri[Periogram Grid]:::secondary
end
subgraph PrintMode ["Print Mode"]
direction TB
O --> PR[Print Styles / Signature Line]:::secondary
end

Tech Stack: React, TypeScript, Vitest, CSS @media print.


Files:

  • Modify: [ToothPanel.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/ToothPanel.tsx)

  • Modify: [ToothPanel.test.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/__tests__/ToothPanel.test.tsx)

  • Modify: [Odontogram.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/Odontogram.tsx)

  • Step 1: Write the failing test

import React from 'react';
import { render, screen } from '@testing-library/react';
import { ToothPanel } from '../ToothPanel';
describe('Tooth History Timeline', () => {
it('renders a chronological timeline with date and doctor name', () => {
const historyData = [
{
id: '1',
tooth_fdi: 16,
surfaces: ['O'],
condition: 'caries',
status: 'planned',
created_at: '2026-06-25T10:00:00Z',
doctor_name: 'Dr. House'
}
];
const { container } = render(
<ToothPanel
patientId="123"
isOpen={true}
onClose={() => {}}
selectedTooth={{ fdi: 16 }}
catalog={[]}
onSave={() => {}}
history={historyData}
/>
);
// Expecting to find the timeline elements
expect(screen.getByText('Dr. House')).toBeInTheDocument();
// Ensure the history list has a timeline line class
const timelineContainer = container.querySelector('.border-l-2');
expect(timelineContainer).toBeInTheDocument();
});
});
  • Step 2: Run test to verify it fails

Run: npm run test frontend/src/components/dental/__tests__/ToothPanel.test.tsx Expected: FAIL.

  • Step 3: Write minimal implementation

Update ChartFinding interface in Odontogram.tsx to include optional created_at?: string and doctor_name?: string. In ToothPanel.tsx: Replace the existing flat history map with a vertical timeline layout:

<div className="flex flex-col gap-0 border-l-2 border-border ml-2 pl-4 relative">
{historyEntries.map(item => (
<div key={item.id} className="relative mb-6">
<div className="absolute -left-[21px] top-1 w-3 h-3 bg-muted border-2 border-border rounded-full" />
<div className="text-xs text-muted-foreground mb-1">
{item.created_at ? new Date(item.created_at).toLocaleDateString() : 'Sin fecha'} • {item.doctor_name || 'Dr. Clínico'}
</div>
<div className="bg-muted/50 border border-border rounded-xl p-3 text-sm">
<div className="font-bold">{item.condition}</div>
<div className="text-muted-foreground text-xs">{item.status}</div>
</div>
</div>
))}
</div>
  • Step 4: Run test to verify it passes

Run: npm run test frontend/src/components/dental/__tests__/ToothPanel.test.tsx Expected: PASS.

  • Step 5: Commit
Terminal window
git add frontend/src/components/dental/ToothPanel.tsx frontend/src/components/dental/__tests__/ToothPanel.test.tsx frontend/src/components/dental/Odontogram.tsx
git commit -m "feat: implement chronological tooth history timeline in panel"

Files:

  • Modify: [Odontogram.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/Odontogram.tsx)

  • Modify: [index.css](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/index.css)

  • Modify: [Odontogram.test.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/__tests__/Odontogram.test.tsx)

  • Step 1: Write the failing test

import React from 'react';
import { render, screen } from '@testing-library/react';
import { Odontogram } from '../Odontogram';
import { MemoryRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
describe('Odontogram Print', () => {
it('renders a print button and a hidden signature line that becomes visible on print', () => {
const { container } = render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<Odontogram patientId="123" patientName="Ana" />
</MemoryRouter>
</QueryClientProvider>
);
const printBtn = screen.getByText(/Imprimir/i);
expect(printBtn).toBeInTheDocument();
const signature = container.querySelector('.print-signature');
expect(signature).toBeInTheDocument();
});
});
  • Step 2: Run test to verify it fails

Run: npm run test frontend/src/components/dental/__tests__/Odontogram.test.tsx Expected: FAIL.

  • Step 3: Write minimal implementation

In Odontogram.tsx: Add a “Imprimir” button next to the dentition toggles that calls window.print(). At the bottom of the component, add:

<div className="print-signature hidden mt-16 justify-around w-full">
<div className="text-center border-t border-black pt-2 w-1/3">Firma del Doctor</div>
<div className="text-center border-t border-black pt-2 w-1/3">Firma del Paciente</div>
</div>

In index.css:

@media print {
.btn-icon, button, .PlanBuilderBar, .ToothPanel { display: none !important; }
.print-signature { display: flex !important; }
body { background: white; }
.OdontogramContainer { border: none !important; box-shadow: none !important; }
}
  • Step 4: Run test to verify it passes

Run: npm run test frontend/src/components/dental/__tests__/Odontogram.test.tsx Expected: PASS.

  • Step 5: Commit
Terminal window
git add frontend/src/components/dental/Odontogram.tsx frontend/src/index.css frontend/src/components/dental/__tests__/Odontogram.test.tsx
git commit -m "feat: add print-to-pdf layout and signature line"

Task 3: Periogram View Toggle & Placeholder

Section titled “Task 3: Periogram View Toggle & Placeholder”

Files:

  • Modify: [Odontogram.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/Odontogram.tsx)

  • Create: [Periogram.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/Periogram.tsx)

  • Test: [Odontogram.test.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/__tests__/Odontogram.test.tsx)

  • Step 1: Write the failing test

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { Odontogram } from '../Odontogram';
import { MemoryRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
describe('Periogram Toggle', () => {
it('toggles between Odontograma and Periodontograma views', () => {
render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<Odontogram patientId="123" />
</MemoryRouter>
</QueryClientProvider>
);
// Should start in Odontograma
expect(screen.getByText('Odontograma')).toBeInTheDocument();
// Find toggle and switch
const perioBtn = screen.getByRole('button', { name: /Periodontograma/i });
fireEvent.click(perioBtn);
// Periogram UI should render
expect(screen.getByText('Métricas Periodontales')).toBeInTheDocument();
});
});
  • Step 2: Run test to verify it fails

Run: npm run test frontend/src/components/dental/__tests__/Odontogram.test.tsx Expected: FAIL.

  • Step 3: Write minimal implementation

Create Periogram.tsx:

import React from 'react';
export const Periogram: React.FC = () => {
return (
<div className="w-full min-h-[500px] flex items-center justify-center bg-muted/20 rounded-xl border border-border">
<div className="text-center">
<h3 className="text-xl font-bold mb-2">Métricas Periodontales</h3>
<p className="text-muted-foreground">Tabla de recesión, bolsa periodontal y sangrado (Próximamente)</p>
</div>
</div>
);
};

In Odontogram.tsx: Add state: const [activeView, setActiveView] = useState<'odontogram' | 'periogram'>('odontogram'); Add toggle buttons above the dentitionMode toggles:

<div className="flex gap-4 mb-4 border-b border-border pb-4 w-full">
<button onClick={() => setActiveView('odontogram')} className={activeView === 'odontogram' ? 'font-bold' : ''}>Odontograma</button>
<button onClick={() => setActiveView('periogram')} className={activeView === 'periogram' ? 'font-bold' : ''}>Periodontograma</button>
</div>

Conditionally render <FullOdontogram /> or <Periogram />.

  • Step 4: Run test to verify it passes

Run: npm run test frontend/src/components/dental/__tests__/Odontogram.test.tsx Expected: PASS.

  • Step 5: Commit
Terminal window
git add frontend/src/components/dental/Odontogram.tsx frontend/src/components/dental/Periogram.tsx frontend/src/components/dental/__tests__/Odontogram.test.tsx
git commit -m "feat: implement periogram view toggle and placeholder component"