# SQL_RULES (OBLIGATORIAS) - PostgreSQL

## -1) Modo de respuesta (OBLIGATORIO)
- SIEMPRE devolver una consulta SQL PostgreSQL valida, aunque no se pueda ejecutar.
- Aplicar SIEMPRE este SQL_RULES.md como si fueran reglas de compilacion.
- NO inventar tablas ni columnas: usar SOLO nombres presentes en los MD del RAG.
- La salida debe ser:
  1) Primero SOLO el SQL en un bloque ```sql```
  2) Despues una explicacion breve en español (maximo 8 lineas)
- NO usar SELECT *.
- NO repetir columnas en el SELECT.

---

## 0) Salida
- El SQL devuelto debe ser ejecutable en PostgreSQL.
- NO incluir texto fuera del bloque SQL antes del mismo.

---

## 1) Estilo SQL
- Usar alias cortos:
  - precli p
  - lineasdoc ld
  - albcli a
  - faccli f
- Fechas por rango cerrado-abierto:
  - fecha >= DATE 'YYYY-MM-01'
  - fecha <  DATE 'YYYY-MM-01' + INTERVAL '1 month'
- Evitar JOIN con OR.
  - Si hay alternativas, usar UNION ALL o subconsultas.
- ORDER BY recomendado en listados (fecha, serie, numero).

---

## 2) Glosario operativo (terminos del usuario)
- "Ticket" (cliente):
  faccli.modulo = 'TPV'
  (equivalente a factura a efectos de ventas)
- Si el usuario dice EXPLICITAMENTE "ticket" o "tickets":
  - La tabla principal DEBE ser faccli (f).
  - Aplicar SIEMPRE el filtro:
      f.modulo = 'TPV'
  - PROHIBIDO:
      - incluir facturas NO TPV
      - interpretar "ticket" como "albaran"
      - incluir albcli
  - La fecha oficial es SIEMPRE f.ffaccl.
- Si el usuario mezcla "tickets y facturas":
  - Usar faccli (f)
  - SIN filtrar por modulo
  - Permitir clasificar por:
      CASE WHEN f.modulo='TPV' THEN 'ticket' ELSE 'factura' END

- "Albaranes" (cliente) / "nota de entrega":
  - Documento: albcli
  - Filtro canonico:
    a.tipodoc = 'SV'
  - Lineas asociadas (si se piden articulos/lineas):
    ld.tipodoc='SV' AND ld.seriealb=a.seriealb AND ld.numalb=a.numalb
    AND COALESCE(ld.numalb,0) > 0
  - Fecha oficial: a.ffaccl

- "Facturas" / "Facturas de venta":
  - Documento: faccli
  - Lineas asociadas:
    ld.tipodoc='SV' AND ld.seriefac=f.seriefac AND ld.numfac=f.numfac
    AND COALESCE(ld.numfac,0) > 0
  - Fecha oficial: f.ffaccl

- "Ventas" (generico):
  - Si el usuario NO especifica albaran o factura:
    - usar albcli como cabecera por defecto (ventas por albaran)
    - y a.ffaccl como fecha oficial
  - Si el usuario especifica "factura(s)", aplicar la regla de "Facturas de venta".

- "Ventas" (cliente) (REGLA GLOBAL):
  - Ventas = facturas/tickets (faccli) + albaranes SIN factura asociada (ver 12d).
  - NUNCA sumar albcli con factura asociada porque duplican (las lineas se comparten con faccli).
  - Fecha oficial:
    - faccli: faccli.ffaccl
    - albcli (sin factura asociada): albcli.ffaccl

- "Pedidos" (cliente):
  precli.tipodoc = 'PC'
  AND precli.marcapre IN ('T','L','F','O')

- "Presupuestos" (cliente):
  precli.tipodoc = 'PC'
  AND precli.marcapre = 'P'

- "Anulados" o "Rechazados":
  precli.tipodoc = 'PC'
  AND precli.marcapre = 'A'

- "Ordenes de fabricacion":
  precli.tipodoc = 'OF'

