Commit Graph

104 Commits

Author SHA1 Message Date
Cauê Faleiros
f5fed013ab fix: ask 3-D Secure of debit cards only, and send the cardholder as the card payer
All checks were successful
Build and deploy / Validate source (push) Successful in 4s
Build and deploy / Integration suite on a real stack (push) Successful in 2m13s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m9s
A credit card payment with the test credentials was refused with 10113
("the payment method is excluded by a rule"). Every card was sent with
three_d_secure_mode, which only debit needs, and with the order's CNPJ as
payer instead of the cardholder's document from the card form. Debit
methods keep 3-D Secure, and the card form's document is the payer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:57:19 -03:00
Cauê Faleiros
593ddf82c8 feat: add a read-only Mercado Pago account probe
All checks were successful
Build and deploy / Validate source (push) Successful in 4s
Build and deploy / Integration suite on a real stack (push) Successful in 2m3s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 45s
Card payments with the test credentials are refused with 10111 and 10113,
which depend on the account behind the token. python -m
app.mercadopago_probe shows that account (and whether it is a test user),
the card methods it accepts, and how Mercado Pago classifies a card's first
digits: type, issuer and instalments. It creates and charges nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:47:39 -03:00
Cauê Faleiros
8b5aafa299 fix: let Mercado Pago take the card issuer from the card number
All checks were successful
Build and deploy / Validate source (push) Successful in 4s
Build and deploy / Integration suite on a real stack (push) Successful in 2m9s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 50s
Paying with Mercado Pago's own test card failed with 10111 ("the issuer
does not have the BIN configured"): the issuer_id suggested by the card
form did not match the card. issuer_id is optional, and without it Mercado
Pago resolves the issuer from the BIN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:37:16 -03:00
Cauê Faleiros
e47f88a6d6 test: expect the 5 GB limit in the browser suite, and grade a large sheet from its header
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m16s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 51s
The artwork suite still asserted the old 128 MB refusal. It now refuses a
file above 5 GB and checks that a 3 GB sheet is graded from the pixel size
in its PNG header (300 DPI, 3 m) without being decoded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:31:21 -03:00
Cauê Faleiros
5f2be7ea20 feat: accept sheets of up to 5 GB end to end
Some checks failed
Build and deploy / Validate source (push) Successful in 12s
Build and deploy / Integration suite on a real stack (push) Failing after 2m36s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Has been skipped
Sheets of several GB are the normal order. The upload limit is now 5 GB.
ClamAV scans files up to 2 GB; a larger file is released only when its
first bytes match the format its name claims, and a disguised file is
refused. The Site grades a sheet over 150 MB from the pixel size in its
PNG, JPEG or WebP header without decoding it, and reads large PDFs in
ranges. The worker never opens a source over 300 MB: a finished sheet
placed whole becomes its own print file, which the Kanban offers to approve
as the final, and anything else goes to hand preparation. Files start
uploading as they enter the cart, with progress in the summary, and each
part renews the reservation so slow uploads do not expire. Quotas grow to
50 GB per customer and 500 GB in total; the Swarm config for ClamAV is
renamed because a deployed config cannot change in place.

