Skip to content

Factura de Servicio (Cable/Internet)

Modulo: Membresias > Facturacion por Lotes > Informes Tipo: Process Estado: Implementado Fecha Implementacion: 2026-06-01


Descripcion

Problema que resuelve

Las membresias de servicios recurrentes (cable / internet / ISP) requieren un comprobante con formato de factura de servicio A4 completo: encabezado con el nombre del servicio, banda de cliente, detalle de facturacion, regimen de Transparencia Fiscal (Ley 27.743), vencimiento, total y el bloque fiscal con QR ARCA + CAE.

El comprobante estandar de membresias (cupon-socio) tiene formato de cupon de pago de dos mitades por hoja, que no se ajusta al formato de una factura de servicio de internet. Este informe produce un comprobante de una factura por hoja con el layout adecuado.

Solucion implementada

Un nuevo tipo de comprobante PDF, factura-servicio, que:

  1. Se activa por categoria de membresia mediante el campo membresia_categoria.cod_reporte.
  2. Reutiliza por composicion toda la logica de datos del cupon (obtenerDatosCupon) y la enriquece con datos de categoria, cantidad contratada, datos utiles y QR ARCA.
  3. Genera el PDF A4 via el servicio externo de render (Puppeteer, puerto 9999) usando PdfGeneratorService.
  4. Soporta lotes de socios (ordcon_ids[]) y agrega una pagina de omitidos cuando algun socio no tiene facturacion en el periodo.

Diferencia con cupon-socio

cupon-sociofactura-servicio
FormatoCupon de pago, 2 mitades por hojaFactura de servicio, 1 por hoja A4
ActivacionComprobante por defecto de membresiasmembresia_categoria.cod_reporte = 'factura-servicio'
Bloque fiscalCodigo de barras InterleavedQR ARCA + bloque CAE
Caso de usoCuotas de socio genericasServicios recurrentes (cable / internet / ISP)

Ambos casos conviven en informes/index.php sin modificarse entre si. La eleccion del informe la determina la categoria de membresia del socio.


Activacion

El comprobante se habilita a nivel de categoria de membresia, no por socio individual.

  • Campo: membresia_categoria.cod_reporte
  • Valor: 'factura-servicio'

Cuando una categoria tiene este valor, los socios pertenecientes a esa categoria deben generar su comprobante con el informe factura-servicio en lugar de cupon-socio.

Adicionalmente, dos campos de la categoria controlan la banda de categoria del comprobante:

  • membresia_categoria.nombre — se renderiza textual en el recuadro de servicio (esquina superior derecha) y en la banda de categoria. No se le agrega prefijo ni etiqueta.
  • membresia_categoria.label_cantidad — etiqueta opcional que acompana al numero de cantidad (por ejemplo, "Mbps", "abonos"). Si esta vacia, solo se muestra el numero.

La cantidad contratada se toma de rel_ordcon_categoria.cantidad para el par id_ordcon + id_categoria.


Endpoint

Se invoca desde el servicio informes (informes/index.php).

ParametroTipoDescripcion
casestringfactura-servicio
ordcon_ids[]array<int>IDs de socios/clientes a facturar. Acepta tambien ordcon_id simple
aniointAnio del periodo
mesintMes del periodo (1-12)

Misma forma de parametros que case 'cupon-socio'.

Ejemplo de invocacion

http
POST /informes/index.php
Content-Type: application/x-www-form-urlencoded

case=factura-servicio&ordcon_ids[]=101&ordcon_ids[]=102&anio=2026&mes=6

Respuesta exitosa:

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="factura-servicio-6-2026.pdf"

Comportamiento del lote

foreach ordcon_id:
    obtenerDatosFacturaServicio(...)
        NoContent      -> el socio se agrega a $omitidos, el lote continua
        RuntimeException -> aborta el lote completo (cliente inexistente, error de datos)