- "Ventas" / "Vendido" / "Se han vendido" (cliente):
  - NO usar precli (PC) porque es pedido/presupuesto, no venta.
  - Si se pide TOTAL (sin articulo):
    - aplicar la REGLA GLOBAL de "Ventas" (faccli + albcli sin factura asociada, ver 12d)
    - SIN join a lineasdoc.
  - Si se pide por ARTICULO / LINEAS:
    - Facturas/tickets:
      FROM faccli f
      JOIN lineasdoc ld ON ld.tipodoc='SV' AND ld.seriefac=f.seriefac AND ld.numfac=f.numfac
    - Albaranes sin factura asociada (pendientes reales):
      FROM albcli a
      JOIN lineasdoc ld ON ld.tipodoc='SV' AND ld.seriealb=a.seriealb AND ld.numalb=a.numalb
      y aplicar NOT EXISTS de 12d para excluir los albaranes con numfac>0
  - Fecha oficial:
    - si se usa faccli: f.ffaccl
    - si se usa albcli: a.ffaccl
    - si solo se usa lineasdoc: ld.fechamov (solo si no hay cabecera disponible)

- Si el usuario pide EXPLICITAMENTE "albaranes" (o "albaranes de cliente"):
  - La tabla principal debe ser albcli (a).
  - NO reinterpretar la pregunta como "ventas" salvo que el usuario lo diga.
  - Si el usuario pide "pendientes de facturar":
    - usar albcli.marcafac = 'P' COMO filtro operativo
    - NOTA: albcli.marcafac NO determina por si solo la existencia real de factura; la verdad documental es 12d.
    - y, si se necesita robustez anti-incoherencias, reforzar con la regla 12d:
      excluir albaranes que tengan numfac>0 en lineasdoc.
  - Si el usuario pide "facturados":
    - usar albcli.marcafac = 'F' COMO estado informado por usuario/sistema
    - y NO asumir que existe factura: la existencia real de factura se comprueba con 12d.

- Si el usuario dice EXPLICITAMENTE "en facturas", "ventas en facturas",
  "facturación" o "ventas facturadas":
  - La tabla principal DEBE ser faccli (f).
  - Usar SIEMPRE:
      SUM(f.tfaccl)
      con fecha oficial f.ffaccl.
  - Agrupar por periodo usando f.ffaccl (mes/año).
  - PROHIBIDO:
      - incluir albcli
      - incluir albaranes sin factura
      - unir con lineasdoc salvo que se pidan articulos o lineas.
  - Esta regla ANULA la REGLA GLOBAL de "Ventas"
    (NO se incluyen albaranes sin factura asociada).


---

## 3) Cabecera vs lineas (estructura)
- precli, albcli y faccli son CABECERAS.
- lineasdoc son LINEAS.
- Las cabeceras NO contienen datos de articulo.
- Los datos de articulo SIEMPRE salen de lineasdoc.
- PROHIBIDO interpretar "vendido/ventas" como pedidos:
  - Si el usuario dice "vendido/ventas" y no dice "pedido",
    NO usar p.tipodoc='PC' ni marcapre como filtro principal.

---

## 4) JOIN correctos
- Para pedidos, presupuestos y OF (tipodoc PC u OF):
  ld.tipodoc  = p.tipodoc
  AND ld.seriepre = p.seriepre
  AND ld.numpre   = p.numpre

- Para albaranes (SV):
  ld.tipodoc = 'SV'
  AND ld.seriealb = a.seriealb
  AND ld.numalb   = a.numalb

- Para facturas/tickets (SV):
  ld.tipodoc = 'SV'
  AND ld.seriefac = f.seriefac
  AND ld.numfac   = f.numfac

- En consultas con faccli como tabla principal:
  WHERE f.tipodoc = 'SV'

- Para ALBARANES (SV -> albcli):
  ld.tipodoc = 'SV'
  AND ld.seriealb = a.seriealb
  AND ld.numalb   = a.numalb
  AND COALESCE(ld.numalb,0) > 0

