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:
503
app/generar_manual_saldos_vencidos.py
Normal file
503
app/generar_manual_saldos_vencidos.py
Normal 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}')
|
||||
Reference in New Issue
Block a user