Skip to main content

¿Qué es el Resumen Diario?

El Resumen Diario de Boletas es un documento electrónico que agrupa todas las boletas emitidas en un día y las envía a SUNAT en un solo lote.
Obligatorio para boletas (03): Todas las boletas deben enviarse a SUNAT vía Resumen Diario. A diferencia de las facturas (que se envían individualmente), las boletas NO se aceptan con envío individual.

Características

  • Frecuencia: Diaria (puede ser hasta 7 días después de la emisión)
  • Tipo de envío: Asíncrono (ticket + polling)
  • Documentos incluidos: Solo boletas (tipo 03)
  • Plazo máximo: 7 días calendario desde la fecha de emisión
  • Correlativo: Secuencial por día (001, 002, 003…)

Tipos de Resumen Diario

1. Resumen de Adición (Estado ‘1’)

Envío de boletas nuevas emitidas. Uso:
  • Primera vez que se envían las boletas del día
  • Boletas que no fueron incluidas en envíos anteriores

2. Resumen de Anulación (Estado ‘3’)

Anulación de boletas previamente aceptadas. Uso:
  • Anular boletas con errores
  • Cancelar boletas por devolución
  • Reemplazar boletas (anular + emitir nueva)
Diferencia con Comunicación de Baja:
  • Comunicación de Baja: Para facturas (01), notas (07/08)
  • Resumen Diario de Anulación: Solo para boletas (03)
Ambos son procesos asíncronos con tickets.

Flujo de Envío de Resumen Diario

1

Seleccionar boletas a enviar

En el módulo de Ventas, filtre las boletas pendientes de envío:Criterios:
  • tipo_documento.cod_sunat = '03' (boleta)
  • estado_sunat IS NULL o estado_sunat = '0' (sin enviar o solo XML generado)
  • fecha_emision dentro de los últimos 7 días
Seleccione las boletas usando los checkboxes de la tabla.
2

Iniciar envío de resumen

Haga clic en el botón Enviar Resumen Diario en la parte superior de la tabla.Se abrirá un modal solicitando:
  • Fecha del resumen: Fecha que agrupa las boletas (generalmente la fecha de emisión)
  • Boletas seleccionadas: Listado con serie-número y montos
  • Total del lote: Suma de todas las boletas
Validaciones automáticas del sistema:
// En ResumenDiarioController.php (línea 40-54)
foreach ($ventas as $venta) {
    $codSunat = $venta->tipoDocumento->cod_sunat;
    
    if ($codSunat !== '03') {
        $errores[] = "{$venta->numero_completo}: Solo boletas (03) se envían por Resumen Diario.";
        continue;
    }
    
    $fechaEmision = $venta->fecha_emision;
    if ($fechaEmision && $fechaEmision->diffInDays(now()) > 7) {
        $errores[] = "{$venta->numero_completo}: El plazo máximo es 7 días desde la emisión.";
        continue;
    }
}
3

Generar XML del resumen

El sistema genera un XML especial de tipo Summary (RC):En SunatService::resumenDiario() (línea 1125-1219):
  1. Construye detalles por boleta:
    foreach ($ventas as $venta) {
        $detail = (new SummaryDetail())
            ->setTipoDoc('03')  // Boleta
            ->setSerieNro($venta->serie . '-' . $venta->numero)
            ->setEstado('1')  // Adición
            ->setClienteTipo($tipoDocCliente)
            ->setClienteNro($numDocCliente)
            ->setTotal($total)
            ->setMtoOperGravadas($montoGravada)
            ->setMtoOperExoneradas($montoExonerada)
            ->setMtoIGV($igvMonto);
        
        $details[] = $detail;
    }
    
  2. Crea el objeto Summary:
    $summary = (new Summary())
        ->setFecGeneracion(new \DateTime())  // Hoy
        ->setFecResumen(\DateTime::createFromFormat('Y-m-d', $fechaResumen))  // Fecha de las boletas
        ->setCorrelativo('001')  // Secuencial
        ->setCompany($company)
        ->setDetails($details);
    
  3. Genera nombre del archivo:
    {RUC}-RC-{YYYYMMDD}-{correlativo}.xml
    
    Ejemplo:
    20612706702-RC-20260306-001.xml
    
4

Enviar a SUNAT y obtener ticket

$see = $this->getSee($empresa);
$result = $see->send($summary);

