chore: initial commit del deploy de utilerias-recon-2

Incluye:
- app.ipynb con las 10 pestanas (Conexion, Descargas, Analisis, NLP, KGS,
  CTM, Saldos Vencidos, DataStage, Estructuras SCAII, Valores).
- launcher.py + run_dev.bat para lanzar Voila localmente.
- docker-compose.yml para levantar Postgres 16 con healthcheck.
- schema_registro.sql con las 27 tablas (Registro501..Registro701,
  RegistroInci/Resumen/Sel, base_numpartes).
- requirements.txt con pandas, pyodbc, psycopg2-binary, sqlalchemy,
  scikit-learn, openpyxl, voila, ipywidgets, python-docx.
- 6 manuales de usuario (CTM, SaldosVencidos, DataStage, EstructurasSCAII,
  Valores, GENPACT_V2 general) en .docx.
- 6 generadores de manuales para regenerar los .docx tras editar.
- .gitignore que excluye .env, .venv, outputs generados y caches.
- README.md con instrucciones de setup en server.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ernesto Herrera
2026-05-21 08:18:13 -06:00
commit a0081f54d2
21 changed files with 8509 additions and 0 deletions

View File

@@ -0,0 +1,503 @@
"""
Genera el manual del módulo Saldos Vencidos (Utilería Forma 5).
Cubre los dos modos de uso: por Excel y automático por rango de fechas.
"""
from docx import Document
from docx.shared import Pt, RGBColor, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
import datetime
doc = Document()
for sec in doc.sections:
sec.top_margin = Cm(2.5)
sec.bottom_margin = Cm(2.5)
sec.left_margin = Cm(2.2)
sec.right_margin = Cm(2.2)
style = doc.styles['Normal']
style.font.name = 'Calibri'
style.font.size = Pt(11)
def h1(text):
p = doc.add_heading(text, level=1)
for r in p.runs: r.font.color.rgb = RGBColor(0x15, 0x65, 0xC0)
def h2(text):
p = doc.add_heading(text, level=2)
for r in p.runs: r.font.color.rgb = RGBColor(0x1E, 0x88, 0xE5)
def h3(text):
p = doc.add_heading(text, level=3)
for r in p.runs: r.font.color.rgb = RGBColor(0x42, 0xA5, 0xF5)
def parr(text, bold=False):
p = doc.add_paragraph()
r = p.add_run(text); r.bold = bold
return p
def code_block(text):
p = doc.add_paragraph()
p.paragraph_format.left_indent = Cm(0.4)
r = p.add_run(text); r.font.name = 'Consolas'; r.font.size = Pt(9.5)
pPr = p._p.get_or_add_pPr()
shd = OxmlElement('w:shd'); shd.set(qn('w:val'),'clear'); shd.set(qn('w:color'),'auto'); shd.set(qn('w:fill'),'F0F0F0')
pPr.append(shd)
return p
def bullet(text):
p = doc.add_paragraph(text, style='List Bullet')
p.paragraph_format.left_indent = Cm(0.6)
return p
def numbered(text):
p = doc.add_paragraph(text, style='List Number')
p.paragraph_format.left_indent = Cm(0.6)
return p
def caja(titulo, text, color_fill='FFF8E1', color_text=RGBColor(0xE6, 0x5C, 0x00)):
p = doc.add_paragraph()
p.paragraph_format.left_indent = Cm(0.4)
pPr = p._p.get_or_add_pPr()
shd = OxmlElement('w:shd'); shd.set(qn('w:val'),'clear'); shd.set(qn('w:color'),'auto'); shd.set(qn('w:fill'), color_fill)
pPr.append(shd)
r = p.add_run(titulo + ': '); r.bold = True; r.font.color.rgb = color_text
p.add_run(text)
def nota(titulo, text): caja(titulo, text, 'FFF8E1', RGBColor(0xE6, 0x5C, 0x00))
def tip(titulo, text): caja(titulo, text, 'E8F5E9', RGBColor(0x2E, 0x7D, 0x32))
def alerta(titulo, text): caja(titulo, text, 'FFEBEE', RGBColor(0xC6, 0x28, 0x28))
def info(titulo, text): caja(titulo, text, 'E3F2FD', RGBColor(0x0D, 0x47, 0xA1))
def table_simple(headers, rows, col_widths_cm=None):
t = doc.add_table(rows=1+len(rows), cols=len(headers))
t.style = 'Light Grid Accent 1'
hdr = t.rows[0].cells
for i, h in enumerate(headers):
hdr[i].text = h
for p in hdr[i].paragraphs:
for r in p.runs:
r.bold = True; r.font.color.rgb = RGBColor(0xFF,0xFF,0xFF)
tcPr = hdr[i]._tc.get_or_add_tcPr()
shd = OxmlElement('w:shd'); shd.set(qn('w:val'),'clear'); shd.set(qn('w:fill'),'1565C0')
tcPr.append(shd)
for r_idx, row in enumerate(rows, start=1):
cells = t.rows[r_idx].cells
for c_idx, val in enumerate(row):
cells[c_idx].text = str(val)
if col_widths_cm:
for i, w in enumerate(col_widths_cm):
for row in t.rows:
row.cells[i].width = Cm(w)
return t
# ====== PORTADA ======
title = doc.add_paragraph(); title.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = title.add_run('\n\n\nMódulo Saldos Vencidos')
r.bold = True; r.font.size = Pt(36); r.font.color.rgb = RGBColor(0x15, 0x65, 0xC0)
sub = doc.add_paragraph(); sub.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = sub.add_run('Utilería Forma 5\nProrrateo masivo de saldos IMPO vencidos')
r.font.size = Pt(18); r.font.color.rgb = RGBColor(0x42, 0xA5, 0xF5)
p = doc.add_paragraph(); p.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = p.add_run('\n\nSistema de Utilerías 2.0 SCAII\nManual de Uso')
r.font.size = Pt(14)
r = p.add_run(f'\n\nVersión {datetime.date.today().isoformat()}')
r.font.size = Pt(11); r.italic = True
doc.add_page_break()
# ====== CONTENIDO ======
h1('Contenido')
for it in [
'1. ¿Qué es el módulo Saldos Vencidos?',
'2. ¿Por qué se necesita esta herramienta?',
'3. Antes de empezar',
'4. Abrir la pestaña Saldos Vencidos',
'5. Los dos modos de uso',
'6. Modo Excel — paso a paso',
' • Descargar la plantilla',
' • Subir el Excel y definir fechas',
' • Analizar (Paso A)',
' • Exportar el plan a Excel',
' • Ejecutar el prorrateo (Paso B)',
'7. Modo Automático — paso a paso',
' • Rango de fechas',
' • Buscar saldos vencidos',
' • Exportar y ejecutar',
'8. Cómo funciona el prorrateo',
'9. Validación contra el saldo real',
'10. Estatus de cada saldo en el resumen',
'11. Diferencias entre los dos modos',
'12. Casos típicos y preguntas frecuentes',
'13. Apéndice técnico',
'14. Glosario',
]:
parr(it).paragraph_format.left_indent = Cm(0.4)
doc.add_page_break()
# ====== 1 ======
h1('1. ¿Qué es el módulo Saldos Vencidos?')
parr('Saldos Vencidos es una pestaña dentro del Sistema de Utilerías 2.0 que automatiza '
'la Utilería Forma 5: tomar los saldos pendientes de una factura de importación '
'que ya vencieron y repartirlos (prorratearlos) entre las descargas de exportación '
'donde esa misma mercancía se haya consumido.')
parr('En vez de hacer el cálculo línea por línea desde la utilería tradicional —que en bases '
'grandes puede tardar muchísimo— esta pestaña carga, calcula y aplica los movimientos '
'masivamente con un proceso mucho más rápido.')
nota('En palabras simples',
'Tienes saldos de importación que ya se vencieron y necesitas "amortizarlos" en las '
'descargas de exportación donde el material se fue. Esta herramienta los encuentra, '
'los distribuye proporcionalmente y aplica los UPDATEs en SDescargaT y SSaldoTem.')
# ====== 2 ======
h1('2. ¿Por qué se necesita esta herramienta?')
parr('La utilería original recorre todas las facturas de exportación que comparten fracción '
'arancelaria con cada saldo y reparte el saldo entre las facturas encontradas. En una base '
'pesada (millones de filas) este proceso puede llevar horas o quedarse colgado.')
parr('Esta herramienta hace lo mismo pero:')
bullet('Solo carga las filas que realmente necesita (filtros por rango de fechas).')
bullet('Agrupa las descargas en lotes para reducir consultas.')
bullet('Hace todos los UPDATEs por transacción atómica por saldo (rollback automático ante error).')
bullet('Permite ver el plan antes de aplicar (DRY_RUN).')
# ====== 3 ======
h1('3. Antes de empezar')
bullet('Necesitas estar conectado a la base SCAII (revísalo en la pestaña Conexión).')
bullet('Define con claridad el rango de fechas que quieres procesar. El rango filtra '
'la columna FECHAFACTURA_ISO de la tabla SSaldoTem (la fecha de la factura IMPO).')
bullet('Si vas a usar el modo Excel: prepara el archivo con las 3 columnas obligatorias '
'(o descarga la plantilla desde la misma pestaña).')
alerta('Recomendación',
'La primera vez SIEMPRE corre el Paso B con DRY_RUN activado. Te muestra qué se '
'iba a actualizar sin tocar la base. Cuando confirmes los números, lo desactivas y '
'aplicas en firme.')
# ====== 4 ======
h1('4. Abrir la pestaña Saldos Vencidos')
parr('En la barra superior de la app verás varias pestañas (Conexión, Descargas, Análisis '
'Saldos, Sustitutos NLP, Descarga % KGS, CTM, Saldos Vencidos). Da clic en '
'"Saldos Vencidos".')
# ====== 5 ======
h1('5. Los dos modos de uso')
parr('La pestaña tiene dos secciones, una arriba de la otra. Ambas hacen exactamente lo '
'mismo internamente; lo único que cambia es de dónde sacan la lista de saldos a procesar:')
table_simple(
['Modo', '¿De dónde toma los saldos?', '¿Cuándo usarlo?'],
[
['Excel (sección superior)',
'De un archivo .xlsx que tú subes con las facturas IMPO específicas que quieres prorratear.',
'Cuando ya tienes una lista filtrada del cliente o del área legal con los saldos exactos a tocar.'],
['Automático (sección inferior)',
'El sistema busca solo en SSaldoTem todos los saldos con FECHAVENC_ISO menor a hoy y SALDO_DISPONIBLE > 0 dentro del rango que indiques.',
'Cuando quieres "barrer" todo lo vencido de un periodo sin armar listas previas.'],
],
col_widths_cm=[3.5, 6.5, 6])
# ====== 6 ======
h1('6. Modo Excel — paso a paso')
h2('6.1 Descargar la plantilla')
parr('Si es la primera vez, da clic en el botón "Descargar plantilla Excel". Se generará '
'un archivo con 3 columnas de ejemplo. Ábrelo, borra las filas de ejemplo y pega tus datos.')
h2('6.2 Estructura del Excel')
table_simple(
['Columna', 'Significado', 'Ejemplo'],
[
['FACTURAIMPO', 'Número de la factura de importación cuyo saldo está vencido.', 'F1234567'],
['CANTIDAD_SALDO', 'Cantidad pendiente a prorratear (en la UM del saldo).', '100.0'],
['FRACCION_IMPO', 'Fracción arancelaria de importación.', '85044010'],
],
col_widths_cm=[3.5, 8, 4])
nota('Sobre los nombres de columna',
'La herramienta acepta variaciones (FACTURAIMPO / FACTURA_IMPO / FACTURA, '
'FRACCION_IMPO / FRACCIONIMPO / FRACCION, CANTIDAD_SALDO / CANT_SALDO / CANTIDAD / SALDO). '
'No hace falta que sea exactamente el nombre, pero sí debe coincidir con alguno de los aceptados.')
h2('6.3 Subir el Excel y definir fechas')
numbered('Da clic en "Subir Excel" y selecciona el archivo.')
numbered('Pon una fecha de inicio y una fecha de fin. Estas fechas filtran SSaldoTem por la '
'columna FECHAFACTURA_ISO (la fecha de la factura IMPO).')
tip('Para qué sirve el rango',
'Aunque el Excel ya trae las facturas IMPO específicas, el rango limita la consulta a '
'SSaldoTem para que sea más rápida. Si no estás seguro, pon un rango amplio que cubra '
'todas las fechas de tus facturas.')
h2('6.4 Analizar (Paso A — solo lectura)')
parr('Da clic en "Analizar (sin escribir)". El sistema:')
bullet('Consulta SSaldoTem dentro del rango definido.')
bullet('Cruza con tu Excel por (FACTURAIMPO, FRACCIONIMPO).')
bullet('Para cada saldo, busca en SDescargaT las descargas que comparten (FACTIMPO, NUMPARTE, UNIMED).')
bullet('Calcula la porción que le toca a cada descarga (proporcional a su CANTDESC).')
bullet('Muestra el resumen y las primeras 50 filas del plan.')
alerta('Paso A es 100% lectura',
'No escribe nada en la base. Sirve para verificar números antes de aplicar.')
h2('6.5 Exportar el plan a Excel')
parr('Si quieres ver TODAS las filas del plan (no solo las 50 que se muestran en pantalla), '
'da clic en "Exportar Excel completo". El archivo tiene dos hojas:')
bullet('Plan_Detalle: una fila por cada descarga que recibe prorrateo (con la porción exacta de cantidad, valores y pesos).')
bullet('Resumen: una fila por saldo con su estatus (PRORRATEADO, SIN_DESCARGAS, CANTDESC_CERO).')
h2('6.6 Ejecutar el prorrateo (Paso B)')
parr('Cuando el análisis se ve bien:')
numbered('Mantén DRY_RUN activado y da clic en "Ejecutar prorrateo (Paso B)". La herramienta '
'simula los UPDATEs y te muestra los conteos exactos.')
numbered('Si los números cuadran, desactiva DRY_RUN y vuelve a dar clic.')
numbered('La herramienta corre las transacciones; al terminar verás un resumen con cuántas '
'descargas y cuántos saldos se actualizaron, y si hubo capeos (saldos donde el prorrateo '
'habría excedido el disponible real).')
alerta('Importante sobre el capeo',
'Si entre el momento en que corres el Paso A y el Paso B alguien más consume saldo '
'desde otra utilería, la herramienta vuelve a consultar el SALDO_DISPONIBLE real al '
'momento de ejecutar y aplica un factor de escala para que NUNCA se rebase el 100%. '
'Esto se registra como "CAPEO" en el log.')
# ====== 7 ======
h1('7. Modo Automático — paso a paso')
parr('La sección inferior de la pestaña hace lo mismo, pero sin Excel: el sistema busca él '
'mismo qué saldos están vencidos y los procesa.')
h2('7.1 Define el rango de fechas')
bullet('Fecha inicio y fecha fin filtran SSaldoTem por FECHAFACTURA_ISO (la fecha de la factura IMPO).')
bullet('Adicionalmente la herramienta filtra por FECHAVENC_ISO < hoy. Es decir, solo trae '
'saldos que YA están vencidos al día en que corres el análisis.')
info('Criterio exacto',
'Saldo vencido = FECHAFACTURA_ISO dentro del rango + FECHAVENC_ISO menor a la fecha de '
'hoy + (CANTEXITENCIA CANTUSADA CANTUSADADESP) mayor a cero.')
h2('7.2 Buscar saldos vencidos')
parr('Da clic en "Buscar saldos vencidos". El sistema muestra cuántos saldos encontró y '
'genera el plan de prorrateo (igual que el modo Excel, pero sin cruzar con un archivo).')
h2('7.3 Exportar y ejecutar')
parr('Los botones "Exportar Excel completo" y "Ejecutar prorrateo (Paso B)" funcionan '
'exactamente igual que en el modo Excel. El comportamiento de DRY_RUN y el capeo es idéntico.')
nota('Estado independiente',
'El plan del modo Automático se guarda por separado del plan del modo Excel. Puedes '
'usar los dos modos en la misma sesión sin que uno pise al otro.')
# ====== 8 ======
h1('8. Cómo funciona el prorrateo')
parr('Para cada saldo vencido la herramienta hace lo siguiente:')
numbered('Identifica el saldo en SSaldoTem por (FACTURAIMPO, NUMPARTE, UMEXITENCIA). '
'Lee CANTEXITENCIA, CANTUSADA, CANTUSADADESP, VALORIMPOMN/ME, PESONETO/BRUTO.')
numbered('Calcula SALDO_DISPONIBLE = CANTEXITENCIA CANTUSADA CANTUSADADESP. '
'Si está en cero, no hace nada.')
numbered('Calcula SALDO_APLICABLE = mínimo entre lo que viene en el Excel (modo Excel) '
'y el SALDO_DISPONIBLE real. En modo Automático, SALDO_APLICABLE = SALDO_DISPONIBLE.')
numbered('Busca todas las descargas en SDescargaT que cumplan: '
'FACTIMPO = FACTURAIMPO, NUMPARTE = NUMPARTE del saldo, UNIMED = UMEXITENCIA del saldo.')
numbered('Suma todos los CANTDESC de esas descargas. Cada descarga recibe una porción '
'proporcional a su tamaño: PORCION = SALDO_APLICABLE × (CANTDESC_descarga / SUMA_CANTDESC).')
numbered('Para los valores y pesos: la herramienta usa las tasas del saldo IMPO original '
'(VALORIMPOMN / CANTEXITENCIA, etc.) en lugar de las de cada descarga. Esto mantiene '
'consistencia con el costo real del lote vencido.')
h2('Ejemplo numérico')
parr('Imagina un saldo vencido de FACTURAIMPO=F1001, NUMPARTE=ABC123, UM=PZ con:')
code_block('CANTEXITENCIA = 1000\n'
'CANTUSADA + CANTUSADADESP = 700\n'
'SALDO_DISPONIBLE = 300\n'
'VALORIMPOMN = 5000 → tasa_vmn = 5000 / 1000 = 5.0 por pieza')
parr('Y tres descargas EXPO que comparten la llave:')
table_simple(
['CONSECUTIVO', 'FACTEXPO', 'CANTDESC actual'],
[
['101', 'EXP001', '60'],
['102', 'EXP002', '40'],
['103', 'EXP003', '100'],
],
col_widths_cm=[3, 4, 4])
parr('Suma de CANTDESC = 200. Cada descarga recibe (suponiendo Excel con CANTIDAD_SALDO=300):')
table_simple(
['Descarga', 'Proporción', 'PORCION_CANT', 'PORCION_VMN'],
[
['101', '60/200 = 30%', '300 × 30% = 90', '90 × 5.0 = 450'],
['102', '40/200 = 20%', '300 × 20% = 60', '60 × 5.0 = 300'],
['103', '100/200 = 50%', '300 × 50% = 150', '150 × 5.0 = 750'],
],
col_widths_cm=[3, 4, 4, 4])
# ====== 9 ======
h1('9. Validación contra el saldo real (capeo automático)')
parr('Antes de aplicar cualquier UPDATE en el Paso B, la herramienta vuelve a consultar el '
'SALDO_DISPONIBLE real en SSaldoTem. Si la suma de porciones (calculadas en el Paso A) '
'es mayor que ese disponible —porque alguien más consumió saldo entre el análisis y la '
'ejecución— la herramienta:')
bullet('Calcula un factor = SALDO_DISPONIBLE_actual / suma_de_porciones.')
bullet('Multiplica todas las porciones de ese saldo por el factor.')
bullet('Registra en el log: "CAPEO {factura}/{parte}/{um}: sum=X > disp=Y (factor=Z)".')
alerta('Por qué importa',
'Esto garantiza que NUNCA se rebase el 100% del saldo en SSaldoTem. La utilería '
'tradicional no hace esta verificación, por eso a veces dejaba CANTUSADA mayor a '
'CANTEXITENCIA. Aquí se previene en automático.')
# ====== 10 ======
h1('10. Estatus de cada saldo en el resumen')
table_simple(
['Estatus', '¿Qué significa?'],
[
['PRORRATEADO', 'El saldo encontró descargas EXPO con la misma llave y se prorrateó completamente.'],
['SIN_DESCARGAS', 'El saldo existe en SSaldoTem pero no hay ninguna fila en SDescargaT con esa combinación de FACTIMPO + NUMPARTE + UNIMED. No se puede repartir.'],
['CANTDESC_CERO', 'Hay descargas con esa llave pero todas tienen CANTDESC = 0. No hay base para prorratear.'],
],
col_widths_cm=[3.5, 12])
tip('Qué hacer con SIN_DESCARGAS',
'Revísalos manualmente: pueden ser saldos que el cliente quiere amortizar con descargas '
'que aún no se cargaron, o casos donde la UM en el saldo no coincide con la UM en la '
'descarga (típico cuando hay sustitutos con UNIDADMEDIDA1/UNIDADMEDIDA2).')
# ====== 11 ======
h1('11. Diferencias entre los dos modos')
table_simple(
['Aspecto', 'Modo Excel', 'Modo Automático'],
[
['Origen de los saldos', 'Tu archivo .xlsx', 'Búsqueda directa en SSaldoTem'],
['Filtro de vencimiento', 'Tú decides qué incluir', 'FECHAVENC_ISO < hoy'],
['Filtro de rango', 'FECHAFACTURA_ISO BETWEEN inicio Y fin', 'Igual'],
['SALDO_APLICABLE', 'min(CANTIDAD_SALDO Excel, SALDO_DISPONIBLE)', 'SALDO_DISPONIBLE completo'],
['Prorrateo', 'Igual', 'Igual'],
['Validación 100%', 'Igual', 'Igual'],
['UPDATEs aplicados', 'SDescargaT + SSaldoTem', 'Igual'],
['Tablas no tocadas', 'SFacExp', 'Igual'],
],
col_widths_cm=[3.5, 6, 6])
# ====== 12 ======
h1('12. Casos típicos y preguntas frecuentes')
h3('"Corrí el Paso B con DRY_RUN apagado por error pero todo está bien, ¿pasa algo?"')
parr('No, mientras los números del log se vean correctos. Si DRY_RUN está apagado y la '
'herramienta dice "X SDescargaT actualizadas, Y SSaldoTem actualizadas, 0 errores", '
'la información se aplicó como esperabas.')
h3('"Quiero deshacer un Paso B"')
parr('La herramienta no tiene botón de "deshacer". El procedimiento correcto es:')
bullet('Antes de ejecutar, exporta el plan a Excel y guárdalo como respaldo.')
bullet('Si necesitas revertir, usa el Excel del plan para construir los UPDATEs inversos '
'(restar las porciones aplicadas en SDescargaT y SSaldoTem).')
bullet('Considera respaldar la BD antes de Paso B si los volúmenes son grandes.')
h3('"En el modo Automático no aparecen saldos que yo sé que están vencidos"')
parr('Revisa estos tres puntos en SSaldoTem:')
bullet('FECHAFACTURA_ISO está dentro del rango que pusiste.')
bullet('FECHAVENC_ISO existe (NOT NULL) y es menor a hoy.')
bullet('SALDO_DISPONIBLE = CANTEXITENCIA CANTUSADA CANTUSADADESP es mayor a cero.')
parr('Si alguno de estos tres no se cumple, el saldo no aparece.')
h3('"Las cantidades del plan no me cuadran con lo que esperaba"')
parr('Verifica que las descargas en SDescargaT realmente tengan la misma UNIMED que el '
'UMEXITENCIA del saldo. Si hay descargas con UM distinta (típico con sustitutos), no '
'matchean y el saldo aparece como SIN_DESCARGAS aunque "haya material".')
# ====== 13 ======
h1('13. Apéndice técnico')
h2('Columnas leídas de SSaldoTem')
code_block('FACTURAIMPO, FRACCIONIMPO, NUMPARTE, UMEXITENCIA, FECHAFACTURA_ISO, FECHAVENC_ISO,\n'
'CANTEXITENCIA, CANTUSADA, CANTUSADADESP,\n'
'VALORIMPOMN, VALORIMPOME, PESONETO, PESOBRUTO')
h2('Columnas leídas de SDescargaT')
code_block('CONSECUTIVO, FACTIMPO, NUMPARTE, UNIMED, FACTEXPO, CANTDESC')
h2('UPDATE aplicado a cada descarga prorrateada')
code_block('UPDATE SDescargaT\n'
' SET CANTDESC = ISNULL(CANTDESC,0) + ?,\n'
' VALORMN = ISNULL(VALORMN,0) + ?,\n'
' VALORME = ISNULL(VALORME,0) + ?,\n'
' PESONETO = ISNULL(PESONETO,0) + ?,\n'
' PESOBRUTO= ISNULL(PESOBRUTO,0)+ ?\n'
' WHERE CONSECUTIVO = ?')
h2('UPDATE aplicado al saldo en SSaldoTem')
code_block('UPDATE SSaldoTem\n'
' SET CANTUSADA = ISNULL(CANTUSADA,0) + ?,\n'
' VALORUSADOMN = ISNULL(VALORUSADOMN,0) + ?,\n'
' VALORUSADOME = ISNULL(VALORUSADOME,0) + ?,\n'
' PESOUSADO = ISNULL(PESOUSADO,0) + ?,\n'
' PESOBRUTOUSADO = ISNULL(PESOBRUTOUSADO,0) + ?\n'
' WHERE FACTURAIMPO=? AND NUMPARTE=? AND UMEXITENCIA=?')
h2('Transacciones')
bullet('Una transacción por saldo (combinación FACTURAIMPO + NUMPARTE + UMEXITENCIA).')
bullet('Commit automático al terminar todas las descargas + el UPDATE de SSaldoTem.')
bullet('Rollback automático si cualquier UPDATE de la transacción falla.')
h2('Tablas que NO se tocan')
bullet('SFacExp: no cambia ESTATUS, APLICADESCMANUAL ni CANT_PARTIDAS.')
bullet('SDescargaM: este módulo no genera filas espejo (a diferencia de CTM).')
# ====== 14 ======
h1('14. Glosario')
table_simple(
['Término', 'Significado'],
[
['SSaldoTem', 'Tabla maestra de saldos de importación temporales en SCAII.'],
['SDescargaT', 'Tabla de descargas de las facturas de exportación contra saldos IMPO.'],
['CANTEXITENCIA', 'Cantidad original que entró al lote IMPO (sic, así está escrito en SCAII).'],
['CANTUSADA', 'Cantidad ya consumida del lote por descargas normales.'],
['CANTUSADADESP', 'Cantidad usada por desperdicios.'],
['SALDO_DISPONIBLE', 'CANTEXITENCIA CANTUSADA CANTUSADADESP.'],
['CANTDESC', 'Cantidad descargada en una fila específica de SDescargaT.'],
['UMEXITENCIA', 'Unidad de medida del saldo en SSaldoTem (sic).'],
['UNIMED', 'Unidad de medida de la descarga en SDescargaT.'],
['FECHAFACTURA_ISO', 'Fecha de la factura IMPO en formato ISO.'],
['FECHAVENC_ISO', 'Fecha de vencimiento del saldo IMPO en formato ISO.'],
['Prorrateo', 'Repartir un total entre varios destinos proporcionalmente a un peso.'],
['DRY_RUN', 'Modo de simulación: la herramienta calcula y muestra los UPDATEs sin ejecutarlos.'],
['Capeo', 'Reducir proporcionalmente las porciones para no rebasar el SALDO_DISPONIBLE real.'],
],
col_widths_cm=[4, 12])
# ====== Guardar ======
import os
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Manual_SaldosVencidos.docx')
doc.save(out)
print(f'Manual generado: {out}')