- Para FACTURAS (SV -> faccli):
  ld.tipodoc = 'SV'
  AND ld.seriefac = f.seriefac
  AND ld.numfac   = f.numfac
  AND COALESCE(ld.numfac,0) > 0

## 4b) Huérfanos de cabecera (OBLIGATORIA, genérica)

- Si se busca detectar LINEAS que referencian una CABECERA inexistente (documento huérfano):
  - PROHIBIDO depender de campos de la cabecera para enriquecer el resultado (fecha, cliente, total, etc.),
    porque la cabecera puede no existir.
  - La fecha del resultado debe salir SIEMPRE de la propia tabla de líneas:
    - en listados agrupados por documento: usar MIN(ld.fechamov) y MAX(ld.fechamov)
    - en listados de detalle: usar ld.fechamov directamente
  - El patrón de detección debe ser SIEMPRE:
    NOT EXISTS (SELECT 1 FROM <cabecera> h WHERE h.<clave> = ld.<referencia_clave>)
  - Si se necesita cliente/proveedor en el resultado:
    usar ld.codigoclipro y NO el de la cabecera.


---

## 5) Campo polimorfico codigoclipro (solo lineasdoc)
- En lineasdoc NO existen campos codigoc ni codigop.
- El identificador cliente/proveedor es SIEMPRE ld.codigoclipro.
Dominio segun tipodoc:
- tipodoc IN ('PC','OF','SV'):
  ld.codigoclipro corresponde a cliente.codigoc
- tipodoc IN ('PP','EC'):
  ld.codigoclipro corresponde a proveedor.codigop

---

## 6) Estados (marcapre depende de tipodoc)
- Para tipodoc='PC':
  - marcapre='P'  presupuesto pendiente
  - marcapre IN ('T','L','F','O') pedido aceptado o en curso
  - marcapre='A'  pedido anulado
- Para tipodoc='OF':
  - marcapre='T'  orden de fabricacion terminada
  - marcapre IN ('F','O') orden de fabricacion en curso
  - marcapre='A'  orden de fabricacion anulada

---

## 7) Definicion de "pendiente de servir" (lineasdoc)
- Linea pendiente:
  COALESCE(ld.servido,0) = 0
- Linea parcialmente servida (solo si se pide):
  COALESCE(ld.servido,0) < COALESCE(ld.cantidad,0)

---

## 8) Campos minimos recomendados
- Si la consulta incluye precli:
  p.seriepre, p.numpre, p.ffaccl, p.codigoc, p.rsocialc, p.tfaccl
- Si la consulta incluye albcli:
  a.seriealb, a.numalb, a.ffaccl, a.codigoc, a.rsocialc, a.tfaccl
- Si la consulta incluye lineasdoc:
  ld.tipodoc, ld.codigoclipro, ld.codart, ld.concepto
- Si la consulta incluye faccli:
  f.seriefac, f.numfac, f.ffaccl, f.codigoc, f.rsocialc, f.tfaccl

---

## 9) Columnas homonimas
- Se permiten columnas con el mismo nombre en tablas distintas.
- DEBEN aliasarse:
  p.codigoc AS pedido_codigoc
  a.codigoc AS albaran_codigoc

---

## 10) DISTINCT
- NO usar DISTINCT por defecto.
- Usar EXISTS para "al menos una linea".
- DISTINCT solo si es inevitable y se explica.

---

## 11) Fechas y periodos temporales
- Usar SIEMPRE la fecha oficial (segun la tabla principal de la consulta):
  - precli: precli.ffaccl
  - albcli: albcli.ffaccl
  - faccli: faccli.ffaccl
  - lineasdoc: lineasdoc.fechamov (solo si no hay cabecera aplicable)
- PROHIBIDO usar campos inexistentes.
- Mes sin año:
  - NO asumir ningun año concreto.
  - Filtrar SOLO por mes:
    EXTRACT(MONTH FROM fecha_oficial) = <mes>
  - (incluye todos los años)
- Mes y año:
  - Filtrar por rango cerrado-abierto:
    fecha_oficial >= DATE 'YYYY-MM-01'
    AND fecha_oficial <  DATE 'YYYY-MM-01' + INTERVAL '1 month'
- Año (cuando el usuario dice "en YYYY", "del año YYYY", "durante YYYY"):
  - Filtrar por rango cerrado-abierto:
    fecha_oficial >= DATE 'YYYY-01-01'
    AND fecha_oficial <  DATE 'YYYY-01-01' + INTERVAL '1 year'
- Periodos ambiguos (ej: "este mes", "el mes pasado"):
  - Usar CURRENT_DATE y rangos cerrados-abiertos equivalentes.
  - PROHIBIDO fijar fechas absolutas.
- Si el usuario pide "desde YYYY" (ej: "desde el 2025"):
  - Rango abierto (sin limite superior):
    fecha_oficial >= DATE 'YYYY-01-01'
  - PROHIBIDO añadir un tope superior salvo que el usuario lo pida explicitamente.
- Si el usuario pide "desde YYYY-MM" (ej: "desde 2025-03"):
  - Rango abierto (sin limite superior):
    fecha_oficial >= DATE 'YYYY-MM-01'
  - PROHIBIDO añadir un tope superior salvo que el usuario lo pida explicitamente.
- PROHIBIDO generar limites superiores calculados (EXTRACT, formulas, etc.) si el usuario no ha pedido un rango cerrado.

- Cuando el usuario pida resultados "por mes" (ej: "por mes", "mensual", "cada mes"):
  - El resultado DEBE incluir SIEMPRE:
      - el numero de mes (1–12)
      - y el nombre del mes en español.
  - Patrón canonico OBLIGATORIO en el SELECT:
      EXTRACT(MONTH FROM fecha_oficial)        AS mes_num
      TO_CHAR(fecha_oficial, 'TMMonth')       AS mes_nombre
  - El nombre del mes debe:
      - estar en español
      - ir en formato legible (ej: 'enero', 'febrero', etc.)
  - El GROUP BY debe incluir AMBOS campos (mes_num y mes_nombre).
  - El ORDER BY debe hacerse SIEMPRE por mes_num.

- Si no se puede garantizar el idioma del servidor:
  - Usar un CASE sobre EXTRACT(MONTH ...) para el nombre del mes en español.


---

## 12) Trazabilidad pedido -> albaran
- Pedido PC "ha generado albaran" si existe al menos una linea de albaran (SV) en lineasdoc que referencie al pedido:
  EXISTS una linea ld_sv tal que:
    ld_sv.tipodoc = 'SV'
    AND ld_sv.seriepre = p.seriepre
    AND ld_sv.numpre   = p.numpre
    AND COALESCE(ld_sv.numalb,0) > 0

- Albaran "creado desde pedido":
  existe una linea SV en lineasdoc del mismo albaran (seriealb,numalb) con referencia a un origen (pedido/fabricacion):
    ld_tipodoc  = 'SV'
    AND ld.seriealb = a.seriealb
    AND ld.numalb   = a.numalb
    AND COALESCE(ld.numpre,0) > 0

Notas:
- La referencia al pedido/fabricacion NO esta en albcli, solo en lineasdoc.
- NO usar lineasdoc.numalb en lineas con tipodoc='PC' para detectar albaran, porque el albaran crea lineas nuevas con tipodoc='SV'.

## 12b) Regla de referencia REAL a documento origen (OBLIGATORIA)
- Para considerar que un documento destino (ej: albarán, factura, etc.) proviene de otro documento (pedido, orden de fabricación, etc.), NO basta con que exista una línea relacionada.
- Es OBLIGATORIO que la línea de enlace cumpla:
  COALESCE(lineasdoc.numpre,0) > 0
  y que participe en el JOIN completo hacia el documento origen.
- Las líneas con numpre = 0 NO se consideran referencia válida a un documento origen.
- Esta regla aplica a CUALQUIER trazabilidad basada en lineasdoc (pedido -> albarán, fabricación -> albarán, etc.).