if ($result->isSuccess()) {
    $ticket = $result->getTicket();
    
    // Actualizar boletas
    foreach ($boletasValidas as $venta) {
        $venta->update([
            'estado_sunat' => '3',  // En proceso
            'mensaje_sunat' => 'Resumen diario enviado. Ticket: ' . $ticket,
        ]);
    }
    
    return [
        'success' => true,
        'ticket' => $ticket,
        'nombre_archivo' => $nombreArchivo,
        'cantidad' => count($details),
        'message' => 'Resumen diario enviado. Use el ticket para consultar el estado.',
    ];
}
El sistema muestra el ticket en la UI:
Resumen enviado correctamente
Ticket: 1234567890
Cantidad de boletas: 23
5

Consultar estado del ticket

SUNAT procesa el resumen de forma asíncrona (5-30 minutos).Consulta manual:Vaya a Facturación → Consultar Tickets SUNAT.Ingrese el ticket y haga clic en Consultar.Endpoint API:
POST /api/resumen-diario/ticket
Authorization: Bearer {token}
Content-Type: application/json

{
  "ticket": "1234567890"
}
Proceso interno (SunatService::consultarTicket() línea 1232-1279):
$see = $this->getSee($empresa);
$result = $see->getStatus($ticket);

if ($result->isSuccess()) {
    $cdr = $result->getCdrResponse();
    $cdrZip = $result->getCdrZip();
    
    // Guardar CDR
    file_put_contents(
        storage_path("app/sunat/cdr/{$ruc}/R-ticket-{$ticket}.zip"),
        $cdrZip
    );
    
    return [
        'success' => true,
        'codigo' => $cdr->getCode(),  // '0' = aceptado
        'mensaje' => $cdr->getDescription(),
        'notas' => $cdr->getNotes() ?? [],
    ];
}

$code = $result->getCode();
if ($code === '98') {
    return [
        'success' => true,
        'codigo' => '98',
        'mensaje' => 'En proceso. Intente nuevamente en unos segundos.',
        'en_proceso' => true,
    ];
}
Estados del ticket:
  • Código 0: Aceptado - todas las boletas aprobadas
  • Código 98: En proceso - esperar y reintentar
  • Otros códigos: Error - ver mensaje detallado
6

Actualizar boletas al ser aceptadas

Una vez aceptado el resumen (código 0):
foreach ($boletasEnviadas as $venta) {
    $venta->update([
        'estado_sunat' => '1',  // Aceptado
        'mensaje_sunat' => 'Aceptado por SUNAT vía Resumen Diario',
        'codigo_sunat' => '0',
    ]);
}
Las boletas ahora tienen validez tributaria completa.

Anulación de Boletas (Resumen de Baja)

1

Seleccionar boletas a anular

Filtre boletas con:
  • estado_sunat = '1' (aceptadas previamente)
  • tipo_documento.cod_sunat = '03'
Seleccione las que desea anular.
2

Iniciar resumen de anulación

Haga clic en Anular Boletas (Resumen).Endpoint:
POST /api/resumen-diario/anular
Authorization: Bearer {token}
Content-Type: application/json

{
  "ids": [45, 46, 47],
  "fecha_resumen": "2026-03-06"
}
Validaciones:
foreach ($ventas as $venta) {
    if ($venta->estado_sunat != '1') {
        $errores[] = "{$venta->numero_completo}: Solo se anulan boletas aceptadas (estado_sunat=1).";
        continue;
    }
}
3

Generar resumen con estado '3'

En SunatService::resumenDiarioBaja() (línea 1224-1227):
public function resumenDiarioBaja(...$params): array
{
    return $this->resumenDiario(...$params, estado: '3');  // Estado anulación
}
El detalle de cada boleta lleva ->setEstado('3'):
$detail = (new SummaryDetail())
    ->setTipoDoc('03')
    ->setSerieNro($venta->serie . '-' . $venta->numero)
    ->setEstado('3')  // Anulación
    // ... demás campos
4

Consultar ticket y confirmar anulación

Igual que en el resumen de adición, consulte el ticket.Al aceptarse:
foreach ($boletasAnuladas as $venta) {
    $venta->update([
        'estado' => '2',  // Anulado
        'estado_sunat' => '2',
        'mensaje_sunat' => 'Anulado por SUNAT vía Resumen Diario',
    ]);
}

Endpoint de Consulta de Ticket

POST /api/resumen-diario/ticket
Authorization: Bearer {token}
Content-Type: application/json

{
  "ticket": "1234567890"
}
Respuestas posibles:

Aceptado (código 0)

{
  "success": true,
  "codigo": "0",
  "mensaje": "El Resumen numero RC-20260306-001, ha sido aceptado",
  "notas": []
}

En proceso (código 98)

