- sapb1_manager.buscar_factura_existente busca la factura (proveedor, tipo, serie, número) en facturas de compra no anuladas y en drafts; si existe no se crea otro draft y se responde "ya está registrada" sin pisar el registro de una aceptación anterior. - SapNoDisponible (subclase de ConnectionError) distingue una caída (timeout, sin conexión, HTTP 5xx) de un rechazo normal de SAP (HTTP 4xx). Una caída de Service Layer con HANA sin resultados ya no se lee como "guía no existe". - Con SAP o HANA caídos se avisa una sola vez al proveedor con la plantilla "En cola" y el correo se reintenta en los ciclos siguientes (hasta 24), sin repetir avisos; al agotarse se avisa al equipo. - La lógica por factura pasa a _procesar_factura; lo ya resuelto dentro de un correo no se repite al reintentarlo. - seguimiento: columna intentos (con migración), registrar_intento y el estado de notificación en_espera. - database_utilities.DB_NAME pasa a ser absoluto (junto al código). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8.5 KiB
respuesta_proveedor.py — Respuesta al proveedor
Cuando alguien envía un correo con el XML y el PDF de una factura, el sistema valida y responde a esa misma dirección con la plantilla de aceptado o de rechazado (plantilla/plantilla-aceptado.html y plantilla-rechazado.html). Aparte, el equipo recibe un correo técnico (tablas de SAP, JSON adjuntos).
Piezas: respuesta_proveedor.py (contexto, protecciones, envío), observaciones.py (motivos de rechazo y sus textos), plantillas.py (renderizador) y seguimiento.py (código de seguimiento y registro).
Qué se responde
| Situación | Respuesta al remitente |
|---|---|
| Draft creado en SAP | Aceptado |
| Precios que no coinciden, guía inexistente, línea sin guía, cantidad cero | Rechazado con una observación por motivo |
| La factura ya existe en SAP (factura de compra no anulada o draft) | Rechazado: "Esta factura ya está registrada". No se crea otro draft |
| Correo con adjuntos pero sin PDF ni XML válido, o con PDF y sin XML, o sin ninguna factura (tipo 01) | Rechazado sin comprobante: no muestra proveedor, número ni tabla de guías |
| SAP o HANA no responden | Aviso "En cola" (una sola vez): se reintentará solo, no hace falta reenviar. Ver más abajo |
| Fallo inesperado, o un rechazo normal de SAP al crear el draft (HTTP 4xx) | Nada: es una falla nuestra. Solo se avisa al equipo |
| Correo sin adjuntos | Nada |
Una respuesta por comprobante: un correo con 3 facturas genera 3 respuestas. La aceptación significa "draft creado en SAP". La diferencia de valor referencial XML vs SAP es solo informativa y no rechaza.
Guía "no encontrada" y caídas.
get_delivery_notesya no confunde una caída con una guía inexistente: si Service Layer no responde y HANA no aporta nada, lanzaSapNoDisponible. Además, antes de rechazar por guía no encontrada se comprueba que HANA responde; si no, es "servicio fuera de línea" y no un rechazo.
¿La factura ya existe en SAP?
Antes de validar precios, sapb1_manager.buscar_factura_existente busca el comprobante por proveedor (PL + RUC), tipo, serie y número (U_BPP_MDTD/U_BPP_MDSD/U_BPP_MDCD) en las facturas de compra no anuladas y en los drafts de factura de compra. Si existe, no se crea otro draft y se responde con la observación factura_ya_registrada. Si el comprobante ya estaba aceptado en comprobantes, ese registro (estado y doc_entry) no se sobrescribe.
La plantilla de rechazo dice "No pudimos registrar su factura… Corríjalas y envíenos el comprobante nuevamente", que no encaja del todo con un duplicado: la observación aclara que no hace falta reenviarla. Si prefiere una plantilla propia para este caso, es un cambio pequeño.
Servicio fuera de línea y reintentos
SapNoDisponible (subclase de ConnectionError) se lanza solo ante una caída: timeout, sin conexión, HTTP 5xx o login imposible por esos motivos. Un HTTP 4xx (rechazo de SAP) o credenciales inválidas siguen siendo un ConnectionError normal y no se tratan como caída.
Cuando una factura se encuentra con una caída:
- Se suma un intento en
comprobantes.intentosy el resultado quedaerrorcon la observaciónservicio_no_disponible. - Solo en el primer intento se envía al proveedor la plantilla
plantilla-fuera-de-linea.html("En cola, la procesaremos automáticamente, no necesita reenviarla") y se avisa al equipo. La notificación quedaen_espera. Los reintentos siguientes no repiten ningún aviso. - El correo se elimina de
emails, de modo que el siguiente ciclo del demonio lo vuelve a tomar. - Cuando SAP vuelve, se procesa con normalidad y el proveedor recibe el aceptado o rechazado.
- Tras
MAX_REINTENTOS(24) intentos se deja de reintentar, se avisa al equipo ("Reintentos agotados") y el proveedor no recibe nada más.
Al reintentar un correo, lo que ya se resolvió dentro de él (otra factura del mismo correo) no se repite ni se responde dos veces. Sin seguimiento (si falla la base de datos) no se puede contar intentos, así que una caída se trata como error interno y no se reintenta.
Modo prueba y modo real
NOTIFICACION_MODO en el .env:
| Modo | A quién va la respuesta | Asunto |
|---|---|---|
prueba (por defecto) |
Al equipo (NOTIFICACION_EQUIPO), nunca al remitente |
[PRUEBA → remitente] Aceptado · Factura F002-486 · ABR-… |
real |
Al remitente, con copia oculta al equipo | Aceptado · Factura F002-486 · ABR-… |
Cualquier otro valor se trata como prueba. Al arrancar, config_validator deja en el log una advertencia con el modo activo. Pasar a real es una decisión explícita: no ocurre solo.
Protecciones: cuándo NO se responde
El sistema contesta solo y a terceros, así que no responde a:
| Caso | Motivo registrado |
|---|---|
| Remitente vacío o desconocido | remitente desconocido |
Dirección sin dominio válido (por ejemplo test@local) |
dirección no válida |
| El propio buzón del sistema | el remitente es el propio buzón |
no-reply, noreply, do-not-reply, no-responder, mailer-daemon, postmaster, bounce |
dirección automática |
| Mensajes automáticos (respuestas automáticas, rebotes, listas) | mensaje automático |
En correos sin comprobante: asunto con Re:/RV: o que cita un código ABR-… |
parece una respuesta a una notificación nuestra |
| En correos sin comprobante: más de 3 respuestas al mismo remitente en 24 h | límite de respuestas |
Cada omisión queda en comprobantes como notificacion_estado = omitida, con el motivo.
Qué ve el proveedor
- Importe por guía: el importe de la línea aparece una vez, en su primera guía; las demás dicen "incluida en la línea". Todas las guías de una línea llevan el mismo resultado, porque la validación es por línea.
- Rechazo por precio: se muestran "Importe en su factura" e "Importe esperado" (el de SAP).
- Pago estimado: solo si hay condición de pago (fecha de vencimiento posterior a la de contabilización).
- Sin adjuntos internos: la respuesta no lleva archivos.
Observaciones (observaciones.py)
Los textos son un borrador para revisar; se cambian en ese archivo sin tocar la lógica.
| Código | Cuándo |
|---|---|
precio_no_coincide |
El total de la línea del XML no coincide con la suma de SAP |
guia_no_encontrada |
SAP no devuelve datos para la guía |
linea_sin_guia |
Una línea no indica su guía de remisión |
cantidad_cero |
Una línea tiene cantidad cero |
factura_ya_registrada |
La factura ya existe en SAP |
sin_pdf_xml |
El correo trae adjuntos, pero ninguno es PDF ni XML |
falta_xml |
Hay PDF y ningún XML |
sin_factura |
El XML no contiene una factura electrónica (tipo 01) |
Configuración (.env)
| Variable | Para qué | Por defecto |
|---|---|---|
NOTIFICACION_MODO |
prueba o real |
prueba |
NOTIFICACION_EQUIPO |
Direcciones internas (coma o ;): correo técnico, y respuestas en modo prueba |
jlopez@aberio.com.pe, gcalle@pucp.pe |
NOTIFICACION_CONTACTO |
Dirección del botón "Escribir a Aberio" | el buzón EMAIL |
LOGO_URL |
URL pública (https) del logo | vacío: sin logo |
El buzón EMAIL es también el envio_correo al que se pide reenviar el comprobante corregido.
Cómo probar
- Vista previa y envío de ejemplos:
python tools/previsualizar_plantillas.py(ver su ayuda). Los ejemplos están enplantilla/ejemplos.json. - Piloto en modo prueba: con
NOTIFICACION_MODO=pruebael flujo completo corre y las respuestas llegan solo al equipo, indicando en el asunto a quién habrían ido. El correo técnico también. - Pasar a real: cuando el equipo valide los correos, cambiar a
NOTIFICACION_MODO=real. Revisar antes queLOGO_URLtenga valor.
Limitaciones conocidas
- Un fallo de envío no se reintenta. Queda
fallidaencomprobantes, pero el correo ya está marcado como procesado. (Solo se reintentan los correos cuando SAP está caído.) - Los reintentos de SAP caído dependen de que el correo siga dentro de la ventana
--diasdel demonio (2 días por defecto). - Un error que no es una caída (por ejemplo un HTTP 400 de SAP al crear el draft) no se reintenta: se avisa al equipo.
- La respuesta va a la dirección
fromdel mensaje. Si alguien reenvía la factura de un proveedor, la respuesta le llega a quien reenvió. - No se prueba contra el logo real ni contra Outlook de escritorio: hay que revisar los correos a mano.