## 12c) Patrón canónico de JOIN para clasificar origen de documentos
- Cuando se clasifique el ORIGEN de un documento (ej: “albarán creado desde pedido / fabricación”):
  - El JOIN con el documento origen debe hacerse SIEMPRE por la clave documental completa:
      precli.seriepre = lineasdoc.seriepre
      precli.numpre   = lineasdoc.numpre
  - El tipo de origen se determina EXCLUSIVAMENTE filtrando:
      precli.tipodoc = 'PC'  -> pedido
      precli.tipodoc = 'OF'  -> fabricación
- PROHIBIDO:
  - Inferir el origen usando lineasdoc.tipodoc
  - Unir documentos origen y destino directamente sin pasar por lineasdoc
  - Usar JOIN parciales (solo numpre o solo seriepre)
- Si un documento destino tiene líneas que referencian MAS DE UN tipo de origen, debe clasificarse como 'mixto'.

## 12d) Trazabilidad albaran -> factura y regla anti-duplicado (OBLIGATORIA)
- Para determinar si un albaran (albcli) tiene factura asociada, NO fiarse solo de albcli.marcafac.
  Puede existir marcafac='F' y NO existir ninguna factura (albaran justificante / salida de stock).
- Regla canonica (verdad documental):
  Un albaran tiene factura asociada SI y SOLO SI:
    EXISTS una linea ld_sv en lineasdoc tal que:
      ld_sv.tipodoc = 'SV'
      AND ld_sv.seriealb = a.seriealb
      AND ld_sv.numalb   = a.numalb
      AND COALESCE(ld_sv.numfac,0) > 0
- Consecuencia para "ventas" (anti-duplicados):
  - Incluir albcli en ventas SOLO si NO existe ninguna linea SV del albaran con numfac>0:
      NOT EXISTS (...) con COALESCE(ld_sv.numfac,0) > 0
  - Facturas/tickets siempre desde faccli.

---

## 13) Agregaciones: evitar duplicar cabeceras al unir lineas
- Regla general:
  Si se agrega (SUM/COUNT/AVG) un campo de CABECERA (precli.*, albcli.*, faccli.*), NO se debe hacer JOIN con lineasdoc salvo que sea imprescindible por filtros.
- Si la pregunta es "ventas totales", "importe total", "total por mes/año" y NO pide articulo:
  - Ventas (REGLA GLOBAL) =
      SUM(f.tfaccl) sobre faccli f (SV)
    + SUM(a.tfaccl) sobre albcli a (SV) SOLO de albaranes SIN factura asociada (ver 12d)
  - PROHIBIDO sumar albcli "con factura asociada" (duplicaria con faccli).
- Si la pregunta es "importe total" de un tipo de documento concreto (pedido, presupuesto, OF, etc.) y NO pide articulo:
  - Usar SOLO la cabecera de ese documento (sin lineasdoc) siempre que sea posible.
- Si se necesita filtrar por datos de linea (codart, concepto, etc.):
  Opcion A (recomendada):
    - Agregar por lineas:
      SUM(ld.tlineaii) (o el campo de importe de linea definido en el MD)
    - y unir la cabecera SOLO para fecha/cliente si hace falta.
  Opcion B (solo si es imprescindible sumar un total de cabecera y hay que unir lineasdoc):
    - Primero deduplicar por cabecera:
      1) CTE que seleccione DISTINCT (clave cabecera) y el total (tfaccl)
      2) luego SUM(tfaccl) sobre ese conjunto
    - PROHIBIDO: SUM(tfaccl de cabecera) con JOIN directo a lineasdoc.
- Conteos:
  - "numero de pedidos/presupuestos/OF" => COUNT(*) sobre precli (sin lineasdoc), filtrando por tipodoc y marcapre segun glosario.
  - "numero de albaranes"               => COUNT(*) sobre albcli (sin lineasdoc).
  - "numero de facturas/tickets"        => COUNT(*) sobre faccli (sin lineasdoc).
  - "numero de lineas"                  => COUNT(*) sobre lineasdoc.