{
  "success": true,
  "codigo": "98",
  "mensaje": "En proceso. Intente nuevamente en unos segundos.",
  "en_proceso": true
}

Rechazado

{
  "success": false,
  "codigo": "2870",
  "message": "El resumen presenta datos inconsistentes"
}

Automatización con Workers

Para evitar consultas manuales, configure un worker que consulte tickets pendientes:
// En app/Console/Commands/ConsultarTicketsSunat.php

public function handle()
{
    $ticketsPendientes = Cache::get('sunat_tickets_pendientes', []);
    
    foreach ($ticketsPendientes as $key => $data) {
        $ticket = $data['ticket'];
        $empresa = Empresa::find($data['id_empresa']);
        
        $resultado = $this->sunatService->consultarTicket($empresa, $ticket);
        
        if ($resultado['codigo'] === '0') {
            // Aceptado - actualizar boletas y remover del cache
            $this->actualizarBoletas($data['boletas_ids'], '1');
            unset($ticketsPendientes[$key]);
            
        } elseif ($resultado['codigo'] !== '98') {
            // Error - registrar y remover del cache
            Log::error("Resumen rechazado: Ticket {$ticket}", $resultado);
            unset($ticketsPendientes[$key]);
        }
        // Si es '98', mantener en cache para próximo intento
    }
    
    Cache::put('sunat_tickets_pendientes', $ticketsPendientes, now()->addHours(24));
}
Ejecute el comando cada 5 minutos:
# En app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    $schedule->command('sunat:consultar-tickets')
             ->everyFiveMinutes()
             ->withoutOverlapping();
}

Correlativo del Resumen

El correlativo es secuencial por día y se reinicia cada día:
Día 1:
  20612706702-RC-20260306-001.xml  (primer resumen del día)
  20612706702-RC-20260306-002.xml  (segundo resumen del mismo día)

Día 2:
  20612706702-RC-20260307-001.xml  (se reinicia el correlativo)
Para obtener el siguiente correlativo:
$ultimoResumen = ResumenDiario::where('id_empresa', $empresa->id)
    ->where('fecha_resumen', $fechaResumen)
    ->orderBy('correlativo', 'desc')
    ->first();

$correlativo = str_pad(($ultimoResumen->correlativo ?? 0) + 1, 3, '0', STR_PAD_LEFT);
// Resultado: '001', '002', '003', etc.

Solución de Problemas

Ha seleccionado facturas u otros documentos. El Resumen Diario es exclusivamente para boletas.Facturas se envían individualmente con POST /api/ventas/{id}/enviar-sunat.
Las boletas tienen más de 7 días de emitidas. SUNAT no acepta resúmenes de boletas con más de 7 días.Solución:
  • Si es error del sistema (las boletas son recientes), verifique la fecha_emision en la base de datos
  • Si realmente tienen más de 7 días, debe anularlas y emitir nuevas boletas
Si después de 1 hora sigue devolviendo ‘98’ (en proceso), puede haber un problema en SUNAT.Acciones:
  1. Verifique el estado en SUNAT Operaciones en Línea → Consultas → Consultar Envíos
  2. Si SUNAT muestra error, corrija y reenvíe
  3. Si SUNAT muestra exitoso, fuerza la actualización manual en el sistema
No es recomendable pero técnicamente posible. El campo fecha_resumen debe ser la fecha representativa.Mejor práctica: Un resumen por día de emisión.Ejemplo:
  • Resumen del 2026-03-05: Todas las boletas emitidas el 05/03
  • Resumen del 2026-03-06: Todas las boletas emitidas el 06/03
No se puede modificar. Opciones:
  1. Anular con Resumen de Baja y emitir nueva boleta correcta
  2. Emitir Nota de Crédito por el monto total (anula el efecto)
  3. Emitir Nota de Débito para ajustes por aumentos

Reporte de Resúmenes Enviados

Para consultar el historial de resúmenes:
GET /api/resumen-diario/historial?fecha_desde=2026-03-01&fecha_hasta=2026-03-31
Respuesta:
{
  "success": true,
  "data": [
    {
      "id_resumen": 12,
      "fecha_resumen": "2026-03-06",
      "correlativo": "001",
      "nombre_archivo": "20612706702-RC-20260306-001.xml",
      "ticket": "1234567890",
      "estado": "aceptado",
      "codigo_sunat": "0",
      "cantidad_boletas": 23,
      "monto_total": "12450.00",
      "fecha_envio": "2026-03-06T18:30:00",
      "fecha_respuesta": "2026-03-06T18:45:00"
    }
  ]
}

Próximos Pasos

Build docs developers (and LLMs) love