jueves, 16 de julio de 2026

Documentación del sistema: narrativa de flujo de negocio y de interacción (parte 3)

 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:

  1. Modelo conceptual.

  2. Reglas de negocio.

  3. Diagrama de estados.

  4. Modelo de datos.

  5. Relaciones Eloquent.

  6. Endpoints REST.

  7. Flujo completo de las pantallas.

  8. Reglas de concurrencia.

  9. 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 TratamientoPropuesta es inmutable desde el momento en que nace.

  • El estado BORRADOR no 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

CampoObservaciones
idPK
paciente_idFK
estadoEnum
subtotaldecimal
descuento_totaldecimal
totaldecimal
emitida_atdatetime
respondida_atnullable
created_at
updated_at

tratamiento_propuesta_items

CampoObservaciones
idPK
tratamiento_propuesta_idFK
prestacion_idFK
piezanullable
superficienullable
cantidaddecimal
precio_unitariodecimal
descuentodecimal o porcentaje (a definir)
importedecimal
observacionesnullable

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:

CampoDescripción
idPK interna
codigoClave estable usada por el odontograma
nombreNombre visible
categoríaOperatoria, Prótesis, Cirugía, etc.
precio_baseValor sugerido
requiere_superficieSí/No
requiere_piezaSí/No
activoSí/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: