fix(migration): make Phase B additive sync actually work + verify end-to-end
Build and Push Images / Build jorgecuadros-web (push) Successful in 1m37s
Build and Push Images / Build jorgecuadros-api (push) Successful in 2m9s

The --sync path had never been run and was broken in several ways. Fixed and
verified against the dev DB (two consecutive syncs, both exit 0, 32/32
assertions: stable PKs, manual-row preservation, changed-row updates,
legacy-delete, no child duplication, zero FK orphans; idempotent).

- policies/properties: reuse each legacy row's existing id (by provenance)
  BEFORE building child rows, so children no longer point at a discarded fresh
  uuid; rebuild legacy-owned children via scoped delete + reinsert.
- customers: replace zip(customers, refs) (mispaired almost every row) with a
  ref-grouped id remap; names now restore and no spurious customers appear.
- drop the invalid Vehicle @@unique(legacySourceTable, legacyId) — one legacy
  policy row carries up to 3 vehicles sharing a legacyId; handle via delete+reinsert.
- upsert lookup tables (policy_types, insurance_providers, type_transactions,
  adjusters) by natural name and remap child FKs instead of inserting fresh
  uuids that nothing points at.
- transactions: drop updatedAt=NOW() (no such column); guard report formatting
  on NULL legacySourceTable (manual rows). Same report guard in bank.
- add manual-safe prune (prune_empty_customers.py --sync, in SYNC_STEPS): prune
  only legacy-owned empties, never manually-added customers.

web: customer-detail mini tx list now strikes voided rows with an "(anulado)"
tag (was the last void-UI rendering gap; /estado-cuenta already handled it).

docs: RESUME.md updated — Phase B sync marked verified end-to-end, void-UI
browser pass recorded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 13:37:22 -07:00
co-authored by Claude Opus 4.8
parent 8802f08d4f
commit 1b79b43a54
11 changed files with 211 additions and 76 deletions
+10 -2
View File
@@ -781,9 +781,10 @@ function TxRow({ t }: { t: Transaction }) {
const tipo =
t.type?.nameEs || t.type?.nameEn || "—";
const concept = t.message || t.period || "—";
const voided = !!t.voidedAt;
return (
<tr>
<tr style={voided ? { textDecoration: "line-through", opacity: 0.55 } : undefined}>
<td className="mono" style={{ whiteSpace: "nowrap" }}>
{formatDate(t.transactionDate)}
</td>
@@ -793,7 +794,14 @@ function TxRow({ t }: { t: Transaction }) {
</td>
<td>{tipo}</td>
<td className="tx-ref">{t.reference || "—"}</td>
<td className="tx-concept">{concept}</td>
<td className="tx-concept">
{concept}
{voided && (
<span className="tx-cur" style={{ marginLeft: 6, textDecoration: "none" }}>
(anulado)
</span>
)}
</td>
<td className="num">
<span className={`tx-amount ${sign}`}>
{formatMoney(t.amount, t.currency)}
+2
View File
@@ -667,6 +667,8 @@ export interface Transaction {
period?: string | null;
message: string | null;
checkNumber: string | null;
/** App-voided (`voidedAt` set). UI strikes; totals exclude. */
voidedAt?: string | null;
type: TransactionType | null;
}