Por qué editar un gasto debe redefinir el reparto
Los valores por defecto de una edición son errores de dinero, así que Dimesum exige participants y split_type en cada PATCH de gasto, junto con base_version, que rechaza una edición obsoleta en lugar de fusionarla.
Corregir una errata no debería cambiar quién debe dinero. En Dimesum, editar un gasto compartido redefine todo el reparto. participants y split_type son obligatorios en cada PATCH, y una edición que omita cualquiera de los dos devuelve 400 en lugar de rellenar un valor por defecto. Los valores por defecto de la creación son todos los miembros actuales y un reparto igual, que en una edición son errores de dinero.
Dos fallos lo dejan claro. Un compañero de piso que entró en agosto queda incluido en la cena de julio, porque "todos los miembros actuales" se evalúa cuando llega la edición, no cuando se escribió el gasto. Un reparto de alquiler deliberado de 70/30 se aplana a 50/50, porque un split_type ausente significa EQUAL. Ninguno de los dos fallos genera un error, y ambos mueven dinero real.
Los valores por defecto de una edición son errores de dinero. Un valor por defecto en el momento de crear adivina sobre el grupo que el autor está viendo ahora mismo. Una edición llega después, contra un grupo que ha cambiado, y esa misma suposición reescribe en silencio lo que la gente debe.
Los valores por defecto de la creación describen un gasto nuevo, no uno antiguo
En el momento de crear, los valores por defecto son honestos. El autor está viendo el grupo tal como está, y un reparto igual entre todos es el caso habitual, así que Dimesum rellena ambos. El gasto guarda sus entradas y no solo sus resultados: splits.percent_bp, splits.weight y splits.exact_minor conservan lo que el usuario escribió, de modo que una edición posterior puede reabrirlo.
Una edición es otro acto. El mismo gasto puede corregirse semanas después, una vez que ha entrado un nuevo compañero de piso o se ha reclamado un miembro fantasma. La pertenencia al grupo cambia con el tiempo y el reparto no. Reutilizar los valores por defecto de la creación le pide al grupo de hoy que responda una pregunta que el gasto antiguo ya respondió.
La negativa a adivinar no es nueva aquí. Un gasto con varios pagadores también debe redefinir sus pagadores. soleStoredPayer reutiliza el pagador guardado solo cuando el gasto tiene exactamente uno, así que una edición que calla sobre dos pagadores se rechaza en lugar de reasignarse. Una edición que no dice nada sobre los pagadores conserva el pagador propio del gasto, nunca la persona que hace la edición.
La API rechaza una edición que no redefine su reparto
La comprobación de la puerta de enlace se ejecuta antes de calcular ningún importe. Cuando participants está vacío o split_type queda en blanco, la solicitud devuelve 400 con el código invalid_expense y el mensaje "an edit must restate the split: participants and split_type are required". El servicio de gastos repite la regla en validateAmend, así que quien llegue al servicio por otra vía encuentra el mismo rechazo.
| Campo | Al crear | En una edición | Qué costaría el valor por defecto |
|---|---|---|---|
participants | Opcional. Por defecto, todos los miembros actuales | Obligatorio | Un miembro que entró después se une a un gasto antiguo |
split_type | Opcional. Por defecto, EQUAL | Obligatorio | Un reparto 70/30 se aplana a 50/50 |
payers | Opcional. Por defecto, el autor | Si se omite, conserva el único pagador del gasto; dos pagadores deben redefinirse | El editor pasa a ser el pagador, invirtiendo quién debe a quién |
base_version | No se envía | Obligatorio, y debe coincidir con la versión actual | Una edición obsoleta sobrescribe un cambio que su autor nunca leyó |
revision_id | UUIDv7 del cliente, la clave de idempotencia | Igual, uno por versión | Una edición reintentada le cobra al grupo dos veces |
currency | Se indica por gasto | Debe coincidir; un cambio se rechaza | Una fila de saldos guarda una moneda por miembro |
La moneda pertenece a la misma familia de rechazos. Una edición no puede cambiar la moneda de un gasto, porque una fila de saldos guarda una moneda por miembro. El lado de escritura rechaza el cambio, y el libro mayor de solo anexado aparta esa modificación si alguna vez le llega. Rechazar en la puerta evita que las dos mitades se contradigan.
Una edición obsoleta se rechaza para su autor, nunca se fusiona
base_version es la otra mitad del contrato. Cada PATCH lleva la versión que leyó su autor, y checkTransition la compara con la fila que la transacción acaba de bloquear. Si coinciden, la edición se aplica en la versión más uno. Si difieren, quien llama recibe HTTP 409 con el código stale_version.
Fusionar es la alternativa tentadora, y es un error. Dos ediciones sobre un gasto son dos declaraciones completas de lo que significa la cuenta. Fusionarlas produce una tercera declaración que nadie escribió, con partes que ninguno de los dos autores reconocería. El rechazo devuelve el conflicto a la única persona que puede resolverlo.
La idempotencia y la concurrencia se mantienen separadas a propósito. La inserción de la revisión se ejecuta antes de la comprobación de versión, porque una edición reintentada lleva la versión base que leyó al principio, que ahora está obsoleta. Un reenvío debe leerse como un reenvío y no como un conflicto, así que revision_id responde primero y devuelve el resultado guardado.
La respuesta ya lleva lo que la siguiente edición necesita
Exigir más campos en un PATCH solo es justo si un cliente puede obtenerlos sin coste. Cada respuesta de gasto lleva version, así que un cliente que acaba de escribir un gasto puede editarlo sin una segunda lectura. La respuesta de creación, la respuesta de modificación y cada fila de la lista llevan el mismo campo.
Para el cliente que no acaba de escribir el gasto, GET /v1/groups/{id}/expenses/{id} devuelve la versión, las partes calculadas y las entradas que hay detrás. Las entradas vuelven con los mismos nombres que acepta un PATCH: participants, split_type, percents, weights, shares, items, pools. Un cliente lee una sola forma y la reenvía con los cambios, en lugar de traducir entre dos vocabularios para una misma cuenta.
La simetría importa sobre todo para repartos que no se pueden reconstruir. Un reparto PERCENT o una cuenta detallada no se puede redefinir solo a partir de sus partes ya resueltas, porque el redondeo ya se ha aplicado y los puntos básicos y las líneas de detalle han desaparecido. Las instantáneas de revisión también llevan las entradas, así que la hoja de historial puede mostrar qué se detalló en cualquier versión anterior.
Obligatorio ahora, porque un requisito no se puede añadir después
Exigir un campo desde el primer día es una decisión sobre el futuro más que sobre el presente. Relajar después un campo obligatorio es retrocompatible: los clientes ya lo envían, y el servidor empieza a aceptar solicitudes sin él. Añadir un requisito después rompe a todos los clientes que dependían del valor por defecto antiguo.
Por eso la dirección se elige una vez, pronto. Dimesum exige participants, split_type y base_version en una edición mientras el número de clientes es aún lo bastante pequeño como para cambiar. Si alguna vez se encuentra un valor por defecto seguro para las ediciones, los campos pasan a ser opcionales y nada de lo ya publicado deja de funcionar.
Haz que una edición redefina lo que significa
Los valores por defecto pertenecen a la creación, donde el autor puede ver el grupo con el que está de acuerdo. En una edición esos mismos valores por defecto son una suposición sobre un grupo que ya ha cambiado. Tu siguiente paso: abre tu propio endpoint de lectura y comprueba que devuelve las entradas del reparto con los mismos nombres de campo que acepta tu endpoint de escritura. Un cliente que tenga que traducir entre los dos acabará traduciendo mal uno de ellos.
Preguntas frecuentes
¿Por qué al editar un gasto compartido hay que indicar participants y split_type?
Dimesum exige ambos campos porque los valores por defecto de la creación no sirven para una edición. Al crear, Dimesum usa por defecto todos los miembros actuales y un reparto igual, que coincide con el grupo que el autor está viendo. Una edición puede llegar semanas después, cuando ya ha entrado alguien. Reutilizar esos valores por defecto metería a un nuevo compañero de piso en una cena antigua y aplanaría un reparto de alquiler deliberado de 70/30 de vuelta a 50/50, sin mostrar ningún error.
¿Qué pasa si dos personas editan el mismo gasto a la vez?
La segunda edición se rechaza con HTTP 409 y el código de error stale_version. Cada PATCH lleva base_version, la versión que leyó su autor, y la ruta de modificación la compara con la fila bloqueada. Una discrepancia significa que el gasto ha cambiado, así que la edición vuelve para que su autor la reaplique sobre la versión que ahora puede ver. No se fusiona nada.
¿Al editar un gasto se actualizan las filas del libro mayor?
No, editar un gasto nunca actualiza una fila del libro mayor en Dimesum. Una edición registra dos asientos en una sola transacción: un EXPENSE_REVERSAL que anula la versión anterior línea por línea, y luego un asiento EXPENSE para la versión nueva. UPDATE y DELETE están revocados en el propio rol de base de datos del libro mayor, así que una mutación es imposible incluso para código con errores. Verás una insignia de editado y una hoja de historial.
¿Necesito una segunda llamada a la API antes de editar un gasto compartido?
No, un cliente que acaba de escribir el gasto ya tiene la versión que necesita una edición. Cada respuesta de gasto lleva version, que es lo que el siguiente PATCH envía como base_version. Un cliente que no escribió el gasto llama a GET /v1/groups/{id}/expenses/{id}, que devuelve la versión más las entradas del reparto con los mismos nombres de campo que acepta un PATCH.
¿Por qué exigir participants y split_type ahora en vez de añadirlos más adelante?
Exigir un campo desde el primer día es reversible, y añadirlo después no lo es. Relajar más tarde un campo obligatorio es retrocompatible: los clientes ya lo envían, y el servidor empieza a aceptar solicitudes sin él. Añadir un requisito después rompe a todos los clientes que dependían del valor por defecto antiguo, y en una API de dinero la ruptura es silenciosa hasta que el saldo de alguien está mal.
Artículos populares
- Libro mayor de solo anexión para saldos exactos9 min de lectura
- Seis errores de dinero en gastos multidivisa10 min de lectura
- Cómo saldar cuentas de grupo en pocas transferencias5 min de lectura
- Dividir la cuenta cuando un plato no se compartió10 min de lectura
- Dividir el alquiler entre compañeros con justicia6 min de lectura