si $facturas vacio (todos NoContent) -> NoContent global (HTTP 200, modal informativo)
si $omitidos no vacio                -> se agrega pagina final de omitidos
  • Un socio sin facturacion pendiente en el periodo dispara NoContent y se omite; el resto se procesa normalmente.
  • Un RuntimeException (por ejemplo, cliente inexistente) aborta todo el lote y se devuelve un error sin PDF parcial.

Flujo de generacion

mermaid
flowchart TD
    A["index.php · case 'factura-servicio'<br/>ordcon_ids[], anio, mes"] --> B{loop ordcon_ids}
    B -->|NoContent| OM[agregar a $omitidos]
    B -->|RuntimeException| AB[abortar lote]
    B -->|OK| C["obtenerDatosFacturaServicio()"]
    C --> D["obtenerDatosCupon()<br/>cliente · fiscal · empresa · periodo · detalle_grupo"]
    D --> E["enriquecer:<br/>categoria · cantidad · datos_utiles · qr · logo"]
    E --> F["factura-servicio-render.php<br/>(ob_start include)"]
    OM --> G
    F --> G["pagina de omitidos (si corresponde)"]
    G --> H["formatearPagina(A4)"]
    H --> I["PdfGeneratorService::generatePdf()"]
    I --> J["application/pdf"]

Composicion de datos

La funcion obtenerDatosFacturaServicio() (informes/reports/mod-ctacte/factura-servicio-datos.php) no modifica la logica del cupon: llama a obtenerDatosCupon() y extiende el array resultante. Esto garantiza cero efectos secundarios sobre cupon-socio, case 0004 y FA2.

Datos heredados de obtenerDatosCupon(): cliente, fiscal, empresa, periodo, facturacion, detalle_grupo, modo_prueba.

Campos agregados por composicion:

CampoOrigenFallback
categoria.nombremembresia_categoria.nombre via membresia_facturacion.id_categoria'' si no hay id_categoria
categoria.cantidadrel_ordcon_categoria.cantidad (id_ordcon + id_categoria)null si no hay fila
categoria.label_cantidadmembresia_categoria.label_cantidadnull
datos_utilesCotizacion en moneda alternativa (factura_moneda_alt + monedas)null si no hay moneda alt
qr.svggenerarQrArca(...) — solo cuando fiscal.tiene_caenull sin CAE
logodata_config['logo-rectangular-factura']null

Nota sobre datos_utiles

En la implementacion final, datos_utiles se compone a partir de la cotizacion en moneda alternativa del comprobante (factura_moneda_alt JOIN monedas), con el formato {signo} REFERENCIA {fecha} {signo} ${cotizacion}. Solo se genera cuando la factura tiene una cotizacion alt registrada; en caso contrario es null y la seccion de Datos Utiles se omite.

Consideraciones multi-tenant

  • Las consultas de enriquecimiento de categoria (membresia_facturacion, membresia_categoria, rel_ordcon_categoria) corren sobre la conexion oficial ($oficialConn).
  • La re-consulta a factura y factura_moneda_alt respeta el modo prueba: si modo_prueba = true, usa la base _p (igual criterio que cupon-pago-datos.php); de lo contrario, usa la oficial.
  • monedas y data_config son tablas de referencia globales: siempre se leen desde la oficial.

Secciones del PDF

El template (factura-servicio-render.php) produce un A4 vertical con hasta 8 secciones. Las secciones 3, 7 y 8 son condicionales.

#SeccionDatosCondicion
1HeaderLogo / empresa (izquierda) + recuadro de servicio con categoria.nombre (derecha)Siempre
2Banda de clienteNombre, direccion, condicion IVA, CUIT, F. Emision, Periodo, ORIGINAL / FACTURA [letra] / numeroSiempre
3Banda de categoriacategoria.nombre + etiqueta "Cantidad" + categoria.cantidad (con label_cantidad si existe)Solo si categoria.nombre != '' o categoria.cantidad != null
4Cuerpo (2 columnas)Detalle de Facturacion (izquierda) + Observaciones (derecha)Siempre
5Transparencia FiscalIVA + O.IMP.NAC.INDIRECTOS (Ley 27.743)Siempre
6Pie de totalesVENCIMIENTO DE ESTA FACTURA + TOTAL A PAGARSiempre
7Datos Utilesdatos_utiles (cotizacion de referencia)Solo si datos_utiles no es null/vacio
8QR + CAEQR ARCA + Numero de CAE + Vencimiento del CAESolo si fiscal.tiene_cae y qr.svg no es null