- Ventas por periodo (mes/año) (REGLA CANONICA):
  - Las ventas por periodo se calculan SIEMPRE separando documentos:
    1) Facturas / tickets (faccli)
    2) Albaranes SIN factura asociada (albcli, ver 12d)
  - Cada conjunto se agrupa por SU fecha oficial:
    - faccli: faccli.ffaccl
    - albcli: albcli.ffaccl
  - El resultado final se obtiene combinando ambos conjuntos con UNION ALL y agregando despues por el periodo solicitado.
  - PROHIBIDO:
    - Unir faccli y albcli entre si.
    - Calcular ventas por periodo usando solo albcli.
    - Calcular ventas por periodo mezclando faccli y albcli en el mismo SELECT.

---

## 14) Patrones canónicos para evitar duplicados sin DISTINCT (lineasdoc multiplica filas)
Si se une una CABECERA (precli/albcli/faccli) con lineasdoc y se quieren columnas de cabecera sin duplicados, está PROHIBIDO usar DISTINCT por defecto.
Patrón obligatorio:
Opción A (recomendada): usar EXISTS si NO hacen falta campos de la tabla “muchos”.
Opción B (detalle sin duplicar): usar CTE con claves únicas y luego:
o bien GROUP BY de todas las columnas seleccionadas
o bien seleccionar desde una lista de claves únicas (CTE) y volver a unir.
Para “pedido con VARIOS albaranes”:
detectar en CTE:
GROUP BY p.seriepre,p.numpre HAVING COUNT(DISTINCT (ld_sv.seriealb,ld_sv.numalb)) > 1
para listar pedido+albarán:
unir ese CTE con lineasdoc (SV) y albcli
y evitar duplicados con:
GROUP BY de las columnas seleccionadas (preferido)
o CTE “albaranes_unicos” con (seriealb,numalb) por pedido

---

## 15) COUNT DISTINCT compuesto (PostgreSQL)
- Regla general:
  Para contar documentos distintos por CLAVE COMPUESTA (serie+numero), usar preferentemente:
  COUNT(DISTINCT (serie, numero))

- Ejemplos (segun documento):
  - Albaranes (SV) por (seriealb,numalb):
    COUNT(DISTINCT (ld.seriealb, ld.numalb))
  - Facturas/Tickets (SV) por (seriefac,numfac):
    COUNT(DISTINCT (ld.seriefac, ld.numfac))
  - Pedidos/Presupuestos/OF por (seriepre,numpre):
    COUNT(DISTINCT (ld.seriepre, ld.numpre))

- Si el modelo tiene problemas con COUNT DISTINCT compuesto, alternativa permitida:
  COUNT(DISTINCT (serie || '/' || numero::text))

- Ejemplos alternativos:
  - COUNT(DISTINCT (ld.seriealb || '/' || ld.numalb::text))
  - COUNT(DISTINCT (ld.seriefac || '/' || ld.numfac::text))
  - COUNT(DISTINCT (ld.seriepre || '/' || ld.numpre::text))

---

## 16) Clasificacion por documento origen (trazabilidad)
Objetivo:
- Clasificar un documento "destino" (ej: albcli) segun el tipo de documento "origen" (ej: precli PC u OF) usando SIEMPRE la tabla de lineas como puente (lineasdoc).
Regla general (aplicable a futuras tablas):
1) Identificar la tabla "destino" (cabecera) y su clave (ej: albcli (seriealb,numalb)).
2) Identificar la tabla "origen" (cabecera) y su clave (ej: precli (seriepre,numpre)).
3) La relacion destino-origen se detecta SIEMPRE en la tabla puente (lineasdoc), que contiene a la vez la clave del destino y la referencia al origen.
Caso canonico: albcli (SV) clasificado por origen precli (PC u OF)
- Unir destino a lineas (pertenencia al destino):
  ld.tipodoc  = 'SV'
  AND ld.seriealb = a.seriealb
  AND ld.numalb   = a.numalb
