Commit Graph
5 Commits
Author SHA1 Message Date
rmancinas 8802f08d4f feat(reports): reports module + /inicio + edo-cuenta-datos prefill
Build and Push Images / Build jorgecuadros-web (push) Successful in 1m35s
Build and Push Images / Build jorgecuadros-api (push) Successful in 2m11s
- New reports backend (registry, service, controller, outputs, types)
  with catalog endpoint + slug/CSV/XLSX/PDF/print outputs.
- /reportes catalog + /reportes/[slug] runner; ReportRunner + ContextReports
  components wire pre-filtered links from domain pages.
- Fix: /reportes/[slug] now reads searchParams and forwards initialParams to
  ReportRunner so /reportes/edo-cuenta-datos?customerId=... auto-runs
  instead of dropping the id and forcing a manual customer search.
- /inicio landing page; root + login redirect to /inicio.
- Company header env vars + logo asset for PDF/print rendering.
- exceljs + pdfkit deps.
2026-07-23 23:20:41 -07:00
rmancinasandClaude Opus 4.8 afe2411c86 feat(storage): wire MinIO/S3 document upload & download into the API + web
Build and Push Images / Build jorgecuadros-web (push) Successful in 1m37s
Build and Push Images / Build jorgecuadros-api (push) Successful in 2m8s
The schema has carried `storageKey` pointers and the migration has written
blobs to MinIO since day one, but the API had no S3 client — documents could
only be deleted, never uploaded or retrieved. This adds the missing wiring.

API
- StorageModule/StorageService (@aws-sdk/client-s3, path-style for MinIO):
  put/getStream/delete, best-effort bucket ensure on boot, gracefully disabled
  when S3 env is absent (ServiceUnavailable on use).
- Reads S3_ENDPOINT/S3_BUCKET + S3_ACCESS_KEY/S3_SECRET_KEY, falling back to
  MINIO_ROOT_USER/MINIO_ROOT_PASSWORD so one credential set drives both the
  migration and the API.
- Property service documents: POST :id/documents (multipart), GET
  :id/documents/:childId/download (streamed), delete now also drops the blob.
- Policy documents: same upload/download/delete (previously had none).
- Keys stay under the service/<id>/… and policy/<id>/… prefixes the migration
  established.

Web
- api.ts: shared uploadFile() helper (uploadIngest refactored onto it),
  upload/download/remove helpers for property & policy documents.
- Servicios, polizas, clientes detail pages: real Descargar links and an
  upload control (gated by policy:update / property:update) replacing the
  "storage pending" notes.

Infra
- docker-compose: minio service (9000/9001, healthcheck, named volume) + S3
  env wired into the api service.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 19:19:57 -07:00
rmancinasandClaude Opus 4.8 7a46c30d9b feat(policies): full CRUD + child editors + insurance lookups (plan phase 3)
Policy header, all five child collections, and the insurance reference
catalogs become create/edit/delete-able on the RBAC foundation.

API:
- Policy gains archivedAt (soft-delete); list/browser default to
  archivedAt=null with ?includeArchived opt-in.
- PoliciesService: header create/update/archive/restore (customer FK
  validated for a clean 404); add/update/remove for installments,
  vehicles, drivers, beneficiaries, claims — each scoped to its policy so
  one policy's id can't touch another's rows; lookups CRUD for providers,
  policy types, adjusters.
- PoliciesController write routes: header create/update need STAFF+
  (policy:create/update), archive/restore need MANAGER+ (policy:delete),
  every child route needs policy:update. New LookupsController at /lookups
  (read open; mutate needs lookup:manage / MANAGER+). Mutations audited.
- DTOs (policy header, children, lookups); dates coerced; shared coerce.ts.

Web:
- Generic ChildCollection editor (config-driven add/edit/remove table),
  reused by both the policy detail child editors and the catalogs screen.
- PolicyForm (header) with type/provider selects and a debounced
  CustomerPicker; /polizas/nuevo (accepts ?customerId prefill) and
  /polizas/[id]/editar. Policy detail: gated action bar (Editar/Archivar)
  + "Administrar detalles" child editors for all five collections.