Banda de categoria

La seccion 3 se omite por completo cuando la categoria no tiene nombre y la cantidad es null. Cuando hay cantidad null pero si nombre, la columna de cantidad muestra .

Transparencia Fiscal (Seccion 5)

Bloque obligatorio segun el Regimen de Transparencia Fiscal al Consumidor — Ley 27.743. Discrimina al consumidor final el IVA contenido (fiscal.iva_total) y otros impuestos nacionales indirectos (O.IMP.NAC.INDIRECTOS). Se renderiza siempre.

El valor de IVA proviene de factura.iva (importe ARS correcto); en el enriquecimiento se hace fiscal.iva_total = factura.iva y se recalcula fiscal.neto = total - iva.

QR ARCA y CAE (Seccion 8)

  • QR ARCA: codigo QR fiscal generado por generarQrArca(...) (informes/reports/util/QRGenerator.php) a partir de CUIT de la empresa, sucursal, tipo de comprobante, numero, total, identificacion y tipo de documento del receptor, numero de CAE y fecha. Se renderiza como <img src="data:..."> (data URI).
  • CAE: numero de CAE (fiscal.nrocae) y su vencimiento (fiscal.fevtocae).

Esta seccion solo aparece cuando el comprobante tiene CAE. En modo prueba / borrador, sin CAE, la seccion 8 se omite y el resto del comprobante (secciones 1-7) se renderiza normalmente.


Pagina de omitidos

Cuando el lote finaliza con uno o mas socios omitidos (cada uno por una excepcion NoContent durante su procesamiento), se agrega una pagina final al PDF con una tabla:

Socio IDMotivo
999(mensaje de la excepcion NoContent)

El motivo es el texto del mensaje de la excepcion (por ejemplo, "no hay facturacion pendiente para el periodo"). El PDF sigue devolviendose con HTTP 200.

Si todos los socios del lote resultan omitidos (lista de facturas vacia), se lanza un NoContent global: HTTP 200 con modal informativo, sin PDF.


Generacion del PDF

  • El HTML se construye con ob_start() + include del render, mas la pagina de omitidos inline.
  • Se formatea con formatearPagina($html, TipoPagina::A4).
  • Se genera con PdfGeneratorService::generatePdf($html) — servicio externo Puppeteer (puerto 9999). No se usa el PDFGenerator legacy.
  • La respuesta fija Content-Disposition con formatNameFile('factura-servicio-{mes}-{anio}').

Archivos relacionados

ArchivoRol
informes/index.php (case 'factura-servicio')Orquestacion del lote, omitidos y respuesta PDF
informes/reports/mod-ctacte/factura-servicio-datos.phpobtenerDatosFacturaServicio() — composicion + enriquecimiento
informes/reports/mod-ctacte/factura-servicio-render.phpTemplate A4 con las 8 secciones
informes/reports/mod-ctacte/cupon-pago-datos.phpobtenerDatosCupon() — base reutilizada (no se modifica)
informes/reports/util/QRGenerator.phpgenerarQrArca() — QR fiscal ARCA

Consideraciones tecnicas

  • Composicion, no modificacion: cupon-socio, case 0004 y FA2 no se tocan. El informe reutiliza obtenerDatosCupon() y extiende su salida.
  • Idempotencia visual: las secciones condicionales (3, 7, 8) degradan limpiamente cuando faltan datos; el comprobante nunca aborta por ausencia de categoria, cotizacion o CAE.
  • Modo prueba: la re-consulta a factura / factura_moneda_alt cambia a la base _p cuando modo_prueba = true, manteniendo coherencia con el comportamiento del cupon.
  • Sin migracion: no requiere cambios de schema. La activacion depende del valor existente en membresia_categoria.cod_reporte.