- Unir lineas (SV) a origen (precli) SOLO por la referencia al origen:
  p.seriepre = ld.seriepre
  AND p.numpre   = ld.numpre
- PROHIBIDO en trazabilidad destino-origen:
  - unir por p.tipodoc = ld.tipodoc (porque ld.tipodoc='SV' y p.tipodoc es 'PC' u 'OF')
Etiquetas de origen (CASE recomendado):
- 'pedido' si EXISTS al menos una linea SV del albaran que referencie un precli con p.tipodoc='PC'
- 'fabricacion' si EXISTS al menos una linea SV del albaran que referencie un precli con p.tipodoc='OF'
- 'mixto' si existen ambas (PC y OF) en el mismo albaran
- 'directo' si NO existe referencia al origen (COALESCE(ld.numpre,0)=0)

---

## 17) Regla anti-bucle (OBLIGATORIA)
- PROHIBIDO incluir en la respuesta texto de auto-chequeo (ej: "ensure...", "we need to ensure...").
- Tras leer la pregunta y las reglas, el modelo DEBE pasar directamente a construir el SQL.
- Si hay incertidumbre, el modelo IGUALMENTE debe devolver un SQL valido usando SOLO campos/tablas del RAG.

---

## 18) Regla canonica "clasificar destino por origen" (OBLIGATORIA, generica)
Cuando se pida: "<documento destino> que proviene de <origen A> vs <origen B>":
1) La tabla destino es una CABECERA (ej: albcli a).
2) La trazabilidad SIEMPRE se determina por una tabla puente de lineas (ej: lineasdoc ld) que contiene:
   - clave del destino (ej: ld.seriealb, ld.numalb)
   - referencia al origen (ej: ld.seriepre, ld.numpre)
3) Para detectar el tipo de origen, se usa EXISTS contra la cabecera de origen (ej: precli p) uniendo SOLO por la referencia de origen:
     p.seriepre = ld.seriepre AND p.numpre = ld.numpre
   y NUNCA por p.tipodoc = ld.tipodoc.

---

## 19) Regla anti-error tipodoc (OBLIGATORIA)
- PROHIBIDO unir cabecera origen con lineas del destino usando "p.tipodoc = ld.tipodoc" cuando ld.tipodoc es el del documento destino (ej: 'SV') y p.tipodoc es el del origen (ej: 'PC'/'OF').
- En esos casos, el filtro de tipodoc del origen va en WHERE del EXISTS:
  p.tipodoc = 'PC' o p.tipodoc='OF'

---

## 20) GROUP BY y ORDER BY (OBLIGATORIO - PostgreSQL)
- En consultas con GROUP BY:
  - TODA columna usada en:
    - SELECT
    - ORDER BY
  debe cumplir UNA de estas condiciones:
    - estar incluida en el GROUP BY
    - o estar envuelta en una función de agregación (SUM, COUNT, MAX, etc.)
- PROHIBIDO en PostgreSQL:
  - usar en ORDER BY columnas que no estén en GROUP BY ni agregadas.
- Si se quiere ordenar por una columna de cabecera (ej: fecha):
  - incluir explícitamente dicha columna en el GROUP BY.
- Esta regla aplica SIEMPRE, incluso en consultas de chequeo.

- Si una consulta mezcla agregaciones (GROUP BY) y acumulados (window functions OVER):
  - Patrón OBLIGATORIO: 2 fases
    1) CTE/agregado por el nivel pedido (ej: por mes)
    2) SELECT final que aplica OVER(...) sobre las columnas ya agregadas
  - PROHIBIDO usar OVER(...) en el mismo nivel donde se usa f.<fecha> que no está en GROUP BY.


## ANEXO A) Plantillas SQL de chequeo (NO son reglas)
Estas plantillas son ejemplos reutilizables.
NO sustituyen a las reglas anteriores.


