Appearance
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:
- Se activa por categoria de membresia mediante el campo
membresia_categoria.cod_reporte. - 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. - Genera el PDF A4 via el servicio externo de render (Puppeteer, puerto 9999) usando
PdfGeneratorService. - 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-socio | factura-servicio | |
|---|---|---|
| Formato | Cupon de pago, 2 mitades por hoja | Factura de servicio, 1 por hoja A4 |
| Activacion | Comprobante por defecto de membresias | membresia_categoria.cod_reporte = 'factura-servicio' |
| Bloque fiscal | Codigo de barras Interleaved | QR ARCA + bloque CAE |
| Caso de uso | Cuotas de socio genericas | Servicios 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).
| Parametro | Tipo | Descripcion |
|---|---|---|
case | string | factura-servicio |
ordcon_ids[] | array<int> | IDs de socios/clientes a facturar. Acepta tambien ordcon_id simple |
anio | int | Anio del periodo |
mes | int | Mes 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=6Respuesta 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
NoContenty 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:
| Campo | Origen | Fallback |
|---|---|---|
categoria.nombre | membresia_categoria.nombre via membresia_facturacion.id_categoria | '' si no hay id_categoria |
categoria.cantidad | rel_ordcon_categoria.cantidad (id_ordcon + id_categoria) | null si no hay fila |
categoria.label_cantidad | membresia_categoria.label_cantidad | null |
datos_utiles | Cotizacion en moneda alternativa (factura_moneda_alt + monedas) | null si no hay moneda alt |
qr.svg | generarQrArca(...) — solo cuando fiscal.tiene_cae | null sin CAE |
logo | data_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
facturayfactura_moneda_altrespeta el modo prueba: simodo_prueba = true, usa la base_p(igual criterio quecupon-pago-datos.php); de lo contrario, usa la oficial. monedasydata_configson 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.
| # | Seccion | Datos | Condicion |
|---|---|---|---|
| 1 | Header | Logo / empresa (izquierda) + recuadro de servicio con categoria.nombre (derecha) | Siempre |
| 2 | Banda de cliente | Nombre, direccion, condicion IVA, CUIT, F. Emision, Periodo, ORIGINAL / FACTURA [letra] / numero | Siempre |
| 3 | Banda de categoria | categoria.nombre + etiqueta "Cantidad" + categoria.cantidad (con label_cantidad si existe) | Solo si categoria.nombre != '' o categoria.cantidad != null |
| 4 | Cuerpo (2 columnas) | Detalle de Facturacion (izquierda) + Observaciones (derecha) | Siempre |
| 5 | Transparencia Fiscal | IVA + O.IMP.NAC.INDIRECTOS (Ley 27.743) | Siempre |
| 6 | Pie de totales | VENCIMIENTO DE ESTA FACTURA + TOTAL A PAGAR | Siempre |
| 7 | Datos Utiles | datos_utiles (cotizacion de referencia) | Solo si datos_utiles no es null/vacio |
| 8 | QR + CAE | QR ARCA + Numero de CAE + Vencimiento del CAE | Solo 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 ID | Motivo |
|---|---|
| 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()+includedel 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 elPDFGeneratorlegacy. - La respuesta fija
Content-DispositionconformatNameFile('factura-servicio-{mes}-{anio}').
Archivos relacionados
| Archivo | Rol |
|---|---|
informes/index.php (case 'factura-servicio') | Orquestacion del lote, omitidos y respuesta PDF |
informes/reports/mod-ctacte/factura-servicio-datos.php | obtenerDatosFacturaServicio() — composicion + enriquecimiento |
informes/reports/mod-ctacte/factura-servicio-render.php | Template A4 con las 8 secciones |
informes/reports/mod-ctacte/cupon-pago-datos.php | obtenerDatosCupon() — base reutilizada (no se modifica) |
informes/reports/util/QRGenerator.php | generarQrArca() — QR fiscal ARCA |
Consideraciones tecnicas
- Composicion, no modificacion:
cupon-socio,case 0004yFA2no se tocan. El informe reutilizaobtenerDatosCupon()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_altcambia a la base_pcuandomodo_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.