Problema
Una clínica odontológica necesita cosas de un consultorio médico y cosas de un comercio, y casi ningún software resuelve bien las dos. Tiene historia clínica —con el peso legal que eso implica—, pero también agenda, caja en dos monedas, trabajos que salen y vuelven de un laboratorio, inventario y comprobantes electrónicos que SUNAT tiene que aceptar.
En Diseña Estudio Dental el histórico de los pacientes —fichas y evoluciones clínicas— había que traerlo desde hojas de cálculo. Parte del proyecto fue precisamente eso: importar el histórico para que el sistema empezara con la clínica entera dentro, y no en blanco.
Contexto
En odontología el historial no es una lista de citas: es un mapa. Cada pieza dental tiene hallazgos, cada hallazgo tiene fecha, y el tratamiento de hoy se decide mirando lo que se registró antes. Y ese mapa tiene norma: en el Perú el odontograma sigue una nomenclatura y una simbología definidas.
El proyecto se llevó por contrato y con un backlog de cierre por épicas —cumplimiento del contrato, seguridad, infraestructura, calidad, documentación y entrega—, cada tarea con criterios de aceptación verificables, un guion de pruebas firmable por módulo y un manual para el personal. Sobre la marcha, la clínica cambió el módulo de ortodoncia por otros que le servían más: inventario, compras, tareas de pacientes, cuestionarios y consentimientos.
Arquitectura
Una SPA en Vue 3 servida por Laravel 11, con autenticación JWT y MySQL debajo.
| Capa | Qué es |
|---|
| Backend por dominios | app/Domains/<Área>/<Entidad>/ en ocho áreas —citas, autenticación, facturación, negocio, ubicación, marketing, odontograma y recetas—, todas con la misma forma: Models, Fields, Contracts, Services, Controllers, Requests, Resources y Filters |
| Datos | 73 modelos y 145 migraciones sobre MySQL 8 |
| Frontend | Vue 3 con <script setup>, Pinia, Vue Router con guards y Element Plus. Los módulos se cargan bajo demanda y los cinco últimos se quedan vivos en memoria |
| Permisos | Granulares por acción y entidad, con roles predefinidos —recepción, caja, doctor— y una interfaz que solo muestra lo que cada uno puede hacer |
| Calidad | ~700 casos de prueba en Pest para el backend y ~590 en Vitest para el frontend, en CI con GitHub Actions. Errores del frontend en Sentry |
La forma uniforme del backend es la decisión que más se nota con el tiempo: con 73 modelos, saber dónde está cada cosa sin buscarla vale más que cualquier abstracción ingeniosa.
Qué resuelve
- Odontograma. Notación FDI completa —piezas permanentes de la 11 a la 48 y deciduas de la 51 a la 85—, superficies clicables, 37 tipos de hallazgo alineados al odontograma normado peruano y dibujo libre encima. Varios odontogramas por paciente, con estados (borrador, completado, archivado) y un PDF con la marca de la clínica.
- Historia clínica. General y pediátrica, con apoderados, archivos, etiquetas, notas, cuestionarios y consentimientos.
- Recetas. Con el catálogo de medicamentos del Perú: de 22 a más de 9,300 marcas y genéricos, limpiado de caracteres rotos y 417 duplicados, con búsqueda por palabras.
- Agenda. Por doctor, con sus horarios y especialidades; no deja agendar fuera de la jornada del doctor. Distingue «No asistió» de «Cancelada», porque medir quién no se presenta es un dato de negocio.
- Laboratorio. Órdenes con firmas digitales, adjuntos, calendario y exportación a PDF, Excel y ZIP.
- CRM. Leads con fuentes de contacto, tareas con vencimiento automático y conversión a paciente.
- Caja multimoneda. Apertura, ingresos, egresos y cierre en soles y dólares, con el tipo de cambio de SUNAT sincronizado a diario. Los presupuestos llevan sus totales por moneda.
- Facturación electrónica. Boletas y facturas ante SUNAT a través de SFE Perú, notas de venta, anulación y conversión a comprobante electrónico.
- Productividad, inventario y compras. Reportes de caja, servicios, agenda, pacientes y comisiones; productos, órdenes de compra y proveedores.
Al registrar un paciente, un familiar, un doctor o una empresa, el DNI o el RUC se consulta en NexoID: un RUC, o un DNI que está en el padrón, responde en milisegundos. SUNAT publica el nombre en un solo texto: NexoID lo separa en nombres y apellidos, y si la separación es dudosa, el formulario la rellena y pide revisarla antes de guardar. Es el mismo problema que en Podoplus, resuelto una sola vez y consumido por los dos.
El odontograma, en el iPad
El odontograma se usa en el consultorio, en un iPad con Apple Pencil. Y ahí apareció el trabajo más fino del proyecto: el trazo del lápiz tardaba de 3 a 5 segundos en aparecer.
La causa no estaba en el código del dibujo. El trazo vivía en la misma capa que los dientes, y Safari repintaba el odontograma entero con cada movimiento del lápiz —que además encendía el resaltado de cada diente por el que pasaba—. La solución fue separar el dibujo en una capa propia que se pinta una vez por cuadro, y que los dientes ignoren el lápiz mientras se dibuja.
Detrás vinieron los detalles que solo aparecen con el hardware real en la mano:
- El grosor del trazo sigue la presión del lápiz, y en cuanto se detecta el Apple Pencil la palma deja de rayar: el dedo pasa a mover y hacer zoom, hasta 400 %.
- Se bloquean los gestos propios de Safari —Scribble, el desplazamiento de la página, la lupa— que cortaban el trazo.
- Con teclado conectado, el iPad se hacía pasar por un ordenador y el editor no se abría a pantalla completa. Ahora se reconoce como táctil.
- Se instala como app desde «Añadir a pantalla de inicio», con el icono de la clínica y sin barras del navegador.
Decisiones clave
- Quien llama por primera vez es un lead, no un paciente. Se agenda con nombre y celular; la ficha clínica y el número de historia se abren cuando asiste. Antes de crearlo se buscan duplicados por celular —con o sin 51—, documento y nombre, en leads y en pacientes.
- Un lead no puede tener cita con presupuesto. Primero se convierte en paciente: el dinero siempre cuelga de alguien con ficha.
- Los procesos programados fallan en voz alta. El tipo de cambio se rellena solo si falta algún día, y si no lo consigue, avisa. No siempre fue así (ver Aprendizajes).
- El despliegue no toca lo que sube el cliente. Se despliega con
rsync desde GitHub Actions, con un modo de comprobación en seco antes de cada pase.
Qué queda por delante
- El plan de tratamiento desde el odontograma. El backend está construido —ítems por diente con servicios sugeridos— pero sin exponer. Es la pieza que uniría el diagnóstico con el presupuesto: hallazgo → plan → presupuesto.
- Odontograma inicial inmutable más evolutivos, como pide la norma. Hoy el archivado lo aproxima, pero no lo garantiza.
- WhatsApp de verdad. El servicio de mensajería existe y corre en modo de prueba; conectar el canal para recordatorios de cita y campañas es la siguiente fase. En el Perú es ahí donde está el paciente.
Aprendizajes
La versión 1.1 fue la primera numerada, y salió de un repaso largo usando el sistema, no de un backlog. Ahí aparecieron cosas que ningún test había visto: archivar un presupuesto borraba su importe y lo mostraba como S/ 0.00, y un campo que la API devolvía como 1 en lugar de true hacía que un registro dado de baja se leyera como activo en siete pantallas.
El peor de todos fue silencioso: el tipo de cambio de SUNAT se quedó congelado 42 días. El planificador encolaba la tarea, pero faltaba el segundo cron que la ejecutaba, y nadie se enteró porque nada fallaba. Un proceso programado que falla en silencio es peor que uno que no existe: al menos del que no existe sabes que no está.
Y una lección de despliegue que vale para cualquier proyecto con archivos del cliente: el despliegue original era un zip manual que borraba en cada pase los archivos que la clínica había subido. Pasar a rsync con comprobación en seco no fue una mejora de comodidad; fue dejar de perder datos.
Resultado
La clínica opera sobre un solo sistema, en producción y en su versión 1.9: el odontograma y la historia clínica se consultan en el iPad del consultorio, la agenda distingue a quien llama de quien ya es paciente, el laboratorio lleva sus órdenes con firma y calendario, la caja cierra en dos monedas y los comprobantes salen del mismo flujo del cobro, con el documento validado contra la fuente oficial.