A) Líneas de FACTURA huérfanas (ld.numfac>0 sin faccli)
SELECT
  ld.seriefac,
  ld.numfac,
  MIN(ld.fechamov) AS fecha_min_mov,
  MAX(ld.fechamov) AS fecha_max_mov,
  COUNT(*) AS num_lineas,
  SUM(COALESCE(ld.tlineaii,0)) AS total_lineas
FROM lineasdoc ld
WHERE ld.tipodoc='SV'
  AND COALESCE(ld.numfac,0) > 0
  AND NOT EXISTS (
    SELECT 1
    FROM faccli f
    WHERE f.tipodoc='SV'
      AND f.seriefac = ld.seriefac
      AND f.numfac   = ld.numfac
  )
GROUP BY ld.seriefac, ld.numfac
ORDER BY fecha_min_mov, ld.seriefac, ld.numfac;

Líneas de ALBARÁN huérfanas (ld.numalb>0 sin albcli)
SELECT
  ld.seriealb,
  ld.numalb,
  MIN(ld.fechamov) AS fecha_min_mov,
  MAX(ld.fechamov) AS fecha_max_mov,
  COUNT(*) AS num_lineas,
  SUM(COALESCE(ld.tlineaii,0)) AS total_lineas
FROM lineasdoc ld
WHERE ld.tipodoc='SV'
  AND COALESCE(ld.numalb,0) > 0
  AND NOT EXISTS (
    SELECT 1
    FROM albcli a
    WHERE a.tipodoc='SV'
      AND a.seriealb = ld.seriealb
      AND a.numalb   = ld.numalb
  )
GROUP BY ld.seriealb, ld.numalb
ORDER BY fecha_min_mov, ld.seriealb, ld.numalb;

C) Líneas con ORIGEN huérfano (ld.numpre>0 sin precli)
SELECT
  ld.seriepre,
  ld.numpre,
  MIN(ld.fechamov) AS fecha_min_mov,
  MAX(ld.fechamov) AS fecha_max_mov,
  COUNT(*) AS num_lineas,
  SUM(COALESCE(ld.tlineaii,0)) AS total_lineas
FROM lineasdoc ld
WHERE ld.tipodoc='SV'
  AND COALESCE(ld.numpre,0) > 0
  AND NOT EXISTS (
    SELECT 1
    FROM precli p
    WHERE p.seriepre = ld.seriepre
      AND p.numpre   = ld.numpre
      AND p.tipodoc IN ('PC','OF')
  )
GROUP BY ld.seriepre, ld.numpre
ORDER BY fecha_min_mov, ld.seriepre, ld.numpre;

## 21) SV (ventas): distinguir ALBARAN vs FACTURA (regla canonica)

- tipodoc='SV' es una familia documental de VENTAS.
- Dentro de 'SV', el documento real se determina por que clave esta informada en lineasdoc:

  A) Linea de ALBARAN (albcli):
     - COALESCE(ld.numalb,0) > 0
     - Clave: (ld.seriealb, ld.numalb)
     - JOIN cabecera: albcli a
       ld.tipodoc='SV' AND ld.seriealb=a.seriealb AND ld.numalb=a.numalb

  B) Linea de FACTURA (faccli):
     - COALESCE(ld.numfac,0) > 0
     - Clave: (ld.seriefac, ld.numfac)
     - JOIN cabecera: faccli f
       ld.tipodoc='SV' AND ld.seriefac=f.seriefac AND ld.numfac=f.numfac

- Si el usuario dice "albaran(es)" o "nota de entrega":
  - usar SIEMPRE el patron A (numalb>0) y albcli.

- Si el usuario dice "factura(s)" o "facturas de venta":
  - usar SIEMPRE el patron B (numfac>0) y faccli.

- PROHIBIDO interpretar "factura(s)" como "cualquier SV" sin filtrar numfac>0.
- PROHIBIDO mezclar albcli y faccli en el mismo SELECT salvo que el usuario pida trazabilidad.
  En ese caso, usar CTEs separadas (una para albaranes y otra para facturas) y luego unir por claves de trazabilidad documentadas.


