Skip to content

feat(sire): TUS.IO upload — reemplazar propuesta + importar comprobantes#6

Merged
Railly merged 1 commit into
mainfrom
feat/sire-tus-upload
Apr 29, 2026
Merged

feat(sire): TUS.IO upload — reemplazar propuesta + importar comprobantes#6
Railly merged 1 commit into
mainfrom
feat/sire-tus-upload

Conversation

@Railly
Copy link
Copy Markdown
Contributor

@Railly Railly commented Apr 29, 2026

Summary

Adds the second half of SIRE: not just downloading SUNAT's proposal but uploading your own .zip files to replace it or import extra comprobantes.

This was the biggest blocker called out in PR #4. SUNAT's manual notes "deben ser desarrollados en JAVA" for these endpoints — that's an implementation suggestion (only Java samples ship), NOT a protocol requirement. TUS.IO is HTTP-based and language-agnostic. Verified by spec review and 15 unit tests.

What's in this PR

TUS.IO 1.0.0 client (src/sunat-rest/tus.ts)

  • encodeMetadata() — pairs of key base64Value joined with , per spec
  • tusCreate() — POST with Tus-Resumable + Upload-Length + Upload-Metadata, returns Location → uploadUrl
  • tusHead() — read current Upload-Offset (interface for resumption)
  • tusPatch() — PATCH chunk with Upload-Offset + application/offset+octet-stream
  • tusUpload() — high-level create + chunked PATCH loop with onProgress
  • Default chunk size: 8 MB. Configurable per upload.

SIRE upload wrappers (src/sunat-rest/sire.ts)

5 upload kinds (codProceso from Anexo I):

Kind codProceso Endpoint
reemplazoPropuesta 3 /receptorpropuesta/.../upload
importarPropuestaCp 1 /receptorpropuesta/.../upload
importarPreliminarCp 4 /receptorpreliminar/.../upload
ajustesPosteriores 6 /receptorajustesposteriores/.../upload
ajustesPosterioresAnteriores 7 /receptorajustesposteriores/.../upload

SUNAT-required metadata keys: filename, filetype, perTributario, codOrigenEnvio (=2), codProceso, codTipoCorrelativo (=01), nomArchivoImportacion, codLibro

Commands

# Replace SUNAT's proposal with your own .zip
sunat sire ventas reemplazar --periodo 202404 --file mi-propuesta.zip --yes --wait

# Import additional comprobantes (--tipo: propuesta | preliminar | ajustes | ajustes-anteriores)
sunat sire ventas importar --periodo 202404 --file extra.zip --tipo propuesta --yes --wait

# Same flags work for compras
sunat sire compras importar --periodo 202404 --file compras.zip --tipo propuesta --yes --wait
  • File size validated client-side (0 < size ≤ 6 GB per SUNAT limit 1346)
  • Progress bar to stderr (suppressed in --json mode)
  • Audit log entry per upload with bytesSent + numTicket
  • T2 — requires --yes

Tests

238 pass / 2 skip / 0 fail in 2.8s (was 223)

15 new unit tests in tus.test.ts:
- encodeMetadata: single key, multiple pairs, UTF-8 multibyte (acentos), empty
- TUS_VERSION = 1.0.0
- tusCreate: required headers, relative Location resolution, error paths
- tusHead: reads Upload-Offset + Upload-Length, missing-header error
- tusPatch: PATCH method + Upload-Offset header + offset+octet-stream content-type
- tusUpload: chunked end-to-end (20MB → 3 chunks), --chunk-size respected

Known limitations (added to LIMITATIONS.md)

  • ⚠️ Never tested live — same SIRE caveat as PR feat(sire): RVIE Ventas + RCE Compras automation #4 (test RUC 20000000001 has no SIRE history). Upload code matches Manual v22 March 2024 byte-for-byte. Verify with real RUC + small test ZIP first.
  • ⚠️ Ticket extraction best-effort — SUNAT's upload response shape varies; if numTicket returns empty, upload itself succeeded but operator polls consultaestadotickets manually using perTributario + codProceso.
  • 🚧 Auto-resume on partial upload failure — TUS spec supports it (tusHead() is implemented), but current tusUpload() doesn't auto-retry from last offset on network errors. Re-run command. Future PR.
  • ⚠️ CORS warning from SUNAT — server-side only; not affected since CLI is server-side.

