Acepto y hago saber mi decisión de que usemos los nombres tratamiento_propuestas y tratamiento_propuesta_items.
Quiero empezar a diseñar las migraciones, los modelos, las APIs y las pantallas.
Me parece un muy buen momento para empezar a construir. Y, antes de escribir una sola migración, quisiera proponerte una estrategia de diseño.
Hasta ahora hemos tomado una decisión muy importante: estamos diseñando primero el dominio y después el código, no al revés. Creo que eso está haciendo que Dentalia tenga una arquitectura bastante consistente.
Lo que propongo
No diseñaría solamente las migraciones.
Diseñaría el módulo completo, siguiendo este orden:
Dominio
↓
Modelo de datos
↓
Modelos Eloquent
↓
Endpoints API
↓
Pantallas Inertia
↓
Componentes React
Así cada capa nace para satisfacer a la anterior.
Etapa 1 (la que haría ahora)
Modelo de datos
Definir completamente las tablas.
Por ejemplo:
tratamiento_propuestas
tratamiento_propuesta_items
Con:
claves
índices
restricciones
estados
timestamps
relaciones
Todavía sin escribir migraciones.
Etapa 2
Modelos Eloquent
Aquí definiríamos cosas como
TratamientoPropuesta
TratamientoPropuestaItem
y decidiríamos:
casts
relaciones
accessors
mutators
scopes
enums
Etapa 3
API REST
Aquí definiríamos el contrato.
Por ejemplo:
GET /api/pacientes/{paciente}/tratamiento-propuesta
POST /api/pacientes/{paciente}/tratamiento-propuesta
PUT /api/tratamiento-propuestas/{propuesta}
POST /api/tratamiento-propuestas/{propuesta}/emitir
POST /api/tratamiento-propuestas/{propuesta}/aceptar
POST /api/tratamiento-propuestas/{propuesta}/rechazar
Todavía sin escribir controladores.
Etapa 4
Pantallas Inertia
Algo parecido a
Odontograma
↓
Propuesta de tratamiento
↓
Cobros
↓
Facturación
y dentro de Propuesta:
Index
Show
Edit
Emitir
Detalle histórico
Etapa 5
React
Aquí recién pensaríamos
ProposalTable
ProposalToolbar
ProposalTotals
ProposalSummary
ProposalStatusBadge
TreatmentSelector
PriceBreakdown
Creo que nos falta una única decisión de dominio
Es la única que todavía no me animo a asumir.
¿Una propuesta puede editarse después de haber sido emitida?
Yo diría que no.
Una vez emitida:
Emitida
↓
Aceptada
o
Emitida
↓
Rechazada
Pero jamás vuelve a edición.
Si hay que cambiar algo:
Nueva propuesta
No modificar la existente.
Creo que es consistente con todo lo que venimos diseñando.
Otra decisión que empezaría a tomar
Yo dejaría de hablar de un campo
estado
como string libre.
Preferiría un enum.
Algo así:
enum TratamientoPropuestaEstado
{
case BORRADOR;
case EMITIDA;
case ACEPTADA;
case RECHAZADA;
}
O incluso, si mantenemos la idea de que el borrador no existe como entidad persistente, podríamos simplificar todavía más:
enum TratamientoPropuestaEstado
{
case EMITIDA;
case ACEPTADA;
case RECHAZADA;
}
Es decir, una fila en tratamiento_propuestas nunca estaría en estado BORRADOR, porque el borrador vive en el odontograma.
Esa idea me parece especialmente elegante.
Mi propuesta concreta
Yo empezaría mañana mismo por escribir un documento de diseño del módulo, que sirva como especificación técnica. Incluiría:
Modelo conceptual.
Reglas de negocio.
Diagrama de estados.
Modelo de datos.
Relaciones Eloquent.
Endpoints REST.
Flujo completo de las pantallas.
Reglas de concurrencia.
Casos límite.
Después de ese documento, escribir las migraciones y el resto del código sería casi un trabajo mecánico. Además, ese documento nos servirá como referencia para los futuros módulos de Cobros y Facturación, que ya nacerán alineados con esta arquitectura.
Respondo pregunta: ¿Una propuesta puede editarse después de haber sido emitida? No. En mi sistema, si hay que cambiar algo de la propuesta, se hará mediante una nueva propuesta.
Respecto a la enum de estados, estoy de acuerdo con la versión simplificada:
enum TratamientoPropuestaEstado
{
case EMITIDA;
case ACEPTADA;
case RECHAZADA;
}
Quiero que avancemos hoy mismo con esa manera de trabajar que has propuesto.
Excelente. Creo que acabamos de tomar dos decisiones de arquitectura muy importantes:
Una
TratamientoPropuestaes inmutable desde el momento en que nace.El estado
BORRADORno existe en la base de datos; el borrador vive exclusivamente en el JSON del odontograma.
Esas dos decisiones simplifican enormemente el resto del sistema.
Documento de Diseño — Módulo de Propuestas de Tratamiento (v0.1)
1. Objetivo
El módulo de Propuestas de Tratamiento representa la transición entre el mundo clínico (odontograma) y el mundo administrativo (cobros y facturación).
Una propuesta es un documento histórico que registra exactamente qué tratamiento se ofreció al paciente, con qué precios, descuentos y condiciones.
Una vez emitida, nunca vuelve a modificarse.
2. Flujo general
Odontograma
│
▼
Confección de propuesta
│
▼
Emitir propuesta
│
▼
TratamientoPropuesta (inmutable)
│
├── Aceptada
└── Rechazada
3. Responsabilidades
Odontograma
Responsable de:
diagnóstico
tratamientos
selección para próxima propuesta
evolución clínica
No conoce:
precios
descuentos
cobros
facturación
excepto el flag
incluirEnPropuesta
que representa solamente una intención de selección.
TratamientoPropuesta
Representa una fotografía histórica.
Debe conservar:
tratamientos incluidos
precios
descuentos
importes
fecha de emisión
decisión del paciente
No vuelve a editarse.
4. Estados
enum TratamientoPropuestaEstado
{
case EMITIDA;
case ACEPTADA;
case RECHAZADA;
}
No existe BORRADOR.
5. Reglas de negocio
Regla 1
Mientras no exista una propuesta emitida, toda la información vive únicamente en el odontograma.
Regla 2
Emitir una propuesta realiza una única transacción.
Durante ella:
crea TratamientoPropuesta
crea TratamientoPropuestaItems
copia precios
copia descuentos
copia cantidades
limpia incluirEnPropuesta del odontograma
Todo o nada.
Regla 3
Aceptar o rechazar una propuesta únicamente modifica su estado.
Nunca modifica el odontograma.
Regla 4
Una propuesta aceptada tampoco se modifica.
Si cambia el tratamiento:
Nueva propuesta.
Nunca UPDATE.
6. Modelo de datos
tratamiento_propuestas
| Campo | Observaciones |
|---|---|
| id | PK |
| paciente_id | FK |
| estado | Enum |
| subtotal | decimal |
| descuento_total | decimal |
| total | decimal |
| emitida_at | datetime |
| respondida_at | nullable |
| created_at | |
| updated_at |
tratamiento_propuesta_items
| Campo | Observaciones |
|---|---|
| id | PK |
| tratamiento_propuesta_id | FK |
| prestacion_id | FK |
| pieza | nullable |
| superficie | nullable |
| cantidad | decimal |
| precio_unitario | decimal |
| descuento | decimal o porcentaje (a definir) |
| importe | decimal |
| observaciones | nullable |
7. Relaciones Eloquent
Paciente
hasMany
TratamientoPropuesta
hasMany
TratamientoPropuestaItem
Prestacion
hasMany
TratamientoPropuestaItem
8. API
Obtener propuesta actual en edición
No existe.
Se obtiene leyendo el odontograma.
Emitir propuesta
POST
/api/pacientes/{paciente}/tratamiento-propuestas
Acciones:
leer odontograma
leer catálogo
aplicar descuentos
crear propuesta
crear items
limpiar flags
Aceptar
POST
/api/tratamiento-propuestas/{propuesta}/aceptar
Rechazar
POST
/api/tratamiento-propuestas/{propuesta}/rechazar
Historial
GET
/api/pacientes/{paciente}/tratamiento-propuestas
9. Pantallas
Odontograma
Selecciona tratamientos para próxima propuesta.
Propuesta de tratamiento
Lee directamente el odontograma.
Permite:
seleccionar tratamientos
consultar catálogo
modificar descuentos
calcular totales
No guarda nada.
Confirmación
Botón
Emitir propuesta
Recién aquí nace la entidad persistente.
Historial de propuestas
Lista cronológica.
Estado.
Fecha.
Total.
Botón "Ver".
Nunca "Editar".
10. Cobros
Todavía no se implementan.
Pero nacerán exclusivamente desde una propuesta aceptada.
Nunca desde el odontograma.
Primer punto que quisiera discutir antes de escribir una migración
Hay un aspecto que todavía no definimos y creo que es el siguiente paso lógico: el catálogo de prestaciones.
Hoy sabemos que cada TratamientoPropuestaItem tendrá un prestacion_id. Sin embargo, aún no definimos qué representa exactamente una prestación ni cómo se relaciona con los elementos del JSON del odontograma.
Por ejemplo, cuando en el JSON aparece:
{
"codigo": "obturacion-pendiente",
"casillero": 47,
"superficie": 3
}
¿Ese codigo coincide exactamente con un registro de la futura tabla prestaciones, o existe una capa de traducción entre los códigos clínicos del odontograma y las prestaciones facturables?
Mi intuición es que éste debería ser el próximo módulo que diseñemos, porque de él dependerán tanto las propuestas como los cobros y la facturación. Si logramos modelar bien Prestacion, gran parte del resto del sistema se apoyará sobre una base muy sólida.
Respondo a lo siguiente:
{
"codigo": "obturacion-pendiente",
"casillero": 47,
"superficie": 3
}
¿Ese codigo coincide exactamente con un registro de la futura tabla prestaciones, o existe una capa de traducción entre los códigos clínicos del odontograma y las prestaciones facturables?
Mi respuesta es que ese código coincide exactamente con un registro de la futura tabla prestaciones.
Esta respuesta me parece excelente porque simplifica muchísimo la arquitectura.
Acabamos de establecer una regla de diseño muy fuerte:
Los códigos de tratamiento del odontograma son la clave primaria funcional del catálogo de prestaciones.
Es decir, el odontograma deja de contener "strings mágicos" y pasa a contener referencias directas a una entidad del dominio.
Consecuencias
Ya no necesitaremos una tabla de equivalencias ni una capa de traducción.
Podremos hacer algo conceptualmente tan simple como:
JSON del odontograma
│
▼
codigo = "obturacion-pendiente"
│
▼
prestaciones.codigo = "obturacion-pendiente"
│
▼
precio
nombre
categoría
etc.
Eso tiene varias ventajas.
1. Una única fuente de verdad
El código vive una sola vez.
No hay que mantener:
obturacion-pendiente
↓
OBT001
↓
Prestación 37
Todo es el mismo concepto.
2. El odontograma pasa a estar tipado
Hoy el JSON contiene:
{
"codigo": "obturacion-pendiente"
}
En realidad ese codigo ya no es texto libre.
Es una clave foránea lógica.
Aunque no exista una FK sobre un documento JSON, semánticamente se comporta como una.
3. Las prestaciones pueden evolucionar
La tabla prestaciones podrá contener, por ejemplo:
| Campo | Descripción |
|---|---|
| id | PK interna |
| codigo | Clave estable usada por el odontograma |
| nombre | Nombre visible |
| categoría | Operatoria, Prótesis, Cirugía, etc. |
| precio_base | Valor sugerido |
| requiere_superficie | Sí/No |
| requiere_pieza | Sí/No |
| activo | Sí/No |
| created_at | ... |
| updated_at | ... |
Observá que el JSON nunca necesitaría cambiar si el nombre visible pasa de:
Obturación simple
a
Restauración con resina fotopolimerizable
El código seguiría siendo exactamente el mismo.
4. El catálogo pasa a ser el centro del sistema
Empiezo a visualizar algo así:
Prestaciones
│
├──────────────┐
│ │
▼ ▼
Odontograma PropuestaItems
│
▼
Cobros
│
▼
Facturación
Es una arquitectura muy limpia porque todos los módulos hablan el mismo idioma.
Una mejora que propondría
Hasta ahora venimos usando valores como:
{
"codigo": "obturacion-pendiente"
}
Yo establecería desde el principio una convención para esos códigos:
inmutables;
en minúsculas;
separados por guiones (
kebab-case);independientes del idioma visible para el usuario.
Ejemplos:
obturacion-simple
obturacion-compuesta
endodoncia-unirradicular
endodoncia-multirradicular
extraccion-simple
extraccion-compleja
perno-corona
corona-metal-porcelana
protesis-removible-superior
Esos códigos nunca deberían cambiar, aunque cambien los nombres comerciales o la terminología utilizada en la interfaz.
Conclusión
Con esta decisión considero que el próximo módulo a diseñar ya no es TratamientoPropuesta, sino Prestacion, porque acabamos de convertirlo en el eje común que conecta el odontograma con las propuestas, los cobros y la facturación.
Una vez diseñado ese catálogo, gran parte del resto del sistema se derivará de forma bastante natural.
No hay comentarios:
Publicar un comentario