- /catalogos admin screen (aseguradoras/tipos/ajustadores), nav-gated on
  lookup:manage. "Nueva póliza" buttons on the list and on the customer
  detail (prefilled). api.ts + types for all of the above.

Verified against dev: policy create (dates coerced, archivedAt null),
installment/vehicle add, VIEWER child-add 403, cross-policy child guard
404, lookups CRUD with VIEWER 403 / MANAGER 201, archive drops from the
default list and includeArchived surfaces it. Both apps compile clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:22:02 -07:00
rmancinasandClaude Opus 4.8 61193586a5 Utilities module: property browser (list/search/detail) + trust renewals
Plan step 5. Properties, services and trust accounts become a first-class
browser the way /polizas is for insurance.

API (apps/api/src/properties):
  GET /properties         search over address, customer, service account
                          number, meter, trust number and phones; filters for
                          service kind, municipality, trust bank, trust bucket
                          (with|without|active|expiring|expired|undated) and
                          hasServices; 5 sorts
  GET /properties/stats   properties/owners/services/trusts, renewal counts,
                          service mix per kind
  GET /properties/facets  kinds, municipalities, banks — all with counts
  GET /properties/:id     services, fideicomiso, linked policy, owner and
                          sibling properties, owner-level utility ledger

Web: /servicios (renewals-first browser, clickable stat cells and service-mix
strip) and /servicios/[id]. Property cards on /clientes/[id] and linked
properties on /polizas/[id] now navigate into it.

Data findings baked into the design:
  - The trust deadline staff chase is trust_accounts.dueDate2 (DATMEX vence2),
    one year after vence1 on 531 of 541 dated trusts: 18 due within 30 days,
    119 already overdue. Every renewal bucket keys off dueDate2 alone.
  - properties.zone is dead (1444 of 1519 null, the rest near-unique), so the
    geographic filter is the municipality carried in the predial service's
    notes (ROSARITO 566 / TIJUANA 221 / ENSENADA 152, 939/939 populated).
  - PropertyService.notes means a different thing per kind (municipality, CFE
    PAR/IMPAR cycle, gas supply type, cable provider) and is labelled as such.
  - 240 of 1519 properties have no service rows at all — its own bucket.

Sorting by trust due date scopes to properties that have a trust, since MySQL
would otherwise float the ~966 trust-less NULLs above every real due date;
the sort label and the result meta both say so.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 22:04:07 -07:00
rmancinasandClaude Opus 4.8 e2aba8bd17 Insurance module: policy browser (list/search/detail) + renewals view
Plan step 4. Adds the policies API and the Spanish-first /polizas pages on
top of the customer records the customer module already exposes.

API (apps/api/src/policies):
- GET /policies — search over policy number, customer name, agent, vehicle
  license plate, insured-driver name and legacy id; filters for vigencia
  bucket, ramo, aseguradora and liquidation state; five sort orders.
- GET /policies/stats — bucket counts plus premium in force split by
  currency (MXN and USD can't be summed).
- GET /policies/facets — ramos/aseguradoras with counts for the dropdowns.
- GET /policies/:id — full policy plus the owning customer.

Vigencia is derived from policyTo as active/expiring/expired/undated.
"undated" is a real bucket rather than an error case: 528 of the 2378
migrated policies carry no end date at all.

Web:
- /polizas — renewals-first browser; the stat cells double as vigencia
  filters, with a secondary row for ramo, aseguradora and sort order.
- /polizas/[id] — vigencia hero, condiciones y primas, pagos, vehículos,
  asegurados/beneficiarios, siniestros, the verbatim legacy coverage
  columns, and documents.
- Nav gains Clientes | Pólizas with a real active state, and the two
  modules cross-link in both directions.

Also fixes a display bug on the customer detail page: it headlined
policies.total, which is dead data — only 2 of 2378 rows are non-zero
(1585 are literally 0, 791 null), and one of those two is lower than its
own net premium. That rendered "$0.00 Total" on 1585 policies. Premium
headlines and the premium sort now use netPremium (2377/2378 populated);
total is shown only where it is non-zero, as raw source data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:27:38 -07:00