Diecinueve meses, 922 commits, 715 tickets, un solo autor. Un sistema de gestión de clínicas dentales que maneja citas, historia clínica, facturación, laboratorios y expedientes de pacientes para clínicas en América Latina — Django 5.2, PostgreSQL 17 e información médica protegida en producción.
El sistema
Radiant Dental es un SaaS multi-inquilino: una clínica tiene sucursales, las sucursales tienen especialidades y servicios, y el personal — odontólogos, higienistas, asistentes, recepcionistas, gerentes — se asigna por sucursal. Sobre eso se apoyan las citas y listas de espera, los expedientes de pacientes y sus cuestionarios de salud, la historia clínica, los planes de tratamiento, la facturación y los pagos, la gestión de casos de laboratorio, los programas de lealtad, los prospectos de marketing y una superficie pública de agendamiento en línea.
Lo interesante no es la lista de funcionalidades. Es que todas esas superficies tocan el mismo expediente del paciente, y ese expediente es información médica protegida.
Modelado de dominio
Un diente no es un registro. Los dientes se numeran con el estándar FDI de dos dígitos, y una condición aplica a un conjunto de superficies del diente — mesial, oclusal, distal, vestibular, lingual, incisal — o al diente completo, que es mutuamente excluyente con el resto. El modelo lo almacena como una cadena validada: cada carácter es un código de superficie, sin repeticiones, o el literal FULL.
Un odontograma es una instantánea, no un estado actual. Varios odontogramas por paciente permiten comparar en el tiempo, y un odontograma puede bloquearse con un motivo registrado, porque un registro clínico firmado debe dejar de cambiar. El periodontograma agrega seis sitios de medición por diente; los ítems del plan de tratamiento llevan códigos de procedimiento CDT.
Los conflictos de agenda son advertencias, no errores. El verificador de conflictos ejecuta siete comprobaciones independientes — feriados de la sucursal, horario de atención, agenda del profesional, permisos aprobados, bloqueos personales, citas superpuestas y duración del servicio — y devuelve una lista de advertencias categorizadas en lugar de rechazar la cita.
Las recepcionistas sobreagendan a propósito. Un sistema que se los impide termina esquivado en una semana; uno que les dice exactamente qué están pasando por alto termina usado.
Criterio
El registro clínico por voz — que el odontólogo dicte las mediciones en vez de escribirlas — es la funcionalidad que el producto más obviamente pide. Está especificada en tres documentos de diseño dentro del repositorio, y nada de eso está en INSTALLED_APPS.
La razón está en la especificación. La salida de voz es provisional hasta que un profesional la firma, así que la capa de captura es una app separada que escribe hacia los modelos clínicos solo al confirmar. El audio y las transcripciones nunca cuelgan del expediente clínico: eso mezclaría la procedencia con el registro y obligaría a cada consulta clínica a esquivarla. Los límites de bloqueo y borrador que ya existen se convierten en el destino de la confirmación, en vez de reinventarse.
Escribir eso antes de escribir código es lo que evita que una base de código asistida por IA acumule estructura plausible. También es la razón por la que la funcionalidad no está construida a medias.
Seguridad
En mayo de 2026 realicé una revisión de seguridad a nivel de código fuente, con alcance en configuración y secretos, autenticación y control de acceso, inyección y manejo de entradas, y protección de información médica. Trece hallazgos, calificados para un sistema con obligaciones tipo HIPAA, cada uno rastreado a código específico y contrastado contra el ruteo de URLs para separar los problemas realmente alcanzables de los inseguros pero no enrutados.
Después los corregí. La concentración estaba exactamente donde uno esperaría: la superficie pública sin autenticar y el servido de archivos estáticos.
| Área | Corrección | Estado |
|---|---|---|
| Archivos de pacientes | Todo el contenido con información médica pasó a servirse tras vistas autenticadas; almacenamiento de objetos conmutable por entorno, con alias privados y públicos separados | Cerrado |
| Registros clínicos | Acceso a la historia clínica acotado por clínica; auditoría de acceso a información médica en las superficies de expediente y citas | Cerrado |
| Agendamiento público | La API pública de citas insegura se eliminó en vez de parchearse; redirecciones de login protegidas | Cerrado |
| Carga de archivos | Validadores de subida y redacción de datos sensibles en logs | Cerrado |
| Superficie del navegador | Content-Security-Policy aplicada con django-csp; orígenes de bucket permitidos solo cuando el almacenamiento de objetos está activo | Cerrado |
| Formularios públicos | Honeypot y tiempo mínimo de llenado contra bots; registro de la IP real del cliente detrás del proxy; log de intentos fallidos | Cerrado |
Dos cosas que me gustaría que un revisor se lleve de aquí. Los viewsets inseguros sin enrutar se eliminaron, no se corrigieron — código muerto que habría sido peligroso el día que alguien lo conectara. Y la corrección incluye una prueba que verifica que el router público exponga únicamente los endpoints previstos, porque la falla nunca estuvo en el código sino en el ruteo.
Método
Cada commit de este repositorio es mío, y la mayor parte del código se escribió con agentes de IA. Con 922 commits eso solo funciona con andamiaje, y el andamiaje es la habilidad real.
El CLAUDE.md del repositorio abre con la única regla que rompe todo si se viola: los comandos de Django se ejecutan dentro de Docker, nunca en el host. No son preferencias de estilo — es la restricción que produce fallas silenciosas y difíciles de diagnosticar.
Un revisor de código, un arquitecto de dominio dental y un arquitecto Django/React, cada uno con su propio encargo. Revisar el dominio e implementar son trabajos distintos y reciben agentes distintos.
Diez apps tienen un paquete services/. La lógica de negocio vive ahí, no en los modelos ni en las vistas — el límite que evita que el código generado se desparrame a la capa equivocada.
416 módulos de prueba más 77 suites end-to-end de Playwright, con datos sembrados y un subconjunto de ruta crítica. El código generado es barato; la suite es lo que hace seguro aceptarlo.
Especificaciones escritas, contrastadas contra los modelos existentes y marcadas explícitamente como aún no construidas. La capa de registro por voz tiene tres y cero migraciones.
La revisión de seguridad existe porque el código que uno no tecleó a mano merece más escrutinio, no menos. Trece hallazgos sobre una base de código propia son el argumento a favor de la práctica.
Stack
| Backend | Django 5.2, Django REST Framework 3.15, drf-spectacular para OpenAPI |
| Datos | PostgreSQL 17, llaves primarias UUID, borrado lógico con espejo de auditoría |
| Asíncrono | Celery + Redis — recordatorios, recitas, solicitudes de reseña, sincronización de calendario |
| Autenticación | Sesiones de Django, tokens Knox, acceso sin contraseña para pacientes por enlace mágico y OTP por SMS, bloqueo con django-axes, SSO de Google para el personal |
| Frontend | Stimulus.js + TypeScript como mejora progresiva sobre plantillas de Django; Jest |
| Integraciones | Google Calendar, SMS por Twilio, WhatsApp Business API y un puente Baileys de WhatsApp Web corriendo como su propio servicio Node |
| Operación | Docker Compose, nginx, respaldos nocturnos del volumen de archivos con rotación, PgHero, Sentry con depurador de datos sensibles |
| Idiomas | Español, inglés y portugués de Brasil |
En resumen
Nineteen months, 922 commits, 715 tickets, one author. A practice-management system handling appointments, clinical charting, billing, labs and patient records for dental clinics in Latin America — Django 5.2, PostgreSQL 17, and protected health information in production.
The system
Radiant Dental is a multi-tenant SaaS: a clinic owns branches, branches own specialties and services, and staff — dentists, hygienists, assistants, receptionists, office managers — are assigned per branch. On top of that sit appointments and waitlists, patient records and health questionnaires, clinical charting, treatment plans, invoicing and payments, lab-case management, loyalty programs, marketing leads, and a public online-booking surface.
The interesting part is not the feature list. It is that every one of those surfaces touches the same patient record, and that record is protected health information.
Domain modelling
A tooth is not a row. Teeth are numbered by the FDI two-digit standard, and a condition applies to a set of surfaces on a tooth — mesial, occlusal, distal, vestibular, lingual, incisal — or to the whole tooth, which is mutually exclusive with the rest. The model stores that as a validated string: each character a surface code, no duplicates, or the literal FULL.
An odontogram is a snapshot, not current state. Multiple charts per patient enable comparison over time, and a chart can be locked with a recorded reason, because a signed clinical record must stop changing. Periodontal charting adds six measurement sites per tooth; treatment plan items carry CDT procedure codes.
Scheduling conflicts are warnings, not errors. The conflict checker runs seven independent checks — branch holiday, operating hours, staff schedule, approved time off, personal blocks, overlapping appointments, and service-duration mismatch — and returns a list of categorised warnings rather than refusing the booking.
Receptionists double-book on purpose. A system that blocks them gets worked around within a week; one that tells them exactly what they are overriding gets used.
Judgment
Voice charting — a clinician calling out measurements instead of typing them — is the feature the product most obviously wants. It is specified across three design documents in the repository, and none of it is in INSTALLED_APPS.
The reason is in the spec. Voice output is provisional until a clinician signs it off, so the capture layer is a separate staging app that writes through to the clinical models only on commit. Audio blobs and transcripts never hang off the clinical record — that would mix provenance into the chart and force every clinical query to step around it. The existing lock and draft boundaries become the commit target rather than being reinvented.
Writing that down before writing code is what stops an AI-assisted codebase from accreting plausible structure. It is also why the feature is not half-built.
Security
In May 2026 I performed a source-level security review of the system, scoped to configuration and secrets, authentication and access control, injection and input handling, and PHI protection. Thirteen findings, rated for a system under HIPAA-style obligations, each traced to specific code and cross-checked against URL routing to separate genuinely reachable issues from insecure-but-unrouted ones.
Then I fixed them. The concentration was exactly where you would expect: the unauthenticated public surface and static-file serving.
| Area | Remediation | State |
|---|---|---|
| Patient media | All PHI media moved behind authenticated views; env-toggled object storage with private PHI and public asset aliases | Closed |
| Clinical records | Chart access clinic-scoped; PHI access audit logging across chart and appointment surfaces | Closed |
| Public booking | Insecure public appointment API removed rather than patched; login redirects guarded | Closed |
| Uploads | Upload validators and log redaction | Closed |
| Browser surface | Content-Security-Policy enforced via django-csp; bucket origins allowed only where object storage is active | Closed |
| Public forms | Honeypot and minimum-fill-time bot checks; real client IPs recorded behind proxy; failed logins logged | Closed |
Two things I would want a reviewer to take from this. The unrouted insecure viewsets were deleted, not fixed — dead code that would have been dangerous the day someone wired it up. And the remediation includes a test asserting the public router exposes only the intended endpoints, because the failure mode was never the code, it was the routing.
Method
Every commit in this repository is mine, and most of the code was written with AI agents. At 922 commits that only works with scaffolding, and the scaffolding is the actual skill.
The repository's CLAUDE.md leads with the one rule that breaks everything when violated: all Django commands run inside Docker, never on the host. Not style preferences — the constraint that produces silent, hard-to-diagnose failure.
A code reviewer, a dental-practice architect, and a Django/React architect, each with its own brief. Domain review and implementation are different jobs and get different agents.
Ten apps carry a services/ package. Business logic lives there, not on models or in views — the boundary that keeps generated code from sprawling into the wrong layer.
416 test modules plus 77 Playwright end-to-end suites, with seeded fixtures and a critical-path subset. Generated code is cheap; the suite is what makes it safe to accept.
Specs written, reviewed against the existing models, and explicitly marked not-yet-built. The voice-charting layer has three of them and zero migrations.
The security review exists because code you did not type by hand deserves more scrutiny, not less. Thirteen findings on a codebase I wrote is the argument for the practice.
Stack
| Backend | Django 5.2, Django REST Framework 3.15, drf-spectacular for OpenAPI |
| Data | PostgreSQL 17, UUID primary keys, soft delete with audit mirroring |
| Async | Celery + Redis — reminders, recalls, review requests, calendar sync |
| Auth | Django sessions, Knox tokens, passwordless patient login by magic link and SMS OTP, django-axes lockout, Google SSO for staff |
| Frontend | Stimulus.js + TypeScript progressively enhancing Django templates; Jest |
| Integrations | Google Calendar, Twilio SMS, WhatsApp Business API and a Baileys WhatsApp-Web bridge running as its own Node service |
| Ops | Docker Compose, nginx, nightly media-volume backups with rotation, PgHero, Sentry with a PHI scrubber |
| i18n | English, Spanish, Brazilian Portuguese |
In short