Verified locally with a 386 MB and a 1.8 GB PNG (scanned, paid, original
as print file), a 2.3 GB PNG (format check) and a disguised 2.3 GB file
(refused).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:18:18 -03:00
Cauê Faleiros
4437232d27 fix: show why a card payment failed instead of leaving the form spinning
All checks were successful
Build and deploy / Validate source (push) Successful in 7s
Build and deploy / Integration suite on a real stack (push) Successful in 3m0s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 56s
When creating the card payment failed, the form's onSubmit rejected with no
message and Mercado Pago's button kept spinning. The page now shows the
reason above the form. A payment Mercado Pago refuses is logged with its
status, message and cause codes (payment_intent_refused) and answered 422
with that reason; other failures log their type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:58:56 -03:00
Cauê Faleiros
8bc57195e8 feat: open the customer's WhatsApp from the order panel
All checks were successful
Build and deploy / Validate source (push) Successful in 4s
Build and deploy / Integration suite on a real stack (push) Successful in 2m13s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 43s
The number in the Kanban's order panel is now a formatted wa.me link, so a
correction or a question about the artwork can be sent by hand in one
click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:51:15 -03:00
Cauê Faleiros
0a575a3be7 fix: drop the repeated total from the PIX instructions
All checks were successful
Build and deploy / Validate source (push) Successful in 7s
Build and deploy / Integration suite on a real stack (push) Successful in 2m19s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 50s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:21:57 -03:00
Cauê Faleiros
15abd589a8 ci: download the vulnerability database once, and tell a failed scan from a finding
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m22s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 55s
The CRITICAL scan of dtf-api failed on a 404 from the database mirror and
was reported as a CRITICAL vulnerability. The image step now downloads the
database once into a cache volume, with three attempts, scans all four
times from it, and exits 5 only on findings: a scan that does not run fails
with its own message, and nothing is published unscanned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:10:58 -03:00
Cauê Faleiros
32c060e1b0 fix: hide the card form's own title on the payment page
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m39s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m50s
Mercado Pago's form titled both card options "Cartão de crédito ou débito";
the option above it already names the card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:04:37 -03:00
Cauê Faleiros
6f5d183365 fix: show the real freight status on the Kanban
Some checks failed
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m16s
Build and deploy / Secret scan and release gate (push) Successful in 18s
Build and deploy / Publish images (push) Failing after 1m25s
The Integrations tab always said freight was not configured. It now reads
the server's adapter and, with Jadlog on, shows the package weight rule and
the production days the Site prices with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:57:34 -03:00
Cauê Faleiros
2215da8d5e fix: centre the arrow in the upload box; record that CDR/AI/PSD stay manual
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m24s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m43s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:49:11 -03:00
Cauê Faleiros
88e65290e1 fix: quote freight on its own, and drop the Calcular button
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m20s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m46s
Going back to home delivery with a CEP already typed, or restoring a saved
cart, now quotes the freight again by itself; before, only pressing Calcular
did. With every case covered, the button is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:40:55 -03:00
Cauê Faleiros
690b15ece1 feat: fill the delivery address from the CEP and quote freight as it is typed
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m21s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m47s
A complete CEP now looks up its address on the server (ViaCEP, rate
limited, keeping the Site's CSP to its own origin) and fills street,
district, city and state, moving the cursor to the first field left, and
quotes the freight without pressing Calcular. The CEP keeps its mask when
the cart is restored. Freight and its delivery time appear in the order
summary instead of a line under the CEP, the delivery option says it ships
with Jadlog, and the address reminder is a hint rather than an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:31:41 -03:00
Cauê Faleiros
e20c8673bb fix: send the Jadlog account exactly as configured
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m19s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m33s
Jadlog issues the account as 000000-0 and documents a six-character field;
stripping the dash sent seven digits. The value now goes as typed, so the
accepted form can be found on the account without a new release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:00:44 -03:00
Cauê Faleiros
4de2151e9a feat: price home delivery with Jadlog
All checks were successful
Build and deploy / Validate source (push) Successful in 1m18s
Build and deploy / Integration suite on a real stack (push) Successful in 2m33s
Build and deploy / Secret scan and release gate (push) Successful in 9s
Build and deploy / Publish images (push) Successful in 1m42s
FREIGHT_ADAPTER=jadlog prices "Receber em casa" through Jadlog's Simulador
de Frete from the order's billed metres and value, adding production days
to Jadlog's delivery time. The package weight is a base plus a weight per
metre from the client, with no default: the adapter refuses to start
without it and without the credentials. The cart re-quotes when the package
changes, and approval quotes again from the server-priced items. The
production stack takes the Jadlog settings, so the read-only probe runs
from the worker's console.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:34:01 -03:00
Cauê Faleiros
641aafc87d feat: PIX codes expire after 30 minutes, with a countdown
All checks were successful
Build and deploy / Validate source (push) Successful in 11s
Build and deploy / Integration suite on a real stack (push) Successful in 2m50s
Build and deploy / Secret scan and release gate (push) Successful in 8s
Build and deploy / Publish images (push) Successful in 1m54s
A PIX is created with a 30-minute date_of_expiration, and the PIX page
counts down to it. When it runs out the page says the code expired and
offers a new one. The API keeps one open code per quote: the same code
until it expires, then exactly one new attempt (serialised per quote, with
its own idempotency key), the old one marked expired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:40:26 -03:00
Cauê Faleiros
875a7ef9c7 fix: drop the change-method button from the PIX page
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m10s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m37s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:18:18 -03:00
Cauê Faleiros
c436936fed fix: drop the confirmation-time promise from the PIX note
Some checks failed
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m3s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:15:08 -03:00
Cauê Faleiros
0ed6de4bd5 fix: shorten the PIX note on the payment page
Some checks failed
Build and deploy / Validate source (push) Successful in 8s
Build and deploy / Publish images (push) Has been cancelled
Build and deploy / Secret scan and release gate (push) Has been cancelled
Build and deploy / Integration suite on a real stack (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:13:20 -03:00
Cauê Faleiros
eae3dad306 feat: offer credit card, debit card and PIX, with the bank's 3-D Secure step
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m3s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m31s
The payment page lists credit card (preselected), debit card and PIX. Each
card option limits Mercado Pago's form to its kind; debit is paid at once.
Card payments ask for 3-D Secure when the issuer requires it, and a
challenge opens the bank's page in a frame, which needs
PAYMENT_CHALLENGE_SOURCES=https: (frames and form posts only). A card left
waiting for that confirmation stops blocking a new attempt after ten
minutes, and a refusal reported by the notification returns the customer to
the payment choice. Written from the documentation; not yet run with a real
debit card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:24:06 -03:00
Cauê Faleiros
7b6675d4d4 feat: two-column payment page with card by default and PIX on its own page
All checks were successful
Build and deploy / Validate source (push) Successful in 7s
Build and deploy / Integration suite on a real stack (push) Successful in 2m19s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m38s
The payment page puts paying on the left and the order summary on the
right (above it on phones). Card is preselected and paid on the page with
Mercado Pago's form, in the Site's colours and with the order's e-mail;
choosing PIX shows a Pagar button that opens /pagamento/pix with the QR code
and copy-and-paste code, waiting there for the confirmation. A confirmed
payment shows "Pagamento confirmado" with the order number and a button to
the customer's orders. The payment buttons no longer restyle every button
inside the form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:10:05 -03:00
Cauê Faleiros
4df74221d2 fix: drop the validated-total line from the payment page
All checks were successful
Build and deploy / Validate source (push) Successful in 8s
Build and deploy / Integration suite on a real stack (push) Successful in 2m10s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m44s
The summary above already shows the total; the line stays only beside the
local stack's simulated payment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:15:00 -03:00
Cauê Faleiros
43043c361c feat: give payment its own page
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m12s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m35s
The PIX and card choices appeared under the cart, on the same page as the
customer's details. "Ir para o pagamento" now sends the order and opens
/pagamento, step 3 of the progress bar: the server's order summary, then PIX
or card, each opening below. The cart keeps only the sending progress and its
errors; a changed cart is sent again instead of offering the old quote.
Portal links open the payment page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:45:44 -03:00
Cauê Faleiros
536510b148 fix: version the Site's scripts by content so a release never meets a cached old one
All checks were successful
Build and deploy / Validate source (push) Successful in 7s
Build and deploy / Integration suite on a real stack (push) Successful in 2m9s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m44s
The proxy in front of production caches .js and .css for hours. After the
last release the Site got the new index.html with the old site-flow.js,
which wrote to an element the new page no longer has; the error left
"Adicionar ao carrinho" disabled. The web build now addresses every local
script and stylesheet by a hash of its content, replacing the hand-kept
?v= markers, so a new release always loads its own files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:27:55 -03:00
Cauê Faleiros
a1877bdf0a fix: remove the pickup and invoice notes from checkout; document R2 CORS
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m15s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m41s
The pickup notice under the delivery options and the invoice and retention
note under the purchase summary are gone. PORTAINER.md records the R2 CORS
policy the browser's direct uploads need: without it the preflight is
refused and checkout fails with a NetworkError before payment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:10:52 -03:00
Cauê Faleiros
275ebf72c4 feat: approve priced carts at checkout so customers can pay at once
All checks were successful
Build and deploy / Validate source (push) Successful in 8s
Build and deploy / Integration suite on a real stack (push) Successful in 2m22s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m34s
Every quote waited for an operator before it could be paid, so an order
placed at night waited for the morning. A cart the Site priced is now
approved when the quote is created, through the same server pricing the
operator's approval uses (app/quote_review.py). Orders above
QUOTE_AUTO_MAX_METRES (50 m) and items claiming a discount on art the Site
could not analyse still wait for review; the Kanban shows which quotes were
approved automatically and why the others wait.

The grade is still computed in the browser (roadmap 3.2, 3.9), so the
discount remains a customer-supplied value until the server computes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:02:56 -03:00
Cauê Faleiros
9bea187ea4 feat: add a read-only Jadlog price probe
All checks were successful
Build and deploy / Validate source (push) Successful in 1m20s
Build and deploy / Integration suite on a real stack (push) Successful in 2m49s
Build and deploy / Secret scan and release gate (push) Successful in 10s
Build and deploy / Publish images (push) Successful in 1m51s
app/jadlog.py prices one package through Jadlog's Simulador de Frete as the
API manual v2.3 describes it; app.jadlog_probe prices test weights to six
regions on the client's account to confirm token, account and contract.
Tested against a fake transport. The roadmap records the Jadlog data and the
Mercado Pago account setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:41:18 -03:00
Cauê Faleiros
bd56170248 feat: let production select Mercado Pago and acknowledge simulated notifications
The production compose hard-coded the fake payment adapter; it now takes
PAYMENT_ADAPTER and the MP_* settings from the stack's environment, so the
sandbox can run with test credentials. A signed notification about a payment
Mercado Pago does not have, such as the panel's "Simular notificação", is
acknowledged instead of answering 500 and being retried; any other lookup
failure still raises.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:41:18 -03:00
Cauê Faleiros
60b7336b37 docs: commit the operator guide's source and record the Week-2 report
The operator guide is now built from docs/guias/operador/ by
docs/guias/imprimir.sh, with the corrections on Tiny and the WhatsApp
notices. The roadmap records the guide, the history fix found while
writing it, and the Week-2 report as sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:07:58 -03:00
Cauê Faleiros
04e4cbc953 fix: hide operators' internal back-move reasons from the customer's order history
A move back undoes an operator's mistake and its reason is internal. The
customer's history now omits back moves and shows a reason only for a
correction; the smoke test checks both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:07:58 -03:00
Cauê Faleiros
aec5b1d054 feat: send order situações to Tiny for the client's WhatsApp notices
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m5s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m45s
The client already sends WhatsApp notices from Tiny's order situação
(Tiny webhook -> middleware -> n8n). With TINY_STATUS_UPDATES on, a paid
order is set to "Aprovada" once and a finished pickup order to "Pronto
para envio"; pickup orders carry the client's pickup forma de envio
(TINY_FORMA_ENVIO_RETIRADA). The ready event now carries the order and
the Tiny id from the sale's receipt. Off by default until go-live, when
n8n stops sending the DTFIMP designer message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:17:00 -03:00
Cauê Faleiros
8b42e684ca docs: record the Tiny account and product findings
All checks were successful
Build and deploy / Validate source (push) Successful in 10s
Build and deploy / Integration suite on a real stack (push) Successful in 2m46s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m46s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:40:28 -03:00
Cauê Faleiros
e768dcb489 feat: keep the Tiny connection alive and show when it is not
All checks were successful
Build and deploy / Validate source (push) Successful in 8s
Build and deploy / Integration suite on a real stack (push) Successful in 2m29s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m49s
Connecting now asks for offline_access, retrying once without it if Tiny
refuses the scope. Renewal failures are stored: a refused refresh token
marks the connection lost and is not sent again (the Kanban previously
still said "conectado"), a transient failure shows as a warning until the
next renewal, and a session grant with under 12 hours left is flagged.
Tiny errors on the callback return to the Kanban instead of a 422.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:04:23 -03:00
Cauê Faleiros
122c645f72 fix: stop Site timers from running after a product is closed
The grade check and the layout preview run on short timers. When the
item went to the cart inside that window, no product was open and both
threw in the customer's browser, which also failed the browser tests
intermittently. Each now returns when no product is open. The cart test
waits for the empty state, which is painted on the next animation frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:04:23 -03:00
Cauê Faleiros
988b252f9d feat: check Tiny products and add a supervised order test
All checks were successful
Build and deploy / Validate source (push) Successful in 11s
Build and deploy / Integration suite on a real stack (push) Successful in 2m39s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m43s
"Testar conexão" now also reads the four configured Tiny products and
requires each to be active. app/tiny_probe.py runs from the worker console
to list products, confirm the configured ids, and create one marked test
order through the worker's own delivery path, proving the duplicate guard
by search before a second delivery. Nothing is sent without --confirmar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:23:07 -03:00
Cauê Faleiros
56021d8451 docs: add the operator and Site guides and bring the roadmap up to date
All checks were successful
Build and deploy / Validate source (push) Successful in 1m39s
Build and deploy / Integration suite on a real stack (push) Successful in 2m32s
Build and deploy / Secret scan and release gate (push) Successful in 10s
Build and deploy / Publish images (push) Successful in 1m58s
The operator guide covers the Kanban flow from quote review to finished
order; the Site guide walks through the customer journey, prices and the
current state of each integration. The roadmap records the Kanban and Site
redesigns, the guides, and what is left for the last day of Week 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 09:07:50 -03:00
Cauê Faleiros
b6a90411f1 feat: let customers remove items and empty the cart, with undo
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m2s
Build and deploy / Secret scan and release gate (push) Successful in 4s
Build and deploy / Publish images (push) Successful in 1m33s
Each cart item has a visible "Remover" button instead of a faint ×, and
carts with two or more items get "Esvaziar carrinho". Both show a
"Desfazer" notice for 8 seconds, so a wrong click costs nothing. The
browser test covers remove and undo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 18:18:10 -03:00
Cauê Faleiros
cbbbdb351a feat: rebuild the Site product page around a buy box
All checks were successful
Build and deploy / Validate source (push) Successful in 10s
Build and deploy / Integration suite on a real stack (push) Successful in 2m46s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m46s
Artworks on the left; on the right a box that stays in view with the live
sheet, the grade, the price per metre, the metres charged, the total, the
resolution note and "Adicionar ao carrinho". The separate quality and
preview panels, the second sheet preview, the per-row mini sheets, the
summary box and the repeated findings list are gone: each piece of
information now appears once.

Each artwork row carries at most one hint (resolution first, otherwise a
width that saves film), the ready-sheet/loose-artwork choice is a toggle
beside the title, the upload area is one bar and the tips are collapsed.
The box only shows the item the page already priced, so it always matches
the cart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 18:02:45 -03:00
Cauê Faleiros
b81ff8d03d feat: give each Site product and the cart its own page
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m28s
Build and deploy / Secret scan and release gate (push) Successful in 8s
Build and deploy / Publish images (push) Successful in 1m52s
The home, each product's Montagem and the cart now have their own
addresses (/artes-avulsas, /arquivo-por-metro, /uv-artes-avulsas,
/uv-arquivo-por-metro, /carrinho) and show only their own content, with
Back, Forward, reload and direct links working as in any store. They stay
one document so uploaded artworks survive moving between pages; nginx
serves index.html for these addresses.

"Adicionar ao carrinho" puts the item in the cart and opens it, and an
empty cart says so. Portal quote links open in the cart. Also fixes the
"57 cm" line break on the ready-sheet option, returns "Novo pedido" to
the home, and says PDF depends on the product.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 16:56:58 -03:00
Cauê Faleiros
177882e77b feat: redesign the Site order flow and fix the cart layout
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m23s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m48s
New landing (hero with a sheet preview, four steps, product cards priced
from the checkout table, price table and benefits), a progress bar that
follows the order through Montagem, Dados e entrega and Pagamento, the
live sheet beside the artworks, and a running total bar on phones.

The cart's saved-in-browser note no longer takes a grid column, which had
pushed the order into a narrow strip and the summary below it. The
previous look is kept in tag ui-v1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 16:33:06 -03:00
Cauê Faleiros
2e03b0362b feat: undo mistaken moves, numbered pagination, and quieter Kanban messages
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m33s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m48s
Moves: an order can go back one stage (BACK in app/runtime.py) with an
internal reason, flagged in the history as movements.back. The customer is
not notified and approved finals stay; "production started" and "ready" are
now enqueued once per order, so undoing and redoing a move sends nothing
twice. Dragging only goes forward and highlights the allowed column. Move
errors are in Portuguese.

Lists: the send log, payments (open, resolved as history, all) and quotes
are paged on the server with a total, 20 rows by default (10/20/50/100),
first/previous/page/next/last. The send log filters by destination, status,
event and order. Older finished orders load on demand. The board no longer
carries the send log or payment rows, only the open-payment count.

Kanban: Pagamentos and Integrações are separate tabs; messages are brief,
bottom notifications that clear themselves; wording is shorter.

Full CI integration sequence passes locally, with new checks for undo, paging
and filters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 16:01:31 -03:00
Cauê Faleiros
99a558ddd9 feat: redesign the Kanban and keep test wording out of production
All checks were successful
Build and deploy / Validate source (push) Successful in 11s
Build and deploy / Integration suite on a real stack (push) Successful in 3m3s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m51s
Kanban: tabs for production, quote review and payments/integrations; compact
cards with products, metres, print-file status, delivery and time in stage;
an order panel with stage progress, one main action, a correction reason in
place, items with a preview drawn from the approved layout, final-file
approval and the history as a timeline. Quote review gets a list and a pane;
payment issues resolve in place; integrations show their real state, Tiny's
connection with a read-only "Testar conexão", and a readable send log. The
previous Kanban is kept in git tag ui-v1 and is no longer served.

Production wording: the customer portal no longer says it is a local test
environment outside the local stack; the checkout no longer tells customers
to use the Kanban or shows internal stage codes; sign-in, session, quota and
print-file messages are Portuguese and never say "local". A simulated freight
price is refused outside the local stack until a real freight provider
exists, so production only offers pickup.

The browser suite drives the new tabs and panel and still checks the whole
upload, quote, payment and production journey. Full CI sequence passes locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:28:04 -03:00
Cauê Faleiros
641dc6053b ci: publish images from every green push to main
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m5s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m54s
The release job enforced the production source preflight, which blocks while
the payment and messaging adapters are fake. They still are, by design, so
since 2026-09-23 no release could succeed and production kept running older
images while main moved on.

Pushes to main that pass validation, the integration suite and the scans now
build, scan and publish the images. Nothing is deployed automatically:
production changes when the stack is pulled and redeployed in Portainer. A
manual run also calls the Portainer webhook when one is configured. The
preflight stays in the scan job, advisory unless ENFORCE_PRODUCTION_PREFLIGHT
is true, in which case a blocked preflight stops publishing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui-v1
2026-09-24 14:30:43 -03:00
Cauê Faleiros
4c01e932c3 feat: place PDF artwork in print files, add card payment, count only failed logins
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m23s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
PDF artwork: a single-page PDF source is placed in the print file as a
vector form through pikepdf, never rasterised, using the CropBox and
inherited /Rotate the Site measured with pdf.js. Multi-page and protected
PDFs go to hand preparation. PyMuPDF was not used because of its AGPL
licence. Raster tests cover crop, page rotation, placement rotation and
mirroring, and fail when the rotation or crop handling is broken.

Card payment: Mercado Pago's Card Payment Brick on the Site when
MP_PUBLIC_KEY is set; the card becomes a one-time token in Mercado Pago's
secure fields. Each card attempt has its own idempotency key, and the intent
route refuses new attempts once a payment is approved or a card is in
review, so a quote cannot be charged twice. The Site CSP admits Mercado
Pago's origins only through PAYMENT_CSP_SOURCES, empty by default.

Logins: every attempt counts against the source address, only failures
against the account. Counting successful sign-ins let ordinary use lock an
operator out and made CI's final browser sign-in fail.

No new required settings; production behaviour is unchanged until the
provider credentials are configured. Verified with the full CI integration
sequence locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 13:14:56 -03:00
Cauê Faleiros
e3d5558198 feat: connect Tiny through its v3 API with OAuth
All checks were successful
Build and deploy / Validate source (push) Successful in 9s
Build and deploy / Integration suite on a real stack (push) Successful in 2m49s
Build and deploy / Secret scan and release gate (push) Successful in 9s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
Tiny v3 replaces the v2 token adapter. An operator connects Tiny once from
the Kanban; the callback is authorised by a single-use state, because Tiny's
cross-site redirect does not carry the SameSite=Strict operator cookie.
Tokens are kept in provider_tokens, the refresh token rotates under a row
lock, and the worker keeps the connection alive while order creation is off.

Orders find or create the customer's contact by CNPJ, then POST /pedidos
with product ids from TINY_PRODUCT_TEXTIL_FOLHA, _TEXTIL_AVULSA, _UV_FOLHA
and _UV_AVULSA and numeroOrdemCompra DTF-<number>; a retry searches the
customer's recent orders for that number first. The product settings avoid a
_FILE suffix, which the secrets loader reads as a secret file path.

Production passes the application credentials through but keeps
TINY_ADAPTER fake: Tiny has no sandbox, so creating real orders waits for a
supervised test. compose.providers.yaml gives the local API and worker an
internet route for provider testing; the default local stack still has none.

Verified with the full CI integration sequence locally, including the new
tiny_oauth_test against the real database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 12:46:09 -03:00
Cauê Faleiros
c18b9e5b87 feat: generate print files, collect delivery addresses, add provider adapters
All checks were successful
Build and deploy / Validate source (push) Successful in 1m45s
Build and deploy / Integration suite on a real stack (push) Successful in 4m48s
Build and deploy / Secret scan and release gate (push) Successful in 11s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
Week 2 work that did not need client inputs.

Print files (1.4): each paid item gets a PDF the width of the film and the
length of the approved layout, with every copy at its reviewed position,
rotation and mirror. Sources are embedded once at original resolution; JPEG
bytes pass through and PNG alpha becomes a soft mask. Artwork the generator
cannot reproduce goes to hand preparation with the reason. The worker renders
outside any transaction, and the operator approves the generated file as the
final one through the existing review.

Delivery address (3.8): required for any non-pickup quote, bound to the
quoted CEP, carried into the order snapshot, the Kanban card and Tiny.

Kanban (1.5): print-file status per item, and a panel of payment events that
need a person (money without an order, refunds after an order) until an
operator records the resolution.

Mercado Pago and Tiny (1.1, 1.3): adapters written from the public API
documentation and tested against fake transports only. Selectable for
sandbox testing with their credentials; the production preflight still
blocks release. Adds payment intents and a PIX step on the Site.

MinIO: Docker Hub and quay.io now refuse anonymous pulls, so local and CI
storage use Chainguard's MinIO build, pinned by digest.

Verified with the full CI integration sequence on a fresh local build,
including the new print_file_test and both browser suites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 11:56:46 -03:00
Cauê Faleiros
cfcbe545f1 ci: require manual gated releases from main
All checks were successful
Build and deploy / Validate source (push) Successful in 1m28s
Build and deploy / Integration suite on a real stack (push) Successful in 4m3s
Build and deploy / Secret scan and release gate (push) Successful in 11s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
2026-09-23 11:27:18 -03:00
Cauê Faleiros
24013458c9 fix: harden week-two ordering, artwork and operations 2026-09-23 10:40:18 -03:00
Cauê Faleiros
ccc25a2d5d feat: accept payment notifications, once, from a verified sender
There was no inbound payment path at all: a button called a fake synchronously
and wrote an order. A real provider does the opposite — it charges, then tells
us, repeatedly, out of order, and sometimes long afterwards.

POST /api/payments/webhook verifies the signature before the body is parsed, so
an unsigned or tampered delivery is refused and recorded without touching an
order. Verified deliveries are stored under the provider's own event id with a
unique constraint, and applied inside the same transaction that marks them
processed: a repeat is a no-op, a crash is retried rather than half-applied.

An approval whose amount disagrees with the reviewed quote does not become an
order. Underpayment would ship artwork nobody paid for, and overpayment means
something a person should look at.

Order creation moved to app/payments.py so the webhook and the local development
checkout share one implementation and cannot drift. That also closes 3.5: the
charge happens inside the transaction that persists the order, rather than
before it.

The adapter contract is create/verify/parse. FakePayment implements it with a
real HMAC scheme so the whole path is exercised now, by tests/payment_test.py:
unsigned, tampered, underpaid, duplicate, re-sent, unknown reference, and
non-approved statuses. Connecting Mercado Pago is one adapter; no service code
changes.

PAYMENT_WEBHOOK_SECRET is optional in production on purpose. Required would
break the next Portainer render, and a guessable default would be worse than
either: with no secret configured the adapter verifies nothing and therefore
accepts nothing, which is the right state until a provider is connected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 13:25:08 -03:00