Appearance
Exportación SICORE Retenciones de Ganancias
Módulo: compra Tipo: process Estado: Implementado Fecha: 2026-03-25
Descripción
Proceso de exportación de retenciones de Ganancias al formato SICORE v9.0 de ARCA. Genera un archivo ZIP con dos archivos TXT de posición fija (retenciones.txt y sujetos_retenidos.txt) que el agente de retención debe declarar ante ARCA para el período seleccionado.
La exportación es siempre consolidada: recopila retenciones de todas las sucursales (schemas) del sistema en un único ZIP.
Frontend
Vistas
- Modal SICORE Retenciones: formulario modal accesible desde el sidebar de Compras → sección Utilidades.
- Componente:
SicoreRetencionesForm(bautista-app/ts/compras/utilidades/views/SicoreRetencionesView.tsx) - No es una vista de página — es un modal montado sobre
ComprasSidebarApp.
- Componente:
Componentes
SicoreRetencionesForm: modal con dos inputs (mes y año). Al exportar, descarga el ZIP automáticamente.- Integrado en
ComprasSidebarApp.tsxviacustomHandler['sicore-compras'].
Interacciones del usuario
- El usuario abre el sidebar de Compras y hace clic en "Exportación SICORE Retenciones" (sección Utilidades).
- Se abre el modal con selección de mes (1-12) y año (YYYY). Por defecto muestra el mes y año actuales.
- El usuario presiona "Exportar".
- Si la validación local falla (mes fuera de rango, año inválido), se muestra error inline sin llamar al servidor.
- Si el servidor devuelve error (período sin datos, proveedores con datos incompletos), se muestra el mensaje textual del backend inline.
- Si la operación es exitosa, el ZIP se descarga automáticamente con el nombre
sicore_consolidado_MMYYYY.zip.
Permisos
| Permiso | ID | Descripción | Acciones permitidas |
|---|---|---|---|
COMPRAS_UTILS_EXP-SICORE | 6023 | Exportar SICORE Retenciones Ganancias | Acceder al modal y exportar el ZIP |
Backend
Endpoints API
GET /compras/utils/exportar-sicore
Nota: internamente registrado como
GET /mod-compra/sicore-retenciones.
- Descripción: Consolida retenciones de Ganancias del período indicado en todos los schemas y devuelve un ZIP base64 con los TXT SICORE.
- Permisos:
COMPRAS_UTILS_EXP-SICORE(id=6023) - Query Params:
mes(integer, 1-12): mes del períodoano(integer, ej: 2026): año del período
- Respuesta exitosa (200):json
{ "data": { "retenciones": { "file_name": "sicore_consolidado_032026.zip", "zip": "<base64>" } } } - Errores:
400: parámetrosmesoanoinválidos422: no existen retenciones para el período, o proveedores con datos incompletos (localidad/código postal/provincia faltantes)
Comportamiento consolidado: El endpoint siempre itera todos los schemas que contienen la tabla detgan (vía SchemaService). No existe parámetro modo. Opera 100% sobre la base oficial — nunca usa $db . '_p'.
Archivos que genera el ZIP
| Archivo | Longitud de línea | Separador |
|---|---|---|
retenciones.txt | 145 chars por línea | CRLF (\r\n) |
sujetos_retenidos.txt | 83 chars por línea | CRLF (\r\n) |
El archivo termina con \r\n final. Una línea = una retención (detgan). Un proveedor = una línea en sujetos_retenidos.txt (DISTINCT ON cuit).
Modelos/Entidades
Tablas involucradas en la query principal:
| Tabla | Schema | Rol |
|---|---|---|
detgan | {schema} | Tabla base — una fila = una retención |
congan | public | Código régimen ARCA (ej: 119) |
ordcte (op) | {schema} | Orden de pago — fecha, proveedor, base de cálculo |
ordcte (mr) | {schema} | Movimiento de retención — importe |
cpdprov | public | Proveedor — CUIT, nombre, inscripto, domicilio |
localidades | public | Localidad y código postal (JOIN via p.cpos) |
provincia | public | Código ARCA de provincia (via localidades.id_prov) |
comprob | public | Código ARCA del tipo de comprobante |
ordcte_subdicom / subdicom | {schema} | Comprobante vinculado (via LATERAL subquery) |
acugan | {schema} | Contexto acumulado del período (LEFT JOIN) |
Nota sobre localidad: El JOIN de localidades usa l.id_loc = p.cpos de forma temporal (BY DESIGN) porque cpdprov.id_localidad no está funcional en la UI de la ficha de proveedor. El campo cpdprov.cloc está vacío en producción y NO debe usarse. Cuando id_localidad sea habilitado en la UI, se debe actualizar el JOIN a l.id_loc = p.id_localidad.
Servicios
SicoreRetenciones.php
Archivo: bautista-backend/models/modulo-compra/SicoreRetenciones.php
Genera los TXT de posición fija y el ZIP base64. Funciones helpers de formato:
| Función | Comportamiento |
|---|---|
padTexto($val, $len) | Alfanumérico — alineado izquierda, relleno con espacios |
padEntero($val, $len) | Numérico — alineado derecha, relleno con ceros |
padImporte($val, $len) | Importe con decimales implícitos (1234.56 → "00000000123456") |
formatFechaSICORE($fecha) | dd/mm/aaaa (10 chars), o 10 espacios si NULL |
limpiarCUIT($cuit) | Elimina guiones y no-dígitos |
formatNroComprobante($nrocom) | Elimina separadores (ej: 0001-12334599 → 000112334599), 16 chars |
SicoreRetencionesController.php
Archivo: bautista-backend/controller/modulo-compra/SicoreRetencionesController.php
- Itera todos los schemas via
SchemaService::getSchemasWithTable('detgan'). - Si no hay retenciones en ningún schema → lanza
RuntimeExceptioncon mensaje descriptivo. - Si hay proveedores con datos incompletos → lanza
RuntimeExceptionbloqueante (no genera ZIP).
Validaciones
Validaciones bloqueantes (antes de generar el ZIP)
- Datos de proveedor incompletos: Si algún proveedor tiene
localidad,cod_postaloprovincia_arcafaltantes, la exportación se bloquea conRuntimeExceptionlistando los proveedores afectados (CUIT + nombre). El usuario debe corregir los datos en la ficha del proveedor antes de poder exportar. - Período sin retenciones: Si
$lineasRetenciones === []tras consolidar todos los schemas, se lanzaRuntimeExceptioncon mensaje"No existen retenciones de Ganancias registradas para el período MM/YYYY.". No se genera ZIP vacío. ordcte.zfno numérico: Las filas conzfno numérico se excluyen conWHERE op.zf ~ '^\d+$'.
Reglas de negocio
- Consolidación obligatoria: La exportación siempre incluye todas las sucursales del período. No existe exportación por sucursal individual.
- Base oficial únicamente: SICORE opera sobre datos reales (
$db), no sobre base de prueba. - Período sin datos es error: No se genera ZIP vacío — ausencia de retenciones retorna HTTP 422 con mensaje descriptivo.
- Datos incompletos bloquean: Un proveedor con localidad/código postal/provincia faltante impide la exportación. El usuario debe completar la ficha antes de reintentar.
- Proveedores sin CUIT son descartados: Si
cpdprov.ccuies NULL o vacío, la fila no se incluye en los TXT (no genera error fatal). - Un certificado por retención: Cada registro
detgangenera exactamente una línea enretenciones.txt. Cada CUIT único aparece exactamente una vez ensujetos_retenidos.txt.
Casos de uso
Caso 1: Exportación exitosa del período
Actor: Usuario con permiso COMPRAS_UTILS_EXP-SICORE
Precondiciones:
- Existen retenciones de Ganancias registradas en
detganpara el mes/año indicado. - Todos los proveedores del período tienen localidad, código postal y provincia mapeados.
Flujo principal:
- El usuario abre el modal desde el sidebar de Compras → Utilidades.
- Selecciona mes y año.
- Presiona "Exportar".
- El backend consolida todas las sucursales y genera el ZIP.
- El ZIP se descarga automáticamente como
sicore_consolidado_MMYYYY.zip.
Postcondiciones:
- El usuario tiene el ZIP listo para importar en SICORE de ARCA.
Caso 2: Período sin retenciones
Actor: Usuario con permiso COMPRAS_UTILS_EXP-SICORE
Flujo principal:
- El usuario selecciona un período sin retenciones registradas.
- El backend itera todos los schemas y no encuentra registros.
- El modal muestra el mensaje:
"No existen retenciones de Ganancias registradas para el período MM/YYYY.".
Flujos alternativos: No se genera ningún archivo.
Caso 3: Proveedor con datos incompletos
Actor: Usuario con permiso COMPRAS_UTILS_EXP-SICORE
Flujo principal:
- El usuario intenta exportar un período donde algún proveedor no tiene localidad o código postal.
- El modal muestra el listado de proveedores afectados con sus CUIT y nombres.
- El usuario debe ir a la ficha del proveedor, completar los datos, y reintentar.
Formato de los archivos TXT (SICORE v9.0)
retenciones.txt — 145 chars por línea
| Posición | Long. | Campo | Valor |
|---|---|---|---|
| 1-2 | 2 | Código comprobante | comprob.codigo via LATERAL (ej: 01=Factura) |
| 3-12 | 10 | Fecha emisión comprobante | dd/mm/aaaa |
| 13-28 | 16 | Número comprobante | Sin separadores, alineado izquierda |
| 29-44 | 16 | Importe comprobante | Decimales implícitos (ordcte_subdicom.subdicom.imptot) |
| 45-48 | 4 | Código impuesto | 0217 (Ganancias, fijo) |
| 49-51 | 3 | Código régimen | congan.codgan (ej: 119) |
| 52 | 1 | Código operación | 1 (Retención, fijo) |
| 53-66 | 14 | Base de cálculo | Decimales implícitos (ordcte.debe) |
| 67-76 | 10 | Fecha emisión retención | dd/mm/aaaa |
| 77-78 | 2 | Código condición | 01=inscripto, 02=no inscripto |
| 79 | 1 | Ret. sujetos suspendidos | 0 (fijo) |
| 80-93 | 14 | Importe retención | Decimales implícitos (ordcte.debe del mov. retención) |
| 94-99 | 6 | Porcentaje exclusión | 000000 (fijo) |
| 100-109 | 10 | Fecha vigencia | 10 espacios (fijo) |
| 110-111 | 2 | Tipo documento retenido | 80 (CUIT, fijo) |
| 112-131 | 20 | CUIT retenido | Sin guiones, alineado izquierda |
| 132-145 | 14 | Número certificado original | detgan.numret |
sujetos_retenidos.txt — 83 chars por línea
| Posición | Long. | Campo | Valor |
|---|---|---|---|
| 1-11 | 11 | CUIT retenido | Sin guiones, padding con ceros a la izquierda |
| 12-31 | 20 | Razón social | cpdprov.cnom |
| 32-51 | 20 | Domicilio fiscal | cpdprov.cdom1 |
| 52-71 | 20 | Localidad | localidades.nombre |
| 72-73 | 2 | Provincia | provincia.codigo_arca (fallback 00 si NULL) |
| 74-81 | 8 | Código postal | localidades.cod_post |
| 82-83 | 2 | Tipo documento | 80 (CUIT, fijo) |
Reglas generales de formato
- Campos alfanuméricos: alineados a la izquierda, relleno con espacios
- Campos numéricos: alineados a la derecha, relleno con ceros
- Importes: decimales implícitos sin separador (
1234.56→"00000000123456") - Fechas:
dd/mm/aaaa(10 chars) - CUIT: sin guiones
- Números de comprobante: sin separadores (ej:
0001-12334599→000112334599) - Separador de línea:
\r\n(CRLF) - El archivo termina con
\r\nfinal
Tablas de códigos ARCA (SICORE v9.0)
Códigos de comprobante (pos 1-2 de retenciones.txt)
| Código | Descripción | Scope |
|---|---|---|
| 01 | Factura | Soportado |
| 02 | Recibo | Soportado |
| 03 | Nota de Crédito | Soportado — ver nota NC |
| 04 | Nota de Débito | Soportado |
| 05 | Otro comprobante | Soportado |
| 06 | Orden de Pago | Soportado |
| 07 | Recibo de Sueldo | Fuera de scope |
| 08 | Recibo de Sueldo - Devolución | Fuera de scope |
| 09 | Escritura Pública | Fuera de scope |
| 10 | C.1116 | Fuera de scope |
| 11 | Factura (16 Dígitos) | Fuera de scope |
El código proviene de comprob.codigo via la LATERAL subquery. Si el LATERAL no encuentra comprobante → 00.
Nota NC: Para Notas de Crédito (código 03), el campo pos 132-145 debe referenciar la retención original que se reversa. La implementación usa detgan.numret, lo cual requiere validación de negocio para flujos de NC.
Códigos de impuesto (pos 45-48 de retenciones.txt)
| Código | Descripción | Scope |
|---|---|---|
| 0217 | Impuesto a las Ganancias | Soportado (hardcodeado) |
| 0218 | Ganancias — Beneficiarios del Exterior | Fuera de scope |
| Otros | Varios impuestos | Fuera de scope |
Códigos de operación (pos 52 de retenciones.txt)
| Código | Descripción | Scope |
|---|---|---|
| 1 | Retención | Soportado (hardcodeado) |
| 2 | Percepción | Fuera de scope |
| 4 | Imposibilidad de Retención | Fuera de scope |
Tipo de documento del retenido (pos 110-111 / 82-83)
| Código | Descripción | Scope |
|---|---|---|
| 80 | C.U.I.T. | Soportado (hardcodeado) |
| 86 | C.U.I.L. | Fuera de scope |
| 87 | C.D.I. | Fuera de scope |
| 83, 84 | Documentos del Exterior | Fuera de scope |
Códigos de provincia (pos 72-73 de sujetos_retenidos.txt)
| Código ARCA | Provincia |
|---|---|
| 00 | Capital Federal / Ciudad Autónoma de Bs.As. |
| 01 | Buenos Aires |
| 02 | Catamarca |
| 03 | Córdoba |
| 04 | Corrientes |
| 05 | Entre Ríos |
| 06 | Jujuy |
| 07 | Mendoza |
| 08 | La Rioja |
| 09 | Salta |
| 10 | San Juan |
| 11 | San Luis |
| 12 | Santa Fe |
| 13 | Santiago del Estero |
| 14 | Tucumán |
| 16 | Chaco |
| 17 | Chubut |
| 18 | Formosa |
| 19 | Misiones |
| 20 | Neuquén |
| 21 | La Pampa |
| 22 | Río Negro |
| 23 | Santa Cruz |
| 24 | Tierra del Fuego |
| 99 | No se informa (Beneficiarios del Exterior — fuera de scope) |
El código proviene de provincia.codigo_arca (columna codigo_arca en tabla cpro). Fallback: 00 si NULL.
Limitaciones conocidas
| ID | Descripción | Severidad | Estado |
|---|---|---|---|
| G1 | Número de comprobante con separadores: Si nrocom contiene 0001-12334599, ARCA requiere 000112334599 (sin guión). El helper formatNroComprobante debe aplicar el strip. | Alto | Pendiente corrección |
| G2 | CRLF vs LF: Si la implementación usa implode("\n", ...) via LibroIVA::encodeContent, se debe verificar que convierta LF→CRLF. ARCA SICORE requiere \r\n. | Medio | Verificar |
| G3 | CUIT sin padding en sujetos_retenidos.txt (pos 1-11): Si el CUIT tiene menos de 11 dígitos tras limpiar, la línea queda corta. Debe usarse str_pad($cuit, 11, '0', STR_PAD_LEFT). | Bajo | Pendiente corrección |
| G4 | NC sin certificado original verificado: Para NC (código 03), el campo pos 132-145 debe referenciar la retención original. El flujo actual usa detgan.numret de la NC, lo cual puede no corresponder al certificado original. | Medio | Verificar con usuario |
| G6 | Sin validación de códigos de comprobante: Si comprob.codigo devuelve un valor fuera de la tabla ARCA (01-11), se emite igualmente. Sin impacto si la tabla comprob está bien configurada. | Info | Sin acción requerida |
Consideraciones técnicas
Multi-tenant
- El controller itera todos los schemas vía
SchemaService::getSchemasWithTable('detgan'). - Cada schema se conecta individualmente con
new Database($db, $schemaActual). - El resultado es un único ZIP consolidado de todas las sucursales.
Migración requerida
La feature requiere la columna provincia.codigo_arca (smallint NULL) en la tabla cpro:
- Migration:
migrations/migrations/tenancy/YYYYMMDD_add_codigo_arca_to_provincia.php - Nivel:
LEVEL_EMPRESA(schemapublic) - Incluye seed con los 24 códigos ARCA de provincias argentinas
LATERAL subquery
El comprobante vinculado a la retención se obtiene via LATERAL subquery buscando por oc2.zf = op.zf (mismo proveedor) con comprob.tipo = 'D' (Débitos: facturas y notas de débito). No usar os2.id_movimiento = op.id — la OP nunca está en ordcte_subdicom.
Seguridad
- El acceso está protegido por el permiso
COMPRAS_UTILS_EXP-SICORE(id=6023). - No existe modo de prueba para este endpoint — opera únicamente sobre la base oficial.
Dependencias
Funcionalidades relacionadas
- Ficha de proveedor (
cpdprov) — datos de localidad y código postal via campocpos(temporal) - Tabla de localidades (
public.localidades) y provincias (public.cpro) — con columnacodigo_arca - Módulo de órdenes de pago (
ordcte) y retenciones de Ganancias (detgan,congan,acugan)
Documentación oficial ARCA
AP_INS_SICORER9.pdf— Instructivo SICORE versión 9.0 (disponible endocs/features/compra/)- Versión del formato: SICORE v9.0
Historial de cambios
| Fecha | Versión | Descripción |
|---|---|---|
| 2026-03-25 | 1.0 | Creación del documento — exportación consolidada multi-schema implementada |