Test plan

  • bun test green (238 pass / 2 skip / 0 fail)
  • sunat sire ventas --help shows reemplazar + importar
  • sunat sire compras --help shows them too
  • All TUS protocol asserts unit-tested
  • LIMITATIONS.md updated (move from 🚧 to ⚠️ + new TUS notes section)
  • Verify with real RUC + small test ZIP post-merge (Hunter)

Adds the second half of SIRE: not just downloading SUNAT's proposal but
uploading your own .zip files to replace it or import extra comprobantes.

This was the biggest blocker called out in PR #4: SUNAT's manual notes
"deben ser desarrollados en JAVA" for these endpoints. That's an
implementation suggestion (only Java samples ship), NOT a protocol
requirement — TUS.IO is HTTP-based and language-agnostic. Verified by
spec review and 15 unit tests.

What's new:

1. TUS.IO 1.0.0 client (src/sunat-rest/tus.ts)
   - encodeMetadata(): pairs of "key base64Value" joined with "," per spec
   - tusCreate(): POST with Tus-Resumable + Upload-Length + Upload-Metadata,
     returns Location → uploadUrl (resolves relative against endpoint)
   - tusHead(): read current Upload-Offset (for resumption — interface
     ready, automatic retry-from-offset deferred)
   - tusPatch(): PATCH chunk with Upload-Offset + offset+octet-stream
   - tusUpload(): high-level create + chunked PATCH loop with onProgress
   - Default chunk size 8 MB; configurable per upload
   - Bearer token from oauth.ts

2. SIRE upload wrappers (src/sunat-rest/sire.ts)
   - sireUpload({kind, codLibro, perTributario, filename, data}, creds)
   - 5 upload kinds (codProceso from Anexo I):
     - reemplazoPropuesta (3)
     - importarPropuestaCp (1)
     - importarPreliminarCp (4)
     - ajustesPosteriores (6)
     - ajustesPosterioresAnteriores (7)
   - SUNAT-required metadata: filename, filetype, perTributario,
     codOrigenEnvio (=2), codProceso, codTipoCorrelativo (=01),
     nomArchivoImportacion, codLibro
   - Best-effort numTicket extraction from upload Location URL

3. Commands: sunat sire {ventas|compras} {reemplazar|importar}
   - reemplazar --periodo --file --yes [--wait] [--timeout] [--chunk-size]
   - importar --periodo --file --tipo X --yes [--wait]
     - --tipo: propuesta | preliminar | ajustes | ajustes-anteriores
   - File size validated client-side (0 < size <= 6GB per SUNAT limit 1346)
   - Progress bar to stderr (suppressed in --json mode)
   - Audit log entry per upload with bytesSent + numTicket

Tests: 238 pass / 2 skip / 0 fail in 2.8s (was 223)

15 new unit tests in tus.test.ts:
- encodeMetadata: single key, multiple pairs, UTF-8 multibyte (acentos),
  empty
- TUS_VERSION = 1.0.0
- tusCreate: headers (Tus-Resumable, Upload-Length, Upload-Metadata,
  Bearer), relative Location resolution, error on non-201, error on
  missing Location
- tusHead: reads Upload-Offset + Upload-Length, errors on missing
- tusPatch: PATCH method, Upload-Offset header, offset+octet-stream
  content-type, body length, error on non-204
- tusUpload: chunked end-to-end (20MB → 3 chunks), respects --chunk-size

LIMITATIONS.md updated:
- Move Reemplazar / Importar from 🚧 to ⚠️ (verified shape, untested live)
- New TUS.IO implementation notes section: spec version, chunk size,
  6GB limit, metadata encoding, codProceso table, resumability caveat,
  why we ignored "JAVA required" note
- Note ticket extraction is best-effort (SUNAT response shape varies)
@Railly Railly marked this pull request as ready for review April 29, 2026 05:48
@Railly Railly merged commit e938b9f into main Apr 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant