name: ingreso-documentos-effi description: Ingresa documentos a Effi (https://effi.com.co) a partir de un origen externo (PDF factura proveedor, pedido cliente, planilla ajuste, mensaje WhatsApp, dictado). Cubre remisiones de compra, cotizaciones de venta, remisiones/facturas de venta, notas crédito, ajustes y traslados de inventario. Usa cuando el usuario adjunta o describe cualquier documento o transacción comercial para registrar en Effi. Aplica el flujo de 10 pasos antecedentes → análisis → mapeo → identificar raro → decidir → JSON → dry-run → POST → verificación rigurosa de totales y detalles.
Ingreso de documentos a Effi
Workflow universal para registrar CUALQUIER transacción comercial en Effi a partir de un origen externo. Cada tipo de documento (compra, venta, ajuste, traslado) usa este mismo flujo, solo cambia la referencia técnica específica.
Saltarse el orden = errores costosos. El paso 1-2 (antecedentes) es el más importante y el que más se omite.
⚠️ Regla cero
NUNCA construir el JSON sin antes investigar cómo se han ingresado transacciones similares. Cada par (tercero × tipo de documento × item) tiene una "tradición" en OS (qué cods, cómo agrupar líneas, qué unidades, qué cuenta/caja). Esa tradición es la verdad — el documento externo es solo materia prima.
Mapa: tipo de documento → referencia técnica
Identificá primero qué tipo de transacción es. Después seguí el flujo de 10 pasos abajo usando el archivo de referencia correspondiente para los campos del endpoint:
| Origen / intención | Tipo Effi | Endpoint | Referencia técnica |
|---|---|---|---|
| Factura de proveedor (compra) | Remisión de compra | /app/remision_c/crear |
reference/remision_compra.md ✅ |
| Cotización a cliente | Cotización de venta | /app/cotizacion/crear |
reference/cotizacion_venta.md (crear al primer caso) |
| Entrega física a cliente | Remisión de venta | /app/remision/crear |
reference/remision_venta.md (crear al primer caso) |
| Documento fiscal a cliente | Factura de venta | /app/factura/crear |
reference/factura_venta.md (crear al primer caso) |
| Devolución cliente con factura | Nota crédito de venta | /app/nota_credito_v/crear |
reference/nota_credito_venta.md (crear al primer caso) |
| Mercancía entre bodegas | Traslado de inventario | /app/traslado_inventario/crear |
reference/traslado_inventario.md (crear al primer caso) |
| Sobrante/faltante físico | Ajuste de inventario | /app/ajuste_inventario/crear |
reference/ajuste_inventario.md (crear al primer caso) |
| Transformación interna | Orden de producción | /app/orden_produccion/crear |
usar skill produccion-recetas + effi-tecnico |
Para detalles transversales (autenticación, sesión, scraping, formato números/fechas, espionaje de endpoints) → usar skill effi-tecnico.
Workflow universal — checklist obligatorio
Copiá este checklist y marcá cada paso al completarlo:
Progreso ingreso:
- [ ] Paso 1: Antecedentes del TERCERO (NIT + variaciones nombre + ID Effi real)
- [ ] Paso 2: Antecedentes del TIPO DE DOCUMENTO con este tercero (últimas 3-5)
- [ ] Paso 3: Análisis de líneas del documento fuente
- [ ] Paso 4: Mapeo cods/conceptos externos → cods Effi
- [ ] Paso 5: Identificar lo RARO (4 sub-casos — ver §5)
- [ ] Paso 6: Decidir lo raro (descifrar con más data O preguntar a Santi)
- [ ] Paso 7: Construir JSON con campos del endpoint específico
- [ ] Paso 8: Dry-run + validación numérica INDEPENDIENTE de montos
- [ ] Paso 9: POST a Effi (interpretar errores e iterar)
- [ ] Paso 10: VERIFICACIÓN — TOTALES primero, DETALLES después
Paso 1 — Antecedentes del tercero
Buscar al tercero por TODAS las pistas:
SELECT _pk, nombre, numero_de_identificacion, forma_de_pago, fecha_ultima_compra
FROM zeffi_proveedores -- o zeffi_clientes
WHERE numero_de_identificacion LIKE '%<NIT_sin_guiones>%'
OR nombre LIKE '%<keyword1>%'
OR nombre LIKE '%<keyword2>%'
⚠️ El nombre en factura suele diferir del nombre Effi (ej: factura dice "UNION COMERCIAL ROPTIE S.A.", Effi tiene "UNICOR S.A.").
Obtener el ID Effi REAL (_pk local ≠ ID que el form espera):
import re
r = s.get('https://effi.com.co/app/<modulo>')
# Buscar documento histórico del tercero → su data-<tercero>
# Regex: capturar data-id y data-proveedor/cliente con cualquier comilla
patron = r'data-id=. (\\d+).\\S* data-(proveedor|cliente)=. (\\d+)'
# (en código real: data-id="(\d+)"...data-(proveedor|cliente)="(\d+)")
m = re.search(patron, r.text) # m.group(3) = ID Effi del tercero
Si el tercero no tiene NINGÚN documento histórico → opciones:
- Crear el primer documento manualmente desde la UI para que su data-* quede registrado
- O scrapear listado paginado de proveedores/clientes
Ver patrones específicos en reference/terceros_conocidos.md.
Paso 2 — Antecedentes del documento con este tercero
Antes de mapear códigos, revisar las 3-5 últimas transacciones del MISMO TIPO con el MISMO tercero:
-- Remisiones de compra (ejemplo):
SELECT id_remision, fecha_de_compra, remision_del_proveedor, total_bruto, total_neto, observacion
FROM zeffi_remisiones_compra_encabezados
WHERE proveedor LIKE '%<tercero>%'
ORDER BY CAST(id_remision AS UNSIGNED) DESC LIMIT 5;
-- Detalle de cada una:
SELECT cod_articulo, descripcion, cantidad, precio_bruto_unitario
FROM zeffi_remisiones_compra_detalle WHERE id_remision='<id>';
Identificar patrones:
- Cods recurrentes que ese tercero suele suministrar/comprar
- Cómo se agrupan líneas (¿envases con tapa juntos? ¿granel y empacado separados?)
- Rangos típicos de cantidad y precio
- Forma de pago habitual
- Bodega típica
- Observaciones recurrentes
Paso 3 — Análisis de líneas del documento fuente
Para cada línea del PDF/origen, extraer:
| Dato | Notas |
|---|---|
| Código interno del tercero | (no nos sirve directo, sirve para cross-reference) |
| Descripción exacta | clave para mapeo |
| UDM (unidad de medida) | PAQUETE, CAJA, KG, UND, LT |
| Cantidad en UDM | número |
| Precio por UDM | número |
| Valor total línea | para validar cálculo |
| % IVA o tasa aplicada | 19%, 5%, Exento, etc. |
| Lote/serie si aplica | número o vacío |
⚠️ UDM atípicas — conversión: si UDM dice PAQUETE X N UNIDADES, en Effi cantidad = cant_paq × N y precio = precio_paq / N.
Ejemplo: 3 PAQUETE × 24 unid × $20,640 = $61,920 → Effi: cantidad=72, precio=$860.
Paso 4 — Mapeo cods externos → cods Effi
Estrategia en orden de prioridad:
- Histórico con el MISMO tercero (del Paso 2) — si la descripción matchea, usar ese cod.
- Histórico con OTROS terceros (importante si el tercero actual no tiene ese item):
SELECT d.cod_articulo, d.descripcion, e.proveedor, COUNT(*) usos FROM zeffi_remisiones_compra_detalle d JOIN zeffi_remisiones_compra_encabezados e ON d.id_remision=e.id_remision WHERE d.descripcion LIKE '%<keyword>%' AND d.vigencia='Vigente' GROUP BY d.cod_articulo, d.descripcion, e.proveedor ORDER BY usos DESC; - Búsqueda directa en catálogo:
SELECT id, nombre, categoria, costo_manual, stock_total_empresa FROM zeffi_inventario WHERE vigencia='Vigente' AND nombre LIKE '%<keyword1>%' AND nombre LIKE '%<keyword2>%' - Glosario OS (SL/LT/SC, abreviaturas) → reference/glosario_origensilvestre.md
Validar cada mapeo: el cod elegido debería tener al menos 1-2 transacciones históricas (con el mismo o con otro tercero).
Paso 5 — Identificar lo RARO
Antes de construir el JSON, clasificar cada línea como estándar o rara. Si es rara, identificar a cuál de estos 4 sub-casos pertenece (cada uno tiene tratamiento distinto en el Paso 6):
Sub-caso A: Item habitual del tercero, agrupación atípica
El item es estándar de este tercero, pero la factura lo presenta agrupado distinto (ej: tapas que normalmente vienen incluidas en el envase, ahora separadas).
Pista: comparar con la línea equivalente en remisiones anteriores del mismo tercero. Si tradicionalmente venía agrupado → AGRUPAR igual en Effi (consolidar precio en una sola línea con el cod del item principal).
Caso conocido — envases UNICOR + sus tapas: tradicionalmente OS ingresa envase + tapa en UNA SOLA LÍNEA con el cod del envase y precio combinado, aunque la factura las traiga separadas.
Sub-caso B: Item NO habitual del tercero pero SÍ habitual de OTRO tercero
El tercero está bien identificado, pero este item normalmente viene de otro proveedor. Usualmente es compra de emergencia / sustituto temporal (el proveedor habitual no tenía disponibilidad).
Pista: el item probablemente sí está en el catálogo Effi con un cod existente (el cod del proveedor habitual). Buscar:
-- 1) ¿Existe en catálogo?
SELECT id, nombre, categoria FROM zeffi_inventario
WHERE vigencia='Vigente' AND nombre LIKE '%<keyword>%';
-- 2) ¿Quién lo suministra normalmente?
SELECT e.proveedor, COUNT(*) usos, MAX(e.fecha_de_compra) ultima
FROM zeffi_remisiones_compra_detalle d
JOIN zeffi_remisiones_compra_encabezados e ON d.id_remision=e.id_remision
WHERE d.cod_articulo='<cod>' GROUP BY e.proveedor ORDER BY usos DESC;
→ Usar el cod existente (no crear cod nuevo). Anotar en la observacion de la línea: "Compra a <tercero> por desabasto de <proveedor habitual>". Esto deja trazabilidad de la sustitución para el análisis de costos futuros.
Sub-caso C: Item genuinamente nuevo (nunca antes comprado/vendido a NADIE)
No existe en el catálogo Effi para ningún cod equivalente, o no hay histórico.
Acción: PARAR. Antes de cargar la transacción, crear el cod en Effi (/app/articulo/crear — ver skill effi-tecnico §3). Después de creado, usar el cod nuevo en la línea.
Sub-caso D: Cantidad o precio atípicos (>20% del histórico)
Item conocido con cod conocido, pero el valor está muy fuera del rango normal.
Pista: investigar antes de cargar — ¿descuento puntual? ¿error de UDM? ¿cambio de presentación? ¿oferta?
Acción: confirmar con Santi o con el proveedor antes de cargar (un precio 30% más alto cargado por error queda mal en costos por meses).
Paso 6 — Decidir lo raro
Para cada línea rara, según su sub-caso:
| Sub-caso | Acción |
|---|---|
| A (agrupación atípica) | Consolidar/separar según tradición histórica |
| B (sustituto temporal) | Usar cod existente + anotar sustitución en observación de línea |
| C (item nuevo) | Crear cod en Effi primero, luego usarlo |
| D (valor atípico) | Confirmar con Santi/proveedor antes de cargar |
Si la pista histórica es ambigua o conflictiva → PREGUNTAR a Santi, mostrándole:
- Las líneas raras tal como vienen en el documento
- Las líneas equivalentes en transacciones previas (para comparar)
- Tu propuesta de resolución
- La ambigüedad concreta
No improvisar nunca. Un supuesto silencioso genera un descuadre que se descubre meses después en un inventario, multiplicado por cada vez que aplicaste ese supuesto.
Paso 7 — Construir JSON
Estructura específica por tipo de documento en reference/<tipo>.md. Reglas universales:
- Fechas: formato según endpoint (OPs
DD/MM/YYYY, comprasYYYY-MM-DD). Vereffi-tecnico§5. - Números: punto decimal en JSON; Effi internamente usa coma.
t_egresoycod_articulomutuamente excluyentes (compras): si hay cod,"t_egreso": "default".descripcionobligatorio cuando haycod_articulo— usar nombre EXACTO dezeffi_inventario.nombre.impuestos: ["1"]= IVA 19% (ver tabla completa eneffi-tecnico§3).valoren formas_pago EXACTO consubtotal + impuestos − retencionescalculados. Effi rechaza diferencias de centavos.proveedor_id/cliente_id= ID Effi (Paso 1), NO_pklocal.
Paso 8 — Dry-run + validación montos
python3 scripts/import_<tipo>_post.py /tmp/<documento>.json --dry-run
Validación numérica INDEPENDIENTE (no del script):
import json
d = json.load(open('/tmp/<documento>.json'))
sub = sum(c['cantidad']*c['precio'] for c in d['conceptos'])
iva = sum(c['cantidad']*c['precio']*0.19 for c in d['conceptos'] if '1' in c['impuestos'])
total = sub + iva
print(f'SUBTOTAL = ${sub:,.2f} (documento: $X)')
print(f'IVA = ${iva:,.2f} (documento: $X)')
print(f'TOTAL = ${total:,.2f} (documento: $X)')
Aceptable: diferencias ≤ $1 por redondeo del proveedor. Diferencias mayores → revisar precios unitarios (suele faltar precisión decimal).
Paso 9 — POST a Effi e interpretar errores
python3 scripts/import_<tipo>_post.py /tmp/<documento>.json
Esperar <TIPO>_CREADA:<id>. Si HTTP 200 pero <p> en body, leer mensaje y corregir.
Errores genéricos:
| Mensaje | Causa probable | Fix |
|---|---|---|
"Ha ocurrido un error al procesar..." |
proveedor_id/cliente_id inválido |
Usar ID Effi (Paso 1), no _pk |
"La suma de... debe ser igual al total..." |
valor no cuadra con subtotal+IVA |
Recalcular y poner valor exacto |
"El campo X es obligatorio" |
Falta campo en concepto | Agregar (típicamente descripcion) |
"No puedes seleccionar Artículo y Tipo de egreso al mismo tiempo" |
t_egreso ≠ "default" cuando hay cod |
"t_egreso": "default" |
Errores específicos por tipo → en reference/<tipo>.md.
Paso 10 — VERIFICACIÓN rigurosa (obligatorio, sin excepciones)
Primero TOTALES, después DETALLES.
10a. Sync BD
cd scripts && node export_<tipo>.js && cd ..
node scripts/import_all.js | grep <tipo>
python3 scripts/sync_a_vps.py --tablas <tipo>_encabezados,<tipo>_detalle
10b. Verificación TOTALES (cabecera)
SELECT id_<tipo>, <campos relevantes>, total_bruto, impuestos, total_neto, estado
FROM zeffi_<tipo>_encabezados WHERE id_<tipo>='<id_creada>'
Cuadrar contra documento fuente:
total_bruto(sin IVA) ✓impuestos(IVA) ✓total_neto(con IVA) — aceptar diff ≤ $1 por redondeo del proveedor- Datos del tercero ✓
- Fechas ✓
- Estado correcto ✓
Si NO cuadra: NO declarar éxito. Investigar.
10c. Verificación DETALLES (líneas)
SELECT cod_articulo, descripcion, cantidad, precio_bruto_unitario, precio_neto_total
FROM zeffi_<tipo>_detalle WHERE id_<tipo>='<id_creada>' ORDER BY CAST(cod_articulo AS UNSIGNED)
Por cada línea:
cantidad × precio_bruto_unitario= subtotal línea ✓precio_neto_total= subtotal × (1 + tasa_iva) ✓cod_articuloydescripcioncorresponden al item ✓
Si NO cuadra: anular la transacción y reintentar con corrección.
Materiales de referencia
- reference/remision_compra.md — campos
form_CR(remisión de compra) reference/cotizacion_venta.md— campos cotización (pendiente, crear al primer caso)- reference/terceros_conocidos.md — mapeo NIT → ID Effi + patrones por tercero
- reference/glosario_origensilvestre.md — SL/LT/SC, presentaciones, abreviaturas internas
- reference/casos_resueltos.md — ejemplos completos (incluye UNICOR MED137765 y sustitutos)
Para flujo técnico Effi (sesión, scraping, espionaje de endpoints, formato números/fechas) → skill effi-tecnico.
Para producción → skills produccion-recetas + effi-tecnico.
Anti-patrones
❌ Construir JSON sin revisar histórico del tercero (Pasos 1-2)
❌ Asumir que items "separados en documento" van separados en Effi (ej: tapas UNICOR)
❌ Crear cod nuevo cuando ya existe uno equivalente (ej: comprarle envase 750 a sustituto y crear cod paralelo)
❌ Usar _pk local de BD como ID del tercero (Effi usa otro ID)
❌ Poner t_egreso: "1" cuando hay cod_articulo
❌ Declarar éxito sin verificar totales+detalles en BD post-creación
❌ Saltarse "lo raro" en lugar de identificar a cuál sub-caso (A/B/C/D) pertenece
❌ Aceptar diferencias >$1 entre documento y BD sin investigar
❌ Crear el documento múltiples veces "porque no cargó" sin verificar primero